blob: 3b46d8b4c782cca20adc3714d55d794a3e1bc009 [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
17#include "compile/Png.h"
18
Adam Lesinski21efb682016-09-14 17:35:43 -070019#include <png.h>
Adam Lesinskicacb28f2016-10-19 12:18:14 -070020#include <zlib.h>
Adam Lesinskice5e56e2016-10-21 17:56:45 -070021
Adam Lesinskicacb28f2016-10-19 12:18:14 -070022#include <algorithm>
Adam Lesinski21efb682016-09-14 17:35:43 -070023#include <unordered_map>
24#include <unordered_set>
Adam Lesinski21efb682016-09-14 17:35:43 -070025
Adam Lesinskice5e56e2016-10-21 17:56:45 -070026#include "android-base/errors.h"
27#include "android-base/logging.h"
28#include "android-base/macros.h"
29
Adam Lesinski21efb682016-09-14 17:35:43 -070030namespace aapt {
31
32// Size in bytes of the PNG signature.
33constexpr size_t kPngSignatureSize = 8u;
34
35/**
36 * Custom deleter that destroys libpng read and info structs.
37 */
38class PngReadStructDeleter {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070039 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070040 PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
41 : read_ptr_(read_ptr), info_ptr_(info_ptr) {}
Adam Lesinski21efb682016-09-14 17:35:43 -070042
Adam Lesinskicacb28f2016-10-19 12:18:14 -070043 ~PngReadStructDeleter() {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070044 png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -070045 }
Adam Lesinski21efb682016-09-14 17:35:43 -070046
Adam Lesinskicacb28f2016-10-19 12:18:14 -070047 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070048 png_structp read_ptr_;
49 png_infop info_ptr_;
Adam Lesinski21efb682016-09-14 17:35:43 -070050
Adam Lesinskicacb28f2016-10-19 12:18:14 -070051 DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
Adam Lesinski21efb682016-09-14 17:35:43 -070052};
53
54/**
55 * Custom deleter that destroys libpng write and info structs.
56 */
57class PngWriteStructDeleter {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070058 public:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070059 PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
60 : write_ptr_(write_ptr), info_ptr_(info_ptr) {}
Adam Lesinski21efb682016-09-14 17:35:43 -070061
Adam Lesinskice5e56e2016-10-21 17:56:45 -070062 ~PngWriteStructDeleter() {
63 png_destroy_write_struct(&write_ptr_, &info_ptr_);
64 }
Adam Lesinski21efb682016-09-14 17:35:43 -070065
Adam Lesinskicacb28f2016-10-19 12:18:14 -070066 private:
Adam Lesinskice5e56e2016-10-21 17:56:45 -070067 png_structp write_ptr_;
68 png_infop info_ptr_;
Adam Lesinski21efb682016-09-14 17:35:43 -070069
Adam Lesinskicacb28f2016-10-19 12:18:14 -070070 DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
Adam Lesinski21efb682016-09-14 17:35:43 -070071};
72
73// Custom warning logging method that uses IDiagnostics.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070074static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) {
75 IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
76 diag->Warn(DiagMessage() << warning_msg);
Adam Lesinski21efb682016-09-14 17:35:43 -070077}
78
79// Custom error logging method that uses IDiagnostics.
Adam Lesinskice5e56e2016-10-21 17:56:45 -070080static void LogError(png_structp png_ptr, png_const_charp error_msg) {
81 IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr);
82 diag->Error(DiagMessage() << error_msg);
Adam Lesinski21efb682016-09-14 17:35:43 -070083}
84
Adam Lesinskice5e56e2016-10-21 17:56:45 -070085static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer,
Adam Lesinskicacb28f2016-10-19 12:18:14 -070086 png_size_t len) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -070087 io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -070088
Adam Lesinskice5e56e2016-10-21 17:56:45 -070089 const void* in_buffer;
90 int in_len;
91 if (!in->Next(&in_buffer, &in_len)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070092 if (in->HadError()) {
93 std::string err = in->GetError();
Adam Lesinskice5e56e2016-10-21 17:56:45 -070094 png_error(png_ptr, err.c_str());
Adam Lesinski21efb682016-09-14 17:35:43 -070095 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -070096 return;
97 }
Adam Lesinski21efb682016-09-14 17:35:43 -070098
Adam Lesinskice5e56e2016-10-21 17:56:45 -070099 const size_t bytes_read = std::min(static_cast<size_t>(in_len), len);
100 memcpy(buffer, in_buffer, bytes_read);
101 if (bytes_read != static_cast<size_t>(in_len)) {
102 in->BackUp(in_len - static_cast<int>(bytes_read));
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700103 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700104}
105
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700106static void WriteDataToStream(png_structp png_ptr, png_bytep buffer,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700107 png_size_t len) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700108 io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700109
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700110 void* out_buffer;
111 int out_len;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700112 while (len > 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700113 if (!out->Next(&out_buffer, &out_len)) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700114 if (out->HadError()) {
115 std::string err = out->GetError();
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700116 png_error(png_ptr, err.c_str());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700117 }
118 return;
Adam Lesinski21efb682016-09-14 17:35:43 -0700119 }
120
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700121 const size_t bytes_written = std::min(static_cast<size_t>(out_len), len);
122 memcpy(out_buffer, buffer, bytes_written);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700123
124 // Advance the input buffer.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700125 buffer += bytes_written;
126 len -= bytes_written;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700127
128 // Advance the output buffer.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700129 out_len -= static_cast<int>(bytes_written);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700130 }
131
132 // If the entire output buffer wasn't used, backup.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700133 if (out_len > 0) {
134 out->BackUp(out_len);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700135 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700136}
137
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700138std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700139 // Read the first 8 bytes of the file looking for the PNG signature.
140 // Bail early if it does not match.
141 const png_byte* signature;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700142 int buffer_size;
143 if (!in->Next((const void**)&signature, &buffer_size)) {
144 context->GetDiagnostics()->Error(
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700145 DiagMessage() << android::base::SystemErrorCodeToString(errno));
146 return {};
147 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700148
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700149 if (static_cast<size_t>(buffer_size) < kPngSignatureSize ||
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700150 png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700151 context->GetDiagnostics()->Error(
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700152 DiagMessage() << "file signature does not match PNG signature");
153 return {};
154 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700155
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700156 // Start at the beginning of the first chunk.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700157 in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize));
Adam Lesinski21efb682016-09-14 17:35:43 -0700158
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700159 // Create and initialize the png_struct with the default error and warning
160 // handlers.
161 // The header version is also passed in to ensure that this was built against
162 // the same
163 // version of libpng.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700164 png_structp read_ptr =
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700165 png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700166 if (read_ptr == nullptr) {
167 context->GetDiagnostics()->Error(
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700168 DiagMessage() << "failed to create libpng read png_struct");
169 return {};
170 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700171
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700172 // Create and initialize the memory for image header and data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700173 png_infop info_ptr = png_create_info_struct(read_ptr);
174 if (info_ptr == nullptr) {
175 context->GetDiagnostics()->Error(
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700176 DiagMessage() << "failed to create libpng read png_info");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700177 png_destroy_read_struct(&read_ptr, nullptr, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700178 return {};
179 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700180
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700181 // Automatically release PNG resources at end of scope.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700182 PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700183
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700184 // libpng uses longjmp to jump to an error handling routine.
185 // setjmp will only return true if it was jumped to, aka there was
186 // an error.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700187 if (setjmp(png_jmpbuf(read_ptr))) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700188 return {};
189 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700190
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700191 // Handle warnings ourselves via IDiagnostics.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700192 png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError,
193 LogWarning);
Adam Lesinski21efb682016-09-14 17:35:43 -0700194
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700195 // Set up the read functions which read from our custom data sources.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700196 png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
Adam Lesinski21efb682016-09-14 17:35:43 -0700197
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700198 // Skip the signature that we already read.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700199 png_set_sig_bytes(read_ptr, kPngSignatureSize);
Adam Lesinski21efb682016-09-14 17:35:43 -0700200
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700201 // Read the chunk headers.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700202 png_read_info(read_ptr, info_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700203
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700204 // Extract image meta-data from the various chunk headers.
205 uint32_t width, height;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700206 int bit_depth, color_type, interlace_method, compression_method,
207 filter_method;
208 png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
209 &interlace_method, &compression_method, &filter_method);
Adam Lesinski21efb682016-09-14 17:35:43 -0700210
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700211 // When the image is read, expand it so that it is in RGBA 8888 format
212 // so that image handling is uniform.
Adam Lesinski21efb682016-09-14 17:35:43 -0700213
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700214 if (color_type == PNG_COLOR_TYPE_PALETTE) {
215 png_set_palette_to_rgb(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700216 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700217
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700218 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
219 png_set_expand_gray_1_2_4_to_8(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700220 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700221
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700222 if (png_get_valid(read_ptr, info_ptr, PNG_INFO_tRNS)) {
223 png_set_tRNS_to_alpha(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700224 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700225
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700226 if (bit_depth == 16) {
227 png_set_strip_16(read_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700228 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700229
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700230 if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
231 png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700232 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700233
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700234 if (color_type == PNG_COLOR_TYPE_GRAY ||
235 color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
236 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.
250 if (width > std::numeric_limits<int32_t>::max() ||
251 height > std::numeric_limits<int32_t>::max()) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700252 context->GetDiagnostics()->Error(DiagMessage()
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700253 << "PNG image dimensions are too large: "
254 << width << "x" << height);
255 return {};
256 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700257
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700258 std::unique_ptr<Image> output_image = util::make_unique<Image>();
259 output_image->width = static_cast<int32_t>(width);
260 output_image->height = static_cast<int32_t>(height);
Adam Lesinski21efb682016-09-14 17:35:43 -0700261
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700262 const size_t row_bytes = png_get_rowbytes(read_ptr, info_ptr);
263 CHECK(row_bytes == 4 * width); // RGBA
Adam Lesinski21efb682016-09-14 17:35:43 -0700264
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700265 // Allocate one large block to hold the image.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700266 output_image->data =
267 std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700268
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700269 // Create an array of rows that index into the data block.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700270 output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700271 for (uint32_t h = 0; h < height; h++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700272 output_image->rows[h] = output_image->data.get() + (h * row_bytes);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700273 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700274
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700275 // Actually read the image pixels.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700276 png_read_image(read_ptr, output_image->rows.get());
Adam Lesinski21efb682016-09-14 17:35:43 -0700277
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700278 // Finish reading. This will read any other chunks after the image data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700279 png_read_end(read_ptr, info_ptr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700280
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700281 return output_image;
Adam Lesinski21efb682016-09-14 17:35:43 -0700282}
283
284/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700285 * Experimentally chosen constant to be added to the overhead of using color
286 * type
287 * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
288 * chunk.
289 * Without this, many small PNGs encoded with palettes are larger after
290 * compression than
Adam Lesinski21efb682016-09-14 17:35:43 -0700291 * the same PNGs encoded as RGBA.
292 */
293constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
294
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700295// Pick a color type by which to encode the image, based on which color type
296// will take
Adam Lesinski21efb682016-09-14 17:35:43 -0700297// the least amount of disk space.
298//
299// 9-patch images traditionally have not been encoded with palettes.
300// The original rationale was to avoid dithering until after scaling,
301// but I don't think this would be an issue with palettes. Either way,
302// our naive size estimation tends to be wrong for small images like 9-patches
303// and using palettes balloons the size of the resulting 9-patch.
304// In order to not regress in size, restrict 9-patch to not use palettes.
305
306// The options are:
307//
308// - RGB
309// - RGBA
310// - RGB + cheap alpha
311// - Color palette
312// - Color palette + cheap alpha
313// - Color palette + alpha palette
314// - Grayscale
315// - Grayscale + cheap alpha
316// - Grayscale + alpha
317//
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700318static int PickColorType(int32_t width, int32_t height, bool grayscale,
319 bool convertible_to_grayscale, bool has_nine_patch,
320 size_t color_palette_size, size_t alpha_palette_size) {
321 const size_t palette_chunk_size = 16 + color_palette_size * 3;
322 const size_t alpha_chunk_size = 16 + alpha_palette_size;
323 const size_t color_alpha_data_chunk_size = 16 + 4 * width * height;
324 const size_t color_data_chunk_size = 16 + 3 * width * height;
325 const size_t grayscale_alpha_data_chunk_size = 16 + 2 * width * height;
326 const size_t palette_data_chunk_size = 16 + width * height;
Adam Lesinski21efb682016-09-14 17:35:43 -0700327
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700328 if (grayscale) {
329 if (alpha_palette_size == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700330 // This is the smallest the data can be.
331 return PNG_COLOR_TYPE_GRAY;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700332 } else if (color_palette_size <= 256 && !has_nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700333 // This grayscale has alpha and can fit within a palette.
334 // See if it is worth fitting into a palette.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700335 const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
336 palette_data_chunk_size +
337 kPaletteOverheadConstant;
338 if (grayscale_alpha_data_chunk_size > palette_threshold) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700339 return PNG_COLOR_TYPE_PALETTE;
340 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700341 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700342 return PNG_COLOR_TYPE_GRAY_ALPHA;
343 }
344
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700345 if (color_palette_size <= 256 && !has_nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700346 // This image can fit inside a palette. Let's see if it is worth it.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700347 size_t total_size_with_palette =
348 palette_data_chunk_size + palette_chunk_size;
349 size_t total_size_without_palette = color_data_chunk_size;
350 if (alpha_palette_size > 0) {
351 total_size_with_palette += alpha_palette_size;
352 total_size_without_palette = color_alpha_data_chunk_size;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700353 }
354
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700355 if (total_size_without_palette >
356 total_size_with_palette + kPaletteOverheadConstant) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700357 return PNG_COLOR_TYPE_PALETTE;
358 }
359 }
360
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700361 if (convertible_to_grayscale) {
362 if (alpha_palette_size == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700363 return PNG_COLOR_TYPE_GRAY;
364 } else {
365 return PNG_COLOR_TYPE_GRAY_ALPHA;
366 }
367 }
368
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700369 if (alpha_palette_size == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700370 return PNG_COLOR_TYPE_RGB;
371 }
372 return PNG_COLOR_TYPE_RGBA;
Adam Lesinski21efb682016-09-14 17:35:43 -0700373}
374
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700375// Assigns indices to the color and alpha palettes, encodes them, and then
376// invokes
Adam Lesinski21efb682016-09-14 17:35:43 -0700377// png_set_PLTE/png_set_tRNS.
378// This must be done before writing image data.
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700379// Image data must be transformed to use the indices assigned within the
380// palette.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700381static void WritePalette(png_structp write_ptr, png_infop write_info_ptr,
382 std::unordered_map<uint32_t, int>* color_palette,
383 std::unordered_set<uint32_t>* alpha_palette) {
384 CHECK(color_palette->size() <= 256);
385 CHECK(alpha_palette->size() <= 256);
Adam Lesinski21efb682016-09-14 17:35:43 -0700386
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700387 // Populate the PNG palette struct and assign indices to the color
388 // palette.
Adam Lesinski21efb682016-09-14 17:35:43 -0700389
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700390 // Colors in the alpha palette should have smaller indices.
391 // This will ensure that we can truncate the alpha palette if it is
392 // smaller than the color palette.
393 int index = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700394 for (uint32_t color : *alpha_palette) {
395 (*color_palette)[color] = index++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700396 }
397
398 // Assign the rest of the entries.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700399 for (auto& entry : *color_palette) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700400 if (entry.second == -1) {
401 entry.second = index++;
Adam Lesinski21efb682016-09-14 17:35:43 -0700402 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700403 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700404
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700405 // Create the PNG color palette struct.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700406 auto color_palette_bytes =
407 std::unique_ptr<png_color[]>(new png_color[color_palette->size()]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700408
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700409 std::unique_ptr<png_byte[]> alpha_palette_bytes;
410 if (!alpha_palette->empty()) {
411 alpha_palette_bytes =
412 std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700413 }
414
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700415 for (const auto& entry : *color_palette) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700416 const uint32_t color = entry.first;
417 const int index = entry.second;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700418 CHECK(index >= 0);
419 CHECK(static_cast<size_t>(index) < color_palette->size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700420
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700421 png_colorp slot = color_palette_bytes.get() + index;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700422 slot->red = color >> 24;
423 slot->green = color >> 16;
424 slot->blue = color >> 8;
425
426 const png_byte alpha = color & 0x000000ff;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700427 if (alpha != 0xff && alpha_palette_bytes) {
428 CHECK(static_cast<size_t>(index) < alpha_palette->size());
429 alpha_palette_bytes[index] = alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700430 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700431 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700432
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700433 // The bytes get copied here, so it is safe to release color_palette_bytes at
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700434 // the end of function
435 // scope.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700436 png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(),
437 color_palette->size());
Adam Lesinski21efb682016-09-14 17:35:43 -0700438
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700439 if (alpha_palette_bytes) {
440 png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(),
441 alpha_palette->size(), nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700442 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700443}
444
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700445// Write the 9-patch custom PNG chunks to write_info_ptr. This must be done
446// before
Adam Lesinski21efb682016-09-14 17:35:43 -0700447// writing image data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700448static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr,
449 const NinePatch* nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700450 // The order of the chunks is important.
451 // 9-patch code in older platforms expects the 9-patch chunk to
452 // be last.
Adam Lesinski21efb682016-09-14 17:35:43 -0700453
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700454 png_unknown_chunk unknown_chunks[3];
455 memset(unknown_chunks, 0, sizeof(unknown_chunks));
Adam Lesinski21efb682016-09-14 17:35:43 -0700456
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700457 size_t index = 0;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700458 size_t chunk_len = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700459
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700460 std::unique_ptr<uint8_t[]> serialized_outline =
461 nine_patch->SerializeRoundedRectOutline(&chunk_len);
462 strcpy((char*)unknown_chunks[index].name, "npOl");
463 unknown_chunks[index].size = chunk_len;
464 unknown_chunks[index].data = (png_bytep)serialized_outline.get();
465 unknown_chunks[index].location = PNG_HAVE_PLTE;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700466 index++;
467
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700468 std::unique_ptr<uint8_t[]> serialized_layout_bounds;
469 if (nine_patch->layout_bounds.nonZero()) {
470 serialized_layout_bounds = nine_patch->SerializeLayoutBounds(&chunk_len);
471 strcpy((char*)unknown_chunks[index].name, "npLb");
472 unknown_chunks[index].size = chunk_len;
473 unknown_chunks[index].data = (png_bytep)serialized_layout_bounds.get();
474 unknown_chunks[index].location = PNG_HAVE_PLTE;
Adam Lesinski21efb682016-09-14 17:35:43 -0700475 index++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700476 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700477
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700478 std::unique_ptr<uint8_t[]> serialized_nine_patch =
479 nine_patch->SerializeBase(&chunk_len);
480 strcpy((char*)unknown_chunks[index].name, "npTc");
481 unknown_chunks[index].size = chunk_len;
482 unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get();
483 unknown_chunks[index].location = PNG_HAVE_PLTE;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700484 index++;
Adam Lesinski21efb682016-09-14 17:35:43 -0700485
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700486 // Handle all unknown chunks. We are manually setting the chunks here,
487 // so we will only ever handle our custom chunks.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700488 png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
Adam Lesinski21efb682016-09-14 17:35:43 -0700489
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700490 // Set the actual chunks here. The data gets copied, so our buffers can
491 // safely go out of scope.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700492 png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
Adam Lesinski21efb682016-09-14 17:35:43 -0700493}
494
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700495bool WritePng(IAaptContext* context, const Image* image,
496 const NinePatch* nine_patch, io::OutputStream* out,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700497 const PngOptions& options) {
498 // Create and initialize the write png_struct with the default error and
499 // warning handlers.
500 // The header version is also passed in to ensure that this was built against
501 // the same
502 // version of libpng.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700503 png_structp write_ptr =
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700504 png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700505 if (write_ptr == nullptr) {
506 context->GetDiagnostics()->Error(
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700507 DiagMessage() << "failed to create libpng write png_struct");
508 return false;
509 }
510
511 // Allocate memory to store image header data.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700512 png_infop write_info_ptr = png_create_info_struct(write_ptr);
513 if (write_info_ptr == nullptr) {
514 context->GetDiagnostics()->Error(
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700515 DiagMessage() << "failed to create libpng write png_info");
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700516 png_destroy_write_struct(&write_ptr, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700517 return false;
518 }
519
520 // Automatically release PNG resources at end of scope.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700521 PngWriteStructDeleter png_write_deleter(write_ptr, write_info_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700522
523 // libpng uses longjmp to jump to error handling routines.
524 // setjmp will return true only if it was jumped to, aka, there was an error.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700525 if (setjmp(png_jmpbuf(write_ptr))) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700526 return false;
527 }
528
529 // Handle warnings with our IDiagnostics.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700530 png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError,
531 LogWarning);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700532
533 // Set up the write functions which write to our custom data sources.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700534 png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700535
536 // We want small files and can take the performance hit to achieve this goal.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700537 png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700538
539 // Begin analysis of the image data.
540 // Scan the entire image and determine if:
541 // 1. Every pixel has R == G == B (grayscale)
542 // 2. Every pixel has A == 255 (opaque)
543 // 3. There are no more than 256 distinct RGBA colors (palette).
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700544 std::unordered_map<uint32_t, int> color_palette;
545 std::unordered_set<uint32_t> alpha_palette;
546 bool needs_to_zero_rgb_channels_of_transparent_pixels = false;
547 bool grayscale = true;
548 int max_gray_deviation = 0;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700549
550 for (int32_t y = 0; y < image->height; y++) {
551 const uint8_t* row = image->rows[y];
552 for (int32_t x = 0; x < image->width; x++) {
553 int red = *row++;
554 int green = *row++;
555 int blue = *row++;
556 int alpha = *row++;
557
558 if (alpha == 0) {
559 // The color is completely transparent.
560 // For purposes of palettes and grayscale optimization,
561 // treat all channels as 0x00.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700562 needs_to_zero_rgb_channels_of_transparent_pixels =
563 needs_to_zero_rgb_channels_of_transparent_pixels ||
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700564 (red != 0 || green != 0 || blue != 0);
565 red = green = blue = 0;
566 }
567
568 // Insert the color into the color palette.
569 const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700570 color_palette[color] = -1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700571
572 // If the pixel has non-opaque alpha, insert it into the
573 // alpha palette.
574 if (alpha != 0xff) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700575 alpha_palette.insert(color);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700576 }
577
578 // Check if the image is indeed grayscale.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700579 if (grayscale) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700580 if (red != green || red != blue) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700581 grayscale = false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700582 }
583 }
584
585 // Calculate the gray scale deviation so that it can be compared
586 // with the threshold.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700587 max_gray_deviation = std::max(std::abs(red - green), max_gray_deviation);
588 max_gray_deviation = std::max(std::abs(green - blue), max_gray_deviation);
589 max_gray_deviation = std::max(std::abs(blue - red), max_gray_deviation);
Adam Lesinski21efb682016-09-14 17:35:43 -0700590 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700591 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700592
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700593 if (context->IsVerbose()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700594 DiagMessage msg;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700595 msg << " paletteSize=" << color_palette.size()
596 << " alphaPaletteSize=" << alpha_palette.size()
597 << " maxGrayDeviation=" << max_gray_deviation
598 << " grayScale=" << (grayscale ? "true" : "false");
599 context->GetDiagnostics()->Note(msg);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700600 }
601
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700602 const bool convertible_to_grayscale =
603 max_gray_deviation <= options.grayscale_tolerance;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700604
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700605 const int new_color_type = PickColorType(
606 image->width, image->height, grayscale, convertible_to_grayscale,
607 nine_patch != nullptr, color_palette.size(), alpha_palette.size());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700608
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700609 if (context->IsVerbose()) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700610 DiagMessage msg;
611 msg << "encoding PNG ";
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700612 if (nine_patch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700613 msg << "(with 9-patch) as ";
Adam Lesinski21efb682016-09-14 17:35:43 -0700614 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700615 switch (new_color_type) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700616 case PNG_COLOR_TYPE_GRAY:
617 msg << "GRAY";
618 break;
619 case PNG_COLOR_TYPE_GRAY_ALPHA:
620 msg << "GRAY + ALPHA";
621 break;
622 case PNG_COLOR_TYPE_RGB:
623 msg << "RGB";
624 break;
625 case PNG_COLOR_TYPE_RGB_ALPHA:
626 msg << "RGBA";
627 break;
628 case PNG_COLOR_TYPE_PALETTE:
629 msg << "PALETTE";
630 break;
631 default:
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700632 msg << "unknown type " << new_color_type;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700633 break;
Adam Lesinski21efb682016-09-14 17:35:43 -0700634 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700635 context->GetDiagnostics()->Note(msg);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700636 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700637
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700638 png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8,
639 new_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700640 PNG_FILTER_TYPE_DEFAULT);
Adam Lesinski21efb682016-09-14 17:35:43 -0700641
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700642 if (new_color_type & PNG_COLOR_MASK_PALETTE) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700643 // Assigns indices to the palette, and writes the encoded palette to the
644 // libpng writePtr.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700645 WritePalette(write_ptr, write_info_ptr, &color_palette, &alpha_palette);
646 png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700647 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700648 png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700649 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700650
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700651 if (nine_patch) {
652 WriteNinePatch(write_ptr, write_info_ptr, nine_patch);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700653 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700654
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700655 // Flush our updates to the header.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700656 png_write_info(write_ptr, write_info_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700657
658 // Write out each row of image data according to its encoding.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700659 if (new_color_type == PNG_COLOR_TYPE_PALETTE) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700660 // 1 byte/pixel.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700661 auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700662
663 for (int32_t y = 0; y < image->height; y++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700664 png_const_bytep in_row = image->rows[y];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700665 for (int32_t x = 0; x < image->width; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700666 int rr = *in_row++;
667 int gg = *in_row++;
668 int bb = *in_row++;
669 int aa = *in_row++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700670 if (aa == 0) {
671 // Zero out color channels when transparent.
672 rr = gg = bb = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700673 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700674
675 const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700676 const int idx = color_palette[color];
677 CHECK(idx != -1);
678 out_row[x] = static_cast<png_byte>(idx);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700679 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700680 png_write_row(write_ptr, out_row.get());
Adam Lesinski21efb682016-09-14 17:35:43 -0700681 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700682 } else if (new_color_type == PNG_COLOR_TYPE_GRAY ||
683 new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
684 const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
685 auto out_row =
686 std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700687
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700688 for (int32_t y = 0; y < image->height; y++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700689 png_const_bytep in_row = image->rows[y];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700690 for (int32_t x = 0; x < image->width; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700691 int rr = in_row[x * 4];
692 int gg = in_row[x * 4 + 1];
693 int bb = in_row[x * 4 + 2];
694 int aa = in_row[x * 4 + 3];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700695 if (aa == 0) {
696 // Zero out the gray channel when transparent.
697 rr = gg = bb = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700698 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700699
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700700 if (grayscale) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700701 // The image was already grayscale, red == green == blue.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700702 out_row[x * bpp] = in_row[x * 4];
Adam Lesinski21efb682016-09-14 17:35:43 -0700703 } else {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700704 // The image is convertible to grayscale, use linear-luminance of
705 // sRGB colorspace:
706 // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700707 out_row[x * bpp] =
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700708 (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
Adam Lesinski21efb682016-09-14 17:35:43 -0700709 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700710
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700711 if (bpp == 2) {
712 // Write out alpha if we have it.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700713 out_row[x * bpp + 1] = aa;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700714 }
715 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700716 png_write_row(write_ptr, out_row.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700717 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700718 } else if (new_color_type == PNG_COLOR_TYPE_RGB ||
719 new_color_type == PNG_COLOR_TYPE_RGBA) {
720 const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4;
721 if (needs_to_zero_rgb_channels_of_transparent_pixels) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700722 // The source RGBA data can't be used as-is, because we need to zero out
723 // the RGB
724 // values of transparent pixels.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700725 auto out_row =
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700726 std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
727
728 for (int32_t y = 0; y < image->height; y++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700729 png_const_bytep in_row = image->rows[y];
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700730 for (int32_t x = 0; x < image->width; x++) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700731 int rr = *in_row++;
732 int gg = *in_row++;
733 int bb = *in_row++;
734 int aa = *in_row++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700735 if (aa == 0) {
736 // Zero out the RGB channels when transparent.
737 rr = gg = bb = 0;
738 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700739 out_row[x * bpp] = rr;
740 out_row[x * bpp + 1] = gg;
741 out_row[x * bpp + 2] = bb;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700742 if (bpp == 4) {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700743 out_row[x * bpp + 3] = aa;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700744 }
745 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700746 png_write_row(write_ptr, out_row.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700747 }
748 } else {
749 // The source image can be used as-is, just tell libpng whether or not to
750 // ignore
751 // the alpha channel.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700752 if (new_color_type == PNG_COLOR_TYPE_RGB) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700753 // Delete the extraneous alpha values that we appended to our buffer
754 // when reading the original values.
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700755 png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700756 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700757 png_write_image(write_ptr, image->rows.get());
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700758 }
759 } else {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700760 LOG(FATAL) << "unreachable";
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700761 }
762
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700763 png_write_end(write_ptr, write_info_ptr);
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700764 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700765}
766
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700767} // namespace aapt