Add compress and image to core.

This commit is contained in:
Jeroen van Rijn
2021-04-30 00:21:52 +02:00
parent 222bab501c
commit 58e023e0cf
9 changed files with 3776 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
package image
import "core:bytes"
Image :: struct {
width: int,
height: int,
channels: int,
depth: u8,
pixels: bytes.Buffer,
/*
Some image loaders/writers can return/take an optional background color.
For convenience, we return them as u16 so we don't need to switch on the type
in our viewer, and can just test against nil.
*/
background: Maybe([3]u16),
sidecar: any,
}
/*
Image_Option:
`.info`
This option behaves as `return_ihdr` and `do_not_decompress_image` and can be used
to gather an image's dimensions and color information.
`.return_header`
Fill out img.sidecar.header with the image's format-specific header struct.
If we only care about the image specs, we can set `return_header` +
`do_not_decompress_image`, or `.info`, which works as if both of these were set.
`.return_metadata`
Returns all chunks not needed to decode the data.
It also returns the header as if `.return_header` is set.
`do_not_decompress_image`
Skip decompressing IDAT chunk, defiltering and the rest.
`alpha_add_if_missing`
If the image has no alpha channel, it'll add one set to max(type).
Turns RGB into RGBA and Gray into Gray+Alpha
`alpha_drop_if_present`
If the image has an alpha channel, drop it.
You may want to use `alpha_premultiply` in this case.
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
unless you select `alpha_premultiply`.
In this case it'll premultiply the specified pixels in question only,
as the others are implicitly fully opaque.
`alpha_premultiply`
If the image has an alpha channel, returns image data as follows:
RGB *= A, Gray = Gray *= A
`blend_background`
If a bKGD chunk is present in a PNG, we normally just set `img.background`
with its value and leave it up to the application to decide how to display the image,
as per the PNG specification.
With `blend_background` selected, we blend the image against the background
color. As this negates the use for an alpha channel, we'll drop it _unless_
you also specify `alpha_add_if_missing`.
Options that don't apply to an image format will be ignored by their loader.
*/
Option :: enum {
info = 0,
do_not_decompress_image,
return_header,
return_metadata,
alpha_add_if_missing,
alpha_drop_if_present,
alpha_premultiply,
blend_background,
}
Options :: distinct bit_set[Option];
PNG_Error :: enum {
Invalid_PNG_Signature,
IHDR_Not_First_Chunk,
IHDR_Corrupt,
IDAT_Missing,
IDAT_Must_Be_Contiguous,
IDAT_Corrupt,
PNG_Does_Not_Adhere_to_Spec,
PLTE_Encountered_Unexpectedly,
PLTE_Invalid_Length,
TRNS_Encountered_Unexpectedly,
BKGD_Invalid_Length,
Invalid_Image_Dimensions,
Unknown_Color_Type,
Invalid_Color_Bit_Depth_Combo,
Unknown_Filter_Method,
Unknown_Interlace_Method,
}
/*
Functions to help with image buffer calculations
*/
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height;
return;
}
+327
View File
@@ -0,0 +1,327 @@
//+ignore
package png
import "core:compress"
import "core:image"
import "core:image/png"
import "core:bytes"
import "core:fmt"
// For PPM writer
import "core:mem"
import "core:os"
main :: proc() {
file: string;
options := image.Options{};
err: compress.Error;
img: ^image.Image;
file = "../../../misc/logo-slim.png";
img, err = png.load(file, options);
defer png.destroy(img);
if !png.is_kind(err, png.E_General.OK) {
fmt.printf("Trying to read PNG file %v returned %v\n", file, err);
} else {
v: png.Info;
ok: bool;
fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth);
if v, ok = img.sidecar.(png.Info); ok {
// Handle ancillary chunks as you wish.
// We provide helper functions for a few types.
for c in v.chunks {
#partial switch (c.header.type) {
case .tIME:
t, _ := png.core_time(c);
fmt.printf("[tIME]: %v\n", t);
case .gAMA:
fmt.printf("[gAMA]: %v\n", png.gamma(c));
case .pHYs:
phys := png.phys(c);
if phys.unit == .Meter {
xm := f32(img.width) / f32(phys.ppu_x);
ym := f32(img.height) / f32(phys.ppu_y);
dpi_x, dpi_y := png.phys_to_dpi(phys);
fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y);
fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y);
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym);
} else {
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y);
}
case .iTXt, .zTXt, .tEXt:
res, ok_text := png.text(c);
if ok_text {
if c.header.type == .iTXt {
fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text);
} else {
fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text);
}
}
defer png.text_destroy(res);
case .bKGD:
fmt.printf("[bKGD] %v\n", img.background);
case .eXIf:
res, ok_exif := png.exif(c);
if ok_exif {
/*
Other than checking the signature and byte order, we don't handle Exif data.
If you wish to interpret it, pass it to an Exif parser.
*/
fmt.printf("[eXIf] %v\n", res);
}
case .PLTE:
plte, plte_ok := png.plte(c);
if plte_ok {
fmt.printf("[PLTE] %v\n", plte);
} else {
fmt.printf("[PLTE] Error\n");
}
case .hIST:
res, ok_hist := png.hist(c);
if ok_hist {
fmt.printf("[hIST] %v\n", res);
}
case .cHRM:
res, ok_chrm := png.chrm(c);
if ok_chrm {
fmt.printf("[cHRM] %v\n", res);
}
case .sPLT:
res, ok_splt := png.splt(c);
if ok_splt {
fmt.printf("[sPLT] %v\n", res);
}
png.splt_destroy(res);
case .sBIT:
if res, ok_sbit := png.sbit(c); ok_sbit {
fmt.printf("[sBIT] %v\n", res);
}
case .iCCP:
res, ok_iccp := png.iccp(c);
if ok_iccp {
fmt.printf("[iCCP] %v\n", res);
}
png.iccp_destroy(res);
case .sRGB:
if res, ok_srgb := png.srgb(c); ok_srgb {
fmt.printf("[sRGB] Rendering intent: %v\n", res);
}
case:
type := c.header.type;
name := png.chunk_type_to_name(&type);
fmt.printf("[%v]: %v\n", name, c.data);
}
}
}
}
if is_kind(err, E_General.OK) && .do_not_decompress_image not_in options && .info not_in options {
if ok := write_image_as_ppm("out.ppm", img); ok {
fmt.println("Saved decoded image.");
} else {
fmt.println("Error saving out.ppm.");
fmt.println(img);
}
}
}
// Crappy PPM writer used during testing. Don't use in production.
write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) {
_bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) {
if v, ok := bg.?; ok {
res = v;
} else {
if high {
l := u16(30 * 256 + 30);
if (x & 4 == 0) ~ (y & 4 == 0) {
res = [3]u16{l, 0, l};
} else {
res = [3]u16{l >> 1, 0, l >> 1};
}
} else {
if (x & 4 == 0) ~ (y & 4 == 0) {
res = [3]u16{30, 30, 30};
} else {
res = [3]u16{15, 15, 15};
}
}
}
return;
}
// profiler.timed_proc();
using image;
using os;
flags: int = O_WRONLY|O_CREATE|O_TRUNC;
img := image;
// PBM 16-bit images are big endian
when ODIN_ENDIAN == "little" {
if img.depth == 16 {
// The pixel components are in Big Endian. Let's byteswap back.
input := mem.slice_data_cast([]u16, img.pixels.buf[:]);
output := mem.slice_data_cast([]u16be, img.pixels.buf[:]);
#no_bounds_check for v, i in input {
output[i] = u16be(v);
}
}
}
pix := bytes.buffer_to_bytes(&img.pixels);
if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) {
return false;
}
mode: int = 0;
when ODIN_OS == "linux" || ODIN_OS == "darwin" {
// NOTE(justasd): 644 (owner read, write; group read; others read)
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
}
fd, err := open(filename, flags, mode);
if err != 0 {
return false;
}
defer close(fd);
write_string(fd,
fmt.tprintf("P6\n%v %v\n%v\n", width, height, (1 << depth -1)),
);
if channels == 3 {
// We don't handle transparency here...
write_ptr(fd, raw_data(pix), len(pix));
} else {
bpp := depth == 16 ? 2 : 1;
bytes_needed := width * height * 3 * bpp;
op := bytes.Buffer{};
bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed);
defer bytes.buffer_destroy(&op);
if channels == 1 {
if depth == 16 {
assert(len(pix) == width * height * 2);
p16 := mem.slice_data_cast([]u16, pix);
o16 := mem.slice_data_cast([]u16, op.buf[:]);
#no_bounds_check for len(p16) != 0 {
r := u16(p16[0]);
o16[0] = r;
o16[1] = r;
o16[2] = r;
p16 = p16[1:];
o16 = o16[3:];
}
} else {
o := 0;
for i := 0; i < len(pix); i += 1 {
r := pix[i];
op.buf[o ] = r;
op.buf[o+1] = r;
op.buf[o+2] = r;
o += 3;
}
}
write_ptr(fd, raw_data(op.buf), len(op.buf));
} else if channels == 2 {
if depth == 16 {
p16 := mem.slice_data_cast([]u16, pix);
o16 := mem.slice_data_cast([]u16, op.buf[:]);
bgcol := img.background;
#no_bounds_check for len(p16) != 0 {
r := f64(u16(p16[0]));
bg: f64;
if bgcol != nil {
v := bgcol.([3]u16)[0];
bg = f64(v);
}
a := f64(u16(p16[1])) / 65535.0;
l := (a * r) + (1 - a) * bg;
o16[0] = u16(l);
o16[1] = u16(l);
o16[2] = u16(l);
p16 = p16[2:];
o16 = o16[3:];
}
} else {
o := 0;
for i := 0; i < len(pix); i += 2 {
r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0;
c := u8(f32(r) * a1);
op.buf[o ] = c;
op.buf[o+1] = c;
op.buf[o+2] = c;
o += 3;
}
}
write_ptr(fd, raw_data(op.buf), len(op.buf));
} else if channels == 4 {
if depth == 16 {
p16 := mem.slice_data_cast([]u16be, pix);
o16 := mem.slice_data_cast([]u16be, op.buf[:]);
#no_bounds_check for len(p16) != 0 {
bg := _bg(img.background, 0, 0);
r := f32(p16[0]);
g := f32(p16[1]);
b := f32(p16[2]);
a := f32(p16[3]) / 65535.0;
lr := (a * r) + (1 - a) * f32(bg[0]);
lg := (a * g) + (1 - a) * f32(bg[1]);
lb := (a * b) + (1 - a) * f32(bg[2]);
o16[0] = u16be(lr);
o16[1] = u16be(lg);
o16[2] = u16be(lb);
p16 = p16[4:];
o16 = o16[3:];
}
} else {
o := 0;
for i := 0; i < len(pix); i += 4 {
x := (i / 4) % width;
y := i / width / 4;
_b := _bg(img.background, x, y, false);
bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])};
r := f32(pix[i]);
g := f32(pix[i+1]);
b := f32(pix[i+2]);
a := f32(pix[i+3]) / 255.0;
lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0]));
lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1]));
lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2]));
op.buf[o ] = lr;
op.buf[o+1] = lg;
op.buf[o+2] = lb;
o += 3;
}
}
write_ptr(fd, raw_data(op.buf), len(op.buf));
} else {
return false;
}
}
return true;
}
+521
View File
@@ -0,0 +1,521 @@
package png
import "core:image"
import "core:compress/zlib"
import coretime "core:time"
import "core:strings"
import "core:bytes"
import "core:mem"
/*
These are a few useful utility functions to work with PNG images.
*/
/*
Cleanup of image-specific data.
There are other helpers for cleanup of PNG-specific data.
Those are named *_destroy, where * is the name of the helper.
*/
destroy :: proc(img: ^Image) {
if img == nil {
/*
Nothing to do.
Load must've returned with an error.
*/
return;
}
bytes.buffer_destroy(&img.pixels);
/*
We don't need to do anything for the individual chunks.
They're allocated on the temp allocator, as is info.chunks
See read_chunk.
*/
free(img);
}
/*
Chunk helpers
*/
gamma :: proc(c: Chunk) -> f32 {
assert(c.header.type == .gAMA);
res := (^gAMA)(raw_data(c.data))^;
when true {
// Returns the wrong result on old backend
// Fixed for -llvm-api
return f32(res.gamma_100k) / 100_000.0;
} else {
return f32(u32(res.gamma_100k)) / 100_000.0;
}
}
INCHES_PER_METER :: 1000.0 / 25.4;
phys :: proc(c: Chunk) -> pHYs {
assert(c.header.type == .pHYs);
res := (^pHYs)(raw_data(c.data))^;
return res;
}
phys_to_dpi :: proc(p: pHYs) -> (x_dpi, y_dpi: f32) {
return f32(p.ppu_x) / INCHES_PER_METER, f32(p.ppu_y) / INCHES_PER_METER;
}
time :: proc(c: Chunk) -> tIME {
assert(c.header.type == .tIME);
res := (^tIME)(raw_data(c.data))^;
return res;
}
core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) {
png_time := time(c);
using png_time;
return coretime.datetime_to_time(
int(year), int(month), int(day),
int(hour), int(minute), int(second));
}
text :: proc(c: Chunk) -> (res: Text, ok: bool) {
#partial switch c.header.type {
case .tEXt:
ok = true;
fields := bytes.split(s=c.data, sep=[]u8{0}, allocator=context.temp_allocator);
if len(fields) == 2 {
res.keyword = strings.clone(string(fields[0]));
res.text = strings.clone(string(fields[1]));
} else {
ok = false;
}
return;
case .zTXt:
ok = true;
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator);
if len(fields) != 3 || len(fields[1]) != 0 {
// Compression method must be 0=Deflate, which thanks to the split above turns
// into an empty slice
ok = false; return;
}
// Set up ZLIB context and decompress text payload.
buf: bytes.Buffer;
zlib_error := zlib.inflate_from_byte_array(&fields[2], &buf);
defer bytes.buffer_destroy(&buf);
if !is_kind(zlib_error, E_General.OK) {
ok = false; return;
}
res.keyword = strings.clone(string(fields[0]));
res.text = strings.clone(bytes.buffer_to_string(&buf));
return;
case .iTXt:
ok = true;
s := string(c.data);
null := strings.index_byte(s, 0);
if null == -1 {
ok = false; return;
}
if len(c.data) < null + 4 {
// At a minimum, including the \0 following the keyword, we require 5 more bytes.
ok = false; return;
}
res.keyword = strings.clone(string(c.data[:null]));
rest := c.data[null+1:];
compression_flag := rest[:1][0];
if compression_flag > 1 {
ok = false; return;
}
compression_method := rest[1:2][0];
if compression_flag == 1 && compression_method > 0 {
// Only Deflate is supported
ok = false; return;
}
rest = rest[2:];
// We now expect an optional language keyword and translated keyword, both followed by a \0
null = strings.index_byte(string(rest), 0);
if null == -1 {
ok = false; return;
}
res.language = strings.clone(string(rest[:null]));
rest = rest[null+1:];
null = strings.index_byte(string(rest), 0);
if null == -1 {
ok = false; return;
}
res.keyword_localized = strings.clone(string(rest[:null]));
rest = rest[null+1:];
if compression_flag == 0 {
res.text = strings.clone(string(rest));
} else {
// Set up ZLIB context and decompress text payload.
buf: bytes.Buffer;
zlib_error := zlib.inflate_from_byte_array(&rest, &buf);
defer bytes.buffer_destroy(&buf);
if !is_kind(zlib_error, E_General.OK) {
ok = false; return;
}
res.text = strings.clone(bytes.buffer_to_string(&buf));
}
return;
case:
// PNG text helper called with an unrecognized chunk type.
ok = false; return;
}
}
text_destroy :: proc(text: Text) {
delete(text.keyword);
delete(text.keyword_localized);
delete(text.language);
delete(text.text);
}
iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) {
ok = true;
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator);
if len(fields[0]) < 1 || len(fields[0]) > 79 {
// Invalid profile name
ok = false; return;
}
if len(fields[1]) != 0 {
// Compression method should be a zero, which the split turned into an empty slice.
ok = false; return;
}
// Set up ZLIB context and decompress iCCP payload
buf: bytes.Buffer;
zlib_error := zlib.inflate_from_byte_array(&fields[2], &buf);
if !is_kind(zlib_error, E_General.OK) {
bytes.buffer_destroy(&buf);
ok = false; return;
}
res.name = strings.clone(string(fields[0]));
res.profile = bytes.buffer_to_bytes(&buf);
return;
}
iccp_destroy :: proc(i: iCCP) {
delete(i.name);
delete(i.profile);
}
srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
ok = true;
if c.header.type != .sRGB || len(c.data) != 1 {
return {}, false;
}
res.intent = sRGB_Rendering_Intent(c.data[0]);
if res.intent > max(sRGB_Rendering_Intent) {
ok = false; return;
}
return;
}
plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) {
if c.header.type != .PLTE {
return {}, false;
}
i := 0; j := 0; ok = true;
for j < int(c.header.length) {
res.entries[i] = {c.data[j], c.data[j+1], c.data[j+2]};
i += 1; j += 3;
}
res.used = u16(i);
return;
}
splt :: proc(c: Chunk) -> (res: sPLT, ok: bool) {
if c.header.type != .sPLT {
return {}, false;
}
ok = true;
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator);
if len(fields) != 2 {
return {}, false;
}
res.depth = fields[1][0];
if res.depth != 8 && res.depth != 16 {
return {}, false;
}
data := fields[1][1:];
count: int;
if res.depth == 8 {
if len(data) % 6 != 0 {
return {}, false;
}
count = len(data) / 6;
if count > 256 {
return {}, false;
}
res.entries = mem.slice_data_cast([][4]u8, data);
} else { // res.depth == 16
if len(data) % 10 != 0 {
return {}, false;
}
count = len(data) / 10;
if count > 256 {
return {}, false;
}
res.entries = mem.slice_data_cast([][4]u16, data);
}
res.name = strings.clone(string(fields[0]));
res.used = u16(count);
return;
}
splt_destroy :: proc(s: sPLT) {
delete(s.name);
}
sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) {
/*
Returns [4]u8 with the significant bits in each channel.
A channel will contain zero if not applicable to the PNG color type.
*/
if len(c.data) < 1 || len(c.data) > 4 {
ok = false; return;
}
ok = true;
for i := 0; i < len(c.data); i += 1 {
res[i] = c.data[i];
}
return;
}
hist :: proc(c: Chunk) -> (res: hIST, ok: bool) {
if c.header.type != .hIST {
return {}, false;
}
if c.header.length & 1 == 1 || c.header.length > 512 {
// The entries are u16be, so the length must be even.
// At most 256 entries must be present
return {}, false;
}
ok = true;
data := mem.slice_data_cast([]u16be, c.data);
i := 0;
for len(data) > 0 {
// HIST entries are u16be, we unpack them to machine format
res.entries[i] = u16(data[0]);
i += 1; data = data[1:];
}
res.used = u16(i);
return;
}
chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
ok = true;
if c.header.length != size_of(cHRM_Raw) {
return {}, false;
}
chrm := (^cHRM_Raw)(raw_data(c.data))^;
res.w.x = f32(chrm.w.x) / 100_000.0;
res.w.y = f32(chrm.w.y) / 100_000.0;
res.r.x = f32(chrm.r.x) / 100_000.0;
res.r.y = f32(chrm.r.y) / 100_000.0;
res.g.x = f32(chrm.g.x) / 100_000.0;
res.g.y = f32(chrm.g.y) / 100_000.0;
res.b.x = f32(chrm.b.x) / 100_000.0;
res.b.y = f32(chrm.b.y) / 100_000.0;
return;
}
exif :: proc(c: Chunk) -> (res: Exif, ok: bool) {
ok = true;
if len(c.data) < 4 {
ok = false; return;
}
if c.data[0] == 'M' && c.data[1] == 'M' {
res.byte_order = .big_endian;
if c.data[2] != 0 || c.data[3] != 42 {
ok = false; return;
}
} else if c.data[0] == 'I' && c.data[1] == 'I' {
res.byte_order = .little_endian;
if c.data[2] != 42 || c.data[3] != 0 {
ok = false; return;
}
} else {
ok = false; return;
}
res.data = c.data;
return;
}
/*
General helper functions
*/
compute_buffer_size :: image.compute_buffer_size;
/*
PNG save helpers
*/
when false {
make_chunk :: proc(c: any, t: Chunk_Type) -> (res: Chunk) {
data: []u8;
if v, ok := c.([]u8); ok {
data = v;
} else {
data = mem.any_to_bytes(c);
}
res.header.length = u32be(len(data));
res.header.type = t;
res.data = data;
// CRC the type
crc := hash.crc32(mem.any_to_bytes(res.header.type));
// Extend the CRC with the data
res.crc = u32be(hash.crc32(data, crc));
return;
}
write_chunk :: proc(fd: os.Handle, chunk: Chunk) {
c := chunk;
// Write length + type
os.write_ptr(fd, &c.header, 8);
// Write data
os.write_ptr(fd, mem.raw_data(c.data), int(c.header.length));
// Write CRC32
os.write_ptr(fd, &c.crc, 4);
}
write_image_as_png :: proc(filename: string, image: Image) -> (err: Error) {
profiler.timed_proc();
using image;
using os;
flags: int = O_WRONLY|O_CREATE|O_TRUNC;
if len(image.pixels) == 0 || len(image.pixels) < image.width * image.height * int(image.channels) {
return E_PNG.Invalid_Image_Dimensions;
}
mode: int = 0;
when ODIN_OS == "linux" || ODIN_OS == "darwin" {
// NOTE(justasd): 644 (owner read, write; group read; others read)
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
}
fd, fderr := open(filename, flags, mode);
if fderr != 0 {
return E_General.Cannot_Open_File;
}
defer close(fd);
magic := Signature;
write_ptr(fd, &magic, 8);
ihdr := IHDR{
width = u32be(width),
height = u32be(height),
bit_depth = depth,
compression_method = 0,
filter_method = 0,
interlace_method = .None,
};
if channels == 1 {
ihdr.color_type = Color_Type{};
} else if channels == 2 {
ihdr.color_type = Color_Type{.Alpha};
} else if channels == 3 {
ihdr.color_type = Color_Type{.Color};
} else if channels == 4 {
ihdr.color_type = Color_Type{.Color, .Alpha};
} else {
// Unhandled
return E_PNG.Unknown_Color_Type;
}
h := make_chunk(ihdr, .IHDR);
write_chunk(fd, h);
bytes_needed := width * height * int(channels) + height;
filter_bytes := mem.make_dynamic_array_len_cap([dynamic]u8, bytes_needed, bytes_needed, context.allocator);
defer delete(filter_bytes);
i := 0; j := 0;
// Add a filter byte 0 per pixel row
for y := 0; y < height; y += 1 {
filter_bytes[j] = 0; j += 1;
for x := 0; x < width; x += 1 {
for z := 0; z < channels; z += 1 {
filter_bytes[j+z] = image.pixels[i+z];
}
i += channels; j += channels;
}
}
assert(j == bytes_needed);
a: []u8 = filter_bytes[:];
out_buf: ^[dynamic]u8;
defer free(out_buf);
ctx := zlib.ZLIB_Context{
in_buf = &a,
out_buf = out_buf,
};
err = zlib.write_zlib_stream_from_memory(&ctx);
b: []u8;
if is_kind(err, E_General, E_General.OK) {
b = ctx.out_buf[:];
} else {
return err;
}
idat := make_chunk(b, .IDAT);
write_chunk(fd, idat);
iend := make_chunk([]u8{}, .IEND);
write_chunk(fd, iend);
return E_General.OK;
}
}
File diff suppressed because it is too large Load Diff