Browse Source

Add new PNG post processing options.

Jeroen van Rijn 4 years ago
parent
commit
7d534769d6
3 changed files with 143 additions and 17 deletions
  1. 1 1
      core/compress/common.odin
  2. 115 14
      core/image/common.odin
  3. 27 2
      core/image/png/png.odin

+ 1 - 1
core/compress/common.odin

@@ -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
core/image/common.odin

@@ -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` +
-		`do_not_decompress_image`, or `.info`, which works as if both of these were set.
+		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`
 	`.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
 */
 */
@@ -104,4 +132,77 @@ 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
core/image/png/png.odin

@@ -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;
 }
 }