xref: /aosp_15_r20/frameworks/base/libs/androidfw/PngCrunch.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker /*
2*d57664e9SAndroid Build Coastguard Worker  * Copyright (C) 2016 The Android Open Source Project
3*d57664e9SAndroid Build Coastguard Worker  *
4*d57664e9SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*d57664e9SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*d57664e9SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*d57664e9SAndroid Build Coastguard Worker  *
8*d57664e9SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*d57664e9SAndroid Build Coastguard Worker  *
10*d57664e9SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*d57664e9SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*d57664e9SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*d57664e9SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*d57664e9SAndroid Build Coastguard Worker  * limitations under the License.
15*d57664e9SAndroid Build Coastguard Worker  */
16*d57664e9SAndroid Build Coastguard Worker 
17*d57664e9SAndroid Build Coastguard Worker #include <png.h>
18*d57664e9SAndroid Build Coastguard Worker #include <zlib.h>
19*d57664e9SAndroid Build Coastguard Worker 
20*d57664e9SAndroid Build Coastguard Worker #include <algorithm>
21*d57664e9SAndroid Build Coastguard Worker #include <unordered_map>
22*d57664e9SAndroid Build Coastguard Worker #include <unordered_set>
23*d57664e9SAndroid Build Coastguard Worker 
24*d57664e9SAndroid Build Coastguard Worker #include "android-base/errors.h"
25*d57664e9SAndroid Build Coastguard Worker #include "android-base/logging.h"
26*d57664e9SAndroid Build Coastguard Worker #include "android-base/macros.h"
27*d57664e9SAndroid Build Coastguard Worker #include "androidfw/Png.h"
28*d57664e9SAndroid Build Coastguard Worker 
29*d57664e9SAndroid Build Coastguard Worker namespace android {
30*d57664e9SAndroid Build Coastguard Worker 
31*d57664e9SAndroid Build Coastguard Worker // Custom deleter that destroys libpng read and info structs.
32*d57664e9SAndroid Build Coastguard Worker class PngReadStructDeleter {
33*d57664e9SAndroid Build Coastguard Worker  public:
PngReadStructDeleter(png_structp read_ptr,png_infop info_ptr)34*d57664e9SAndroid Build Coastguard Worker   PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
35*d57664e9SAndroid Build Coastguard Worker       : read_ptr_(read_ptr), info_ptr_(info_ptr) {
36*d57664e9SAndroid Build Coastguard Worker   }
37*d57664e9SAndroid Build Coastguard Worker 
~PngReadStructDeleter()38*d57664e9SAndroid Build Coastguard Worker   ~PngReadStructDeleter() {
39*d57664e9SAndroid Build Coastguard Worker     png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
40*d57664e9SAndroid Build Coastguard Worker   }
41*d57664e9SAndroid Build Coastguard Worker 
42*d57664e9SAndroid Build Coastguard Worker  private:
43*d57664e9SAndroid Build Coastguard Worker   png_structp read_ptr_;
44*d57664e9SAndroid Build Coastguard Worker   png_infop info_ptr_;
45*d57664e9SAndroid Build Coastguard Worker 
46*d57664e9SAndroid Build Coastguard Worker   DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
47*d57664e9SAndroid Build Coastguard Worker };
48*d57664e9SAndroid Build Coastguard Worker 
49*d57664e9SAndroid Build Coastguard Worker // Custom deleter that destroys libpng write and info structs.
50*d57664e9SAndroid Build Coastguard Worker class PngWriteStructDeleter {
51*d57664e9SAndroid Build Coastguard Worker  public:
PngWriteStructDeleter(png_structp write_ptr,png_infop info_ptr)52*d57664e9SAndroid Build Coastguard Worker   PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
53*d57664e9SAndroid Build Coastguard Worker       : write_ptr_(write_ptr), info_ptr_(info_ptr) {
54*d57664e9SAndroid Build Coastguard Worker   }
55*d57664e9SAndroid Build Coastguard Worker 
~PngWriteStructDeleter()56*d57664e9SAndroid Build Coastguard Worker   ~PngWriteStructDeleter() {
57*d57664e9SAndroid Build Coastguard Worker     png_destroy_write_struct(&write_ptr_, &info_ptr_);
58*d57664e9SAndroid Build Coastguard Worker   }
59*d57664e9SAndroid Build Coastguard Worker 
60*d57664e9SAndroid Build Coastguard Worker  private:
61*d57664e9SAndroid Build Coastguard Worker   png_structp write_ptr_;
62*d57664e9SAndroid Build Coastguard Worker   png_infop info_ptr_;
63*d57664e9SAndroid Build Coastguard Worker 
64*d57664e9SAndroid Build Coastguard Worker   DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
65*d57664e9SAndroid Build Coastguard Worker };
66*d57664e9SAndroid Build Coastguard Worker 
67*d57664e9SAndroid Build Coastguard Worker // Custom warning logging method that uses IDiagnostics.
LogWarning(png_structp png_ptr,png_const_charp warning_msg)68*d57664e9SAndroid Build Coastguard Worker static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
69*d57664e9SAndroid Build Coastguard Worker   android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
70*d57664e9SAndroid Build Coastguard Worker   diag->Warn(android::DiagMessage() << warning_msg);
71*d57664e9SAndroid Build Coastguard Worker }
72*d57664e9SAndroid Build Coastguard Worker 
73*d57664e9SAndroid Build Coastguard Worker // Custom error logging method that uses IDiagnostics.
LogError(png_structp png_ptr,png_const_charp error_msg)74*d57664e9SAndroid Build Coastguard Worker static void LogError(png_structp png_ptr, png_const_charp error_msg) {
75*d57664e9SAndroid Build Coastguard Worker   android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr);
76*d57664e9SAndroid Build Coastguard Worker   diag->Error(android::DiagMessage() << error_msg);
77*d57664e9SAndroid Build Coastguard Worker 
78*d57664e9SAndroid Build Coastguard Worker   // Causes libpng to longjmp to the spot where setjmp was set. This is how libpng does
79*d57664e9SAndroid Build Coastguard Worker   // error handling. If this custom error handler method were to return, libpng would, by
80*d57664e9SAndroid Build Coastguard Worker   // default, print the error message to stdout and call the same png_longjmp method.
81*d57664e9SAndroid Build Coastguard Worker   png_longjmp(png_ptr, 1);
82*d57664e9SAndroid Build Coastguard Worker }
83*d57664e9SAndroid Build Coastguard Worker 
ReadDataFromStream(png_structp png_ptr,png_bytep buffer,png_size_t len)84*d57664e9SAndroid Build Coastguard Worker static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
85*d57664e9SAndroid Build Coastguard Worker   InputStream* in = (InputStream*)png_get_io_ptr(png_ptr);
86*d57664e9SAndroid Build Coastguard Worker 
87*d57664e9SAndroid Build Coastguard Worker   const void* in_buffer;
88*d57664e9SAndroid Build Coastguard Worker   size_t in_len;
89*d57664e9SAndroid Build Coastguard Worker   if (!in->Next(&in_buffer, &in_len)) {
90*d57664e9SAndroid Build Coastguard Worker     if (in->HadError()) {
91*d57664e9SAndroid Build Coastguard Worker       std::stringstream error_msg_builder;
92*d57664e9SAndroid Build Coastguard Worker       error_msg_builder << "failed reading from input";
93*d57664e9SAndroid Build Coastguard Worker       if (!in->GetError().empty()) {
94*d57664e9SAndroid Build Coastguard Worker         error_msg_builder << ": " << in->GetError();
95*d57664e9SAndroid Build Coastguard Worker       }
96*d57664e9SAndroid Build Coastguard Worker       std::string err = error_msg_builder.str();
97*d57664e9SAndroid Build Coastguard Worker       png_error(png_ptr, err.c_str());
98*d57664e9SAndroid Build Coastguard Worker     }
99*d57664e9SAndroid Build Coastguard Worker     return;
100*d57664e9SAndroid Build Coastguard Worker   }
101*d57664e9SAndroid Build Coastguard Worker 
102*d57664e9SAndroid Build Coastguard Worker   const size_t bytes_read = std::min(in_len, len);
103*d57664e9SAndroid Build Coastguard Worker   memcpy(buffer, in_buffer, bytes_read);
104*d57664e9SAndroid Build Coastguard Worker   if (bytes_read != in_len) {
105*d57664e9SAndroid Build Coastguard Worker     in->BackUp(in_len - bytes_read);
106*d57664e9SAndroid Build Coastguard Worker   }
107*d57664e9SAndroid Build Coastguard Worker }
108*d57664e9SAndroid Build Coastguard Worker 
WriteDataToStream(png_structp png_ptr,png_bytep buffer,png_size_t len)109*d57664e9SAndroid Build Coastguard Worker static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
110*d57664e9SAndroid Build Coastguard Worker   OutputStream* out = (OutputStream*)png_get_io_ptr(png_ptr);
111*d57664e9SAndroid Build Coastguard Worker 
112*d57664e9SAndroid Build Coastguard Worker   void* out_buffer;
113*d57664e9SAndroid Build Coastguard Worker   size_t out_len;
114*d57664e9SAndroid Build Coastguard Worker   while (len > 0) {
115*d57664e9SAndroid Build Coastguard Worker     if (!out->Next(&out_buffer, &out_len)) {
116*d57664e9SAndroid Build Coastguard Worker       if (out->HadError()) {
117*d57664e9SAndroid Build Coastguard Worker         std::stringstream err_msg_builder;
118*d57664e9SAndroid Build Coastguard Worker         err_msg_builder << "failed writing to output";
119*d57664e9SAndroid Build Coastguard Worker         if (!out->GetError().empty()) {
120*d57664e9SAndroid Build Coastguard Worker           err_msg_builder << ": " << out->GetError();
121*d57664e9SAndroid Build Coastguard Worker         }
122*d57664e9SAndroid Build Coastguard Worker         std::string err = out->GetError();
123*d57664e9SAndroid Build Coastguard Worker         png_error(png_ptr, err.c_str());
124*d57664e9SAndroid Build Coastguard Worker       }
125*d57664e9SAndroid Build Coastguard Worker       return;
126*d57664e9SAndroid Build Coastguard Worker     }
127*d57664e9SAndroid Build Coastguard Worker 
128*d57664e9SAndroid Build Coastguard Worker     const size_t bytes_written = std::min(out_len, len);
129*d57664e9SAndroid Build Coastguard Worker     memcpy(out_buffer, buffer, bytes_written);
130*d57664e9SAndroid Build Coastguard Worker 
131*d57664e9SAndroid Build Coastguard Worker     // Advance the input buffer.
132*d57664e9SAndroid Build Coastguard Worker     buffer += bytes_written;
133*d57664e9SAndroid Build Coastguard Worker     len -= bytes_written;
134*d57664e9SAndroid Build Coastguard Worker 
135*d57664e9SAndroid Build Coastguard Worker     // Advance the output buffer.
136*d57664e9SAndroid Build Coastguard Worker     out_len -= bytes_written;
137*d57664e9SAndroid Build Coastguard Worker   }
138*d57664e9SAndroid Build Coastguard Worker 
139*d57664e9SAndroid Build Coastguard Worker   // If the entire output buffer wasn't used, backup.
140*d57664e9SAndroid Build Coastguard Worker   if (out_len > 0) {
141*d57664e9SAndroid Build Coastguard Worker     out->BackUp(out_len);
142*d57664e9SAndroid Build Coastguard Worker   }
143*d57664e9SAndroid Build Coastguard Worker }
144*d57664e9SAndroid Build Coastguard Worker 
ReadPng(InputStream * in,IDiagnostics * diag)145*d57664e9SAndroid Build Coastguard Worker std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag) {
146*d57664e9SAndroid Build Coastguard Worker   // Read the first 8 bytes of the file looking for the PNG signature.
147*d57664e9SAndroid Build Coastguard Worker   // Bail early if it does not match.
148*d57664e9SAndroid Build Coastguard Worker   const png_byte* signature;
149*d57664e9SAndroid Build Coastguard Worker   size_t buffer_size;
150*d57664e9SAndroid Build Coastguard Worker   if (!in->Next((const void**)&signature, &buffer_size)) {
151*d57664e9SAndroid Build Coastguard Worker     if (in->HadError()) {
152*d57664e9SAndroid Build Coastguard Worker       diag->Error(android::DiagMessage() << "failed to read PNG signature: " << in->GetError());
153*d57664e9SAndroid Build Coastguard Worker     } else {
154*d57664e9SAndroid Build Coastguard Worker       diag->Error(android::DiagMessage() << "not enough data for PNG signature");
155*d57664e9SAndroid Build Coastguard Worker     }
156*d57664e9SAndroid Build Coastguard Worker     return {};
157*d57664e9SAndroid Build Coastguard Worker   }
158*d57664e9SAndroid Build Coastguard Worker 
159*d57664e9SAndroid Build Coastguard Worker   if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
160*d57664e9SAndroid Build Coastguard Worker     diag->Error(android::DiagMessage() << "file signature does not match PNG signature");
161*d57664e9SAndroid Build Coastguard Worker     return {};
162*d57664e9SAndroid Build Coastguard Worker   }
163*d57664e9SAndroid Build Coastguard Worker 
164*d57664e9SAndroid Build Coastguard Worker   // Start at the beginning of the first chunk.
165*d57664e9SAndroid Build Coastguard Worker   in->BackUp(buffer_size - kPngSignatureSize);
166*d57664e9SAndroid Build Coastguard Worker 
167*d57664e9SAndroid Build Coastguard Worker   // Create and initialize the png_struct with the default error and warning handlers.
168*d57664e9SAndroid Build Coastguard Worker   // The header version is also passed in to ensure that this was built against the same
169*d57664e9SAndroid Build Coastguard Worker   // version of libpng.
170*d57664e9SAndroid Build Coastguard Worker   png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
171*d57664e9SAndroid Build Coastguard Worker   if (read_ptr == nullptr) {
172*d57664e9SAndroid Build Coastguard Worker     diag->Error(android::DiagMessage() << "failed to create libpng read png_struct");
173*d57664e9SAndroid Build Coastguard Worker     return {};
174*d57664e9SAndroid Build Coastguard Worker   }
175*d57664e9SAndroid Build Coastguard Worker 
176*d57664e9SAndroid Build Coastguard Worker   // Create and initialize the memory for image header and data.
177*d57664e9SAndroid Build Coastguard Worker   png_infop info_ptr = png_create_info_struct(read_ptr);
178*d57664e9SAndroid Build Coastguard Worker   if (info_ptr == nullptr) {
179*d57664e9SAndroid Build Coastguard Worker     diag->Error(android::DiagMessage() << "failed to create libpng read png_info");
180*d57664e9SAndroid Build Coastguard Worker     png_destroy_read_struct(&read_ptr, nullptr, nullptr);
181*d57664e9SAndroid Build Coastguard Worker     return {};
182*d57664e9SAndroid Build Coastguard Worker   }
183*d57664e9SAndroid Build Coastguard Worker 
184*d57664e9SAndroid Build Coastguard Worker   // Automatically release PNG resources at end of scope.
185*d57664e9SAndroid Build Coastguard Worker   PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
186*d57664e9SAndroid Build Coastguard Worker 
187*d57664e9SAndroid Build Coastguard Worker   // libpng uses longjmp to jump to an error handling routine.
188*d57664e9SAndroid Build Coastguard Worker   // setjmp will only return true if it was jumped to, aka there was
189*d57664e9SAndroid Build Coastguard Worker   // an error.
190*d57664e9SAndroid Build Coastguard Worker   if (setjmp(png_jmpbuf(read_ptr))) {
191*d57664e9SAndroid Build Coastguard Worker     return {};
192*d57664e9SAndroid Build Coastguard Worker   }
193*d57664e9SAndroid Build Coastguard Worker 
194*d57664e9SAndroid Build Coastguard Worker   // Handle warnings ourselves via IDiagnostics.
195*d57664e9SAndroid Build Coastguard Worker   png_set_error_fn(read_ptr, (png_voidp)&diag, LogError, LogWarning);
196*d57664e9SAndroid Build Coastguard Worker 
197*d57664e9SAndroid Build Coastguard Worker   // Set up the read functions which read from our custom data sources.
198*d57664e9SAndroid Build Coastguard Worker   png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
199*d57664e9SAndroid Build Coastguard Worker 
200*d57664e9SAndroid Build Coastguard Worker   // Skip the signature that we already read.
201*d57664e9SAndroid Build Coastguard Worker   png_set_sig_bytes(read_ptr, kPngSignatureSize);
202*d57664e9SAndroid Build Coastguard Worker 
203*d57664e9SAndroid Build Coastguard Worker   // Read the chunk headers.
204*d57664e9SAndroid Build Coastguard Worker   png_read_info(read_ptr, info_ptr);
205*d57664e9SAndroid Build Coastguard Worker 
206*d57664e9SAndroid Build Coastguard Worker   // Extract image meta-data from the various chunk headers.
207*d57664e9SAndroid Build Coastguard Worker   uint32_t width, height;
208*d57664e9SAndroid Build Coastguard Worker   int bit_depth, color_type, interlace_method, compression_method, filter_method;
209*d57664e9SAndroid Build Coastguard Worker   png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method,
210*d57664e9SAndroid Build Coastguard Worker                &compression_method, &filter_method);
211*d57664e9SAndroid Build Coastguard Worker 
212*d57664e9SAndroid Build Coastguard Worker   // When the image is read, expand it so that it is in RGBA 8888 format
213*d57664e9SAndroid Build Coastguard Worker   // so that image handling is uniform.
214*d57664e9SAndroid Build Coastguard Worker 
215*d57664e9SAndroid Build Coastguard Worker   if (color_type == PNG_COLOR_TYPE_PALETTE) {
216*d57664e9SAndroid Build Coastguard Worker     png_set_palette_to_rgb(read_ptr);
217*d57664e9SAndroid Build Coastguard Worker   }
218*d57664e9SAndroid Build Coastguard Worker 
219*d57664e9SAndroid Build Coastguard Worker   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
220*d57664e9SAndroid Build Coastguard Worker     png_set_expand_gray_1_2_4_to_8(read_ptr);
221*d57664e9SAndroid Build Coastguard Worker   }
222*d57664e9SAndroid Build Coastguard Worker 
223*d57664e9SAndroid Build Coastguard Worker   if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
224*d57664e9SAndroid Build Coastguard Worker     png_set_tRNS_to_alpha(read_ptr);
225*d57664e9SAndroid Build Coastguard Worker   }
226*d57664e9SAndroid Build Coastguard Worker 
227*d57664e9SAndroid Build Coastguard Worker   if (bit_depth == 16) {
228*d57664e9SAndroid Build Coastguard Worker     png_set_strip_16(read_ptr);
229*d57664e9SAndroid Build Coastguard Worker   }
230*d57664e9SAndroid Build Coastguard Worker 
231*d57664e9SAndroid Build Coastguard Worker   if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
232*d57664e9SAndroid Build Coastguard Worker     png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
233*d57664e9SAndroid Build Coastguard Worker   }
234*d57664e9SAndroid Build Coastguard Worker 
235*d57664e9SAndroid Build Coastguard Worker   if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
236*d57664e9SAndroid Build Coastguard Worker     png_set_gray_to_rgb(read_ptr);
237*d57664e9SAndroid Build Coastguard Worker   }
238*d57664e9SAndroid Build Coastguard Worker 
239*d57664e9SAndroid Build Coastguard Worker   if (interlace_method != PNG_INTERLACE_NONE) {
240*d57664e9SAndroid Build Coastguard Worker     png_set_interlace_handling(read_ptr);
241*d57664e9SAndroid Build Coastguard Worker   }
242*d57664e9SAndroid Build Coastguard Worker 
243*d57664e9SAndroid Build Coastguard Worker   // Once all the options for reading have been set, we need to flush
244*d57664e9SAndroid Build Coastguard Worker   // them to libpng.
245*d57664e9SAndroid Build Coastguard Worker   png_read_update_info(read_ptr, info_ptr);
246*d57664e9SAndroid Build Coastguard Worker 
247*d57664e9SAndroid Build Coastguard Worker   // 9-patch uses int32_t to index images, so we cap the image dimensions to
248*d57664e9SAndroid Build Coastguard Worker   // something
249*d57664e9SAndroid Build Coastguard Worker   // that can always be represented by 9-patch.
250*d57664e9SAndroid Build Coastguard Worker   if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
251*d57664e9SAndroid Build Coastguard Worker     diag->Error(android::DiagMessage()
252*d57664e9SAndroid Build Coastguard Worker                 << "PNG image dimensions are too large: " << width << "x" << height);
253*d57664e9SAndroid Build Coastguard Worker     return {};
254*d57664e9SAndroid Build Coastguard Worker   }
255*d57664e9SAndroid Build Coastguard Worker 
256*d57664e9SAndroid Build Coastguard Worker   std::unique_ptr<Image> output_image = std::make_unique<Image>();
257*d57664e9SAndroid Build Coastguard Worker   output_image->width = static_cast<int32_t>(width);
258*d57664e9SAndroid Build Coastguard Worker   output_image->height = static_cast<int32_t>(height);
259*d57664e9SAndroid Build Coastguard Worker 
260*d57664e9SAndroid Build Coastguard Worker   const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
261*d57664e9SAndroid Build Coastguard Worker   CHECK(row_bytes == 4 * width);  // RGBA
262*d57664e9SAndroid Build Coastguard Worker 
263*d57664e9SAndroid Build Coastguard Worker   // Allocate one large block to hold the image.
264*d57664e9SAndroid Build Coastguard Worker   output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
265*d57664e9SAndroid Build Coastguard Worker 
266*d57664e9SAndroid Build Coastguard Worker   // Create an array of rows that index into the data block.
267*d57664e9SAndroid Build Coastguard Worker   output_image->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
268*d57664e9SAndroid Build Coastguard Worker   for (uint32_t h = 0; h < height; h++) {
269*d57664e9SAndroid Build Coastguard Worker     output_image->rows[h] = output_image->data.get() + (h * row_bytes);
270*d57664e9SAndroid Build Coastguard Worker   }
271*d57664e9SAndroid Build Coastguard Worker 
272*d57664e9SAndroid Build Coastguard Worker   // Actually read the image pixels.
273*d57664e9SAndroid Build Coastguard Worker   png_read_image(read_ptr, output_image->rows.get());
274*d57664e9SAndroid Build Coastguard Worker 
275*d57664e9SAndroid Build Coastguard Worker   // Finish reading. This will read any other chunks after the image data.
276*d57664e9SAndroid Build Coastguard Worker   png_read_end(read_ptr, info_ptr);
277*d57664e9SAndroid Build Coastguard Worker 
278*d57664e9SAndroid Build Coastguard Worker   return output_image;
279*d57664e9SAndroid Build Coastguard Worker }
280*d57664e9SAndroid Build Coastguard Worker 
281*d57664e9SAndroid Build Coastguard Worker // Experimentally chosen constant to be added to the overhead of using color type
282*d57664e9SAndroid Build Coastguard Worker // PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk.
283*d57664e9SAndroid Build Coastguard Worker // Without this, many small PNGs encoded with palettes are larger after compression than
284*d57664e9SAndroid Build Coastguard Worker // the same PNGs encoded as RGBA.
285*d57664e9SAndroid Build Coastguard Worker constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
286*d57664e9SAndroid Build Coastguard Worker 
287*d57664e9SAndroid Build Coastguard Worker // Pick a color type by which to encode the image, based on which color type will take
288*d57664e9SAndroid Build Coastguard Worker // the least amount of disk space.
289*d57664e9SAndroid Build Coastguard Worker //
290*d57664e9SAndroid Build Coastguard Worker // 9-patch images traditionally have not been encoded with palettes.
291*d57664e9SAndroid Build Coastguard Worker // The original rationale was to avoid dithering until after scaling,
292*d57664e9SAndroid Build Coastguard Worker // but I don't think this would be an issue with palettes. Either way,
293*d57664e9SAndroid Build Coastguard Worker // our naive size estimation tends to be wrong for small images like 9-patches
294*d57664e9SAndroid Build Coastguard Worker // and using palettes balloons the size of the resulting 9-patch.
295*d57664e9SAndroid Build Coastguard Worker // In order to not regress in size, restrict 9-patch to not use palettes.
296*d57664e9SAndroid Build Coastguard Worker 
297*d57664e9SAndroid Build Coastguard Worker // The options are:
298*d57664e9SAndroid Build Coastguard Worker //
299*d57664e9SAndroid Build Coastguard Worker // - RGB
300*d57664e9SAndroid Build Coastguard Worker // - RGBA
301*d57664e9SAndroid Build Coastguard Worker // - RGB + cheap alpha
302*d57664e9SAndroid Build Coastguard Worker // - Color palette
303*d57664e9SAndroid Build Coastguard Worker // - Color palette + cheap alpha
304*d57664e9SAndroid Build Coastguard Worker // - Color palette + alpha palette
305*d57664e9SAndroid Build Coastguard Worker // - Grayscale
306*d57664e9SAndroid Build Coastguard Worker // - Grayscale + cheap alpha
307*d57664e9SAndroid Build Coastguard Worker // - Grayscale + alpha
308*d57664e9SAndroid Build Coastguard Worker //
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)309*d57664e9SAndroid Build Coastguard Worker static int PickColorType(int32_t width, int32_t height, bool grayscale,
310*d57664e9SAndroid Build Coastguard Worker                          bool convertible_to_grayscale, bool has_nine_patch,
311*d57664e9SAndroid Build Coastguard Worker                          size_t color_palette_size, size_t alpha_palette_size) {
312*d57664e9SAndroid Build Coastguard Worker   const size_t palette_chunk_size = 16 + color_palette_size * 3;
313*d57664e9SAndroid Build Coastguard Worker   const size_t alpha_chunk_size = 16 + alpha_palette_size;
314*d57664e9SAndroid Build Coastguard Worker   const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
315*d57664e9SAndroid Build Coastguard Worker   const size_t color_data_chunk_size = 16 + 3 * width * height;
316*d57664e9SAndroid Build Coastguard Worker   const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
317*d57664e9SAndroid Build Coastguard Worker   const size_t palette_data_chunk_size = 16 + width * height;
318*d57664e9SAndroid Build Coastguard Worker 
319*d57664e9SAndroid Build Coastguard Worker   if (grayscale) {
320*d57664e9SAndroid Build Coastguard Worker     if (alpha_palette_size == 0) {
321*d57664e9SAndroid Build Coastguard Worker       // This is the smallest the data can be.
322*d57664e9SAndroid Build Coastguard Worker       return PNG_COLOR_TYPE_GRAY;
323*d57664e9SAndroid Build Coastguard Worker     } else if (color_palette_size <= 256 && !has_nine_patch) {
324*d57664e9SAndroid Build Coastguard Worker       // This grayscale has alpha and can fit within a palette.
325*d57664e9SAndroid Build Coastguard Worker       // See if it is worth fitting into a palette.
326*d57664e9SAndroid Build Coastguard Worker       const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
327*d57664e9SAndroid Build Coastguard Worker                                        palette_data_chunk_size + kPaletteOverheadConstant;
328*d57664e9SAndroid Build Coastguard Worker       if (grayscale_alpha_data_chunk_size > palette_threshold) {
329*d57664e9SAndroid Build Coastguard Worker         return PNG_COLOR_TYPE_PALETTE;
330*d57664e9SAndroid Build Coastguard Worker       }
331*d57664e9SAndroid Build Coastguard Worker     }
332*d57664e9SAndroid Build Coastguard Worker     return PNG_COLOR_TYPE_GRAY_ALPHA;
333*d57664e9SAndroid Build Coastguard Worker   }
334*d57664e9SAndroid Build Coastguard Worker 
335*d57664e9SAndroid Build Coastguard Worker   if (color_palette_size <= 256 && !has_nine_patch) {
336*d57664e9SAndroid Build Coastguard Worker     // This image can fit inside a palette. Let's see if it is worth it.
337*d57664e9SAndroid Build Coastguard Worker     size_t total_size_with_palette = palette_data_chunk_size + palette_chunk_size;
338*d57664e9SAndroid Build Coastguard Worker     size_t total_size_without_palette = color_data_chunk_size;
339*d57664e9SAndroid Build Coastguard Worker     if (alpha_palette_size > 0) {
340*d57664e9SAndroid Build Coastguard Worker       total_size_with_palette += alpha_palette_size;
341*d57664e9SAndroid Build Coastguard Worker       total_size_without_palette = color_alpha_data_chunk_size;
342*d57664e9SAndroid Build Coastguard Worker     }
343*d57664e9SAndroid Build Coastguard Worker 
344*d57664e9SAndroid Build Coastguard Worker     if (total_size_without_palette > total_size_with_palette + kPaletteOverheadConstant) {
345*d57664e9SAndroid Build Coastguard Worker       return PNG_COLOR_TYPE_PALETTE;
346*d57664e9SAndroid Build Coastguard Worker     }
347*d57664e9SAndroid Build Coastguard Worker   }
348*d57664e9SAndroid Build Coastguard Worker 
349*d57664e9SAndroid Build Coastguard Worker   if (convertible_to_grayscale) {
350*d57664e9SAndroid Build Coastguard Worker     if (alpha_palette_size == 0) {
351*d57664e9SAndroid Build Coastguard Worker       return PNG_COLOR_TYPE_GRAY;
352*d57664e9SAndroid Build Coastguard Worker     } else {
353*d57664e9SAndroid Build Coastguard Worker       return PNG_COLOR_TYPE_GRAY_ALPHA;
354*d57664e9SAndroid Build Coastguard Worker     }
355*d57664e9SAndroid Build Coastguard Worker   }
356*d57664e9SAndroid Build Coastguard Worker 
357*d57664e9SAndroid Build Coastguard Worker   if (alpha_palette_size == 0) {
358*d57664e9SAndroid Build Coastguard Worker     return PNG_COLOR_TYPE_RGB;
359*d57664e9SAndroid Build Coastguard Worker   }
360*d57664e9SAndroid Build Coastguard Worker   return PNG_COLOR_TYPE_RGBA;
361*d57664e9SAndroid Build Coastguard Worker }
362*d57664e9SAndroid Build Coastguard Worker 
363*d57664e9SAndroid Build Coastguard Worker // Assigns indices to the color and alpha palettes, encodes them, and then invokes
364*d57664e9SAndroid Build Coastguard Worker // png_set_PLTE/png_set_tRNS.
365*d57664e9SAndroid Build Coastguard Worker // This must be done before writing image data.
366*d57664e9SAndroid Build Coastguard Worker // Image data must be transformed to use the indices assigned within the palette.
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)367*d57664e9SAndroid Build Coastguard Worker static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
368*d57664e9SAndroid Build Coastguard Worker                          std::unordered_map<uint32_t, int>* color_palette,
369*d57664e9SAndroid Build Coastguard Worker                          std::unordered_set<uint32_t>* alpha_palette) {
370*d57664e9SAndroid Build Coastguard Worker   CHECK(color_palette->size() <= 256);
371*d57664e9SAndroid Build Coastguard Worker   CHECK(alpha_palette->size() <= 256);
372*d57664e9SAndroid Build Coastguard Worker 
373*d57664e9SAndroid Build Coastguard Worker   // Populate the PNG palette struct and assign indices to the color palette.
374*d57664e9SAndroid Build Coastguard Worker 
375*d57664e9SAndroid Build Coastguard Worker   // Colors in the alpha palette should have smaller indices.
376*d57664e9SAndroid Build Coastguard Worker   // This will ensure that we can truncate the alpha palette if it is
377*d57664e9SAndroid Build Coastguard Worker   // smaller than the color palette.
378*d57664e9SAndroid Build Coastguard Worker   int index = 0;
379*d57664e9SAndroid Build Coastguard Worker   for (uint32_t color : *alpha_palette) {
380*d57664e9SAndroid Build Coastguard Worker     (*color_palette)[color] = index++;
381*d57664e9SAndroid Build Coastguard Worker   }
382*d57664e9SAndroid Build Coastguard Worker 
383*d57664e9SAndroid Build Coastguard Worker   // Assign the rest of the entries.
384*d57664e9SAndroid Build Coastguard Worker   for (auto& entry : *color_palette) {
385*d57664e9SAndroid Build Coastguard Worker     if (entry.second == -1) {
386*d57664e9SAndroid Build Coastguard Worker       entry.second = index++;
387*d57664e9SAndroid Build Coastguard Worker     }
388*d57664e9SAndroid Build Coastguard Worker   }
389*d57664e9SAndroid Build Coastguard Worker 
390*d57664e9SAndroid Build Coastguard Worker   // Create the PNG color palette struct.
391*d57664e9SAndroid Build Coastguard Worker   auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
392*d57664e9SAndroid Build Coastguard Worker 
393*d57664e9SAndroid Build Coastguard Worker   std::unique_ptr<png_byte[]> alpha_palette_bytes;
394*d57664e9SAndroid Build Coastguard Worker   if (!alpha_palette->empty()) {
395*d57664e9SAndroid Build Coastguard Worker     alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
396*d57664e9SAndroid Build Coastguard Worker   }
397*d57664e9SAndroid Build Coastguard Worker 
398*d57664e9SAndroid Build Coastguard Worker   for (const auto& entry : *color_palette) {
399*d57664e9SAndroid Build Coastguard Worker     const uint32_t color = entry.first;
400*d57664e9SAndroid Build Coastguard Worker     const int index = entry.second;
401*d57664e9SAndroid Build Coastguard Worker     CHECK(index >= 0);
402*d57664e9SAndroid Build Coastguard Worker     CHECK(static_cast<size_t>(index) < color_palette->size());
403*d57664e9SAndroid Build Coastguard Worker 
404*d57664e9SAndroid Build Coastguard Worker     png_colorp slot = color_palette_bytes.get() + index;
405*d57664e9SAndroid Build Coastguard Worker     slot->red = color >> 24;
406*d57664e9SAndroid Build Coastguard Worker     slot->green = color >> 16;
407*d57664e9SAndroid Build Coastguard Worker     slot->blue = color >> 8;
408*d57664e9SAndroid Build Coastguard Worker 
409*d57664e9SAndroid Build Coastguard Worker     const png_byte alpha = color & 0x000000ff;
410*d57664e9SAndroid Build Coastguard Worker     if (alpha != 0xff && alpha_palette_bytes) {
411*d57664e9SAndroid Build Coastguard Worker       CHECK(static_cast<size_t>(index) < alpha_palette->size());
412*d57664e9SAndroid Build Coastguard Worker       alpha_palette_bytes[index] = alpha;
413*d57664e9SAndroid Build Coastguard Worker     }
414*d57664e9SAndroid Build Coastguard Worker   }
415*d57664e9SAndroid Build Coastguard Worker 
416*d57664e9SAndroid Build Coastguard Worker   // The bytes get copied here, so it is safe to release color_palette_bytes at
417*d57664e9SAndroid Build Coastguard Worker   // the end of function
418*d57664e9SAndroid Build Coastguard Worker   // scope.
419*d57664e9SAndroid Build Coastguard Worker   png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size());
420*d57664e9SAndroid Build Coastguard Worker 
421*d57664e9SAndroid Build Coastguard Worker   if (alpha_palette_bytes) {
422*d57664e9SAndroid Build Coastguard Worker     png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(),
423*d57664e9SAndroid Build Coastguard Worker                  nullptr);
424*d57664e9SAndroid Build Coastguard Worker   }
425*d57664e9SAndroid Build Coastguard Worker }
426*d57664e9SAndroid Build Coastguard Worker 
427*d57664e9SAndroid Build Coastguard Worker // Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
428*d57664e9SAndroid Build Coastguard Worker // before writing image data.
WriteNinePatch(png_structp write_ptr,png_infop write_info_ptr,const NinePatch * nine_patch)429*d57664e9SAndroid Build Coastguard Worker static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
430*d57664e9SAndroid Build Coastguard Worker                            const NinePatch* nine_patch) {
431*d57664e9SAndroid Build Coastguard Worker   // The order of the chunks is important.
432*d57664e9SAndroid Build Coastguard Worker   // 9-patch code in older platforms expects the 9-patch chunk to be last.
433*d57664e9SAndroid Build Coastguard Worker 
434*d57664e9SAndroid Build Coastguard Worker   png_unknown_chunk unknown_chunks[3];
435*d57664e9SAndroid Build Coastguard Worker   memset(unknown_chunks, 0, sizeof(unknown_chunks));
436*d57664e9SAndroid Build Coastguard Worker 
437*d57664e9SAndroid Build Coastguard Worker   size_t index = 0;
438*d57664e9SAndroid Build Coastguard Worker   size_t chunk_len = 0;
439*d57664e9SAndroid Build Coastguard Worker 
440*d57664e9SAndroid Build Coastguard Worker   std::unique_ptr<uint8_t[]> serialized_outline =
441*d57664e9SAndroid Build Coastguard Worker       nine_patch->SerializeRoundedRectOutline(&chunk_len);
442*d57664e9SAndroid Build Coastguard Worker   strcpy((char*)unknown_chunks[index].name, "npOl");
443*d57664e9SAndroid Build Coastguard Worker   unknown_chunks[index].size = chunk_len;
444*d57664e9SAndroid Build Coastguard Worker   unknown_chunks[index].data = (png_bytep)serialized_outline.get();
445*d57664e9SAndroid Build Coastguard Worker   unknown_chunks[index].location = PNG_HAVE_PLTE;
446*d57664e9SAndroid Build Coastguard Worker   index++;
447*d57664e9SAndroid Build Coastguard Worker 
448*d57664e9SAndroid Build Coastguard Worker   std::unique_ptr<uint8_t[]> serialized_layout_bounds;
449*d57664e9SAndroid Build Coastguard Worker   if (nine_patch->layout_bounds.nonZero()) {
450*d57664e9SAndroid Build Coastguard Worker     serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
451*d57664e9SAndroid Build Coastguard Worker     strcpy((char*)unknown_chunks[index].name, "npLb");
452*d57664e9SAndroid Build Coastguard Worker     unknown_chunks[index].size = chunk_len;
453*d57664e9SAndroid Build Coastguard Worker     unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
454*d57664e9SAndroid Build Coastguard Worker     unknown_chunks[index].location = PNG_HAVE_PLTE;
455*d57664e9SAndroid Build Coastguard Worker     index++;
456*d57664e9SAndroid Build Coastguard Worker   }
457*d57664e9SAndroid Build Coastguard Worker 
458*d57664e9SAndroid Build Coastguard Worker   std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len);
459*d57664e9SAndroid Build Coastguard Worker   strcpy((char*)unknown_chunks[index].name, "npTc");
460*d57664e9SAndroid Build Coastguard Worker   unknown_chunks[index].size = chunk_len;
461*d57664e9SAndroid Build Coastguard Worker   unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
462*d57664e9SAndroid Build Coastguard Worker   unknown_chunks[index].location = PNG_HAVE_PLTE;
463*d57664e9SAndroid Build Coastguard Worker   index++;
464*d57664e9SAndroid Build Coastguard Worker 
465*d57664e9SAndroid Build Coastguard Worker   // Handle all unknown chunks. We are manually setting the chunks here,
466*d57664e9SAndroid Build Coastguard Worker   // so we will only ever handle our custom chunks.
467*d57664e9SAndroid Build Coastguard Worker   png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
468*d57664e9SAndroid Build Coastguard Worker 
469*d57664e9SAndroid Build Coastguard Worker   // Set the actual chunks here. The data gets copied, so our buffers can
470*d57664e9SAndroid Build Coastguard Worker   // safely go out of scope.
471*d57664e9SAndroid Build Coastguard Worker   png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
472*d57664e9SAndroid Build Coastguard Worker }
473*d57664e9SAndroid Build Coastguard Worker 
WritePng(const Image * image,const NinePatch * nine_patch,OutputStream * out,const PngOptions & options,IDiagnostics * diag,bool verbose)474*d57664e9SAndroid Build Coastguard Worker bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
475*d57664e9SAndroid Build Coastguard Worker               const PngOptions& options, IDiagnostics* diag, bool verbose) {
476*d57664e9SAndroid Build Coastguard Worker   // Create and initialize the write png_struct with the default error and
477*d57664e9SAndroid Build Coastguard Worker   // warning handlers.
478*d57664e9SAndroid Build Coastguard Worker   // The header version is also passed in to ensure that this was built against the same
479*d57664e9SAndroid Build Coastguard Worker   // version of libpng.
480*d57664e9SAndroid Build Coastguard Worker   png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
481*d57664e9SAndroid Build Coastguard Worker   if (write_ptr == nullptr) {
482*d57664e9SAndroid Build Coastguard Worker     diag->Error(android::DiagMessage() << "failed to create libpng write png_struct");
483*d57664e9SAndroid Build Coastguard Worker     return false;
484*d57664e9SAndroid Build Coastguard Worker   }
485*d57664e9SAndroid Build Coastguard Worker 
486*d57664e9SAndroid Build Coastguard Worker   // Allocate memory to store image header data.
487*d57664e9SAndroid Build Coastguard Worker   png_infop write_info_ptr = png_create_info_struct(write_ptr);
488*d57664e9SAndroid Build Coastguard Worker   if (write_info_ptr == nullptr) {
489*d57664e9SAndroid Build Coastguard Worker     diag->Error(android::DiagMessage() << "failed to create libpng write png_info");
490*d57664e9SAndroid Build Coastguard Worker     png_destroy_write_struct(&write_ptr, nullptr);
491*d57664e9SAndroid Build Coastguard Worker     return false;
492*d57664e9SAndroid Build Coastguard Worker   }
493*d57664e9SAndroid Build Coastguard Worker 
494*d57664e9SAndroid Build Coastguard Worker   // Automatically release PNG resources at end of scope.
495*d57664e9SAndroid Build Coastguard Worker   PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
496*d57664e9SAndroid Build Coastguard Worker 
497*d57664e9SAndroid Build Coastguard Worker   // libpng uses longjmp to jump to error handling routines.
498*d57664e9SAndroid Build Coastguard Worker   // setjmp will return true only if it was jumped to, aka, there was an error.
499*d57664e9SAndroid Build Coastguard Worker   if (setjmp(png_jmpbuf(write_ptr))) {
500*d57664e9SAndroid Build Coastguard Worker     return false;
501*d57664e9SAndroid Build Coastguard Worker   }
502*d57664e9SAndroid Build Coastguard Worker 
503*d57664e9SAndroid Build Coastguard Worker   // Handle warnings with our IDiagnostics.
504*d57664e9SAndroid Build Coastguard Worker   png_set_error_fn(write_ptr, (png_voidp)&diag, LogError, LogWarning);
505*d57664e9SAndroid Build Coastguard Worker 
506*d57664e9SAndroid Build Coastguard Worker   // Set up the write functions which write to our custom data sources.
507*d57664e9SAndroid Build Coastguard Worker   png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
508*d57664e9SAndroid Build Coastguard Worker 
509*d57664e9SAndroid Build Coastguard Worker   png_set_compression_level(write_ptr, options.compression_level);
510*d57664e9SAndroid Build Coastguard Worker 
511*d57664e9SAndroid Build Coastguard Worker   // Begin analysis of the image data.
512*d57664e9SAndroid Build Coastguard Worker   // Scan the entire image and determine if:
513*d57664e9SAndroid Build Coastguard Worker   // 1. Every pixel has R == G == B (grayscale)
514*d57664e9SAndroid Build Coastguard Worker   // 2. Every pixel has A == 255 (opaque)
515*d57664e9SAndroid Build Coastguard Worker   // 3. There are no more than 256 distinct RGBA colors (palette).
516*d57664e9SAndroid Build Coastguard Worker   std::unordered_map<uint32_t, int> color_palette;
517*d57664e9SAndroid Build Coastguard Worker   std::unordered_set<uint32_t> alpha_palette;
518*d57664e9SAndroid Build Coastguard Worker   bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
519*d57664e9SAndroid Build Coastguard Worker   bool grayscale = true;
520*d57664e9SAndroid Build Coastguard Worker   int max_gray_deviation = 0;
521*d57664e9SAndroid Build Coastguard Worker 
522*d57664e9SAndroid Build Coastguard Worker   for (int32_t y = 0; y < image->height; y++) {
523*d57664e9SAndroid Build Coastguard Worker     const uint8_t* row = image->rows[y];
524*d57664e9SAndroid Build Coastguard Worker     for (int32_t x = 0; x < image->width; x++) {
525*d57664e9SAndroid Build Coastguard Worker       int red = *row++;
526*d57664e9SAndroid Build Coastguard Worker       int green = *row++;
527*d57664e9SAndroid Build Coastguard Worker       int blue = *row++;
528*d57664e9SAndroid Build Coastguard Worker       int alpha = *row++;
529*d57664e9SAndroid Build Coastguard Worker 
530*d57664e9SAndroid Build Coastguard Worker       if (alpha == 0) {
531*d57664e9SAndroid Build Coastguard Worker         // The color is completely transparent.
532*d57664e9SAndroid Build Coastguard Worker         // For purposes of palettes and grayscale optimization,
533*d57664e9SAndroid Build Coastguard Worker         // treat all channels as 0x00.
534*d57664e9SAndroid Build Coastguard Worker         needs_to_zero_rgb_channels_of_transparent_pixels =
535*d57664e9SAndroid Build Coastguard Worker             needs_to_zero_rgb_channels_of_transparent_pixels ||
536*d57664e9SAndroid Build Coastguard Worker             (red != 0 || green != 0 || blue != 0);
537*d57664e9SAndroid Build Coastguard Worker         red = green = blue = 0;
538*d57664e9SAndroid Build Coastguard Worker       }
539*d57664e9SAndroid Build Coastguard Worker 
540*d57664e9SAndroid Build Coastguard Worker       // Insert the color into the color palette.
541*d57664e9SAndroid Build Coastguard Worker       const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
542*d57664e9SAndroid Build Coastguard Worker       color_palette[color] = -1;
543*d57664e9SAndroid Build Coastguard Worker 
544*d57664e9SAndroid Build Coastguard Worker       // If the pixel has non-opaque alpha, insert it into the
545*d57664e9SAndroid Build Coastguard Worker       // alpha palette.
546*d57664e9SAndroid Build Coastguard Worker       if (alpha != 0xff) {
547*d57664e9SAndroid Build Coastguard Worker         alpha_palette.insert(color);
548*d57664e9SAndroid Build Coastguard Worker       }
549*d57664e9SAndroid Build Coastguard Worker 
550*d57664e9SAndroid Build Coastguard Worker       // Check if the image is indeed grayscale.
551*d57664e9SAndroid Build Coastguard Worker       if (grayscale) {
552*d57664e9SAndroid Build Coastguard Worker         if (red != green || red != blue) {
553*d57664e9SAndroid Build Coastguard Worker           grayscale = false;
554*d57664e9SAndroid Build Coastguard Worker         }
555*d57664e9SAndroid Build Coastguard Worker       }
556*d57664e9SAndroid Build Coastguard Worker 
557*d57664e9SAndroid Build Coastguard Worker       // Calculate the gray scale deviation so that it can be compared
558*d57664e9SAndroid Build Coastguard Worker       // with the threshold.
559*d57664e9SAndroid Build Coastguard Worker       max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
560*d57664e9SAndroid Build Coastguard Worker       max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
561*d57664e9SAndroid Build Coastguard Worker       max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
562*d57664e9SAndroid Build Coastguard Worker     }
563*d57664e9SAndroid Build Coastguard Worker   }
564*d57664e9SAndroid Build Coastguard Worker 
565*d57664e9SAndroid Build Coastguard Worker   if (verbose) {
566*d57664e9SAndroid Build Coastguard Worker     android::DiagMessage msg;
567*d57664e9SAndroid Build Coastguard Worker     msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size()
568*d57664e9SAndroid Build Coastguard Worker         << " maxGrayDeviation=" << max_gray_deviation
569*d57664e9SAndroid Build Coastguard Worker         << " grayScale=" << (grayscale ? "true" : "false");
570*d57664e9SAndroid Build Coastguard Worker     diag->Note(msg);
571*d57664e9SAndroid Build Coastguard Worker   }
572*d57664e9SAndroid Build Coastguard Worker 
573*d57664e9SAndroid Build Coastguard Worker   const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
574*d57664e9SAndroid Build Coastguard Worker 
575*d57664e9SAndroid Build Coastguard Worker   const int new_color_type =
576*d57664e9SAndroid Build Coastguard Worker       PickColorType(image->width, image->height, grayscale, convertible_to_grayscale,
577*d57664e9SAndroid Build Coastguard Worker                     nine_patch != nullptr, color_palette.size(), alpha_palette.size());
578*d57664e9SAndroid Build Coastguard Worker 
579*d57664e9SAndroid Build Coastguard Worker   if (verbose) {
580*d57664e9SAndroid Build Coastguard Worker     android::DiagMessage msg;
581*d57664e9SAndroid Build Coastguard Worker     msg << "encoding PNG ";
582*d57664e9SAndroid Build Coastguard Worker     if (nine_patch) {
583*d57664e9SAndroid Build Coastguard Worker       msg << "(with 9-patch) as ";
584*d57664e9SAndroid Build Coastguard Worker     }
585*d57664e9SAndroid Build Coastguard Worker     switch (new_color_type) {
586*d57664e9SAndroid Build Coastguard Worker       case PNG_COLOR_TYPE_GRAY:
587*d57664e9SAndroid Build Coastguard Worker         msg << "GRAY";
588*d57664e9SAndroid Build Coastguard Worker         break;
589*d57664e9SAndroid Build Coastguard Worker       case PNG_COLOR_TYPE_GRAY_ALPHA:
590*d57664e9SAndroid Build Coastguard Worker         msg << "GRAY + ALPHA";
591*d57664e9SAndroid Build Coastguard Worker         break;
592*d57664e9SAndroid Build Coastguard Worker       case PNG_COLOR_TYPE_RGB:
593*d57664e9SAndroid Build Coastguard Worker         msg << "RGB";
594*d57664e9SAndroid Build Coastguard Worker         break;
595*d57664e9SAndroid Build Coastguard Worker       case PNG_COLOR_TYPE_RGB_ALPHA:
596*d57664e9SAndroid Build Coastguard Worker         msg << "RGBA";
597*d57664e9SAndroid Build Coastguard Worker         break;
598*d57664e9SAndroid Build Coastguard Worker       case PNG_COLOR_TYPE_PALETTE:
599*d57664e9SAndroid Build Coastguard Worker         msg << "PALETTE";
600*d57664e9SAndroid Build Coastguard Worker         break;
601*d57664e9SAndroid Build Coastguard Worker       default:
602*d57664e9SAndroid Build Coastguard Worker         msg << "unknown type " << new_color_type;
603*d57664e9SAndroid Build Coastguard Worker         break;
604*d57664e9SAndroid Build Coastguard Worker     }
605*d57664e9SAndroid Build Coastguard Worker     diag->Note(msg);
606*d57664e9SAndroid Build Coastguard Worker   }
607*d57664e9SAndroid Build Coastguard Worker 
608*d57664e9SAndroid Build Coastguard Worker   png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, new_color_type,
609*d57664e9SAndroid Build Coastguard Worker                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
610*d57664e9SAndroid Build Coastguard Worker 
611*d57664e9SAndroid Build Coastguard Worker   if (new_color_type & PNG_COLOR_MASK_PALETTE) {
612*d57664e9SAndroid Build Coastguard Worker     // Assigns indices to the palette, and writes the encoded palette to the
613*d57664e9SAndroid Build Coastguard Worker     // libpng writePtr.
614*d57664e9SAndroid Build Coastguard Worker     WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
615*d57664e9SAndroid Build Coastguard Worker     png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
616*d57664e9SAndroid Build Coastguard Worker   } else {
617*d57664e9SAndroid Build Coastguard Worker     png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
618*d57664e9SAndroid Build Coastguard Worker   }
619*d57664e9SAndroid Build Coastguard Worker 
620*d57664e9SAndroid Build Coastguard Worker   if (nine_patch) {
621*d57664e9SAndroid Build Coastguard Worker     WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
622*d57664e9SAndroid Build Coastguard Worker   }
623*d57664e9SAndroid Build Coastguard Worker 
624*d57664e9SAndroid Build Coastguard Worker   // Flush our updates to the header.
625*d57664e9SAndroid Build Coastguard Worker   png_write_info(write_ptr, write_info_ptr);
626*d57664e9SAndroid Build Coastguard Worker 
627*d57664e9SAndroid Build Coastguard Worker   // Write out each row of image data according to its encoding.
628*d57664e9SAndroid Build Coastguard Worker   if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
629*d57664e9SAndroid Build Coastguard Worker     // 1 byte/pixel.
630*d57664e9SAndroid Build Coastguard Worker     auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
631*d57664e9SAndroid Build Coastguard Worker 
632*d57664e9SAndroid Build Coastguard Worker     for (int32_t y = 0; y < image->height; y++) {
633*d57664e9SAndroid Build Coastguard Worker       png_const_bytep in_row = image->rows[y];
634*d57664e9SAndroid Build Coastguard Worker       for (int32_t x = 0; x < image->width; x++) {
635*d57664e9SAndroid Build Coastguard Worker         int rr = *in_row++;
636*d57664e9SAndroid Build Coastguard Worker         int gg = *in_row++;
637*d57664e9SAndroid Build Coastguard Worker         int bb = *in_row++;
638*d57664e9SAndroid Build Coastguard Worker         int aa = *in_row++;
639*d57664e9SAndroid Build Coastguard Worker         if (aa == 0) {
640*d57664e9SAndroid Build Coastguard Worker           // Zero out color channels when transparent.
641*d57664e9SAndroid Build Coastguard Worker           rr = gg = bb = 0;
642*d57664e9SAndroid Build Coastguard Worker         }
643*d57664e9SAndroid Build Coastguard Worker 
644*d57664e9SAndroid Build Coastguard Worker         const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
645*d57664e9SAndroid Build Coastguard Worker         const int idx = color_palette[color];
646*d57664e9SAndroid Build Coastguard Worker         CHECK(idx != -1);
647*d57664e9SAndroid Build Coastguard Worker         out_row[x] = static_cast<png_byte>(idx);
648*d57664e9SAndroid Build Coastguard Worker       }
649*d57664e9SAndroid Build Coastguard Worker       png_write_row(write_ptr, out_row.get());
650*d57664e9SAndroid Build Coastguard Worker     }
651*d57664e9SAndroid Build Coastguard Worker   } else if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
652*d57664e9SAndroid Build Coastguard Worker     const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
653*d57664e9SAndroid Build Coastguard Worker     auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
654*d57664e9SAndroid Build Coastguard Worker 
655*d57664e9SAndroid Build Coastguard Worker     for (int32_t y = 0; y < image->height; y++) {
656*d57664e9SAndroid Build Coastguard Worker       png_const_bytep in_row = image->rows[y];
657*d57664e9SAndroid Build Coastguard Worker       for (int32_t x = 0; x < image->width; x++) {
658*d57664e9SAndroid Build Coastguard Worker         int rr = in_row[x * 4];
659*d57664e9SAndroid Build Coastguard Worker         int gg = in_row[x * 4 + 1];
660*d57664e9SAndroid Build Coastguard Worker         int bb = in_row[x * 4 + 2];
661*d57664e9SAndroid Build Coastguard Worker         int aa = in_row[x * 4 + 3];
662*d57664e9SAndroid Build Coastguard Worker         if (aa == 0) {
663*d57664e9SAndroid Build Coastguard Worker           // Zero out the gray channel when transparent.
664*d57664e9SAndroid Build Coastguard Worker           rr = gg = bb = 0;
665*d57664e9SAndroid Build Coastguard Worker         }
666*d57664e9SAndroid Build Coastguard Worker 
667*d57664e9SAndroid Build Coastguard Worker         if (grayscale) {
668*d57664e9SAndroid Build Coastguard Worker           // The image was already grayscale, red == green == blue.
669*d57664e9SAndroid Build Coastguard Worker           out_row[x * bpp] = in_row[x * 4];
670*d57664e9SAndroid Build Coastguard Worker         } else {
671*d57664e9SAndroid Build Coastguard Worker           // The image is convertible to grayscale, use linear-luminance of
672*d57664e9SAndroid Build Coastguard Worker           // sRGB colorspace:
673*d57664e9SAndroid Build Coastguard Worker           // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
674*d57664e9SAndroid Build Coastguard Worker           out_row[x * bpp] = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
675*d57664e9SAndroid Build Coastguard Worker         }
676*d57664e9SAndroid Build Coastguard Worker 
677*d57664e9SAndroid Build Coastguard Worker         if (bpp == 2) {
678*d57664e9SAndroid Build Coastguard Worker           // Write out alpha if we have it.
679*d57664e9SAndroid Build Coastguard Worker           out_row[x * bpp + 1] = aa;
680*d57664e9SAndroid Build Coastguard Worker         }
681*d57664e9SAndroid Build Coastguard Worker       }
682*d57664e9SAndroid Build Coastguard Worker       png_write_row(write_ptr, out_row.get());
683*d57664e9SAndroid Build Coastguard Worker     }
684*d57664e9SAndroid Build Coastguard Worker   } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) {
685*d57664e9SAndroid Build Coastguard Worker     const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
686*d57664e9SAndroid Build Coastguard Worker     if (needs_to_zero_rgb_channels_of_transparent_pixels) {
687*d57664e9SAndroid Build Coastguard Worker       // The source RGBA data can't be used as-is, because we need to zero out
688*d57664e9SAndroid Build Coastguard Worker       // the RGB values of transparent pixels.
689*d57664e9SAndroid Build Coastguard Worker       auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
690*d57664e9SAndroid Build Coastguard Worker 
691*d57664e9SAndroid Build Coastguard Worker       for (int32_t y = 0; y < image->height; y++) {
692*d57664e9SAndroid Build Coastguard Worker         png_const_bytep in_row = image->rows[y];
693*d57664e9SAndroid Build Coastguard Worker         for (int32_t x = 0; x < image->width; x++) {
694*d57664e9SAndroid Build Coastguard Worker           int rr = *in_row++;
695*d57664e9SAndroid Build Coastguard Worker           int gg = *in_row++;
696*d57664e9SAndroid Build Coastguard Worker           int bb = *in_row++;
697*d57664e9SAndroid Build Coastguard Worker           int aa = *in_row++;
698*d57664e9SAndroid Build Coastguard Worker           if (aa == 0) {
699*d57664e9SAndroid Build Coastguard Worker             // Zero out the RGB channels when transparent.
700*d57664e9SAndroid Build Coastguard Worker             rr = gg = bb = 0;
701*d57664e9SAndroid Build Coastguard Worker           }
702*d57664e9SAndroid Build Coastguard Worker           out_row[x * bpp] = rr;
703*d57664e9SAndroid Build Coastguard Worker           out_row[x * bpp + 1] = gg;
704*d57664e9SAndroid Build Coastguard Worker           out_row[x * bpp + 2] = bb;
705*d57664e9SAndroid Build Coastguard Worker           if (bpp == 4) {
706*d57664e9SAndroid Build Coastguard Worker             out_row[x * bpp + 3] = aa;
707*d57664e9SAndroid Build Coastguard Worker           }
708*d57664e9SAndroid Build Coastguard Worker         }
709*d57664e9SAndroid Build Coastguard Worker         png_write_row(write_ptr, out_row.get());
710*d57664e9SAndroid Build Coastguard Worker       }
711*d57664e9SAndroid Build Coastguard Worker     } else {
712*d57664e9SAndroid Build Coastguard Worker       // The source image can be used as-is, just tell libpng whether or not to
713*d57664e9SAndroid Build Coastguard Worker       // ignore the alpha channel.
714*d57664e9SAndroid Build Coastguard Worker       if (new_color_type == PNG_COLOR_TYPE_RGB) {
715*d57664e9SAndroid Build Coastguard Worker         // Delete the extraneous alpha values that we appended to our buffer
716*d57664e9SAndroid Build Coastguard Worker         // when reading the original values.
717*d57664e9SAndroid Build Coastguard Worker         png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
718*d57664e9SAndroid Build Coastguard Worker       }
719*d57664e9SAndroid Build Coastguard Worker       png_write_image(write_ptr, image->rows.get());
720*d57664e9SAndroid Build Coastguard Worker     }
721*d57664e9SAndroid Build Coastguard Worker   } else {
722*d57664e9SAndroid Build Coastguard Worker     LOG(FATAL) << "unreachable";
723*d57664e9SAndroid Build Coastguard Worker   }
724*d57664e9SAndroid Build Coastguard Worker 
725*d57664e9SAndroid Build Coastguard Worker   png_write_end(write_ptr, write_info_ptr);
726*d57664e9SAndroid Build Coastguard Worker   return true;
727*d57664e9SAndroid Build Coastguard Worker }
728*d57664e9SAndroid Build Coastguard Worker 
729*d57664e9SAndroid Build Coastguard Worker }  // namespace android
730