diff --git a/Tools/iff2html/CMakeLists.txt b/Tools/iff2html/CMakeLists.txt index 7c7f725..f56a17a 100644 --- a/Tools/iff2html/CMakeLists.txt +++ b/Tools/iff2html/CMakeLists.txt @@ -5,6 +5,7 @@ set(IFF2HTML_SOURCES iff2html.c md5.c image.c + opngreduc.c ../../Libraries/FileHandler/bmp/read_bmp.c ) diff --git a/Tools/iff2html/image.c b/Tools/iff2html/image.c index c9e9bee..337f0ee 100644 --- a/Tools/iff2html/image.c +++ b/Tools/iff2html/image.c @@ -21,6 +21,7 @@ #include #include #include +#include "opngreduc.h" int WritePNG(const char * OutName, const IFFChunk * ChunkData, const IFFSprite * Sprite, size_t * Width, size_t * Height){ FILE * hFile; @@ -121,6 +122,7 @@ int WritePNG(const char * OutName, const IFFChunk * ChunkData, const IFFSprite * PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_rows(png_ptr, info_ptr, row_pointers); + opng_reduce_image(png_ptr, info_ptr, OPNG_REDUCE_ALL); png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL); png_destroy_write_struct(&png_ptr, &info_ptr); diff --git a/Tools/iff2html/opngreduc.c b/Tools/iff2html/opngreduc.c new file mode 100644 index 0000000..538cb7b --- /dev/null +++ b/Tools/iff2html/opngreduc.c @@ -0,0 +1,1311 @@ +/* + * opngreduc.c - libpng extension: lossless image reductions. + * + * Copyright (C) 2003-2011 Cosmin Truta. + * This software is distributed under the same licensing and warranty terms + * as libpng. + */ + +/* CAUTION: + * Image reductions do not work well under certain transformations. + * + * Transformations like PNG_BGR, PNG_SWAP_BYTES, PNG_FILLER, PNG_INVERT_ALPHA, + * and possibly others, require special treatment. However, the libpng API + * does not currently convey the effect of transformations on its internal + * state or on the layout of pixel data. + * + * Transformations which affect pixel depth (e.g. PNG_FILLER) are especially + * dangerous when used in conjunction with this code, and should be avoided. + */ + +#include "opngreduc.h" + +#ifndef OPNG_ASSERT +#include +#define OPNG_ASSERT(cond) assert(cond) +#define OPNG_ASSERT_MSG(cond, msg) assert(cond) +#endif + +#ifdef png_debug +#define opng_debug(level, msg) png_debug(level, msg) +#else +#define opng_debug(level, msg) ((void)0) +#endif + + +#ifdef PNG_INFO_IMAGE_SUPPORTED + +/* + * Check if the image information is valid. + * The image information is said to be valid if all the required + * critical chunk data is present in the png structures. + * The function returns 1 if this information is valid, and 0 otherwise. + */ +int PNGAPI +opng_validate_image(png_structp png_ptr, png_infop info_ptr) +{ + opng_debug(1, "in opng_validate_image"); + + /* Validate IHDR. */ + if (png_get_bit_depth(png_ptr, info_ptr) == 0) + return 0; + + /* Validate PLTE. */ + if (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_PALETTE) + { + if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)) + return 0; + } + + /* Validate IDAT. */ + if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_IDAT)) + return 0; + + return 1; +} + +#endif /* PNG_INFO_IMAGE_SUPPORTED */ + + +#ifdef OPNG_IMAGE_REDUCTIONS_SUPPORTED + +#define OPNG_CMP_RGB(R1, G1, B1, R2, G2, B2) \ + (((int)(R1) != (int)(R2)) ? \ + ((int)(R1) - (int)(R2)) : \ + (((int)(G1) != (int)(G2)) ? \ + ((int)(G1) - (int)(G2)) : \ + ((int)(B1) - (int)(B2)))) + +#define OPNG_CMP_ARGB(A1, R1, G1, B1, A2, R2, G2, B2) \ + (((int)(A1) != (int)(A2)) ? \ + ((int)(A1) - (int)(A2)) : \ + (((int)(R1) != (R2)) ? \ + ((int)(R1) - (int)(R2)) : \ + (((int)(G1) != (int)(G2)) ? \ + ((int)(G1) - (int)(G2)) : \ + ((int)(B1) - (int)(B2))))) + +/* + * Build a color+alpha palette in which the entries are sorted by + * (alpha, red, green, blue), in this particular order. + * Use the insertion sort algorithm. + * The alpha value is ignored if it is not in the range [0 .. 255]. + * The function returns: + * 1 if the insertion is successful; *index = position of new entry. + * 0 if the insertion is unnecessary; *index = position of crt entry. + * -1 if overflow; *num_palette = *num_trans = *index = -1. + */ +static int /* PRIVATE */ +opng_insert_palette_entry(png_colorp palette, int *num_palette, + png_bytep trans_alpha, int *num_trans, int max_tuples, + unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha, + int *index) +{ + int low, high, mid, cmp; + int i; + + OPNG_ASSERT(*num_palette >= 0 && *num_palette <= max_tuples); + OPNG_ASSERT(*num_trans >= 0 && *num_trans <= *num_palette); + + if (alpha < 255) + { + /* Do a binary search among transparent tuples. */ + low = 0; + high = *num_trans - 1; + while (low <= high) + { + mid = (low + high) / 2; + cmp = OPNG_CMP_ARGB(alpha, red, green, blue, + trans_alpha[mid], + palette[mid].red, palette[mid].green, palette[mid].blue); + if (cmp < 0) + high = mid - 1; + else if (cmp > 0) + low = mid + 1; + else + { + *index = mid; + return 0; + } + } + } + else /* alpha == 255 || alpha not in [0 .. 255] */ + { + /* Do a (faster) binary search among opaque tuples. */ + low = *num_trans; + high = *num_palette - 1; + while (low <= high) + { + mid = (low + high) / 2; + cmp = OPNG_CMP_RGB(red, green, blue, + palette[mid].red, palette[mid].green, palette[mid].blue); + if (cmp < 0) + high = mid - 1; + else if (cmp > 0) + low = mid + 1; + else + { + *index = mid; + return 0; + } + } + } + if (alpha > 255) + { + /* The binary search among opaque tuples has failed. */ + /* Do a linear search among transparent tuples, ignoring alpha. */ + for (i = 0; i < *num_trans; ++i) + { + cmp = OPNG_CMP_RGB(red, green, blue, + palette[i].red, palette[i].green, palette[i].blue); + if (cmp == 0) + { + *index = i; + return 0; + } + } + } + + /* Check for overflow. */ + if (*num_palette >= max_tuples) + { + *num_palette = *num_trans = *index = -1; + return -1; + } + + /* Insert new tuple at [low]. */ + OPNG_ASSERT(low >= 0 && low <= *num_palette); + for (i = *num_palette; i > low; --i) + palette[i] = palette[i - 1]; + palette[low].red = (png_byte)red; + palette[low].green = (png_byte)green; + palette[low].blue = (png_byte)blue; + ++(*num_palette); + if (alpha < 255) + { + OPNG_ASSERT(low <= *num_trans); + for (i = *num_trans; i > low; --i) + trans_alpha[i] = trans_alpha[i - 1]; + trans_alpha[low] = (png_byte)alpha; + ++(*num_trans); + } + *index = low; + return 1; +} + +/* + * Retrieve the alpha samples from the given image row. + */ +static void /* PRIVATE */ +opng_get_alpha_row(png_row_infop row_info_ptr, png_color_16p trans_color, + png_bytep row, png_bytep alpha_row) +{ + png_bytep sample_ptr; + png_uint_32 width; + int color_type, bit_depth, channels; + png_byte trans_red, trans_green, trans_blue, trans_gray; + png_uint_32 i; + + width = row_info_ptr->width; + color_type = row_info_ptr->color_type; + bit_depth = row_info_ptr->bit_depth; + channels = row_info_ptr->channels; + + OPNG_ASSERT(!(color_type & PNG_COLOR_MASK_PALETTE)); + OPNG_ASSERT(bit_depth == 8); + + if (!(color_type & PNG_COLOR_MASK_ALPHA)) + { + if (trans_color == NULL) + { + /* All pixels are fully opaque. */ + memset(alpha_row, 255, (size_t)width); + return; + } + if (color_type == PNG_COLOR_TYPE_RGB) + { + OPNG_ASSERT(channels == 3); + trans_red = (png_byte)trans_color->red; + trans_green = (png_byte)trans_color->green; + trans_blue = (png_byte)trans_color->blue; + sample_ptr = row; + for (i = 0; i < width; ++i, sample_ptr += 3) + alpha_row[i] = (png_byte) + ((sample_ptr[0] == trans_red && + sample_ptr[1] == trans_green && + sample_ptr[2] == trans_blue) ? 0 : 255); + } + else + { + OPNG_ASSERT(color_type == PNG_COLOR_TYPE_GRAY); + OPNG_ASSERT(channels == 1); + trans_gray = (png_byte)trans_color->gray; + for (i = 0; i < width; ++i) + alpha_row[i] = (png_byte)((row[i] == trans_gray) ? 0 : 255); + } + return; + } + + /* There is a real alpha channel. The alpha sample is last in RGBA tuple. */ + OPNG_ASSERT(channels > 1); + sample_ptr = row + (channels - 1); + for (i = 0; i < width; ++i, sample_ptr += channels, ++alpha_row) + *alpha_row = *sample_ptr; +} + +/* + * Analyze the redundancy of bits inside the image. + * The parameter reductions indicates the intended reductions. + * The function returns the possible reductions. + */ +static png_uint_32 /* PRIVATE */ +opng_analyze_bits(png_structp png_ptr, png_infop info_ptr, + png_uint_32 reductions) +{ + png_bytepp row_ptr; + png_bytep component_ptr; + png_uint_32 height, width; + int bit_depth, color_type, byte_depth, channels, sample_size, offset_alpha; +#ifdef PNG_bKGD_SUPPORTED + png_color_16p background; +#endif + png_uint_32 i, j; + + opng_debug(1, "in opng_analyze_bits"); + + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + NULL, NULL, NULL); + if (bit_depth < 8) + return OPNG_REDUCE_NONE; /* not applicable */ + if (color_type & PNG_COLOR_MASK_PALETTE) + return OPNG_REDUCE_NONE; /* let opng_reduce_palette() handle it */ + + byte_depth = bit_depth / 8; + channels = png_get_channels(png_ptr, info_ptr); + sample_size = channels * byte_depth; + offset_alpha = (channels - 1) * byte_depth; + + /* Select the applicable reductions. */ + reductions &= (OPNG_REDUCE_16_TO_8 | + OPNG_REDUCE_RGB_TO_GRAY | OPNG_REDUCE_STRIP_ALPHA); + if (bit_depth <= 8) + reductions &= ~OPNG_REDUCE_16_TO_8; + if (!(color_type & PNG_COLOR_MASK_COLOR)) + reductions &= ~OPNG_REDUCE_RGB_TO_GRAY; + if (!(color_type & PNG_COLOR_MASK_ALPHA)) + reductions &= ~OPNG_REDUCE_STRIP_ALPHA; + + /* Check if the ancillary information allows these reductions. */ +#ifdef PNG_bKGD_SUPPORTED + if (png_get_bKGD(png_ptr, info_ptr, &background)) + { + if (reductions & OPNG_REDUCE_16_TO_8) + { + if (background->red % 257 != 0 || + background->green % 257 != 0 || + background->blue % 257 != 0 || + background->gray % 257 != 0) + reductions &= ~OPNG_REDUCE_16_TO_8; + } + if (reductions & OPNG_REDUCE_RGB_TO_GRAY) + { + if (background->red != background->green || + background->red != background->blue) + reductions &= ~OPNG_REDUCE_RGB_TO_GRAY; + } + } +#endif + + /* Check for each possible reduction, row by row. */ + row_ptr = png_get_rows(png_ptr, info_ptr); + for (i = 0; i < height; ++i, ++row_ptr) + { + if (reductions == OPNG_REDUCE_NONE) + return OPNG_REDUCE_NONE; /* no need to go any further */ + + /* Check if it is possible to reduce the bit depth to 8. */ + if (reductions & OPNG_REDUCE_16_TO_8) + { + component_ptr = *row_ptr; + for (j = 0; j < channels * width; ++j, component_ptr += 2) + { + if (component_ptr[0] != component_ptr[1]) + { + reductions &= ~OPNG_REDUCE_16_TO_8; + break; + } + } + } + + if (bit_depth == 8) + { + /* Check if it is possible to reduce rgb --> gray. */ + if (reductions & OPNG_REDUCE_RGB_TO_GRAY) + { + component_ptr = *row_ptr; + for (j = 0; j < width; ++j, component_ptr += sample_size) + { + if (component_ptr[0] != component_ptr[1] || + component_ptr[0] != component_ptr[2]) + { + reductions &= ~OPNG_REDUCE_RGB_TO_GRAY; + break; + } + } + } + + /* Check if it is possible to strip the alpha channel. */ + if (reductions & OPNG_REDUCE_STRIP_ALPHA) + { + component_ptr = *row_ptr + offset_alpha; + for (j = 0; j < width; ++j, component_ptr += sample_size) + { + if (component_ptr[0] != 255) + { + reductions &= ~OPNG_REDUCE_STRIP_ALPHA; + break; + } + } + } + } + else /* bit_depth == 16 */ + { + /* Check if it is possible to reduce rgb --> gray. */ + if (reductions & OPNG_REDUCE_RGB_TO_GRAY) + { + component_ptr = *row_ptr; + for (j = 0; j < width; ++j, component_ptr += sample_size) + { + if (component_ptr[0] != component_ptr[2] || + component_ptr[0] != component_ptr[4] || + component_ptr[1] != component_ptr[3] || + component_ptr[1] != component_ptr[5]) + { + reductions &= ~OPNG_REDUCE_RGB_TO_GRAY; + break; + } + } + } + + /* Check if it is possible to strip the alpha channel. */ + if (reductions & OPNG_REDUCE_STRIP_ALPHA) + { + component_ptr = *row_ptr + offset_alpha; + for (j = 0; j < width; ++j, component_ptr += sample_size) + { + if (component_ptr[0] != 255 || component_ptr[1] != 255) + { + reductions &= ~OPNG_REDUCE_STRIP_ALPHA; + break; + } + } + } + } + } + + return reductions; +} + +/* + * Reduce the image type to a lower bit depth and color type, + * by removing redundant bits. + * Possible reductions: 16bpp to 8bpp; RGB to gray; strip alpha. + * The parameter reductions indicates the intended reductions. + * The function returns the successful reductions. + * All reductions are performed in a single step. + */ +static png_uint_32 /* PRIVATE */ +opng_reduce_bits(png_structp png_ptr, png_infop info_ptr, + png_uint_32 reductions) +{ + png_bytepp row_ptr; + png_bytep src_ptr, dest_ptr; + png_uint_32 width, height; + int interlace_type, compression_type, filter_type; + int src_bit_depth, dest_bit_depth; + int src_byte_depth, dest_byte_depth; + int src_color_type, dest_color_type; + int src_channels, dest_channels; + int src_sample_size, dest_sample_size; + int tran_tbl[8]; + png_color_16p trans_color; +#ifdef PNG_bKGD_SUPPORTED + png_color_16p background; +#endif +#ifdef PNG_sBIT_SUPPORTED + png_color_8p sig_bits; +#endif + png_uint_32 i, j; + int k; + + opng_debug(1, "in opng_reduce_bits"); + + /* See which reductions may be performed. */ + reductions = opng_analyze_bits(png_ptr, info_ptr, reductions); + if (reductions == OPNG_REDUCE_NONE) + return OPNG_REDUCE_NONE; /* exit early */ + + png_get_IHDR(png_ptr, info_ptr, &width, &height, + &src_bit_depth, &src_color_type, + &interlace_type, &compression_type, &filter_type); + + /* Compute the new image parameters bit_depth, color_type, etc. */ + OPNG_ASSERT(src_bit_depth >= 8); + if (reductions & OPNG_REDUCE_16_TO_8) + { + OPNG_ASSERT(src_bit_depth == 16); + dest_bit_depth = 8; + } + else + dest_bit_depth = src_bit_depth; + + src_byte_depth = src_bit_depth / 8; + dest_byte_depth = dest_bit_depth / 8; + + dest_color_type = src_color_type; + if (reductions & OPNG_REDUCE_RGB_TO_GRAY) + { + OPNG_ASSERT(src_color_type & PNG_COLOR_MASK_COLOR); + dest_color_type &= ~PNG_COLOR_MASK_COLOR; + } + if (reductions & OPNG_REDUCE_STRIP_ALPHA) + { + OPNG_ASSERT(src_color_type & PNG_COLOR_MASK_ALPHA); + dest_color_type &= ~PNG_COLOR_MASK_ALPHA; + } + + src_channels = png_get_channels(png_ptr, info_ptr); + dest_channels = + ((dest_color_type & PNG_COLOR_MASK_COLOR) ? 3 : 1) + + ((dest_color_type & PNG_COLOR_MASK_ALPHA) ? 1 : 0); + + src_sample_size = src_channels * src_byte_depth; + dest_sample_size = dest_channels * dest_byte_depth; + + /* Pre-compute the intra-sample translation table. */ + for (k = 0; k < 4 * dest_byte_depth; ++k) + tran_tbl[k] = k * src_bit_depth / dest_bit_depth; + /* If rgb --> gray, shift the alpha component two positions to the left. */ + if ((reductions & OPNG_REDUCE_RGB_TO_GRAY) && + (dest_color_type & PNG_COLOR_MASK_ALPHA)) + { + tran_tbl[dest_byte_depth] = tran_tbl[3 * dest_byte_depth]; + if (dest_byte_depth == 2) + tran_tbl[dest_byte_depth + 1] = tran_tbl[3 * dest_byte_depth + 1]; + } + + /* Translate the samples to the new image type. */ + OPNG_ASSERT(src_sample_size > dest_sample_size); + row_ptr = png_get_rows(png_ptr, info_ptr); + for (i = 0; i < height; ++i, ++row_ptr) + { + src_ptr = dest_ptr = *row_ptr; + for (j = 0; j < width; ++j) + { + for (k = 0; k < dest_sample_size; ++k) + dest_ptr[k] = src_ptr[tran_tbl[k]]; + src_ptr += src_sample_size; + dest_ptr += dest_sample_size; + } + } + + /* Update the ancillary information. */ + if (png_get_tRNS(png_ptr, info_ptr, NULL, NULL, &trans_color)) + { + if (reductions & OPNG_REDUCE_16_TO_8) + { + if (trans_color->red % 257 == 0 && + trans_color->green % 257 == 0 && + trans_color->blue % 257 == 0 && + trans_color->gray % 257 == 0) + { + trans_color->red &= 255; + trans_color->green &= 255; + trans_color->blue &= 255; + trans_color->gray &= 255; + } + else + { + /* 16-bit tRNS in 8-bit samples: all pixels are 100% opaque. */ + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, -1); + png_set_invalid(png_ptr, info_ptr, PNG_INFO_tRNS); + } + } + if (reductions & OPNG_REDUCE_RGB_TO_GRAY) + { + if (trans_color->red == trans_color->green || + trans_color->red == trans_color->blue) + trans_color->gray = trans_color->red; + else + { + /* Non-gray tRNS in grayscale image: all pixels are 100% opaque. */ + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, -1); + png_set_invalid(png_ptr, info_ptr, PNG_INFO_tRNS); + } + } + } +#ifdef PNG_bKGD_SUPPORTED + if (png_get_bKGD(png_ptr, info_ptr, &background)) + { + if (reductions & OPNG_REDUCE_16_TO_8) + { + background->red &= 255; + background->green &= 255; + background->blue &= 255; + background->gray &= 255; + } + if (reductions & OPNG_REDUCE_RGB_TO_GRAY) + background->gray = background->red; + } +#endif +#ifdef PNG_sBIT_SUPPORTED + if (png_get_sBIT(png_ptr, info_ptr, &sig_bits)) + { + if (reductions & OPNG_REDUCE_16_TO_8) + { + if (sig_bits->red > 8) + sig_bits->red = 8; + if (sig_bits->green > 8) + sig_bits->green = 8; + if (sig_bits->blue > 8) + sig_bits->blue = 8; + if (sig_bits->gray > 8) + sig_bits->gray = 8; + if (sig_bits->alpha > 8) + sig_bits->alpha = 8; + } + if (reductions & OPNG_REDUCE_RGB_TO_GRAY) + { + png_byte max_sig_bits = sig_bits->red; + if (max_sig_bits < sig_bits->green) + max_sig_bits = sig_bits->green; + if (max_sig_bits < sig_bits->blue) + max_sig_bits = sig_bits->blue; + sig_bits->gray = max_sig_bits; + } + } +#endif + + /* Update the image information. */ + png_set_IHDR(png_ptr, info_ptr, width, height, + dest_bit_depth, dest_color_type, + interlace_type, compression_type, filter_type); + + return reductions; +} + +/* + * Reduce the bit depth of a palette image to the lowest possible value. + * The parameter reductions should contain OPNG_REDUCE_8_TO_4_2_1. + * The function returns OPNG_REDUCE_8_TO_4_2_1 if successful. + */ +static png_uint_32 /* PRIVATE */ +opng_reduce_palette_bits(png_structp png_ptr, png_infop info_ptr, + png_uint_32 reductions) +{ + png_bytepp row_ptr; + png_bytep src_sample_ptr, dest_sample_ptr; + png_uint_32 width, height; + int color_type, interlace_type, compression_type, filter_type; + int src_bit_depth, dest_bit_depth; + unsigned int src_mask_init, src_mask, src_shift, dest_shift; + unsigned int sample, dest_buf; + png_colorp palette; + int num_palette; + png_uint_32 i, j; + + opng_debug(1, "in opng_reduce_palette_bits"); + + /* Check if the reduction applies. */ + if (!(reductions & OPNG_REDUCE_8_TO_4_2_1)) + return OPNG_REDUCE_NONE; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &src_bit_depth, + &color_type, &interlace_type, &compression_type, &filter_type); + if (color_type != PNG_COLOR_TYPE_PALETTE) + return OPNG_REDUCE_NONE; + if (!png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) + num_palette = 0; + + /* Find the smallest possible bit depth. */ + if (num_palette > 16) + return OPNG_REDUCE_NONE; + else if (num_palette > 4) /* 5 .. 16 entries */ + dest_bit_depth = 4; + else if (num_palette > 2) /* 3 or 4 entries */ + dest_bit_depth = 2; + else /* 1 or 2 entries */ + { + OPNG_ASSERT(num_palette > 0); + dest_bit_depth = 1; + } + + if (src_bit_depth <= dest_bit_depth) + { + OPNG_ASSERT(src_bit_depth == dest_bit_depth); + return OPNG_REDUCE_NONE; + } + + /* Iterate through all sample values. */ + row_ptr = png_get_rows(png_ptr, info_ptr); + if (src_bit_depth == 8) + { + for (i = 0; i < height; ++i, ++row_ptr) + { + src_sample_ptr = dest_sample_ptr = *row_ptr; + dest_shift = 8; + dest_buf = 0; + for (j = 0; j < width; ++j) + { + dest_shift -= dest_bit_depth; + if (dest_shift > 0) + dest_buf |= *src_sample_ptr << dest_shift; + else + { + *dest_sample_ptr++ = (png_byte)(dest_buf | *src_sample_ptr); + dest_shift = 8; + dest_buf = 0; + } + ++src_sample_ptr; + } + if (dest_shift != 0) + *dest_sample_ptr = (png_byte)dest_buf; + } + } + else /* src_bit_depth < 8 */ + { + src_mask_init = (1 << (8 + src_bit_depth)) - (1 << 8); + for (i = 0; i < height; ++i, ++row_ptr) + { + src_sample_ptr = dest_sample_ptr = *row_ptr; + src_shift = dest_shift = 8; + src_mask = src_mask_init; + dest_buf = 0; + for (j = 0; j < width; ++j) + { + src_shift -= src_bit_depth; + src_mask >>= src_bit_depth; + sample = (*src_sample_ptr & src_mask) >> src_shift; + dest_shift -= dest_bit_depth; + if (dest_shift > 0) + dest_buf |= sample << dest_shift; + else + { + *dest_sample_ptr++ = (png_byte)(dest_buf | sample); + dest_shift = 8; + dest_buf = 0; + } + if (src_shift == 0) + { + src_shift = 8; + src_mask = src_mask_init; + ++src_sample_ptr; + } + } + if (dest_shift != 0) + *dest_sample_ptr = (png_byte)dest_buf; + } + } + + /* Update the image information. */ + png_set_IHDR(png_ptr, info_ptr, width, height, dest_bit_depth, + color_type, interlace_type, compression_type, filter_type); + return OPNG_REDUCE_8_TO_4_2_1; +} + +/* + * Reduce the image type from grayscale(+alpha) or RGB(+alpha) to palette, + * if possible. + * The parameter reductions indicates the intended reductions. + * The function returns the successful reductions. + */ +static png_uint_32 /* PRIVATE */ +opng_reduce_to_palette(png_structp png_ptr, png_infop info_ptr, + png_uint_32 reductions) +{ + png_uint_32 result; + png_row_info row_info; + png_bytepp row_ptr; + png_bytep sample_ptr, alpha_row; + png_uint_32 height, width; + int color_type, interlace_type, compression_type, filter_type; + int src_bit_depth, dest_bit_depth, channels; + png_color palette[256]; + png_byte trans_alpha[256]; + png_color_16p trans_color; + int num_palette, num_trans, index; + unsigned int gray, red, green, blue, alpha; + unsigned int prev_gray, prev_red, prev_green, prev_blue, prev_alpha; +#ifdef PNG_bKGD_SUPPORTED + png_color_16p background; +#endif + png_uint_32 i, j; + + opng_debug(1, "in opng_reduce_to_palette"); + + png_get_IHDR(png_ptr, info_ptr, &width, &height, &src_bit_depth, + &color_type, &interlace_type, &compression_type, &filter_type); + if (src_bit_depth != 8) + return OPNG_REDUCE_NONE; /* nothing is done in this case */ + OPNG_ASSERT(!(color_type & PNG_COLOR_MASK_PALETTE)); + + row_ptr = png_get_rows(png_ptr, info_ptr); + channels = png_get_channels(png_ptr, info_ptr); + alpha_row = (png_bytep)png_malloc(png_ptr, width); + + row_info.width = width; + row_info.rowbytes = 0; /* not used */ + row_info.color_type = (png_byte)color_type; + row_info.bit_depth = (png_byte)src_bit_depth; + row_info.channels = (png_byte)channels; + row_info.pixel_depth = 0; /* not used */ + + /* Analyze the possibility of this reduction. */ + num_palette = num_trans = 0; + trans_color = NULL; + png_get_tRNS(png_ptr, info_ptr, NULL, NULL, &trans_color); + prev_gray = prev_red = prev_green = prev_blue = prev_alpha = 256; + for (i = 0; i < height; ++i, ++row_ptr) + { + sample_ptr = *row_ptr; + opng_get_alpha_row(&row_info, trans_color, *row_ptr, alpha_row); + if (color_type & PNG_COLOR_MASK_COLOR) + { + for (j = 0; j < width; ++j, sample_ptr += channels) + { + red = sample_ptr[0]; + green = sample_ptr[1]; + blue = sample_ptr[2]; + alpha = alpha_row[j]; + /* Check the cache first. */ + if (red != prev_red || green != prev_green || blue != prev_blue || + alpha != prev_alpha) + { + prev_red = red; + prev_green = green; + prev_blue = blue; + prev_alpha = alpha; + if (opng_insert_palette_entry(palette, &num_palette, + trans_alpha, &num_trans, 256, + red, green, blue, alpha, &index) < 0) /* overflow */ + { + OPNG_ASSERT(num_palette < 0); + i = height; /* forced exit from outer loop */ + break; + } + } + } + } + else /* grayscale */ + { + for (j = 0; j < width; ++j, sample_ptr += channels) + { + gray = sample_ptr[0]; + alpha = alpha_row[j]; + /* Check the cache first. */ + if (gray != prev_gray || alpha != prev_alpha) + { + prev_gray = gray; + prev_alpha = alpha; + if (opng_insert_palette_entry(palette, &num_palette, + trans_alpha, &num_trans, 256, + gray, gray, gray, alpha, &index) < 0) /* overflow */ + { + OPNG_ASSERT(num_palette < 0); + i = height; /* forced exit from outer loop */ + break; + } + } + } + } + } +#ifdef PNG_bKGD_SUPPORTED + if ((num_palette >= 0) && png_get_bKGD(png_ptr, info_ptr, &background)) + { + /* bKGD has an alpha-agnostic palette entry. */ + if (color_type & PNG_COLOR_MASK_COLOR) + { + red = background->red; + green = background->green; + blue = background->blue; + } + else + red = green = blue = background->gray; + opng_insert_palette_entry(palette, &num_palette, + trans_alpha, &num_trans, 256, + red, green, blue, 256, &index); + if (index >= 0) + background->index = (png_byte)index; + } +#endif + + /* Continue only if the uncompressed indexed image (pixels + PLTE + tRNS) + * is smaller than the uncompressed RGB(A) image. + * Casual overhead (headers, CRCs, etc.) is ignored. + * + * Compare: + * num_pixels * (src_bit_depth * channels - dest_bit_depth) / 8 + * vs. + * sizeof(PLTE) + sizeof(tRNS) + */ + if (num_palette >= 0) + { + OPNG_ASSERT(num_palette > 0 && num_palette <= 256); + OPNG_ASSERT(num_trans >= 0 && num_trans <= num_palette); + if (num_palette <= 2) + dest_bit_depth = 1; + else if (num_palette <= 4) + dest_bit_depth = 2; + else if (num_palette <= 16) + dest_bit_depth = 4; + else + dest_bit_depth = 8; + /* Do the comparison in a way that does not cause overflow. */ + if (channels * 8 == dest_bit_depth || + (3 * num_palette + num_trans) * 8 / (channels * 8 - dest_bit_depth) + / width / height >= 1) + num_palette = -1; + } + + if (num_palette < 0) /* can't reduce */ + { + png_free(png_ptr, alpha_row); + return OPNG_REDUCE_NONE; + } + + /* Reduce. */ + row_ptr = png_get_rows(png_ptr, info_ptr); + index = -1; + prev_red = prev_green = prev_blue = prev_alpha = (unsigned int)(-1); + for (i = 0; i < height; ++i, ++row_ptr) + { + sample_ptr = *row_ptr; + opng_get_alpha_row(&row_info, trans_color, *row_ptr, alpha_row); + if (color_type & PNG_COLOR_MASK_COLOR) + { + for (j = 0; j < width; ++j, sample_ptr += channels) + { + red = sample_ptr[0]; + green = sample_ptr[1]; + blue = sample_ptr[2]; + alpha = alpha_row[j]; + /* Check the cache first. */ + if (red != prev_red || green != prev_green || blue != prev_blue || + alpha != prev_alpha) + { + prev_red = red; + prev_green = green; + prev_blue = blue; + prev_alpha = alpha; + if (opng_insert_palette_entry(palette, &num_palette, + trans_alpha, &num_trans, 256, + red, green, blue, alpha, &index) != 0) + index = -1; /* this should not happen */ + } + OPNG_ASSERT(index >= 0); + (*row_ptr)[j] = (png_byte)index; + } + } + else /* grayscale */ + { + for (j = 0; j < width; ++j, sample_ptr += channels) + { + gray = sample_ptr[0]; + alpha = alpha_row[j]; + /* Check the cache first. */ + if (gray != prev_gray || alpha != prev_alpha) + { + prev_gray = gray; + prev_alpha = alpha; + if (opng_insert_palette_entry(palette, &num_palette, + trans_alpha, &num_trans, 256, + gray, gray, gray, alpha, &index) != 0) + index = -1; /* this should not happen */ + } + OPNG_ASSERT(index >= 0); + (*row_ptr)[j] = (png_byte)index; + } + } + } + + /* Update the image information. */ + png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE, + interlace_type, compression_type, filter_type); + png_set_PLTE(png_ptr, info_ptr, palette, num_palette); + if (num_trans > 0) + png_set_tRNS(png_ptr, info_ptr, trans_alpha, num_trans, NULL); + /* bKGD (if present) is automatically updated. */ + + png_free(png_ptr, alpha_row); + + result = OPNG_REDUCE_RGB_TO_PALETTE; + if (reductions & OPNG_REDUCE_8_TO_4_2_1) + result |= opng_reduce_palette_bits(png_ptr, info_ptr, reductions); + return result; +} + +/* + * Analyze the usage of samples. + * The output value usage_map[n] indicates whether the sample n + * is used. The usage_map[] array must have 256 entries. + * The function requires a valid bit depth between 1 and 8. + */ +static void /* PRIVATE */ +opng_analyze_sample_usage(png_structp png_ptr, png_infop info_ptr, + png_bytep usage_map) +{ + png_bytepp row_ptr; + png_bytep sample_ptr; + png_uint_32 width, height; + int bit_depth, init_shift, init_mask, shift, mask; +#ifdef PNG_bKGD_SUPPORTED + png_color_16p background; +#endif + png_uint_32 i, j; + + opng_debug(1, "in opng_analyze_sample_usage"); + + height = png_get_image_height(png_ptr, info_ptr); + width = png_get_image_width(png_ptr, info_ptr); + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + row_ptr = png_get_rows(png_ptr, info_ptr); + + /* Initialize the output entries with 0. */ + memset(usage_map, 0, 256); + + /* Iterate through all sample values. */ + if (bit_depth == 8) + { + for (i = 0; i < height; ++i, ++row_ptr) + { + for (j = 0, sample_ptr = *row_ptr; j < width; ++j, ++sample_ptr) + usage_map[*sample_ptr] = 1; + } + } + else + { + OPNG_ASSERT(bit_depth < 8); + init_shift = 8 - bit_depth; + init_mask = (1 << 8) - (1 << init_shift); + for (i = 0; i < height; ++i, ++row_ptr) + { + for (j = 0, sample_ptr = *row_ptr; j < width; ++sample_ptr) + { + mask = init_mask; + shift = init_shift; + do + { + usage_map[(*sample_ptr & mask) >> shift] = 1; + mask >>= bit_depth; + shift -= bit_depth; + ++j; + } while (mask > 0 && j < width); + } + } + } + +#ifdef PNG_bKGD_SUPPORTED + /* bKGD also counts as a used sample. */ + if (png_get_bKGD(png_ptr, info_ptr, &background)) + usage_map[background->index] = 1; +#endif +} + +/* + * Set the number of PLTE entries to a new value. + * Setting info_ptr->num_palette to num_palette, avoiding the temporary buffer, + * should have been sufficient, but can't be done using the current libpng API. + */ +static void /* PRIVATE */ +opng_set_num_palette(png_structp png_ptr, png_infop info_ptr, int num_palette) +{ + png_color buffer[PNG_MAX_PALETTE_LENGTH]; + png_colorp palette; + int src_num_palette; + + opng_debug(1, "in opng_set_num_palette"); + + OPNG_ASSERT(num_palette > 0); + src_num_palette = 0; + png_get_PLTE(png_ptr, info_ptr, &palette, &src_num_palette); + if (num_palette == src_num_palette) + return; + memcpy(buffer, palette, num_palette * sizeof(png_color)); + if (num_palette > src_num_palette) + memset(buffer + src_num_palette, 0, + (num_palette - src_num_palette) * sizeof(png_color)); + png_set_PLTE(png_ptr, info_ptr, buffer, num_palette); +} + +/* + * Set the number of tRNS entries to a new value. + * Setting info_ptr->num_trans to num_trans, avoiding the temporary buffer, + * should have been sufficient, but can't be done using the current libpng API. + */ +static void /* PRIVATE */ +opng_set_num_trans(png_structp png_ptr, png_infop info_ptr, int num_trans) +{ + png_byte buffer[PNG_MAX_PALETTE_LENGTH]; + png_bytep trans_alpha; + int src_num_trans; + + opng_debug(1, "in opng_set_num_trans"); + + OPNG_ASSERT(num_trans > 0); /* tRNS should be invalidated in this case */ + src_num_trans = 0; + png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &src_num_trans, NULL); + if (num_trans == src_num_trans) + return; + memcpy(buffer, trans_alpha, (size_t)num_trans); + if (num_trans > src_num_trans) + memset(buffer + src_num_trans, 0, num_trans - src_num_trans); + png_set_tRNS(png_ptr, info_ptr, buffer, num_trans, NULL); +} + +/* + * Reduce the palette. (Only the fast method is implemented.) + * The parameter reductions indicates the intended reductions. + * The function returns the successful reductions. + */ +static png_uint_32 /* PRIVATE */ +opng_reduce_palette(png_structp png_ptr, png_infop info_ptr, + png_uint_32 reductions) +{ + png_uint_32 result; + png_colorp palette; + png_bytep trans_alpha; + png_bytepp row_ptr; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, compression_type, filter_type; + int src_num_palette, src_num_trans; + int last_color_index, last_trans_index; + png_byte crt_trans_value, last_trans_value; + png_byte is_used[256]; + png_color_16 gray_trans; + int is_gray; +#ifdef PNG_bKGD_SUPPORTED + png_color_16p background; +#endif +#ifdef PNG_hIST_SUPPORTED + png_uint_16p hist; +#endif +#ifdef PNG_sBIT_SUPPORTED + png_color_8p sig_bits; +#endif + png_uint_32 i, j; + int k; + + opng_debug(1, "in opng_reduce_palette"); + + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, + &color_type, &interlace_type, &compression_type, &filter_type); + row_ptr = png_get_rows(png_ptr, info_ptr); + if (!png_get_PLTE(png_ptr, info_ptr, &palette, &src_num_palette)) + { + palette = NULL; + src_num_palette = 0; + } + if (!png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &src_num_trans, NULL)) + { + trans_alpha = NULL; + src_num_trans = 0; + } + else + OPNG_ASSERT(trans_alpha != NULL && src_num_trans > 0); + + opng_analyze_sample_usage(png_ptr, info_ptr, is_used); + /* Palette-to-gray does not work (yet) if the bit depth is below 8. */ + is_gray = (reductions & OPNG_REDUCE_PALETTE_TO_GRAY) && (bit_depth == 8); + last_color_index = last_trans_index = -1; + for (k = 0; k < 256; ++k) + { + if (!is_used[k]) + continue; + last_color_index = k; + if (k < src_num_trans && trans_alpha[k] < 255) + last_trans_index = k; + if (is_gray) + if (palette[k].red != palette[k].green || + palette[k].red != palette[k].blue) + is_gray = 0; + } + OPNG_ASSERT(last_color_index >= 0); + OPNG_ASSERT(last_color_index >= last_trans_index); + + /* Check the integrity of PLTE and tRNS. */ + if (last_color_index >= src_num_palette) + { + png_warning(png_ptr, "Too few colors in palette"); + /* Fix the palette by adding blank entries at the end. */ + src_num_palette = last_color_index + 1; + opng_set_num_palette(png_ptr, info_ptr, src_num_palette); + } + if (src_num_trans > src_num_palette) + { + png_warning(png_ptr, "Too many alpha values in tRNS"); + /* Transparency will be fixed further below. */ + } + + /* Check if tRNS can be reduced to grayscale. */ + if (is_gray && last_trans_index >= 0) + { + gray_trans.gray = palette[last_trans_index].red; + last_trans_value = trans_alpha[last_trans_index]; + for (k = 0; k <= last_color_index; ++k) + { + if (!is_used[k]) + continue; + if (k <= last_trans_index) + { + crt_trans_value = trans_alpha[k]; + /* Cannot reduce if different colors have transparency. */ + if (crt_trans_value < 255 && palette[k].red != gray_trans.gray) + { + is_gray = 0; + break; + } + } + else + crt_trans_value = 255; + /* Cannot reduce if same color has multiple transparency levels. */ + if (palette[k].red == gray_trans.gray && + crt_trans_value != last_trans_value) + { + is_gray = 0; + break; + } + } + } + + /* Initialize result value. */ + result = OPNG_REDUCE_NONE; + + /* Remove tRNS if it is entirely sterile. */ + if (src_num_trans > 0 && last_trans_index < 0) + { + src_num_trans = 0; + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, -1); + png_set_invalid(png_ptr, info_ptr, PNG_INFO_tRNS); + result = OPNG_REDUCE_PALETTE_FAST; + } + + if (reductions & OPNG_REDUCE_PALETTE_FAST) + { + if (src_num_palette != last_color_index + 1) + { + /* Reduce PLTE. */ + /* hIST is reduced automatically. */ + opng_set_num_palette(png_ptr, info_ptr, last_color_index + 1); + result = OPNG_REDUCE_PALETTE_FAST; + } + + if (src_num_trans > 0 && src_num_trans != last_trans_index + 1) + { + /* Reduce tRNS. */ + opng_set_num_trans(png_ptr, info_ptr, last_trans_index + 1); + result = OPNG_REDUCE_PALETTE_FAST; + } + } + + if (reductions & OPNG_REDUCE_8_TO_4_2_1) + { + result |= opng_reduce_palette_bits(png_ptr, info_ptr, reductions); + /* Refresh the image information. */ + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + } + if ((bit_depth < 8) || !is_gray) + return result; + + /* Reduce palette --> grayscale. */ + for (i = 0; i < height; ++i) + { + for (j = 0; j < width; ++j) + row_ptr[i][j] = palette[row_ptr[i][j]].red; + } + + /* Update the ancillary information. */ + if (src_num_trans > 0) + png_set_tRNS(png_ptr, info_ptr, NULL, 0, &gray_trans); +#ifdef PNG_bKGD_SUPPORTED + if (png_get_bKGD(png_ptr, info_ptr, &background)) + background->gray = palette[background->index].red; +#endif +#ifdef PNG_hIST_SUPPORTED + if (png_get_hIST(png_ptr, info_ptr, &hist)) + { + png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, -1); + png_set_invalid(png_ptr, info_ptr, PNG_INFO_hIST); + } +#endif +#ifdef PNG_sBIT_SUPPORTED + if (png_get_sBIT(png_ptr, info_ptr, &sig_bits)) + { + png_byte max_sig_bits = sig_bits->red; + if (max_sig_bits < sig_bits->green) + max_sig_bits = sig_bits->green; + if (max_sig_bits < sig_bits->blue) + max_sig_bits = sig_bits->blue; + sig_bits->gray = max_sig_bits; + } +#endif + + /* Update the image information. */ + png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, + PNG_COLOR_TYPE_GRAY, interlace_type, compression_type, filter_type); + png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, -1); + png_set_invalid(png_ptr, info_ptr, PNG_INFO_PLTE); + return OPNG_REDUCE_PALETTE_TO_GRAY; /* ignore the former result */ +} + +/* + * Reduce the image (bit depth + color type + palette) without + * losing any information. The palette (if applicable) and the + * image data must be present (e.g. by calling png_set_rows(), + * or by loading IDAT). + * The parameter reductions indicates the intended reductions. + * The function returns the successful reductions. + */ +png_uint_32 PNGAPI +opng_reduce_image(png_structp png_ptr, png_infop info_ptr, + png_uint_32 reductions) +{ + png_uint_32 result; + int color_type; + + opng_debug(1, "in opng_reduce_image_type"); + + if (!opng_validate_image(png_ptr, info_ptr)) + { + png_warning(png_ptr, + "Image reduction requires the presence of all critical information"); + return OPNG_REDUCE_NONE; + } + + color_type = png_get_color_type(png_ptr, info_ptr); + + /* The reductions below must be applied in this particular order. */ + + /* Try to reduce the high bits and color/alpha channels. */ + result = opng_reduce_bits(png_ptr, info_ptr, reductions); + + /* Try to reduce the palette image. */ + if (color_type == PNG_COLOR_TYPE_PALETTE && + (reductions & + (OPNG_REDUCE_PALETTE_TO_GRAY | + OPNG_REDUCE_PALETTE_FAST | + OPNG_REDUCE_8_TO_4_2_1))) + result |= opng_reduce_palette(png_ptr, info_ptr, reductions); + + /* Try to reduce RGB to palette or grayscale to palette. */ + if (((color_type & ~PNG_COLOR_MASK_ALPHA) == PNG_COLOR_TYPE_GRAY && + (reductions & OPNG_REDUCE_GRAY_TO_PALETTE)) || + ((color_type & ~PNG_COLOR_MASK_ALPHA) == PNG_COLOR_TYPE_RGB && + (reductions & OPNG_REDUCE_RGB_TO_PALETTE))) + { + if (!(result & OPNG_REDUCE_PALETTE_TO_GRAY)) + result |= opng_reduce_to_palette(png_ptr, info_ptr, reductions); + } + + return result; +} + +#endif /* OPNG_IMAGE_REDUCTIONS_SUPPORTED */ diff --git a/Tools/iff2html/opngreduc.h b/Tools/iff2html/opngreduc.h new file mode 100644 index 0000000..b0ab92e --- /dev/null +++ b/Tools/iff2html/opngreduc.h @@ -0,0 +1,97 @@ +/* + * opngreduc.h - libpng extension: lossless image reductions. + * + * Copyright (C) 2003-2011 Cosmin Truta. + * This software is distributed under the same licensing and warranty terms + * as libpng. + * + * This code is functional, although it is still work in progress. + * Upon completion, it will be submitted for incorporation into libpng. + */ + +#ifndef OPNGREDUC_H +#define OPNGREDUC_H + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef PNG_INFO_IMAGE_SUPPORTED + +/* + * Indicate whether the image information is valid, i.e. + * all the required critical information is present in the png structures. + */ +int PNGAPI opng_validate_image(png_structp png_ptr, png_infop info_ptr); + +#endif /* PNG_INFO_IMAGE_SUPPORTED */ + + +#ifndef OPNG_NO_IMAGE_REDUCTIONS +#define OPNG_IMAGE_REDUCTIONS_SUPPORTED +#endif + +#ifdef OPNG_IMAGE_REDUCTIONS_SUPPORTED + +#ifndef PNG_INFO_IMAGE_SUPPORTED +#error OPNG_IMAGE_REDUCTIONS_SUPPORTED requires PNG_INFO_IMAGE_SUPPORTED +#endif + +#ifndef PNG_tRNS_SUPPORTED +#error OPNG_IMAGE_REDUCTIONS_SUPPORTED requires proper transparency support +#endif + +/* + * Reduce the image (bit depth + color type + palette) without + * losing any information. The image data must be present + * (e.g. after calling png_set_rows(), or after loading IDAT). + */ +png_uint_32 PNGAPI opng_reduce_image(png_structp png_ptr, png_infop info_ptr, + png_uint_32 reductions); + +/* + * PNG reduction flags. + */ +#define OPNG_REDUCE_NONE 0x0000 +#define OPNG_REDUCE_16_TO_8 0x0001 /* discard bits 8-15 */ +#define OPNG_REDUCE_8_TO_4_2_1 0x0002 /* discard bits 4-7, 2-7 or 1-7 */ +#define OPNG_REDUCE_RGB_TO_GRAY 0x0004 /* ...also RGBA to GA */ +#define OPNG_REDUCE_STRIP_ALPHA 0x0008 /* ...and create tRNS if needed */ +#define OPNG_REDUCE_RGB_TO_PALETTE 0x0010 /* ...also RGBA to palette/tRNS */ +#define OPNG_REDUCE_PALETTE_TO_RGB 0x0020 /* TODO */ +#define OPNG_REDUCE_GRAY_TO_PALETTE 0x0040 /* ...also GA to palette/tRNS */ +#define OPNG_REDUCE_PALETTE_TO_GRAY 0x0080 /* ...also palette/tRNS to GA */ +#define OPNG_REDUCE_PALETTE_SLOW 0x0100 /* TODO: remove all sterile entries + and reorder PLTE */ +#define OPNG_REDUCE_PALETTE_FAST 0x0200 /* remove trailing sterile entries + only; do not reorder PLTE */ +#define OPNG_REDUCE_ANCILLARY 0x1000 /* TODO */ + +#define OPNG_REDUCE_BIT_DEPTH \ + (OPNG_REDUCE_16_TO_8 | OPNG_REDUCE_8_TO_4_2_1) + +#define OPNG_REDUCE_COLOR_TYPE \ + (OPNG_REDUCE_RGB_TO_GRAY | OPNG_REDUCE_STRIP_ALPHA | \ + OPNG_REDUCE_RGB_TO_PALETTE | OPNG_REDUCE_PALETTE_TO_RGB | \ + OPNG_REDUCE_GRAY_TO_PALETTE | OPNG_REDUCE_PALETTE_TO_GRAY) + +#define OPNG_REDUCE_PALETTE \ + (OPNG_REDUCE_PALETTE_SLOW | OPNG_REDUCE_PALETTE_FAST) + +#define OPNG_REDUCE_ALL \ + (OPNG_REDUCE_BIT_DEPTH | OPNG_REDUCE_COLOR_TYPE | \ + OPNG_REDUCE_PALETTE | OPNG_REDUCE_ANCILLARY) + +#endif /* OPNG_IMAGE_REDUCTIONS_SUPPORTED */ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif /* OPNGREDUC_H */