summaryrefslogtreecommitdiff
path: root/libs/androidfw/PngCrunch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/androidfw/PngCrunch.cpp')
-rw-r--r--libs/androidfw/PngCrunch.cpp730
1 files changed, 0 insertions, 730 deletions
diff --git a/libs/androidfw/PngCrunch.cpp b/libs/androidfw/PngCrunch.cpp
deleted file mode 100644
index cf3c0eeff402..000000000000
--- a/libs/androidfw/PngCrunch.cpp
+++ /dev/null
@@ -1,730 +0,0 @@
-/*
- * 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