/* * 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 */ }