diff options
author | 2023-11-30 19:29:50 +0000 | |
---|---|---|
committer | 2023-11-30 11:58:16 -0800 | |
commit | b4f83ff6a99aad503463c178f57d737f0ba05679 (patch) | |
tree | c79a52636c9e9106be90cbcebefbf5894cecf1bc /libs/androidfw/PngCrunch.cpp | |
parent | ee26e36bde533955663a99e225fc93ed6da7148d (diff) |
Revert^2 "Move some image/9patch code to androidfw"
This reverts commit 917043bc2586743afda5a21386893fa8c787800b.
Reason for revert: Roll forward with fix
Test: Automatic
Bug: 296324826
Change-Id: I42a0b48c02fd497b2174c0c65f300265202f7ab1
Diffstat (limited to 'libs/androidfw/PngCrunch.cpp')
-rw-r--r-- | libs/androidfw/PngCrunch.cpp | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/libs/androidfw/PngCrunch.cpp b/libs/androidfw/PngCrunch.cpp new file mode 100644 index 000000000000..cf3c0eeff402 --- /dev/null +++ b/libs/androidfw/PngCrunch.cpp @@ -0,0 +1,730 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <png.h> +#include <zlib.h> + +#include <algorithm> +#include <unordered_map> +#include <unordered_set> + +#include "android-base/errors.h" +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "androidfw/Png.h" + +namespace android { + +// Custom deleter that destroys libpng read and info structs. +class PngReadStructDeleter { + public: + PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr) + : read_ptr_(read_ptr), info_ptr_(info_ptr) { + } + + ~PngReadStructDeleter() { + png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr); + } + + private: + png_structp read_ptr_; + png_infop info_ptr_; + + DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter); +}; + +// Custom deleter that destroys libpng write and info structs. +class PngWriteStructDeleter { + public: + PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr) + : write_ptr_(write_ptr), info_ptr_(info_ptr) { + } + + ~PngWriteStructDeleter() { + png_destroy_write_struct(&write_ptr_, &info_ptr_); + } + + private: + png_structp write_ptr_; + png_infop info_ptr_; + + DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter); +}; + +// Custom warning logging method that uses IDiagnostics. +static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) { + android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr); + diag->Warn(android::DiagMessage() << warning_msg); +} + +// Custom error logging method that uses IDiagnostics. +static void LogError(png_structp png_ptr, png_const_charp error_msg) { + android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr); + diag->Error(android::DiagMessage() << error_msg); + + // Causes libpng to longjmp to the spot where setjmp was set. This is how libpng does + // error handling. If this custom error handler method were to return, libpng would, by + // default, print the error message to stdout and call the same png_longjmp method. + png_longjmp(png_ptr, 1); +} + +static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) { + InputStream* in = (InputStream*)png_get_io_ptr(png_ptr); + + const void* in_buffer; + size_t in_len; + if (!in->Next(&in_buffer, &in_len)) { + if (in->HadError()) { + std::stringstream error_msg_builder; + error_msg_builder << "failed reading from input"; + if (!in->GetError().empty()) { + error_msg_builder << ": " << in->GetError(); + } + std::string err = error_msg_builder.str(); + png_error(png_ptr, err.c_str()); + } + return; + } + + const size_t bytes_read = std::min(in_len, len); + memcpy(buffer, in_buffer, bytes_read); + if (bytes_read != in_len) { + in->BackUp(in_len - bytes_read); + } +} + +static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) { + OutputStream* out = (OutputStream*)png_get_io_ptr(png_ptr); + + void* out_buffer; + size_t out_len; + while (len > 0) { + if (!out->Next(&out_buffer, &out_len)) { + if (out->HadError()) { + std::stringstream err_msg_builder; + err_msg_builder << "failed writing to output"; + if (!out->GetError().empty()) { + err_msg_builder << ": " << out->GetError(); + } + std::string err = out->GetError(); + png_error(png_ptr, err.c_str()); + } + return; + } + + const size_t bytes_written = std::min(out_len, len); + memcpy(out_buffer, buffer, bytes_written); + + // Advance the input buffer. + buffer += bytes_written; + len -= bytes_written; + + // Advance the output buffer. + out_len -= bytes_written; + } + + // If the entire output buffer wasn't used, backup. + if (out_len > 0) { + out->BackUp(out_len); + } +} + +std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag) { + // Read the first 8 bytes of the file looking for the PNG signature. + // Bail early if it does not match. + const png_byte* signature; + size_t buffer_size; + if (!in->Next((const void**)&signature, &buffer_size)) { + if (in->HadError()) { + diag->Error(android::DiagMessage() << "failed to read PNG signature: " << in->GetError()); + } else { + diag->Error(android::DiagMessage() << "not enough data for PNG signature"); + } + return {}; + } + + if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { + diag->Error(android::DiagMessage() << "file signature does not match PNG signature"); + return {}; + } + + // Start at the beginning of the first chunk. + in->BackUp(buffer_size - kPngSignatureSize); + + // Create and initialize the png_struct with the default error and warning handlers. + // The header version is also passed in to ensure that this was built against the same + // version of libpng. + png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (read_ptr == nullptr) { + diag->Error(android::DiagMessage() << "failed to create libpng read png_struct"); + return {}; + } + + // Create and initialize the memory for image header and data. + png_infop info_ptr = png_create_info_struct(read_ptr); + if (info_ptr == nullptr) { + diag->Error(android::DiagMessage() << "failed to create libpng read png_info"); + png_destroy_read_struct(&read_ptr, nullptr, nullptr); + return {}; + } + + // Automatically release PNG resources at end of scope. + PngReadStructDeleter png_read_deleter(read_ptr, info_ptr); + + // libpng uses longjmp to jump to an error handling routine. + // setjmp will only return true if it was jumped to, aka there was + // an error. + if (setjmp(png_jmpbuf(read_ptr))) { + return {}; + } + + // Handle warnings ourselves via IDiagnostics. + png_set_error_fn(read_ptr, (png_voidp)&diag, LogError, LogWarning); + + // Set up the read functions which read from our custom data sources. + png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream); + + // Skip the signature that we already read. + png_set_sig_bytes(read_ptr, kPngSignatureSize); + + // Read the chunk headers. + png_read_info(read_ptr, info_ptr); + + // Extract image meta-data from the various chunk headers. + uint32_t width, height; + int bit_depth, color_type, interlace_method, compression_method, filter_method; + png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method, + &compression_method, &filter_method); + + // When the image is read, expand it so that it is in RGBA 8888 format + // so that image handling is uniform. + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(read_ptr); + } + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(read_ptr); + } + + if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(read_ptr); + } + + if (bit_depth == 16) { + png_set_strip_16(read_ptr); + } + + if (!(color_type & PNG_COLOR_MASK_ALPHA)) { + png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER); + } + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(read_ptr); + } + + if (interlace_method != PNG_INTERLACE_NONE) { + png_set_interlace_handling(read_ptr); + } + + // Once all the options for reading have been set, we need to flush + // them to libpng. + png_read_update_info(read_ptr, info_ptr); + + // 9-patch uses int32_t to index images, so we cap the image dimensions to + // something + // that can always be represented by 9-patch. + if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) { + diag->Error(android::DiagMessage() + << "PNG image dimensions are too large: " << width << "x" << height); + return {}; + } + + std::unique_ptr<Image> output_image = std::make_unique<Image>(); + output_image->width = static_cast<int32_t>(width); + output_image->height = static_cast<int32_t>(height); + + const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr); + CHECK(row_bytes == 4 * width); // RGBA + + // Allocate one large block to hold the image. + output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]); + + // Create an array of rows that index into the data block. + output_image->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]); + for (uint32_t h = 0; h < height; h++) { + output_image->rows[h] = output_image->data.get() + (h * row_bytes); + } + + // Actually read the image pixels. + png_read_image(read_ptr, output_image->rows.get()); + + // Finish reading. This will read any other chunks after the image data. + png_read_end(read_ptr, info_ptr); + + return output_image; +} + +// Experimentally chosen constant to be added to the overhead of using color type +// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk. +// Without this, many small PNGs encoded with palettes are larger after compression than +// the same PNGs encoded as RGBA. +constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u; + +// Pick a color type by which to encode the image, based on which color type will take +// the least amount of disk space. +// +// 9-patch images traditionally have not been encoded with palettes. +// The original rationale was to avoid dithering until after scaling, +// but I don't think this would be an issue with palettes. Either way, +// our naive size estimation tends to be wrong for small images like 9-patches +// and using palettes balloons the size of the resulting 9-patch. +// In order to not regress in size, restrict 9-patch to not use palettes. + +// The options are: +// +// - RGB +// - RGBA +// - RGB + cheap alpha +// - Color palette +// - Color palette + cheap alpha +// - Color palette + alpha palette +// - Grayscale +// - Grayscale + cheap alpha +// - Grayscale + alpha +// +static int PickColorType(int32_t width, int32_t height, bool grayscale, + bool convertible_to_grayscale, bool has_nine_patch, + size_t color_palette_size, size_t alpha_palette_size) { + const size_t palette_chunk_size = 16 + color_palette_size * 3; + const size_t alpha_chunk_size = 16 + alpha_palette_size; + const size_t color_alpha_data_chunk_size = 16 + 4 * width * height; + const size_t color_data_chunk_size = 16 + 3 * width * height; + const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height; + const size_t palette_data_chunk_size = 16 + width * height; + + if (grayscale) { + if (alpha_palette_size == 0) { + // This is the smallest the data can be. + return PNG_COLOR_TYPE_GRAY; + } else if (color_palette_size <= 256 && !has_nine_patch) { + // This grayscale has alpha and can fit within a palette. + // See if it is worth fitting into a palette. + const size_t palette_threshold = palette_chunk_size + alpha_chunk_size + + palette_data_chunk_size + kPaletteOverheadConstant; + if (grayscale_alpha_data_chunk_size > palette_threshold) { + return PNG_COLOR_TYPE_PALETTE; + } + } + return PNG_COLOR_TYPE_GRAY_ALPHA; + } + + if (color_palette_size <= 256 && !has_nine_patch) { + // This image can fit inside a palette. Let's see if it is worth it. + size_t total_size_with_palette = palette_data_chunk_size + palette_chunk_size; + size_t total_size_without_palette = color_data_chunk_size; + if (alpha_palette_size > 0) { + total_size_with_palette += alpha_palette_size; + total_size_without_palette = color_alpha_data_chunk_size; + } + + if (total_size_without_palette > total_size_with_palette + kPaletteOverheadConstant) { + return PNG_COLOR_TYPE_PALETTE; + } + } + + if (convertible_to_grayscale) { + if (alpha_palette_size == 0) { + return PNG_COLOR_TYPE_GRAY; + } else { + return PNG_COLOR_TYPE_GRAY_ALPHA; + } + } + + if (alpha_palette_size == 0) { + return PNG_COLOR_TYPE_RGB; + } + return PNG_COLOR_TYPE_RGBA; +} + +// Assigns indices to the color and alpha palettes, encodes them, and then invokes +// png_set_PLTE/png_set_tRNS. +// This must be done before writing image data. +// Image data must be transformed to use the indices assigned within the palette. +static void WritePalette(png_structp write_ptr, png_infop write_info_ptr, + std::unordered_map<uint32_t, int>* color_palette, + std::unordered_set<uint32_t>* alpha_palette) { + CHECK(color_palette->size() <= 256); + CHECK(alpha_palette->size() <= 256); + + // Populate the PNG palette struct and assign indices to the color palette. + + // Colors in the alpha palette should have smaller indices. + // This will ensure that we can truncate the alpha palette if it is + // smaller than the color palette. + int index = 0; + for (uint32_t color : *alpha_palette) { + (*color_palette)[color] = index++; + } + + // Assign the rest of the entries. + for (auto& entry : *color_palette) { + if (entry.second == -1) { + entry.second = index++; + } + } + + // Create the PNG color palette struct. + auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]); + + std::unique_ptr<png_byte[]> alpha_palette_bytes; + if (!alpha_palette->empty()) { + alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]); + } + + for (const auto& entry : *color_palette) { + const uint32_t color = entry.first; + const int index = entry.second; + CHECK(index >= 0); + CHECK(static_cast<size_t>(index) < color_palette->size()); + + png_colorp slot = color_palette_bytes.get() + index; + slot->red = color >> 24; + slot->green = color >> 16; + slot->blue = color >> 8; + + const png_byte alpha = color & 0x000000ff; + if (alpha != 0xff && alpha_palette_bytes) { + CHECK(static_cast<size_t>(index) < alpha_palette->size()); + alpha_palette_bytes[index] = alpha; + } + } + + // The bytes get copied here, so it is safe to release color_palette_bytes at + // the end of function + // scope. + png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size()); + + if (alpha_palette_bytes) { + png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(), + nullptr); + } +} + +// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done +// before writing image data. +static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr, + const NinePatch* nine_patch) { + // The order of the chunks is important. + // 9-patch code in older platforms expects the 9-patch chunk to be last. + + png_unknown_chunk unknown_chunks[3]; + memset(unknown_chunks, 0, sizeof(unknown_chunks)); + + size_t index = 0; + size_t chunk_len = 0; + + std::unique_ptr<uint8_t[]> serialized_outline = + nine_patch->SerializeRoundedRectOutline(&chunk_len); + strcpy((char*)unknown_chunks[index].name, "npOl"); + unknown_chunks[index].size = chunk_len; + unknown_chunks[index].data = (png_bytep)serialized_outline.get(); + unknown_chunks[index].location = PNG_HAVE_PLTE; + index++; + + std::unique_ptr<uint8_t[]> serialized_layout_bounds; + if (nine_patch->layout_bounds.nonZero()) { + serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len); + strcpy((char*)unknown_chunks[index].name, "npLb"); + unknown_chunks[index].size = chunk_len; + unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get(); + unknown_chunks[index].location = PNG_HAVE_PLTE; + index++; + } + + std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len); + strcpy((char*)unknown_chunks[index].name, "npTc"); + unknown_chunks[index].size = chunk_len; + unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get(); + unknown_chunks[index].location = PNG_HAVE_PLTE; + index++; + + // Handle all unknown chunks. We are manually setting the chunks here, + // so we will only ever handle our custom chunks. + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0); + + // Set the actual chunks here. The data gets copied, so our buffers can + // safely go out of scope. + png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index); +} + +bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out, + const PngOptions& options, IDiagnostics* diag, bool verbose) { + // Create and initialize the write png_struct with the default error and + // warning handlers. + // The header version is also passed in to ensure that this was built against the same + // version of libpng. + png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (write_ptr == nullptr) { + diag->Error(android::DiagMessage() << "failed to create libpng write png_struct"); + return false; + } + + // Allocate memory to store image header data. + png_infop write_info_ptr = png_create_info_struct(write_ptr); + if (write_info_ptr == nullptr) { + diag->Error(android::DiagMessage() << "failed to create libpng write png_info"); + png_destroy_write_struct(&write_ptr, nullptr); + return false; + } + + // Automatically release PNG resources at end of scope. + PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr); + + // libpng uses longjmp to jump to error handling routines. + // setjmp will return true only if it was jumped to, aka, there was an error. + if (setjmp(png_jmpbuf(write_ptr))) { + return false; + } + + // Handle warnings with our IDiagnostics. + png_set_error_fn(write_ptr, (png_voidp)&diag, LogError, LogWarning); + + // Set up the write functions which write to our custom data sources. + png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr); + + // We want small files and can take the performance hit to achieve this goal. + png_set_compression_level(write_ptr, Z_BEST_COMPRESSION); + + // Begin analysis of the image data. + // Scan the entire image and determine if: + // 1. Every pixel has R == G == B (grayscale) + // 2. Every pixel has A == 255 (opaque) + // 3. There are no more than 256 distinct RGBA colors (palette). + std::unordered_map<uint32_t, int> color_palette; + std::unordered_set<uint32_t> alpha_palette; + bool needs_to_zero_rgb_channels_of_transparent_pixels = false; + bool grayscale = true; + int max_gray_deviation = 0; + + for (int32_t y = 0; y < image->height; y++) { + const uint8_t* row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int red = *row++; + int green = *row++; + int blue = *row++; + int alpha = *row++; + + if (alpha == 0) { + // The color is completely transparent. + // For purposes of palettes and grayscale optimization, + // treat all channels as 0x00. + needs_to_zero_rgb_channels_of_transparent_pixels = + needs_to_zero_rgb_channels_of_transparent_pixels || + (red != 0 || green != 0 || blue != 0); + red = green = blue = 0; + } + + // Insert the color into the color palette. + const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha; + color_palette[color] = -1; + + // If the pixel has non-opaque alpha, insert it into the + // alpha palette. + if (alpha != 0xff) { + alpha_palette.insert(color); + } + + // Check if the image is indeed grayscale. + if (grayscale) { + if (red != green || red != blue) { + grayscale = false; + } + } + + // Calculate the gray scale deviation so that it can be compared + // with the threshold. + max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation); + max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation); + max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation); + } + } + + if (verbose) { + android::DiagMessage msg; + msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size() + << " maxGrayDeviation=" << max_gray_deviation + << " grayScale=" << (grayscale ? "true" : "false"); + diag->Note(msg); + } + + const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance; + + const int new_color_type = + PickColorType(image->width, image->height, grayscale, convertible_to_grayscale, + nine_patch != nullptr, color_palette.size(), alpha_palette.size()); + + if (verbose) { + android::DiagMessage msg; + msg << "encoding PNG "; + if (nine_patch) { + msg << "(with 9-patch) as "; + } + switch (new_color_type) { + case PNG_COLOR_TYPE_GRAY: + msg << "GRAY"; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + msg << "GRAY + ALPHA"; + break; + case PNG_COLOR_TYPE_RGB: + msg << "RGB"; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + msg << "RGBA"; + break; + case PNG_COLOR_TYPE_PALETTE: + msg << "PALETTE"; + break; + default: + msg << "unknown type " << new_color_type; + break; + } + diag->Note(msg); + } + + png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, new_color_type, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + if (new_color_type & PNG_COLOR_MASK_PALETTE) { + // Assigns indices to the palette, and writes the encoded palette to the + // libpng writePtr. + WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette); + png_set_filter(write_ptr, 0, PNG_NO_FILTERS); + } else { + png_set_filter(write_ptr, 0, PNG_ALL_FILTERS); + } + + if (nine_patch) { + WriteNinePatch(write_ptr, write_info_ptr, nine_patch); + } + + // Flush our updates to the header. + png_write_info(write_ptr, write_info_ptr); + + // Write out each row of image data according to its encoding. + if (new_color_type == PNG_COLOR_TYPE_PALETTE) { + // 1 byte/pixel. + auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]); + + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep in_row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = *in_row++; + int gg = *in_row++; + int bb = *in_row++; + int aa = *in_row++; + if (aa == 0) { + // Zero out color channels when transparent. + rr = gg = bb = 0; + } + + const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa; + const int idx = color_palette[color]; + CHECK(idx != -1); + out_row[x] = static_cast<png_byte>(idx); + } + png_write_row(write_ptr, out_row.get()); + } + } else if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2; + auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); + + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep in_row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = in_row[x * 4]; + int gg = in_row[x * 4 + 1]; + int bb = in_row[x * 4 + 2]; + int aa = in_row[x * 4 + 3]; + if (aa == 0) { + // Zero out the gray channel when transparent. + rr = gg = bb = 0; + } + + if (grayscale) { + // The image was already grayscale, red == green == blue. + out_row[x * bpp] = in_row[x * 4]; + } else { + // The image is convertible to grayscale, use linear-luminance of + // sRGB colorspace: + // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale + out_row[x * bpp] = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); + } + + if (bpp == 2) { + // Write out alpha if we have it. + out_row[x * bpp + 1] = aa; + } + } + png_write_row(write_ptr, out_row.get()); + } + } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) { + const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4; + if (needs_to_zero_rgb_channels_of_transparent_pixels) { + // The source RGBA data can't be used as-is, because we need to zero out + // the RGB values of transparent pixels. + auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); + + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep in_row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = *in_row++; + int gg = *in_row++; + int bb = *in_row++; + int aa = *in_row++; + if (aa == 0) { + // Zero out the RGB channels when transparent. + rr = gg = bb = 0; + } + out_row[x * bpp] = rr; + out_row[x * bpp + 1] = gg; + out_row[x * bpp + 2] = bb; + if (bpp == 4) { + out_row[x * bpp + 3] = aa; + } + } + png_write_row(write_ptr, out_row.get()); + } + } else { + // The source image can be used as-is, just tell libpng whether or not to + // ignore the alpha channel. + if (new_color_type == PNG_COLOR_TYPE_RGB) { + // Delete the extraneous alpha values that we appended to our buffer + // when reading the original values. + png_set_filler(write_ptr, 0, PNG_FILLER_AFTER); + } + png_write_image(write_ptr, image->rows.get()); + } + } else { + LOG(FATAL) << "unreachable"; + } + + png_write_end(write_ptr, write_info_ptr); + return true; +} + +} // namespace android |