blob: e94540544cf53fd986d4b7d702b26f6c36d2e782 [file] [log] [blame]
Adam Lesinski21efb682016-09-14 17:35:43 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Adam Lesinski21efb682016-09-14 17:35:43 -070017#include <png.h>
Adam Lesinskicacb28f2016-10-19 12:18:14 -070018#include <zlib.h>
Adam Lesinskice5e56e2016-10-21 17:56:45 -070019
Adam Lesinskicacb28f2016-10-19 12:18:14 -070020#include <algorithm>
Adam Lesinski21efb682016-09-14 17:35:43 -070021#include <unordered_map>
22#include <unordered_set>
Adam Lesinski21efb682016-09-14 17:35:43 -070023
Adam Lesinskice5e56e2016-10-21 17:56:45 -070024#include "android-base/errors.h"
25#include "android-base/logging.h"
26#include "android-base/macros.h"
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000027#include "androidfw/Png.h"
Adam Lesinskice5e56e2016-10-21 17:56:45 -070028
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000029namespace android {
Adam Lesinski21efb682016-09-14 17:35:43 -070030
Adam Lesinski06460ef2017-03-14 18:52:13 -070031// Custom deleter that destroys libpng read and info structs.
Adam Lesinski21efb682016-09-14 17:35:43 -070032class PngReadStructDeleter {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070033 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070034 PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000035 : read_ptr_(read_ptr), info_ptr_(info_ptr) {
36 }
Adam Lesinski21efb682016-09-14 17:35:43 -070037
Adam Lesinskicacb28f2016-10-19 12:18:14 -070038 ~PngReadStructDeleter() {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070039 png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070040 }
Adam Lesinski21efb682016-09-14 17:35:43 -070041
Adam Lesinskicacb28f2016-10-19 12:18:14 -070042 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070043 png_structp read_ptr_;
44 png_infop info_ptr_;
Adam Lesinski21efb682016-09-14 17:35:43 -070045
Adam Lesinskicacb28f2016-10-19 12:18:14 -070046 DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
Adam Lesinski21efb682016-09-14 17:35:43 -070047};
48
Adam Lesinski06460ef2017-03-14 18:52:13 -070049// Custom deleter that destroys libpng write and info structs.
Adam Lesinski21efb682016-09-14 17:35:43 -070050class PngWriteStructDeleter {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070051 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070052 PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000053 : write_ptr_(write_ptr), info_ptr_(info_ptr) {
54 }
Adam Lesinski21efb682016-09-14 17:35:43 -070055
Adam Lesinskice5e56e2016-10-21 17:56:45 -070056 ~PngWriteStructDeleter() {
57 png_destroy_write_struct(&write_ptr_, &info_ptr_);
58 }
Adam Lesinski21efb682016-09-14 17:35:43 -070059
Adam Lesinskicacb28f2016-10-19 12:18:14 -070060 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070061 png_structp write_ptr_;
62 png_infop info_ptr_;
Adam Lesinski21efb682016-09-14 17:35:43 -070063
Adam Lesinskicacb28f2016-10-19 12:18:14 -070064 DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
Adam Lesinski21efb682016-09-14 17:35:43 -070065};
66
67// Custom warning logging method that uses IDiagnostics.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070068static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +000069 android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
70 diag->Warn(android::DiagMessage() << warning_msg);
Adam Lesinski21efb682016-09-14 17:35:43 -070071}
72
73// Custom error logging method that uses IDiagnostics.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070074static void LogError(png_structp png_ptr, png_const_charp error_msg) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +000075 android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
76 diag->Error(android::DiagMessage() << error_msg);
Adam Lesinskicc73e992017-05-12 18:16:44 -070077
78 // Causes libpng to longjmp to the spot where setjmp was set. This is how libpng does
79 // error handling. If this custom error handler method were to return, libpng would, by
80 // default, print the error message to stdout and call the same png_longjmp method.
81 png_longjmp(png_ptr, 1);
Adam Lesinski21efb682016-09-14 17:35:43 -070082}
83
Adam Lesinski06460ef2017-03-14 18:52:13 -070084static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +000085 InputStream* in = (InputStream*)png_get_io_ptr(png_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -070086
Adam Lesinskice5e56e2016-10-21 17:56:45 -070087 const void* in_buffer;
Adam Lesinski06460ef2017-03-14 18:52:13 -070088 size_t in_len;
Adam Lesinskice5e56e2016-10-21 17:56:45 -070089 if (!in->Next(&in_buffer, &in_len)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070090 if (in->HadError()) {
Adam Lesinskicc73e992017-05-12 18:16:44 -070091 std::stringstream error_msg_builder;
92 error_msg_builder << "failed reading from input";
93 if (!in->GetError().empty()) {
94 error_msg_builder << ": " << in->GetError();
95 }
96 std::string err = error_msg_builder.str();
Adam Lesinskice5e56e2016-10-21 17:56:45 -070097 png_error(png_ptr, err.c_str());
Adam Lesinski21efb682016-09-14 17:35:43 -070098 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -070099 return;
100 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700101
Adam Lesinski06460ef2017-03-14 18:52:13 -0700102 const size_t bytes_read = std::min(in_len, len);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700103 memcpy(buffer, in_buffer, bytes_read);
Adam Lesinski06460ef2017-03-14 18:52:13 -0700104 if (bytes_read != in_len) {
105 in->BackUp(in_len - bytes_read);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700106 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700107}
108
Adam Lesinski06460ef2017-03-14 18:52:13 -0700109static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000110 OutputStream* out = (OutputStream*)png_get_io_ptr(png_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700111
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700112 void* out_buffer;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700113 size_t out_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700114 while (len > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700115 if (!out->Next(&out_buffer, &out_len)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700116 if (out->HadError()) {
Adam Lesinskicc73e992017-05-12 18:16:44 -0700117 std::stringstream err_msg_builder;
118 err_msg_builder << "failed writing to output";
119 if (!out->GetError().empty()) {
120 err_msg_builder << ": " << out->GetError();
121 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700122 std::string err = out->GetError();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700123 png_error(png_ptr, err.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700124 }
125 return;
Adam Lesinski21efb682016-09-14 17:35:43 -0700126 }
127
Adam Lesinski06460ef2017-03-14 18:52:13 -0700128 const size_t bytes_written = std::min(out_len, len);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700129 memcpy(out_buffer, buffer, bytes_written);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700130
131 // Advance the input buffer.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700132 buffer += bytes_written;
133 len -= bytes_written;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700134
135 // Advance the output buffer.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700136 out_len -= bytes_written;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700137 }
138
139 // If the entire output buffer wasn't used, backup.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700140 if (out_len > 0) {
141 out->BackUp(out_len);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700142 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700143}
144
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000145std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700146 // Read the first 8 bytes of the file looking for the PNG signature.
147 // Bail early if it does not match.
148 const png_byte* signature;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700149 size_t buffer_size;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700150 if (!in->Next((const void**)&signature, &buffer_size)) {
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700151 if (in->HadError()) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000152 diag->Error(android::DiagMessage() << "failed to read PNG signature: " << in->GetError());
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700153 } else {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000154 diag->Error(android::DiagMessage() << "not enough data for PNG signature");
Adam Lesinski60d9c2f2017-05-17 16:07:45 -0700155 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700156 return {};
157 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700158
Adam Lesinski06460ef2017-03-14 18:52:13 -0700159 if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000160 diag->Error(android::DiagMessage() << "file signature does not match PNG signature");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700161 return {};
162 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700163
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700164 // Start at the beginning of the first chunk.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700165 in->BackUp(buffer_size - kPngSignatureSize);
Adam Lesinski21efb682016-09-14 17:35:43 -0700166
Adam Lesinski06460ef2017-03-14 18:52:13 -0700167 // Create and initialize the png_struct with the default error and warning handlers.
168 // The header version is also passed in to ensure that this was built against the same
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700169 // version of libpng.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700170 png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700171 if (read_ptr == nullptr) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000172 diag->Error(android::DiagMessage() << "failed to create libpng read png_struct");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700173 return {};
174 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700175
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700176 // Create and initialize the memory for image header and data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700177 png_infop info_ptr = png_create_info_struct(read_ptr);
178 if (info_ptr == nullptr) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000179 diag->Error(android::DiagMessage() << "failed to create libpng read png_info");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700180 png_destroy_read_struct(&read_ptr, nullptr, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700181 return {};
182 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700183
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700184 // Automatically release PNG resources at end of scope.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700185 PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700186
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700187 // libpng uses longjmp to jump to an error handling routine.
188 // setjmp will only return true if it was jumped to, aka there was
189 // an error.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700190 if (setjmp(png_jmpbuf(read_ptr))) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700191 return {};
192 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700193
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700194 // Handle warnings ourselves via IDiagnostics.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000195 png_set_error_fn(read_ptr, (png_voidp)&diag, LogError, LogWarning);
Adam Lesinski21efb682016-09-14 17:35:43 -0700196
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700197 // Set up the read functions which read from our custom data sources.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700198 png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
Adam Lesinski21efb682016-09-14 17:35:43 -0700199
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700200 // Skip the signature that we already read.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700201 png_set_sig_bytes(read_ptr, kPngSignatureSize);
Adam Lesinski21efb682016-09-14 17:35:43 -0700202
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700203 // Read the chunk headers.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700204 png_read_info(read_ptr, info_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700205
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700206 // Extract image meta-data from the various chunk headers.
207 uint32_t width, height;
Adam Lesinski06460ef2017-03-14 18:52:13 -0700208 int bit_depth, color_type, interlace_method, compression_method, filter_method;
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000209 png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method,
210 &compression_method, &filter_method);
Adam Lesinski21efb682016-09-14 17:35:43 -0700211
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700212 // When the image is read, expand it so that it is in RGBA 8888 format
213 // so that image handling is uniform.
Adam Lesinski21efb682016-09-14 17:35:43 -0700214
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700215 if (color_type == PNG_COLOR_TYPE_PALETTE) {
216 png_set_palette_to_rgb(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700217 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700218
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700219 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
220 png_set_expand_gray_1_2_4_to_8(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700221 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700222
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700223 if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
224 png_set_tRNS_to_alpha(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700225 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700226
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700227 if (bit_depth == 16) {
228 png_set_strip_16(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700229 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700230
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700231 if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
232 png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700233 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700234
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000235 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700236 png_set_gray_to_rgb(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700237 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700238
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700239 if (interlace_method != PNG_INTERLACE_NONE) {
240 png_set_interlace_handling(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700241 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700242
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700243 // Once all the options for reading have been set, we need to flush
244 // them to libpng.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700245 png_read_update_info(read_ptr, info_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700246
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700247 // 9-patch uses int32_t to index images, so we cap the image dimensions to
248 // something
249 // that can always be represented by 9-patch.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700250 if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000251 diag->Error(android::DiagMessage()
252 << "PNG image dimensions are too large: " << width << "x" << height);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700253 return {};
254 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700255
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000256 std::unique_ptr<Image> output_image = std::make_unique<Image>();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700257 output_image->width = static_cast<int32_t>(width);
258 output_image->height = static_cast<int32_t>(height);
Adam Lesinski21efb682016-09-14 17:35:43 -0700259
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700260 const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
261 CHECK(row_bytes == 4 * width); // RGBA
Adam Lesinski21efb682016-09-14 17:35:43 -0700262
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700263 // Allocate one large block to hold the image.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700264 output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700265
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700266 // Create an array of rows that index into the data block.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000267 output_image->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700268 for (uint32_t h = 0; h < height; h++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700269 output_image->rows[h] = output_image->data.get() + (h * row_bytes);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700270 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700271
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700272 // Actually read the image pixels.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700273 png_read_image(read_ptr, output_image->rows.get());
Adam Lesinski21efb682016-09-14 17:35:43 -0700274
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700275 // Finish reading. This will read any other chunks after the image data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700276 png_read_end(read_ptr, info_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700277
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700278 return output_image;
Adam Lesinski21efb682016-09-14 17:35:43 -0700279}
280
Adam Lesinski06460ef2017-03-14 18:52:13 -0700281// Experimentally chosen constant to be added to the overhead of using color type
282// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
283// Without this, many small PNGs encoded with palettes are larger after compression than
284// the same PNGs encoded as RGBA.
Adam Lesinski21efb682016-09-14 17:35:43 -0700285constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
286
Adam Lesinski06460ef2017-03-14 18:52:13 -0700287// Pick a color type by which to encode the image, based on which color type will take
Adam Lesinski21efb682016-09-14 17:35:43 -0700288// the least amount of disk space.
289//
290// 9-patch images traditionally have not been encoded with palettes.
291// The original rationale was to avoid dithering until after scaling,
292// but I don't think this would be an issue with palettes. Either way,
293// our naive size estimation tends to be wrong for small images like 9-patches
294// and using palettes balloons the size of the resulting 9-patch.
295// In order to not regress in size, restrict 9-patch to not use palettes.
296
297// The options are:
298//
299// - RGB
300// - RGBA
301// - RGB + cheap alpha
302// - Color palette
303// - Color palette + cheap alpha
304// - Color palette + alpha palette
305// - Grayscale
306// - Grayscale + cheap alpha
307// - Grayscale + alpha
308//
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700309static int PickColorType(int32_t width, int32_t height, bool grayscale,
310 bool convertible_to_grayscale, bool has_nine_patch,
311 size_t color_palette_size, size_t alpha_palette_size) {
312 const size_t palette_chunk_size = 16 + color_palette_size * 3;
313 const size_t alpha_chunk_size = 16 + alpha_palette_size;
314 const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
315 const size_t color_data_chunk_size = 16 + 3 * width * height;
316 const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
317 const size_t palette_data_chunk_size = 16 + width * height;
Adam Lesinski21efb682016-09-14 17:35:43 -0700318
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700319 if (grayscale) {
320 if (alpha_palette_size == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700321 // This is the smallest the data can be.
322 return PNG_COLOR_TYPE_GRAY;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700323 } else if (color_palette_size <= 256 && !has_nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700324 // This grayscale has alpha and can fit within a palette.
325 // See if it is worth fitting into a palette.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700326 const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000327 palette_data_chunk_size + kPaletteOverheadConstant;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700328 if (grayscale_alpha_data_chunk_size > palette_threshold) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700329 return PNG_COLOR_TYPE_PALETTE;
330 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700331 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700332 return PNG_COLOR_TYPE_GRAY_ALPHA;
333 }
334
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700335 if (color_palette_size <= 256 && !has_nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700336 // This image can fit inside a palette. Let's see if it is worth it.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000337 size_t total_size_with_palette = palette_data_chunk_size + palette_chunk_size;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700338 size_t total_size_without_palette = color_data_chunk_size;
339 if (alpha_palette_size > 0) {
340 total_size_with_palette += alpha_palette_size;
341 total_size_without_palette = color_alpha_data_chunk_size;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700342 }
343
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000344 if (total_size_without_palette > total_size_with_palette + kPaletteOverheadConstant) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700345 return PNG_COLOR_TYPE_PALETTE;
346 }
347 }
348
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700349 if (convertible_to_grayscale) {
350 if (alpha_palette_size == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700351 return PNG_COLOR_TYPE_GRAY;
352 } else {
353 return PNG_COLOR_TYPE_GRAY_ALPHA;
354 }
355 }
356
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700357 if (alpha_palette_size == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700358 return PNG_COLOR_TYPE_RGB;
359 }
360 return PNG_COLOR_TYPE_RGBA;
Adam Lesinski21efb682016-09-14 17:35:43 -0700361}
362
Adam Lesinski06460ef2017-03-14 18:52:13 -0700363// Assigns indices to the color and alpha palettes, encodes them, and then invokes
Adam Lesinski21efb682016-09-14 17:35:43 -0700364// png_set_PLTE/png_set_tRNS.
365// This must be done before writing image data.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700366// Image data must be transformed to use the indices assigned within the palette.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700367static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
368 std::unordered_map<uint32_t, int>* color_palette,
369 std::unordered_set<uint32_t>* alpha_palette) {
370 CHECK(color_palette->size() <= 256);
371 CHECK(alpha_palette->size() <= 256);
Adam Lesinski21efb682016-09-14 17:35:43 -0700372
Adam Lesinski06460ef2017-03-14 18:52:13 -0700373 // Populate the PNG palette struct and assign indices to the color palette.
Adam Lesinski21efb682016-09-14 17:35:43 -0700374
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700375 // Colors in the alpha palette should have smaller indices.
376 // This will ensure that we can truncate the alpha palette if it is
377 // smaller than the color palette.
378 int index = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700379 for (uint32_t color : *alpha_palette) {
380 (*color_palette)[color] = index++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700381 }
382
383 // Assign the rest of the entries.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700384 for (auto& entry : *color_palette) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700385 if (entry.second == -1) {
386 entry.second = index++;
Adam Lesinski21efb682016-09-14 17:35:43 -0700387 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700388 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700389
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700390 // Create the PNG color palette struct.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700391 auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700392
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700393 std::unique_ptr<png_byte[]> alpha_palette_bytes;
394 if (!alpha_palette->empty()) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700395 alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700396 }
397
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700398 for (const auto& entry : *color_palette) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700399 const uint32_t color = entry.first;
400 const int index = entry.second;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700401 CHECK(index >= 0);
402 CHECK(static_cast<size_t>(index) < color_palette->size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700403
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700404 png_colorp slot = color_palette_bytes.get() + index;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700405 slot->red = color >> 24;
406 slot->green = color >> 16;
407 slot->blue = color >> 8;
408
409 const png_byte alpha = color & 0x000000ff;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700410 if (alpha != 0xff && alpha_palette_bytes) {
411 CHECK(static_cast<size_t>(index) < alpha_palette->size());
412 alpha_palette_bytes[index] = alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700413 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700414 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700415
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700416 // The bytes get copied here, so it is safe to release color_palette_bytes at
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700417 // the end of function
418 // scope.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700419 png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size());
Adam Lesinski21efb682016-09-14 17:35:43 -0700420
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700421 if (alpha_palette_bytes) {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700422 png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(),
423 nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700424 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700425}
426
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700427// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
Adam Lesinski06460ef2017-03-14 18:52:13 -0700428// before writing image data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700429static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
430 const NinePatch* nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700431 // The order of the chunks is important.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700432 // 9-patch code in older platforms expects the 9-patch chunk to be last.
Adam Lesinski21efb682016-09-14 17:35:43 -0700433
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700434 png_unknown_chunk unknown_chunks[3];
435 memset(unknown_chunks, 0, sizeof(unknown_chunks));
Adam Lesinski21efb682016-09-14 17:35:43 -0700436
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700437 size_t index = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700438 size_t chunk_len = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700439
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700440 std::unique_ptr<uint8_t[]> serialized_outline =
441 nine_patch->SerializeRoundedRectOutline(&chunk_len);
442 strcpy((char*)unknown_chunks[index].name, "npOl");
443 unknown_chunks[index].size = chunk_len;
444 unknown_chunks[index].data = (png_bytep)serialized_outline.get();
445 unknown_chunks[index].location = PNG_HAVE_PLTE;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700446 index++;
447
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700448 std::unique_ptr<uint8_t[]> serialized_layout_bounds;
449 if (nine_patch->layout_bounds.nonZero()) {
450 serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
451 strcpy((char*)unknown_chunks[index].name, "npLb");
452 unknown_chunks[index].size = chunk_len;
453 unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
454 unknown_chunks[index].location = PNG_HAVE_PLTE;
Adam Lesinski21efb682016-09-14 17:35:43 -0700455 index++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700456 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700457
Adam Lesinski06460ef2017-03-14 18:52:13 -0700458 std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700459 strcpy((char*)unknown_chunks[index].name, "npTc");
460 unknown_chunks[index].size = chunk_len;
461 unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
462 unknown_chunks[index].location = PNG_HAVE_PLTE;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700463 index++;
Adam Lesinski21efb682016-09-14 17:35:43 -0700464
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700465 // Handle all unknown chunks. We are manually setting the chunks here,
466 // so we will only ever handle our custom chunks.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700467 png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
Adam Lesinski21efb682016-09-14 17:35:43 -0700468
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700469 // Set the actual chunks here. The data gets copied, so our buffers can
470 // safely go out of scope.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700471 png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
Adam Lesinski21efb682016-09-14 17:35:43 -0700472}
473
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000474bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
475 const PngOptions& options, IDiagnostics* diag, bool verbose) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700476 // Create and initialize the write png_struct with the default error and
477 // warning handlers.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700478 // The header version is also passed in to ensure that this was built against the same
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700479 // version of libpng.
Adam Lesinski06460ef2017-03-14 18:52:13 -0700480 png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700481 if (write_ptr == nullptr) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000482 diag->Error(android::DiagMessage() << "failed to create libpng write png_struct");
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700483 return false;
484 }
485
486 // Allocate memory to store image header data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700487 png_infop write_info_ptr = png_create_info_struct(write_ptr);
488 if (write_info_ptr == nullptr) {
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000489 diag->Error(android::DiagMessage() << "failed to create libpng write png_info");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700490 png_destroy_write_struct(&write_ptr, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700491 return false;
492 }
493
494 // Automatically release PNG resources at end of scope.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700495 PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700496
497 // libpng uses longjmp to jump to error handling routines.
498 // setjmp will return true only if it was jumped to, aka, there was an error.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700499 if (setjmp(png_jmpbuf(write_ptr))) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700500 return false;
501 }
502
503 // Handle warnings with our IDiagnostics.
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000504 png_set_error_fn(write_ptr, (png_voidp)&diag, LogError, LogWarning);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700505
506 // Set up the write functions which write to our custom data sources.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700507 png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700508
Yurii Zubrytskyic9414082024-10-29 17:53:57 -0700509 png_set_compression_level(write_ptr, options.compression_level);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700510
511 // Begin analysis of the image data.
512 // Scan the entire image and determine if:
513 // 1. Every pixel has R == G == B (grayscale)
514 // 2. Every pixel has A == 255 (opaque)
515 // 3. There are no more than 256 distinct RGBA colors (palette).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700516 std::unordered_map<uint32_t, int> color_palette;
517 std::unordered_set<uint32_t> alpha_palette;
518 bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
519 bool grayscale = true;
520 int max_gray_deviation = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700521
522 for (int32_t y = 0; y < image->height; y++) {
523 const uint8_t* row = image->rows[y];
524 for (int32_t x = 0; x < image->width; x++) {
525 int red = *row++;
526 int green = *row++;
527 int blue = *row++;
528 int alpha = *row++;
529
530 if (alpha == 0) {
531 // The color is completely transparent.
532 // For purposes of palettes and grayscale optimization,
533 // treat all channels as 0x00.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700534 needs_to_zero_rgb_channels_of_transparent_pixels =
535 needs_to_zero_rgb_channels_of_transparent_pixels ||
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700536 (red != 0 || green != 0 || blue != 0);
537 red = green = blue = 0;
538 }
539
540 // Insert the color into the color palette.
541 const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700542 color_palette[color] = -1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700543
544 // If the pixel has non-opaque alpha, insert it into the
545 // alpha palette.
546 if (alpha != 0xff) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700547 alpha_palette.insert(color);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700548 }
549
550 // Check if the image is indeed grayscale.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700551 if (grayscale) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700552 if (red != green || red != blue) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700553 grayscale = false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700554 }
555 }
556
557 // Calculate the gray scale deviation so that it can be compared
558 // with the threshold.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700559 max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
560 max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
561 max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
Adam Lesinski21efb682016-09-14 17:35:43 -0700562 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700563 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700564
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000565 if (verbose) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000566 android::DiagMessage msg;
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000567 msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size()
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700568 << " maxGrayDeviation=" << max_gray_deviation
569 << " grayScale=" << (grayscale ? "true" : "false");
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000570 diag->Note(msg);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700571 }
572
Adam Lesinski06460ef2017-03-14 18:52:13 -0700573 const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700574
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000575 const int new_color_type =
576 PickColorType(image->width, image->height, grayscale, convertible_to_grayscale,
577 nine_patch != nullptr, color_palette.size(), alpha_palette.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700578
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000579 if (verbose) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000580 android::DiagMessage msg;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700581 msg << "encoding PNG ";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700582 if (nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700583 msg << "(with 9-patch) as ";
Adam Lesinski21efb682016-09-14 17:35:43 -0700584 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700585 switch (new_color_type) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700586 case PNG_COLOR_TYPE_GRAY:
587 msg << "GRAY";
588 break;
589 case PNG_COLOR_TYPE_GRAY_ALPHA:
590 msg << "GRAY + ALPHA";
591 break;
592 case PNG_COLOR_TYPE_RGB:
593 msg << "RGB";
594 break;
595 case PNG_COLOR_TYPE_RGB_ALPHA:
596 msg << "RGBA";
597 break;
598 case PNG_COLOR_TYPE_PALETTE:
599 msg << "PALETTE";
600 break;
601 default:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700602 msg << "unknown type " << new_color_type;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700603 break;
Adam Lesinski21efb682016-09-14 17:35:43 -0700604 }
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000605 diag->Note(msg);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700606 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700607
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000608 png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, new_color_type,
609 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
Adam Lesinski21efb682016-09-14 17:35:43 -0700610
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700611 if (new_color_type & PNG_COLOR_MASK_PALETTE) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700612 // Assigns indices to the palette, and writes the encoded palette to the
613 // libpng writePtr.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700614 WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
615 png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700616 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700617 png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700618 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700619
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700620 if (nine_patch) {
621 WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700622 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700623
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700624 // Flush our updates to the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700625 png_write_info(write_ptr, write_info_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700626
627 // Write out each row of image data according to its encoding.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700628 if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700629 // 1 byte/pixel.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700630 auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700631
632 for (int32_t y = 0; y < image->height; y++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700633 png_const_bytep in_row = image->rows[y];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700634 for (int32_t x = 0; x < image->width; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700635 int rr = *in_row++;
636 int gg = *in_row++;
637 int bb = *in_row++;
638 int aa = *in_row++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700639 if (aa == 0) {
640 // Zero out color channels when transparent.
641 rr = gg = bb = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700642 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700643
644 const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700645 const int idx = color_palette[color];
646 CHECK(idx != -1);
647 out_row[x] = static_cast<png_byte>(idx);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700648 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700649 png_write_row(write_ptr, out_row.get());
Adam Lesinski21efb682016-09-14 17:35:43 -0700650 }
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000651 } else if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700652 const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000653 auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700654
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700655 for (int32_t y = 0; y < image->height; y++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700656 png_const_bytep in_row = image->rows[y];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700657 for (int32_t x = 0; x < image->width; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700658 int rr = in_row[x * 4];
659 int gg = in_row[x * 4 + 1];
660 int bb = in_row[x * 4 + 2];
661 int aa = in_row[x * 4 + 3];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700662 if (aa == 0) {
663 // Zero out the gray channel when transparent.
664 rr = gg = bb = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700665 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700666
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700667 if (grayscale) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700668 // The image was already grayscale, red == green == blue.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700669 out_row[x * bpp] = in_row[x * 4];
Adam Lesinski21efb682016-09-14 17:35:43 -0700670 } else {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700671 // The image is convertible to grayscale, use linear-luminance of
672 // sRGB colorspace:
673 // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000674 out_row[x * bpp] = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
Adam Lesinski21efb682016-09-14 17:35:43 -0700675 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700676
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700677 if (bpp == 2) {
678 // Write out alpha if we have it.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700679 out_row[x * bpp + 1] = aa;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700680 }
681 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700682 png_write_row(write_ptr, out_row.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700683 }
Adam Lesinski06460ef2017-03-14 18:52:13 -0700684 } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700685 const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
686 if (needs_to_zero_rgb_channels_of_transparent_pixels) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700687 // The source RGBA data can't be used as-is, because we need to zero out
Adam Lesinski06460ef2017-03-14 18:52:13 -0700688 // the RGB values of transparent pixels.
689 auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700690
691 for (int32_t y = 0; y < image->height; y++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700692 png_const_bytep in_row = image->rows[y];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700693 for (int32_t x = 0; x < image->width; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700694 int rr = *in_row++;
695 int gg = *in_row++;
696 int bb = *in_row++;
697 int aa = *in_row++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700698 if (aa == 0) {
699 // Zero out the RGB channels when transparent.
700 rr = gg = bb = 0;
701 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700702 out_row[x * bpp] = rr;
703 out_row[x * bpp + 1] = gg;
704 out_row[x * bpp + 2] = bb;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700705 if (bpp == 4) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700706 out_row[x * bpp + 3] = aa;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700707 }
708 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700709 png_write_row(write_ptr, out_row.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700710 }
711 } else {
712 // The source image can be used as-is, just tell libpng whether or not to
Adam Lesinski06460ef2017-03-14 18:52:13 -0700713 // ignore the alpha channel.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700714 if (new_color_type == PNG_COLOR_TYPE_RGB) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700715 // Delete the extraneous alpha values that we appended to our buffer
716 // when reading the original values.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700717 png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700718 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700719 png_write_image(write_ptr, image->rows.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700720 }
721 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700722 LOG(FATAL) << "unreachable";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700723 }
724
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700725 png_write_end(write_ptr, write_info_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700726 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700727}
728
Jeremy Meyerb4f83ff2023-11-30 19:29:50 +0000729} // namespace android