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