mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-31 20:00:09 +00:00
Add new PNG post processing options.
This commit is contained in:
@@ -19,7 +19,7 @@ Error :: union {
|
|||||||
This is here because png.load will return a this type of error union,
|
This is here because png.load will return a this type of error union,
|
||||||
as it may involve an I/O error, a Deflate error, etc.
|
as it may involve an I/O error, a Deflate error, etc.
|
||||||
*/
|
*/
|
||||||
image.PNG_Error,
|
image.Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
General_Error :: enum {
|
General_Error :: enum {
|
||||||
|
|||||||
+115
-14
@@ -1,6 +1,9 @@
|
|||||||
package image
|
package image
|
||||||
|
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
Image :: struct {
|
Image :: struct {
|
||||||
width: int,
|
width: int,
|
||||||
@@ -17,49 +20,69 @@ Image :: struct {
|
|||||||
sidecar: any,
|
sidecar: any,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IMPORTANT: `.do_not_expand_*` options currently skip handling of the `alpha_*` options,
|
||||||
|
therefore Gray+Alpha will be returned as such even if you add `.alpha_drop_if_present`,
|
||||||
|
and `.alpha_add_if_missing` and keyed transparency will likewise be ignored.
|
||||||
|
|
||||||
|
The same goes for indexed images. This will be remedied in a near future update.
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Image_Option:
|
Image_Option:
|
||||||
`.info`
|
`.info`
|
||||||
This option behaves as `return_ihdr` and `do_not_decompress_image` and can be used
|
This option behaves as `.return_ihdr` and `.do_not_decompress_image` and can be used
|
||||||
to gather an image's dimensions and color information.
|
to gather an image's dimensions and color information.
|
||||||
|
|
||||||
`.return_header`
|
`.return_header`
|
||||||
Fill out img.sidecar.header with the image's format-specific header struct.
|
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` +
|
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.
|
`.do_not_decompress_image`, or `.info`, which works as if both of these were set.
|
||||||
|
|
||||||
`.return_metadata`
|
`.return_metadata`
|
||||||
Returns all chunks not needed to decode the data.
|
Returns all chunks not needed to decode the data.
|
||||||
It also returns the header as if `.return_header` is set.
|
It also returns the header as if `.return_header` was set.
|
||||||
|
|
||||||
`do_not_decompress_image`
|
`.do_not_decompress_image`
|
||||||
Skip decompressing IDAT chunk, defiltering and the rest.
|
Skip decompressing IDAT chunk, defiltering and the rest.
|
||||||
|
|
||||||
`alpha_add_if_missing`
|
`.do_not_expand_grayscale`
|
||||||
|
Do not turn grayscale (+ Alpha) images into RGB(A).
|
||||||
|
Returns just the 1 or 2 channels present, although 1, 2 and 4 bit are still scaled to 8-bit.
|
||||||
|
|
||||||
|
`.do_not_expand_indexed`
|
||||||
|
Do not turn indexed (+ Alpha) images into RGB(A).
|
||||||
|
Returns just the 1 or 2 (with `tRNS`) channels present.
|
||||||
|
Make sure to use `return_metadata` to also return the palette chunk so you can recolor it yourself.
|
||||||
|
|
||||||
|
`.do_not_expand_channels`
|
||||||
|
Applies both `.do_not_expand_grayscale` and `.do_not_expand_indexed`.
|
||||||
|
|
||||||
|
`.alpha_add_if_missing`
|
||||||
If the image has no alpha channel, it'll add one set to max(type).
|
If the image has no alpha channel, it'll add one set to max(type).
|
||||||
Turns RGB into RGBA and Gray into Gray+Alpha
|
Turns RGB into RGBA and Gray into Gray+Alpha
|
||||||
|
|
||||||
`alpha_drop_if_present`
|
`.alpha_drop_if_present`
|
||||||
If the image has an alpha channel, drop it.
|
If the image has an alpha channel, drop it.
|
||||||
You may want to use `alpha_premultiply` in this case.
|
You may want to use `.alpha_premultiply` in this case.
|
||||||
|
|
||||||
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
|
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
|
||||||
unless you select `alpha_premultiply`.
|
unless you select `alpha_premultiply`.
|
||||||
In this case it'll premultiply the specified pixels in question only,
|
In this case it'll premultiply the specified pixels in question only,
|
||||||
as the others are implicitly fully opaque.
|
as the others are implicitly fully opaque.
|
||||||
|
|
||||||
`alpha_premultiply`
|
`.alpha_premultiply`
|
||||||
If the image has an alpha channel, returns image data as follows:
|
If the image has an alpha channel, returns image data as follows:
|
||||||
RGB *= A, Gray = Gray *= A
|
RGB *= A, Gray = Gray *= A
|
||||||
|
|
||||||
`blend_background`
|
`.blend_background`
|
||||||
If a bKGD chunk is present in a PNG, we normally just set `img.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,
|
with its value and leave it up to the application to decide how to display the image,
|
||||||
as per the PNG specification.
|
as per the PNG specification.
|
||||||
|
|
||||||
With `blend_background` selected, we blend the image against the background
|
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_
|
color. As this negates the use for an alpha channel, we'll drop it _unless_
|
||||||
you also specify `alpha_add_if_missing`.
|
you also specify `.alpha_add_if_missing`.
|
||||||
|
|
||||||
Options that don't apply to an image format will be ignored by their loader.
|
Options that don't apply to an image format will be ignored by their loader.
|
||||||
*/
|
*/
|
||||||
@@ -73,10 +96,14 @@ Option :: enum {
|
|||||||
alpha_drop_if_present,
|
alpha_drop_if_present,
|
||||||
alpha_premultiply,
|
alpha_premultiply,
|
||||||
blend_background,
|
blend_background,
|
||||||
|
// Unimplemented
|
||||||
|
do_not_expand_grayscale,
|
||||||
|
do_not_expand_indexed,
|
||||||
|
do_not_expand_channels,
|
||||||
}
|
}
|
||||||
Options :: distinct bit_set[Option];
|
Options :: distinct bit_set[Option];
|
||||||
|
|
||||||
PNG_Error :: enum {
|
Error :: enum {
|
||||||
Invalid_PNG_Signature,
|
Invalid_PNG_Signature,
|
||||||
IHDR_Not_First_Chunk,
|
IHDR_Not_First_Chunk,
|
||||||
IHDR_Corrupt,
|
IHDR_Corrupt,
|
||||||
@@ -93,9 +120,10 @@ PNG_Error :: enum {
|
|||||||
Invalid_Color_Bit_Depth_Combo,
|
Invalid_Color_Bit_Depth_Combo,
|
||||||
Unknown_Filter_Method,
|
Unknown_Filter_Method,
|
||||||
Unknown_Interlace_Method,
|
Unknown_Interlace_Method,
|
||||||
|
Requested_Channel_Not_Present,
|
||||||
|
Post_Processing_Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Functions to help with image buffer calculations
|
Functions to help with image buffer calculations
|
||||||
*/
|
*/
|
||||||
@@ -105,3 +133,76 @@ compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes
|
|||||||
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height;
|
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
For when you have an RGB(A) image, but want a particular channel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Channel :: enum u8 {
|
||||||
|
R = 1,
|
||||||
|
G = 2,
|
||||||
|
B = 3,
|
||||||
|
A = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) {
|
||||||
|
|
||||||
|
ok = false;
|
||||||
|
t: bytes.Buffer;
|
||||||
|
|
||||||
|
idx := int(channel);
|
||||||
|
|
||||||
|
if idx > img.channels {
|
||||||
|
return {}, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if img.channels == 2 && idx == 4 {
|
||||||
|
// Alpha requested, which in a two channel image is index 2: G.
|
||||||
|
idx = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(img.depth) {
|
||||||
|
case 8:
|
||||||
|
buffer_size := compute_buffer_size(img.width, img.height, 1, 8);
|
||||||
|
t = bytes.Buffer{};
|
||||||
|
resize(&t.buf, buffer_size);
|
||||||
|
|
||||||
|
i := bytes.buffer_to_bytes(&img.pixels);
|
||||||
|
o := bytes.buffer_to_bytes(&t);
|
||||||
|
|
||||||
|
for len(i) > 0 {
|
||||||
|
o[0] = i[idx];
|
||||||
|
i = i[img.channels:];
|
||||||
|
o = o[1:];
|
||||||
|
}
|
||||||
|
case 16:
|
||||||
|
buffer_size := compute_buffer_size(img.width, img.height, 2, 8);
|
||||||
|
t = bytes.Buffer{};
|
||||||
|
resize(&t.buf, buffer_size);
|
||||||
|
|
||||||
|
i := mem.slice_data_cast([]u16, img.pixels.buf[:]);
|
||||||
|
o := mem.slice_data_cast([]u16, t.buf[:]);
|
||||||
|
|
||||||
|
for len(i) > 0 {
|
||||||
|
o[0] = i[idx];
|
||||||
|
i = i[img.channels:];
|
||||||
|
o = o[1:];
|
||||||
|
}
|
||||||
|
case 1, 2, 4:
|
||||||
|
// We shouldn't see this case, as the loader already turns these into 8-bit.
|
||||||
|
return {}, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = new(Image);
|
||||||
|
res.width = img.width;
|
||||||
|
res.height = img.height;
|
||||||
|
res.channels = 1;
|
||||||
|
res.depth = img.depth;
|
||||||
|
res.pixels = t;
|
||||||
|
res.background = img.background;
|
||||||
|
res.sidecar = img.sidecar;
|
||||||
|
|
||||||
|
fmt.println(t);
|
||||||
|
|
||||||
|
return res, true;
|
||||||
|
}
|
||||||
+27
-2
@@ -14,7 +14,7 @@ import "core:intrinsics"
|
|||||||
|
|
||||||
Error :: compress.Error;
|
Error :: compress.Error;
|
||||||
E_General :: compress.General_Error;
|
E_General :: compress.General_Error;
|
||||||
E_PNG :: image.PNG_Error;
|
E_PNG :: image.Error;
|
||||||
E_Deflate :: compress.Deflate_Error;
|
E_Deflate :: compress.Deflate_Error;
|
||||||
is_kind :: compress.is_kind;
|
is_kind :: compress.is_kind;
|
||||||
|
|
||||||
@@ -382,13 +382,17 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
|||||||
options := options;
|
options := options;
|
||||||
if .info in options {
|
if .info in options {
|
||||||
options |= {.return_metadata, .do_not_decompress_image};
|
options |= {.return_metadata, .do_not_decompress_image};
|
||||||
options ~= {.info};
|
options -= {.info};
|
||||||
}
|
}
|
||||||
|
|
||||||
if .alpha_drop_if_present in options && .alpha_add_if_missing in options {
|
if .alpha_drop_if_present in options && .alpha_add_if_missing in options {
|
||||||
return {}, E_General.Incompatible_Options;
|
return {}, E_General.Incompatible_Options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if .do_not_expand_channels in options {
|
||||||
|
options |= {.do_not_expand_grayscale, .do_not_expand_indexed};
|
||||||
|
}
|
||||||
|
|
||||||
if img == nil {
|
if img == nil {
|
||||||
img = new(Image);
|
img = new(Image);
|
||||||
}
|
}
|
||||||
@@ -723,6 +727,14 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
|||||||
will become the default.
|
will become the default.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if .Paletted in header.color_type && .do_not_expand_indexed in options {
|
||||||
|
return img, E_General.OK;
|
||||||
|
}
|
||||||
|
if .Color not_in header.color_type && .do_not_expand_grayscale in options {
|
||||||
|
return img, E_General.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
raw_image_channels := img.channels;
|
raw_image_channels := img.channels;
|
||||||
out_image_channels := 3;
|
out_image_channels := 3;
|
||||||
|
|
||||||
@@ -1218,6 +1230,19 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c
|
|||||||
unreachable("We should never see bit depths other than 8, 16 and 'Paletted' here.");
|
unreachable("We should never see bit depths other than 8, 16 and 'Paletted' here.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rather than first expanding to RGB(A) and then dropping channels, give these their own path.
|
||||||
|
if .do_not_expand_grayscale in options && .Color not_in info.header.color_type {
|
||||||
|
|
||||||
|
single, single_ok := image.return_single_channel(img, .R);
|
||||||
|
if single_ok {
|
||||||
|
destroy(img);
|
||||||
|
img = single;
|
||||||
|
} else {
|
||||||
|
destroy(single);
|
||||||
|
return img, E_PNG.Post_Processing_Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return img, E_General.OK;
|
return img, E_General.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user