/* * Input handler */ static void opng_read_data(png_structp png_ptr, png_bytep data, size_t length) { struct opng_codec_context * context = (struct opng_codec_context *)png_get_io_ptr(png_ptr); struct opng_encoding_stats * stats = context->stats; FILE * stream = context->stream; /* Read the data. */ if (fread(data, 1, length, stream) != length) png_error(png_ptr, "Can't read file or unexpected end of file"); if (stats->first == false) /* first piece of PNG data */ { OPNG_ASSERT(length == 8, "PNG I/O must start with the first 8 bytes"); stats->datastream_offset = ftell(stream) - 8; if (stats->datastream_offset < 0) png_error(png_ptr,"Can't get the file-position indicator in file"); stats->first = true; } /* Handle the optipng-specific events. */ if ((png_get_io_state(png_ptr) & PNG_IO_MASK_LOC) == PNG_IO_CHUNK_HDR) { /* In libpng 1.4.x and later, the chunk length and the chunk name * are serialized in a single operation. This is also ensured by * the opngio add-on for libpng 1.2.x and earlier. */ OPNG_ASSERT(length == 8, "Reading chunk header, expecting 8 bytes"); png_bytep chunk_sig = data + 4; if (memcmp(chunk_sig, opng_sig_IDAT, 4) != 0) { opng_handle_chunk(png_ptr, chunk_sig); } } }
/* * Retrieve the alpha samples from the given image row. */ static void /* PRIVATE */ opng_get_alpha_row(png_structp png_ptr, png_infop info_ptr, png_bytep row, png_bytep alpha_row) { png_bytep sample_ptr; png_uint_32 width, i; unsigned int channels; png_color_16p trans_color; OPNG_ASSERT(info_ptr->bit_depth == 8); OPNG_ASSERT(!(info_ptr->color_type & PNG_COLOR_MASK_PALETTE)); width = info_ptr->width; if (!(info_ptr->color_type & PNG_COLOR_MASK_ALPHA)) { if (!(info_ptr->valid & PNG_INFO_tRNS)) { memset(alpha_row, 255, (size_t)width); return; } trans_color = &info_ptr->trans_color; if (info_ptr->color_type == PNG_COLOR_TYPE_RGB) { png_byte trans_red = (png_byte)trans_color->red; png_byte trans_green = (png_byte)trans_color->green; png_byte trans_blue = (png_byte)trans_color->blue; for (i = 0; i < width; ++i) alpha_row[i] = (png_byte) ((row[3*i] == trans_red && row[3*i+1] == trans_green && row[3*i+2] == trans_blue) ? 0 : 255); } else { png_byte trans_gray = (png_byte)trans_color->gray; OPNG_ASSERT(info_ptr->color_type == PNG_COLOR_TYPE_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. */ channels = (png_ptr->usr_channels > 0) ? png_ptr->usr_channels : info_ptr->channels; sample_ptr = row; if (!(png_ptr->transformations & PNG_FILLER) || (png_ptr->flags & PNG_FLAG_FILLER_AFTER)) sample_ptr += channels - 1; /* alpha sample is the last in RGBA tuple */ for (i = 0; i < width; ++i, sample_ptr += channels, ++alpha_row) *alpha_row = *sample_ptr; }
/* * 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. */ 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, i, j; unsigned int bit_depth, init_shift, init_mask, shift, mask; opng_debug(1, "in opng_analyze_sample_usage"); row_ptr = info_ptr->row_pointers; height = info_ptr->height; width = info_ptr->width; if (png_ptr->usr_bit_depth > 0) bit_depth = png_ptr->usr_bit_depth; else bit_depth = info_ptr->bit_depth; /* 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); } } #if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) /* bKGD also counts as a used sample. */ if (info_ptr->valid & PNG_INFO_bKGD) usage_map[info_ptr->background.index] = 1; #endif }
/* * 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 opng_analyze_sample_usage(png_structp png_ptr, png_infop info_ptr, png_bytep usage_map) { png_bytep sample_ptr; int init_shift, init_mask, shift, mask; png_uint_32 i, j; png_uint_32 height = png_get_image_height(png_ptr, info_ptr); png_uint_32 width = png_get_image_width(png_ptr, info_ptr); int bit_depth = png_get_bit_depth(png_ptr, info_ptr); png_bytepp 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 png_color_16p background; /* bKGD also counts as a used sample. */ if (png_get_bKGD(png_ptr, info_ptr, &background)) usage_map[background->index] = 1; #endif }
/* * Retrieve the alpha samples from the given image row. */ static void 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 i; png_uint_32 width = row_info_ptr->width; int color_type = row_info_ptr->color_type; int channels = row_info_ptr->channels; OPNG_ASSERT(!(color_type & PNG_COLOR_MASK_PALETTE)); OPNG_ASSERT(row_info_ptr->bit_depth == 8); if (!(color_type & PNG_COLOR_MASK_ALPHA)) { png_byte trans_red, trans_green, trans_blue, trans_gray; if (!trans_color) { /* 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; }
/* * 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. */ 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_bytepp row_ptr; png_bytep sample_ptr, alpha_row; png_uint_32 height, width, channels, i, j; unsigned int color_type, dest_bit_depth; png_color palette[256]; png_byte trans_alpha[256]; int num_palette, num_trans, index; png_color_16p background; unsigned int gray, red, green, blue, alpha; unsigned int prev_gray, prev_red, prev_green, prev_blue, prev_alpha; opng_debug(1, "in opng_reduce_to_palette"); if (info_ptr->bit_depth != 8) return OPNG_REDUCE_NONE; /* nothing is done in this case */ color_type = info_ptr->color_type; OPNG_ASSERT(!(info_ptr->color_type & PNG_COLOR_MASK_PALETTE)); row_ptr = info_ptr->row_pointers; height = info_ptr->height; width = info_ptr->width; channels = info_ptr->channels; alpha_row = (png_bytep)png_malloc(png_ptr, width); /* Analyze the possibility of this reduction. */ num_palette = num_trans = 0; 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(png_ptr, info_ptr, *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; } } } } } #if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) if ((num_palette >= 0) && (info_ptr->valid & PNG_INFO_bKGD)) { /* bKGD has an alpha-agnostic palette entry. */ background = &info_ptr->background; 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 = info_ptr->row_pointers; 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(png_ptr, info_ptr, *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 info. */ png_ptr->rowbytes = info_ptr->rowbytes = 0; png_ptr->color_type = info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; png_ptr->channels = info_ptr->channels = 1; png_ptr->pixel_depth = info_ptr->pixel_depth = 8; 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 already 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; }
/* * 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. */ 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, i, j; unsigned int src_bit_depth, dest_bit_depth; unsigned int src_mask_init, src_mask, src_shift, dest_shift; unsigned int sample, dest_buf; opng_debug(1, "in opng_reduce_palette_bits"); /* Check if the reduction applies. */ if (!(reductions & OPNG_REDUCE_8_TO_4_2_1) || (info_ptr->color_type != PNG_COLOR_TYPE_PALETTE) || (info_ptr->num_palette > 16)) return OPNG_REDUCE_NONE; row_ptr = info_ptr->row_pointers; height = info_ptr->height; width = info_ptr->width; if (png_ptr->usr_bit_depth > 0) src_bit_depth = png_ptr->usr_bit_depth; else src_bit_depth = info_ptr->bit_depth; /* Find the smallest bit depth. */ OPNG_ASSERT(info_ptr->num_palette > 0); if (info_ptr->num_palette <= 2) dest_bit_depth = 1; else if (info_ptr->num_palette <= 4) dest_bit_depth = 2; else if (info_ptr->num_palette <= 16) dest_bit_depth = 4; else dest_bit_depth = 8; if (dest_bit_depth >= src_bit_depth) return OPNG_REDUCE_NONE; /* Iterate through all sample values. */ 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 info. */ png_ptr->rowbytes = info_ptr->rowbytes = 0; png_ptr->bit_depth = info_ptr->bit_depth = (png_byte)dest_bit_depth; png_ptr->pixel_depth = info_ptr->pixel_depth = (png_byte)dest_bit_depth; return OPNG_REDUCE_8_TO_4_2_1; }
/* * 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. */ 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 height, width, i, j; unsigned int src_bit_depth, dest_bit_depth; unsigned int src_byte_depth, dest_byte_depth; unsigned int src_color_type, dest_color_type; unsigned int src_channels, dest_channels; unsigned int src_sample_size, dest_sample_size; unsigned int dest_pixel_depth; unsigned int src_offset_alpha; unsigned int tran_tbl[8]; unsigned int k; opng_debug(1, "in opng_reduce_bits"); /* See which reductions may be performed. */ reductions = opng_analyze_bits(png_ptr, info_ptr, reductions); /* Strip the filler even if it is not an alpha channel. */ if (png_ptr->transformations & PNG_FILLER) reductions |= OPNG_REDUCE_STRIP_ALPHA; if (reductions == OPNG_REDUCE_NONE) return OPNG_REDUCE_NONE; /* nothing can be reduced */ /* Compute the new image parameters bit_depth, color_type, etc. */ src_bit_depth = info_ptr->bit_depth; 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; src_color_type = dest_color_type = info_ptr->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_ptr->usr_channels > 0) ? png_ptr->usr_channels : info_ptr->channels; 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; dest_pixel_depth = dest_channels * dest_bit_depth; if (!(png_ptr->transformations & PNG_FILLER) || (png_ptr->flags & PNG_FLAG_FILLER_AFTER)) src_offset_alpha = (src_channels - 1) * src_byte_depth; else src_offset_alpha = 0; /* 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 and the alpha channel remains in the right, shift the alpha component two positions to the left. */ if ((reductions & OPNG_REDUCE_RGB_TO_GRAY) && (dest_color_type & PNG_COLOR_MASK_ALPHA) && (src_offset_alpha != 0)) { 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]; } /* If alpha is in the left, and it is being stripped, shift the components that come after it. */ if ((src_channels == 2 || src_channels == 4) /* alpha or filler */ && !(dest_color_type & PNG_COLOR_MASK_ALPHA) && (src_offset_alpha == 0)) { for (k = 0; k < dest_sample_size; ) { if (dest_byte_depth == 1) { tran_tbl[k] = tran_tbl[k + 1]; ++k; } else { tran_tbl[k] = tran_tbl[k + 2]; tran_tbl[k + 1] = tran_tbl[k + 3]; k += 2; } } } /* Translate the samples to the new image type. */ OPNG_ASSERT(src_sample_size > dest_sample_size); row_ptr = info_ptr->row_pointers; height = info_ptr->height; width = info_ptr->width; 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; } } #if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) /* Update the ancillary chunk info. */ if (info_ptr->valid & PNG_INFO_bKGD) { png_color_16p background = &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 #if defined(PNG_sBIT_SUPPORTED) if (info_ptr->valid & PNG_INFO_sBIT) { png_color_8p sig_bits = &info_ptr->sig_bit; if (reductions & OPNG_REDUCE_16_TO_8) { if (sig_bits->red > 8) png_ptr->sig_bit.red = sig_bits->red = 8; if (sig_bits->green > 8) png_ptr->sig_bit.green = sig_bits->green = 8; if (sig_bits->blue > 8) png_ptr->sig_bit.blue = sig_bits->blue = 8; if (sig_bits->gray > 8) png_ptr->sig_bit.gray = sig_bits->gray = 8; if (sig_bits->alpha > 8) png_ptr->sig_bit.alpha = sig_bits->alpha = 8; } if (reductions & OPNG_REDUCE_RGB_TO_GRAY) { png_byte max_sig_bit = sig_bits->red; if (max_sig_bit < sig_bits->green) max_sig_bit = sig_bits->green; if (max_sig_bit < sig_bits->blue) max_sig_bit = sig_bits->blue; png_ptr->sig_bit.gray = sig_bits->gray = max_sig_bit; } } #endif if (info_ptr->valid & PNG_INFO_tRNS) { png_color_16p trans_color = &info_ptr->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); info_ptr->valid &= ~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); info_ptr->valid &= ~PNG_INFO_tRNS; } } } /* Update the image info. */ png_ptr->rowbytes = info_ptr->rowbytes = 0; png_ptr->bit_depth = info_ptr->bit_depth = (png_byte)dest_bit_depth; png_ptr->color_type = info_ptr->color_type = (png_byte)dest_color_type; png_ptr->channels = info_ptr->channels = (png_byte)dest_channels; png_ptr->pixel_depth = info_ptr->pixel_depth = (png_byte)dest_pixel_depth; if (reductions & OPNG_REDUCE_STRIP_ALPHA) { png_ptr->transformations &= ~PNG_FILLER; if (png_ptr->usr_channels > 0) --png_ptr->usr_channels; } return reductions; }
/* * 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, 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_ALPHA_COLOR(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_COLOR(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_COLOR(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; }
/* * Reduce the palette (only the fast method is implemented). * The parameter reductions indicates the intended reductions. * The function returns the successful reductions. */ 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 rows; png_uint_32 width, height, i, j; png_byte is_used[256]; int num_palette, num_trans, last_color_index, last_trans_index, is_gray, k; png_color_16 gray_trans; png_byte crt_trans_value, last_trans_value; opng_debug(1, "in opng_reduce_palette"); height = info_ptr->height; width = info_ptr->width; palette = info_ptr->palette; num_palette = info_ptr->num_palette; rows = info_ptr->row_pointers; if (info_ptr->valid & PNG_INFO_tRNS) { trans_alpha = info_ptr->trans_alpha; num_trans = info_ptr->num_trans; OPNG_ASSERT(trans_alpha != NULL && num_trans > 0); } else { trans_alpha = NULL; num_trans = 0; } /* Analyze the possible reductions. */ /* Also check the integrity of PLTE and tRNS. */ 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) && (info_ptr->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 < 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); if (last_color_index >= num_palette) { png_warning(png_ptr, "Too few colors in palette"); /* Fix the palette by adding blank entries at the end. */ num_palette = last_color_index + 1; info_ptr->num_palette = (png_uint_16)num_palette; } if (num_trans > num_palette) { png_warning(png_ptr, "Too many alpha values in tRNS"); info_ptr->num_trans = info_ptr->num_palette; } num_trans = last_trans_index + 1; OPNG_ASSERT(num_trans <= num_palette); /* Check if tRNS can be reduced to grayscale. */ if (is_gray && num_trans > 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 possible. */ if ((info_ptr->valid & PNG_INFO_tRNS) && num_trans == 0) { png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, -1); info_ptr->valid &= ~PNG_INFO_tRNS; result = OPNG_REDUCE_PALETTE_FAST; } if (reductions & OPNG_REDUCE_PALETTE_FAST) { if (num_palette != last_color_index + 1) { /* Reduce PLTE. */ /* hIST is reduced automatically. */ info_ptr->num_palette = (png_uint_16)(last_color_index + 1); result = OPNG_REDUCE_PALETTE_FAST; } if ((info_ptr->valid & PNG_INFO_tRNS) && (int)info_ptr->num_trans != num_trans) { /* Reduce tRNS. */ info_ptr->num_trans = (png_uint_16)num_trans; result = OPNG_REDUCE_PALETTE_FAST; } } if (reductions & OPNG_REDUCE_8_TO_4_2_1) result |= opng_reduce_palette_bits(png_ptr, info_ptr, reductions); if (info_ptr->bit_depth < 8 || !is_gray) return result; /* Reduce palette -> grayscale. */ for (i = 0; i < height; ++i) for (j = 0; j < width; ++j) rows[i][j] = palette[rows[i][j]].red; #if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) /* Update the ancillary chunk info. */ if (info_ptr->valid & PNG_INFO_bKGD) info_ptr->background.gray = palette[info_ptr->background.index].red; #endif #if defined(PNG_hIST_SUPPORTED) if (info_ptr->valid & PNG_INFO_hIST) { png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, -1); info_ptr->valid &= ~PNG_INFO_hIST; } #endif #if defined(PNG_sBIT_SUPPORTED) if (info_ptr->valid & PNG_INFO_sBIT) { png_color_8p sig_bit_ptr = &info_ptr->sig_bit; png_byte max_sig_bit = sig_bit_ptr->red; if (max_sig_bit < sig_bit_ptr->green) max_sig_bit = sig_bit_ptr->green; if (max_sig_bit < sig_bit_ptr->blue) max_sig_bit = sig_bit_ptr->blue; png_ptr->sig_bit.gray = info_ptr->sig_bit.gray = max_sig_bit; } #endif if (info_ptr->valid & PNG_INFO_tRNS) png_set_tRNS(png_ptr, info_ptr, NULL, 0, &gray_trans); /* Update the image info. */ png_ptr->rowbytes = info_ptr->rowbytes = 0; png_ptr->color_type = info_ptr->color_type = PNG_COLOR_TYPE_GRAY; png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, -1); info_ptr->valid &= ~PNG_INFO_PLTE; return OPNG_REDUCE_PALETTE_TO_GRAY; /* ignore the former result */ }
/* * 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 opng_reduce_palette(png_structp png_ptr, png_infop info_ptr, png_uint_32 reductions) { png_colorp palette; png_bytep trans_alpha; png_uint_32 width, height; int bit_depth, color_type, interlace_type, compression_type, filter_type; int num_palette, 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; png_uint_32 result = OPNG_REDUCE_NONE; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_type); if (!height || !width){ return OPNG_REDUCE_NONE; } png_bytepp row_ptr = png_get_rows(png_ptr, info_ptr); if (!png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) { palette = 0; num_palette = 0; } if (!png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, 0)) { trans_alpha = 0; num_trans = 0; } else OPNG_ASSERT(trans_alpha && 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. */ int is_gray = (reductions & OPNG_REDUCE_PALETTE_TO_GRAY) && (bit_depth == 8); last_color_index = last_trans_index = -1; int k; for (k= 0; k < 256; ++k) { if (!is_used[k]) continue; last_color_index = k; if (k < 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 >= num_palette) { png_warning(png_ptr, "Too few colors in PLTE"); /* Fix the palette by adding blank entries at the end. */ opng_realloc_PLTE(png_ptr, info_ptr, last_color_index + 1); png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); OPNG_ASSERT(num_palette == last_color_index + 1); result |= OPNG_REDUCE_REPAIR; } if (num_trans > num_palette) { png_warning(png_ptr, "Too many alpha values in tRNS"); /* Transparency will be fixed further below. */ result |= OPNG_REDUCE_REPAIR; } /* 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; } } } /* Remove tRNS if it is entirely sterile. */ if (num_trans > 0 && last_trans_index < 0) { 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 (num_palette != last_color_index + 1) { /* Reduce PLTE. */ /* hIST is reduced automatically. */ opng_realloc_PLTE(png_ptr, info_ptr, last_color_index + 1); png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); OPNG_ASSERT(num_palette == last_color_index + 1); result |= OPNG_REDUCE_PALETTE_FAST; } if (num_trans > 0 && num_trans != last_trans_index + 1) { /* Reduce tRNS. */ opng_realloc_tRNS(png_ptr, info_ptr, last_trans_index + 1); png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, 0); OPNG_ASSERT(num_trans == 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 (png_uint_32 i = 0; i < height; ++i) { for (png_uint_32 j = 0; j < width; ++j) row_ptr[i][j] = palette[row_ptr[i][j]].red; } /* Update the ancillary information. */ if (num_trans > 0) png_set_tRNS(png_ptr, info_ptr, 0, 0, &gray_trans); #ifdef PNG_bKGD_SUPPORTED png_color_16p background; if (png_get_bKGD(png_ptr, info_ptr, &background)) background->gray = palette[background->index].red; #endif #ifdef PNG_hIST_SUPPORTED png_uint_16p hist; 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 png_color_8p sig_bits; 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 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 opng_reduce_to_palette(png_structp png_ptr, png_infop info_ptr, png_uint_32 reductions) { png_row_info row_info; png_bytep sample_ptr; png_uint_32 height, width; int color_type, interlace_type, compression_type, filter_type; int src_bit_depth; png_color palette[256]; png_byte trans_alpha[256]; int num_trans, index; unsigned gray, red, green, blue, alpha; unsigned prev_red, prev_green, prev_blue, prev_alpha; png_uint_32 j; 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 || !height || !width) return OPNG_REDUCE_NONE; /* nothing is done in this case */ OPNG_ASSERT(!(color_type & PNG_COLOR_MASK_PALETTE)); png_bytepp row_ptr = png_get_rows(png_ptr, info_ptr); int channels = png_get_channels(png_ptr, info_ptr); png_bytep alpha_row = (png_bytep)png_malloc(png_ptr, width); if (!alpha_row){ exit(1); } 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. */ int num_palette = num_trans = 0; png_color_16p trans_color = 0; png_get_tRNS(png_ptr, info_ptr, 0, 0, &trans_color); unsigned prev_gray = prev_red = prev_green = prev_blue = prev_alpha = 256; png_uint_32 i; 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 png_color_16p background; 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) */ /* 5/3 times sizeof(PLTE) + sizeof(tRNS) as: 1. Palette is uncompressed additional IDAT data is 2. Headers */ if (num_palette >= 0) { int dest_bit_depth; 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) */ if (channels * 8 == dest_bit_depth || (12 + (5 * num_palette + num_trans)) * 8 / (channels * 8 - dest_bit_depth) / width / height >= 1) {num_palette = -1;} if ((num_palette && width * height < 12u * num_palette) || width * height < 8000){ 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, 0); /* bKGD (if present) is automatically updated. */ png_free(png_ptr, alpha_row); png_uint_32 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; }
/* * 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_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; /* 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 || !height || !width) 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. */ png_bytepp 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 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 opng_reduce_bits(png_structp png_ptr, png_infop info_ptr, png_uint_32 reductions) { png_bytep src_ptr, dest_ptr; png_uint_32 width, height; int interlace_type, compression_type, filter_type, src_bit_depth, dest_bit_depth, src_color_type; /* 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); if (!height || !width){ return OPNG_REDUCE_NONE; } /* 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; int src_byte_depth = src_bit_depth / 8; int dest_byte_depth = dest_bit_depth / 8; int 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; } int src_channels = png_get_channels(png_ptr, info_ptr); int dest_channels = ((dest_color_type & PNG_COLOR_MASK_COLOR) ? 3 : 1) + ((dest_color_type & PNG_COLOR_MASK_ALPHA) ? 1 : 0); int src_sample_size = src_channels * src_byte_depth; int dest_sample_size = dest_channels * dest_byte_depth; /* Pre-compute the intra-sample translation table. */ int k; int tran_tbl[8]; 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); png_bytepp row_ptr = png_get_rows(png_ptr, info_ptr); for (png_uint_32 i = 0; i < height; ++i, ++row_ptr) { src_ptr = dest_ptr = *row_ptr; for (png_uint_32 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; } } png_color_16p trans_color; /* Update the ancillary information. */ if (png_get_tRNS(png_ptr, info_ptr, 0, 0, &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 png_color_16p background; 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 png_color_8p sig_bits; 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; }
/*Remove RGB components or transparent pixels in RGB+alpha images. The function returns OPNG_REDUCE_DIRTY_ALPHA if any pixels were cleared.*/ static png_uint_32 opng_reduce_dirty_alpha(png_structp png_ptr, png_infop info_ptr) { png_uint_32 result = OPNG_REDUCE_NONE; png_bytep sample_ptr; png_uint_32 height, width, j; int bit_depth, color_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); if (bit_depth != 8){ return OPNG_REDUCE_NONE; /* nothing is done in this case */ } OPNG_ASSERT(!(color_type & PNG_COLOR_MASK_PALETTE)); png_bytepp row_ptr = png_get_rows(png_ptr, info_ptr); png_byte channels = png_get_channels(png_ptr, info_ptr); png_bytep alpha_row = (png_bytep)png_malloc(png_ptr, width); png_row_info row_info; row_info.width = width; row_info.rowbytes = 0; /* not used */ row_info.color_type = (png_byte)color_type; row_info.bit_depth = (png_byte)bit_depth; row_info.channels = (png_byte)channels; row_info.pixel_depth = 0; /* not used */ png_color_16p trans_color = 0; png_get_tRNS(png_ptr, info_ptr, 0, 0, &trans_color); /* Search for transparent pixels. */ for (unsigned i = 0; i < height; ++i, ++row_ptr) { sample_ptr = *row_ptr; opng_get_alpha_row(&row_info, trans_color, *row_ptr, alpha_row); //static void opng_get_alpha_row(png_row_infop row_info_ptr, png_color_16p trans_color, png_bytep row, png_bytep alpha_row) if (color_type & PNG_COLOR_MASK_COLOR) { for (j = 0; j < width; ++j, sample_ptr += channels) { if (alpha_row[j] == 0) { sample_ptr[0] = 0; sample_ptr[1] = 0; sample_ptr[2] = 0; result = OPNG_REDUCE_DIRTY_ALPHA; } } } else /* grayscale */ { for (j = 0; j < width; ++j, sample_ptr += channels) { if (alpha_row[j] == 0) { sample_ptr[0] = 0; result = OPNG_REDUCE_DIRTY_ALPHA; } } } } png_free(png_ptr, alpha_row); return result; }
/* * Output handler */ static void opng_write_data(png_structp png_ptr, png_bytep data, size_t length) { struct opng_codec_context * context = (struct opng_codec_context *)png_get_io_ptr(png_ptr); struct opng_encoding_stats * stats = context->stats; FILE * stream = context->stream; unsigned io_state = png_get_io_state(png_ptr); unsigned io_state_loc = io_state & PNG_IO_MASK_LOC; OPNG_ASSERT((io_state & PNG_IO_WRITING) && (io_state_loc != 0), "Incorrect info in png_ptr->io_state"); /* Handle the optipng-specific events. */ if (io_state_loc == PNG_IO_CHUNK_HDR) { OPNG_ASSERT(length == 8, "Writing chunk header, expecting 8 bytes"); png_bytep chunk_sig = data + 4; context->crt_chunk_is_allowed = opng_allow_chunk(context, chunk_sig); if (memcmp(chunk_sig, opng_sig_IDAT, 4) == 0) { context->crt_chunk_is_idat = 1; stats->idat_size += png_get_uint_32(data); } else /* not IDAT */ { context->crt_chunk_is_idat = 0; } } if (context->no_write) { return; } /* Continue only if the current chunk type is allowed. */ if (io_state_loc != PNG_IO_SIGNATURE && !context->crt_chunk_is_allowed) return; /* Here comes an elaborate way of writing the data, in which all IDATs * are joined into a single chunk. * Normally, the user-supplied I/O routines are not so complicated. */ switch (io_state_loc) { case PNG_IO_CHUNK_HDR: if (context->crt_chunk_is_idat) { if (context->crt_idat_offset == 0) { /* This is the header of the first IDAT. */ context->crt_idat_offset = ftell(stream); context->crt_idat_size = length; png_save_uint_32(data, (png_uint_32)context->crt_idat_size); /* Start computing the CRC of the final IDAT. */ context->crt_idat_crc = crc32(0, opng_sig_IDAT, 4); } else { /* This is not the first IDAT. Do not write its header. */ return; } } else { if (context->crt_idat_offset != 0) { png_byte buf[4]; /* This is the header of the first chunk after IDAT. * Finalize IDAT before resuming the normal operation. */ png_save_uint_32(buf, context->crt_idat_crc); fwrite(buf, 1, 4, stream); if (stats->idat_size != context->crt_idat_size) { /* The IDAT size, unknown at the start of encoding, * has not been guessed correctly. * It must be updated in a non-streamable way. */ png_save_uint_32(buf, (png_uint_32)stats->idat_size); fpos_t pos; if (fgetpos(stream, &pos) != 0 || fflush(stream) != 0 || (fseek(stream, context->crt_idat_offset, SEEK_SET) != 0) || (fwrite(buf, 1, 4, stream)!=4) || (fflush(stream) != 0) || (fsetpos(stream, &pos) != 0)) { io_state = 0; } } if (io_state == 0) png_error(png_ptr, "Can't finalize IDAT"); context->crt_idat_offset = 0; } } break; case PNG_IO_CHUNK_DATA: if (context->crt_chunk_is_idat) context->crt_idat_crc = crc32(context->crt_idat_crc, data, length); break; case PNG_IO_CHUNK_CRC: if (context->crt_chunk_is_idat) return; /* defer writing until the first non-IDAT occurs */ break; } /* Write the data. */ if (fwrite(data, 1, length, stream) != length) png_error(png_ptr, "Can't write file"); }