blob: 4a74f7af79d81725aa3d87e25595723e7c14e03d [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 <android-base/errors.h>
20#include <android-base/macros.h>
21#include <png.h>
Adam Lesinskicacb28f2016-10-19 12:18:14 -070022#include <zlib.h>
23#include <algorithm>
Adam Lesinski21efb682016-09-14 17:35:43 -070024#include <unordered_map>
25#include <unordered_set>
Adam Lesinski21efb682016-09-14 17:35:43 -070026
27namespace aapt {
28
29// Size in bytes of the PNG signature.
30constexpr size_t kPngSignatureSize = 8u;
31
32/**
33 * Custom deleter that destroys libpng read and info structs.
34 */
35class PngReadStructDeleter {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070036 public:
37 explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr)
38 : mReadPtr(readPtr), mInfoPtr(infoPtr) {}
Adam Lesinski21efb682016-09-14 17:35:43 -070039
Adam Lesinskicacb28f2016-10-19 12:18:14 -070040 ~PngReadStructDeleter() {
41 png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr);
42 }
Adam Lesinski21efb682016-09-14 17:35:43 -070043
Adam Lesinskicacb28f2016-10-19 12:18:14 -070044 private:
45 png_structp mReadPtr;
46 png_infop mInfoPtr;
Adam Lesinski21efb682016-09-14 17:35:43 -070047
Adam Lesinskicacb28f2016-10-19 12:18:14 -070048 DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter);
Adam Lesinski21efb682016-09-14 17:35:43 -070049};
50
51/**
52 * Custom deleter that destroys libpng write and info structs.
53 */
54class PngWriteStructDeleter {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070055 public:
56 explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr)
57 : mWritePtr(writePtr), mInfoPtr(infoPtr) {}
Adam Lesinski21efb682016-09-14 17:35:43 -070058
Adam Lesinskicacb28f2016-10-19 12:18:14 -070059 ~PngWriteStructDeleter() { png_destroy_write_struct(&mWritePtr, &mInfoPtr); }
Adam Lesinski21efb682016-09-14 17:35:43 -070060
Adam Lesinskicacb28f2016-10-19 12:18:14 -070061 private:
62 png_structp mWritePtr;
63 png_infop mInfoPtr;
Adam Lesinski21efb682016-09-14 17:35:43 -070064
Adam Lesinskicacb28f2016-10-19 12:18:14 -070065 DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter);
Adam Lesinski21efb682016-09-14 17:35:43 -070066};
67
68// Custom warning logging method that uses IDiagnostics.
69static void logWarning(png_structp pngPtr, png_const_charp warningMsg) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070070 IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
71 diag->warn(DiagMessage() << warningMsg);
Adam Lesinski21efb682016-09-14 17:35:43 -070072}
73
74// Custom error logging method that uses IDiagnostics.
75static void logError(png_structp pngPtr, png_const_charp errorMsg) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070076 IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr);
77 diag->error(DiagMessage() << errorMsg);
Adam Lesinski21efb682016-09-14 17:35:43 -070078}
79
Adam Lesinskicacb28f2016-10-19 12:18:14 -070080static void readDataFromStream(png_structp pngPtr, png_bytep buffer,
81 png_size_t len) {
82 io::InputStream* in = (io::InputStream*)png_get_io_ptr(pngPtr);
Adam Lesinski21efb682016-09-14 17:35:43 -070083
Adam Lesinskicacb28f2016-10-19 12:18:14 -070084 const void* inBuffer;
85 int inLen;
86 if (!in->Next(&inBuffer, &inLen)) {
87 if (in->HadError()) {
88 std::string err = in->GetError();
89 png_error(pngPtr, err.c_str());
Adam Lesinski21efb682016-09-14 17:35:43 -070090 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -070091 return;
92 }
Adam Lesinski21efb682016-09-14 17:35:43 -070093
Adam Lesinskicacb28f2016-10-19 12:18:14 -070094 const size_t bytesRead = std::min(static_cast<size_t>(inLen), len);
95 memcpy(buffer, inBuffer, bytesRead);
96 if (bytesRead != static_cast<size_t>(inLen)) {
97 in->BackUp(inLen - static_cast<int>(bytesRead));
98 }
Adam Lesinski21efb682016-09-14 17:35:43 -070099}
100
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700101static void writeDataToStream(png_structp pngPtr, png_bytep buffer,
102 png_size_t len) {
103 io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(pngPtr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700104
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700105 void* outBuffer;
106 int outLen;
107 while (len > 0) {
108 if (!out->Next(&outBuffer, &outLen)) {
109 if (out->HadError()) {
110 std::string err = out->GetError();
111 png_error(pngPtr, err.c_str());
112 }
113 return;
Adam Lesinski21efb682016-09-14 17:35:43 -0700114 }
115
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700116 const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len);
117 memcpy(outBuffer, buffer, bytesWritten);
118
119 // Advance the input buffer.
120 buffer += bytesWritten;
121 len -= bytesWritten;
122
123 // Advance the output buffer.
124 outLen -= static_cast<int>(bytesWritten);
125 }
126
127 // If the entire output buffer wasn't used, backup.
128 if (outLen > 0) {
129 out->BackUp(outLen);
130 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700131}
132
133std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700134 // Read the first 8 bytes of the file looking for the PNG signature.
135 // Bail early if it does not match.
136 const png_byte* signature;
137 int bufferSize;
138 if (!in->Next((const void**)&signature, &bufferSize)) {
139 context->getDiagnostics()->error(
140 DiagMessage() << android::base::SystemErrorCodeToString(errno));
141 return {};
142 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700143
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700144 if (static_cast<size_t>(bufferSize) < kPngSignatureSize ||
145 png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
146 context->getDiagnostics()->error(
147 DiagMessage() << "file signature does not match PNG signature");
148 return {};
149 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700150
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700151 // Start at the beginning of the first chunk.
152 in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize));
Adam Lesinski21efb682016-09-14 17:35:43 -0700153
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700154 // Create and initialize the png_struct with the default error and warning
155 // handlers.
156 // The header version is also passed in to ensure that this was built against
157 // the same
158 // version of libpng.
159 png_structp readPtr =
160 png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
161 if (readPtr == nullptr) {
162 context->getDiagnostics()->error(
163 DiagMessage() << "failed to create libpng read png_struct");
164 return {};
165 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700166
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700167 // Create and initialize the memory for image header and data.
168 png_infop infoPtr = png_create_info_struct(readPtr);
169 if (infoPtr == nullptr) {
170 context->getDiagnostics()->error(
171 DiagMessage() << "failed to create libpng read png_info");
172 png_destroy_read_struct(&readPtr, nullptr, nullptr);
173 return {};
174 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700175
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700176 // Automatically release PNG resources at end of scope.
177 PngReadStructDeleter pngReadDeleter(readPtr, infoPtr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700178
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700179 // libpng uses longjmp to jump to an error handling routine.
180 // setjmp will only return true if it was jumped to, aka there was
181 // an error.
182 if (setjmp(png_jmpbuf(readPtr))) {
183 return {};
184 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700185
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700186 // Handle warnings ourselves via IDiagnostics.
187 png_set_error_fn(readPtr, (png_voidp)context->getDiagnostics(), logError,
188 logWarning);
Adam Lesinski21efb682016-09-14 17:35:43 -0700189
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700190 // Set up the read functions which read from our custom data sources.
191 png_set_read_fn(readPtr, (png_voidp)in, readDataFromStream);
Adam Lesinski21efb682016-09-14 17:35:43 -0700192
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700193 // Skip the signature that we already read.
194 png_set_sig_bytes(readPtr, kPngSignatureSize);
Adam Lesinski21efb682016-09-14 17:35:43 -0700195
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700196 // Read the chunk headers.
197 png_read_info(readPtr, infoPtr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700198
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700199 // Extract image meta-data from the various chunk headers.
200 uint32_t width, height;
201 int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod;
202 png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType,
203 &interlaceMethod, &compressionMethod, &filterMethod);
Adam Lesinski21efb682016-09-14 17:35:43 -0700204
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700205 // When the image is read, expand it so that it is in RGBA 8888 format
206 // so that image handling is uniform.
Adam Lesinski21efb682016-09-14 17:35:43 -0700207
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700208 if (colorType == PNG_COLOR_TYPE_PALETTE) {
209 png_set_palette_to_rgb(readPtr);
210 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700211
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700212 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
213 png_set_expand_gray_1_2_4_to_8(readPtr);
214 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700215
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700216 if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
217 png_set_tRNS_to_alpha(readPtr);
218 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700219
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700220 if (bitDepth == 16) {
221 png_set_strip_16(readPtr);
222 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700223
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700224 if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
225 png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
226 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700227
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700228 if (colorType == PNG_COLOR_TYPE_GRAY ||
229 colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
230 png_set_gray_to_rgb(readPtr);
231 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700232
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700233 if (interlaceMethod != PNG_INTERLACE_NONE) {
234 png_set_interlace_handling(readPtr);
235 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700236
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700237 // Once all the options for reading have been set, we need to flush
238 // them to libpng.
239 png_read_update_info(readPtr, infoPtr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700240
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700241 // 9-patch uses int32_t to index images, so we cap the image dimensions to
242 // something
243 // that can always be represented by 9-patch.
244 if (width > std::numeric_limits<int32_t>::max() ||
245 height > std::numeric_limits<int32_t>::max()) {
246 context->getDiagnostics()->error(DiagMessage()
247 << "PNG image dimensions are too large: "
248 << width << "x" << height);
249 return {};
250 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700251
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700252 std::unique_ptr<Image> outputImage = util::make_unique<Image>();
253 outputImage->width = static_cast<int32_t>(width);
254 outputImage->height = static_cast<int32_t>(height);
Adam Lesinski21efb682016-09-14 17:35:43 -0700255
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700256 const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
257 assert(rowBytes == 4 * width); // RGBA
Adam Lesinski21efb682016-09-14 17:35:43 -0700258
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700259 // Allocate one large block to hold the image.
260 outputImage->data =
261 std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700262
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700263 // Create an array of rows that index into the data block.
264 outputImage->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
265 for (uint32_t h = 0; h < height; h++) {
266 outputImage->rows[h] = outputImage->data.get() + (h * rowBytes);
267 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700268
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700269 // Actually read the image pixels.
270 png_read_image(readPtr, outputImage->rows.get());
Adam Lesinski21efb682016-09-14 17:35:43 -0700271
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700272 // Finish reading. This will read any other chunks after the image data.
273 png_read_end(readPtr, infoPtr);
Adam Lesinski21efb682016-09-14 17:35:43 -0700274
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700275 return outputImage;
Adam Lesinski21efb682016-09-14 17:35:43 -0700276}
277
278/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700279 * Experimentally chosen constant to be added to the overhead of using color
280 * type
281 * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette
282 * chunk.
283 * Without this, many small PNGs encoded with palettes are larger after
284 * compression than
Adam Lesinski21efb682016-09-14 17:35:43 -0700285 * the same PNGs encoded as RGBA.
286 */
287constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u;
288
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700289// Pick a color type by which to encode the image, based on which color type
290// will take
Adam Lesinski21efb682016-09-14 17:35:43 -0700291// the least amount of disk space.
292//
293// 9-patch images traditionally have not been encoded with palettes.
294// The original rationale was to avoid dithering until after scaling,
295// but I don't think this would be an issue with palettes. Either way,
296// our naive size estimation tends to be wrong for small images like 9-patches
297// and using palettes balloons the size of the resulting 9-patch.
298// In order to not regress in size, restrict 9-patch to not use palettes.
299
300// The options are:
301//
302// - RGB
303// - RGBA
304// - RGB + cheap alpha
305// - Color palette
306// - Color palette + cheap alpha
307// - Color palette + alpha palette
308// - Grayscale
309// - Grayscale + cheap alpha
310// - Grayscale + alpha
311//
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700312static int pickColorType(int32_t width, int32_t height, bool grayScale,
313 bool convertibleToGrayScale, bool hasNinePatch,
Adam Lesinski21efb682016-09-14 17:35:43 -0700314 size_t colorPaletteSize, size_t alphaPaletteSize) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700315 const size_t paletteChunkSize = 16 + colorPaletteSize * 3;
316 const size_t alphaChunkSize = 16 + alphaPaletteSize;
317 const size_t colorAlphaDataChunkSize = 16 + 4 * width * height;
318 const size_t colorDataChunkSize = 16 + 3 * width * height;
319 const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height;
320 const size_t paletteDataChunkSize = 16 + width * height;
Adam Lesinski21efb682016-09-14 17:35:43 -0700321
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700322 if (grayScale) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700323 if (alphaPaletteSize == 0) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700324 // This is the smallest the data can be.
325 return PNG_COLOR_TYPE_GRAY;
326 } else if (colorPaletteSize <= 256 && !hasNinePatch) {
327 // This grayscale has alpha and can fit within a palette.
328 // See if it is worth fitting into a palette.
329 const size_t paletteThreshold = paletteChunkSize + alphaChunkSize +
330 paletteDataChunkSize +
331 kPaletteOverheadConstant;
332 if (grayScaleAlphaDataChunkSize > paletteThreshold) {
333 return PNG_COLOR_TYPE_PALETTE;
334 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700335 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700336 return PNG_COLOR_TYPE_GRAY_ALPHA;
337 }
338
339 if (colorPaletteSize <= 256 && !hasNinePatch) {
340 // This image can fit inside a palette. Let's see if it is worth it.
341 size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize;
342 size_t totalSizeWithoutPalette = colorDataChunkSize;
343 if (alphaPaletteSize > 0) {
344 totalSizeWithPalette += alphaPaletteSize;
345 totalSizeWithoutPalette = colorAlphaDataChunkSize;
346 }
347
348 if (totalSizeWithoutPalette >
349 totalSizeWithPalette + kPaletteOverheadConstant) {
350 return PNG_COLOR_TYPE_PALETTE;
351 }
352 }
353
354 if (convertibleToGrayScale) {
355 if (alphaPaletteSize == 0) {
356 return PNG_COLOR_TYPE_GRAY;
357 } else {
358 return PNG_COLOR_TYPE_GRAY_ALPHA;
359 }
360 }
361
362 if (alphaPaletteSize == 0) {
363 return PNG_COLOR_TYPE_RGB;
364 }
365 return PNG_COLOR_TYPE_RGBA;
Adam Lesinski21efb682016-09-14 17:35:43 -0700366}
367
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700368// Assigns indices to the color and alpha palettes, encodes them, and then
369// invokes
Adam Lesinski21efb682016-09-14 17:35:43 -0700370// png_set_PLTE/png_set_tRNS.
371// This must be done before writing image data.
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700372// Image data must be transformed to use the indices assigned within the
373// palette.
Adam Lesinski21efb682016-09-14 17:35:43 -0700374static void writePalette(png_structp writePtr, png_infop writeInfoPtr,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700375 std::unordered_map<uint32_t, int>* colorPalette,
376 std::unordered_set<uint32_t>* alphaPalette) {
377 assert(colorPalette->size() <= 256);
378 assert(alphaPalette->size() <= 256);
Adam Lesinski21efb682016-09-14 17:35:43 -0700379
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700380 // Populate the PNG palette struct and assign indices to the color
381 // palette.
Adam Lesinski21efb682016-09-14 17:35:43 -0700382
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700383 // Colors in the alpha palette should have smaller indices.
384 // This will ensure that we can truncate the alpha palette if it is
385 // smaller than the color palette.
386 int index = 0;
387 for (uint32_t color : *alphaPalette) {
388 (*colorPalette)[color] = index++;
389 }
390
391 // Assign the rest of the entries.
392 for (auto& entry : *colorPalette) {
393 if (entry.second == -1) {
394 entry.second = index++;
Adam Lesinski21efb682016-09-14 17:35:43 -0700395 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700396 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700397
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700398 // Create the PNG color palette struct.
399 auto colorPaletteBytes =
400 std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]);
401
402 std::unique_ptr<png_byte[]> alphaPaletteBytes;
403 if (!alphaPalette->empty()) {
404 alphaPaletteBytes =
405 std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]);
406 }
407
408 for (const auto& entry : *colorPalette) {
409 const uint32_t color = entry.first;
410 const int index = entry.second;
411 assert(index >= 0);
412 assert(static_cast<size_t>(index) < colorPalette->size());
413
414 png_colorp slot = colorPaletteBytes.get() + index;
415 slot->red = color >> 24;
416 slot->green = color >> 16;
417 slot->blue = color >> 8;
418
419 const png_byte alpha = color & 0x000000ff;
420 if (alpha != 0xff && alphaPaletteBytes) {
421 assert(static_cast<size_t>(index) < alphaPalette->size());
422 alphaPaletteBytes[index] = alpha;
Adam Lesinski21efb682016-09-14 17:35:43 -0700423 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700424 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700425
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700426 // The bytes get copied here, so it is safe to release colorPaletteBytes at
427 // the end of function
428 // scope.
429 png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(),
430 colorPalette->size());
Adam Lesinski21efb682016-09-14 17:35:43 -0700431
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700432 if (alphaPaletteBytes) {
433 png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(),
434 alphaPalette->size(), nullptr);
435 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700436}
437
438// Write the 9-patch custom PNG chunks to writeInfoPtr. This must be done before
439// writing image data.
440static void writeNinePatch(png_structp writePtr, png_infop writeInfoPtr,
441 const NinePatch* ninePatch) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700442 // The order of the chunks is important.
443 // 9-patch code in older platforms expects the 9-patch chunk to
444 // be last.
Adam Lesinski21efb682016-09-14 17:35:43 -0700445
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700446 png_unknown_chunk unknownChunks[3];
447 memset(unknownChunks, 0, sizeof(unknownChunks));
Adam Lesinski21efb682016-09-14 17:35:43 -0700448
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700449 size_t index = 0;
450 size_t chunkLen = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700451
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700452 std::unique_ptr<uint8_t[]> serializedOutline =
453 ninePatch->serializeRoundedRectOutline(&chunkLen);
454 strcpy((char*)unknownChunks[index].name, "npOl");
455 unknownChunks[index].size = chunkLen;
456 unknownChunks[index].data = (png_bytep)serializedOutline.get();
457 unknownChunks[index].location = PNG_HAVE_PLTE;
458 index++;
459
460 std::unique_ptr<uint8_t[]> serializedLayoutBounds;
461 if (ninePatch->layoutBounds.nonZero()) {
462 serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen);
463 strcpy((char*)unknownChunks[index].name, "npLb");
Adam Lesinski21efb682016-09-14 17:35:43 -0700464 unknownChunks[index].size = chunkLen;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700465 unknownChunks[index].data = (png_bytep)serializedLayoutBounds.get();
Adam Lesinski21efb682016-09-14 17:35:43 -0700466 unknownChunks[index].location = PNG_HAVE_PLTE;
467 index++;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700468 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700469
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700470 std::unique_ptr<uint8_t[]> serializedNinePatch =
471 ninePatch->serializeBase(&chunkLen);
472 strcpy((char*)unknownChunks[index].name, "npTc");
473 unknownChunks[index].size = chunkLen;
474 unknownChunks[index].data = (png_bytep)serializedNinePatch.get();
475 unknownChunks[index].location = PNG_HAVE_PLTE;
476 index++;
Adam Lesinski21efb682016-09-14 17:35:43 -0700477
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700478 // Handle all unknown chunks. We are manually setting the chunks here,
479 // so we will only ever handle our custom chunks.
480 png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0);
Adam Lesinski21efb682016-09-14 17:35:43 -0700481
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700482 // Set the actual chunks here. The data gets copied, so our buffers can
483 // safely go out of scope.
484 png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index);
Adam Lesinski21efb682016-09-14 17:35:43 -0700485}
486
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700487bool writePng(IAaptContext* context, const Image* image,
488 const NinePatch* ninePatch, io::OutputStream* out,
489 const PngOptions& options) {
490 // Create and initialize the write png_struct with the default error and
491 // warning handlers.
492 // The header version is also passed in to ensure that this was built against
493 // the same
494 // version of libpng.
495 png_structp writePtr =
496 png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
497 if (writePtr == nullptr) {
498 context->getDiagnostics()->error(
499 DiagMessage() << "failed to create libpng write png_struct");
500 return false;
501 }
502
503 // Allocate memory to store image header data.
504 png_infop writeInfoPtr = png_create_info_struct(writePtr);
505 if (writeInfoPtr == nullptr) {
506 context->getDiagnostics()->error(
507 DiagMessage() << "failed to create libpng write png_info");
508 png_destroy_write_struct(&writePtr, nullptr);
509 return false;
510 }
511
512 // Automatically release PNG resources at end of scope.
513 PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr);
514
515 // libpng uses longjmp to jump to error handling routines.
516 // setjmp will return true only if it was jumped to, aka, there was an error.
517 if (setjmp(png_jmpbuf(writePtr))) {
518 return false;
519 }
520
521 // Handle warnings with our IDiagnostics.
522 png_set_error_fn(writePtr, (png_voidp)context->getDiagnostics(), logError,
523 logWarning);
524
525 // Set up the write functions which write to our custom data sources.
526 png_set_write_fn(writePtr, (png_voidp)out, writeDataToStream, nullptr);
527
528 // We want small files and can take the performance hit to achieve this goal.
529 png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
530
531 // Begin analysis of the image data.
532 // Scan the entire image and determine if:
533 // 1. Every pixel has R == G == B (grayscale)
534 // 2. Every pixel has A == 255 (opaque)
535 // 3. There are no more than 256 distinct RGBA colors (palette).
536 std::unordered_map<uint32_t, int> colorPalette;
537 std::unordered_set<uint32_t> alphaPalette;
538 bool needsToZeroRGBChannelsOfTransparentPixels = false;
539 bool grayScale = true;
540 int maxGrayDeviation = 0;
541
542 for (int32_t y = 0; y < image->height; y++) {
543 const uint8_t* row = image->rows[y];
544 for (int32_t x = 0; x < image->width; x++) {
545 int red = *row++;
546 int green = *row++;
547 int blue = *row++;
548 int alpha = *row++;
549
550 if (alpha == 0) {
551 // The color is completely transparent.
552 // For purposes of palettes and grayscale optimization,
553 // treat all channels as 0x00.
554 needsToZeroRGBChannelsOfTransparentPixels =
555 needsToZeroRGBChannelsOfTransparentPixels ||
556 (red != 0 || green != 0 || blue != 0);
557 red = green = blue = 0;
558 }
559
560 // Insert the color into the color palette.
561 const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha;
562 colorPalette[color] = -1;
563
564 // If the pixel has non-opaque alpha, insert it into the
565 // alpha palette.
566 if (alpha != 0xff) {
567 alphaPalette.insert(color);
568 }
569
570 // Check if the image is indeed grayscale.
571 if (grayScale) {
572 if (red != green || red != blue) {
573 grayScale = false;
574 }
575 }
576
577 // Calculate the gray scale deviation so that it can be compared
578 // with the threshold.
579 maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation);
580 maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation);
581 maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation);
Adam Lesinski21efb682016-09-14 17:35:43 -0700582 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700583 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700584
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700585 if (context->verbose()) {
586 DiagMessage msg;
587 msg << " paletteSize=" << colorPalette.size()
588 << " alphaPaletteSize=" << alphaPalette.size()
589 << " maxGrayDeviation=" << maxGrayDeviation
590 << " grayScale=" << (grayScale ? "true" : "false");
591 context->getDiagnostics()->note(msg);
592 }
593
594 const bool convertibleToGrayScale =
595 maxGrayDeviation <= options.grayScaleTolerance;
596
597 const int newColorType = pickColorType(
598 image->width, image->height, grayScale, convertibleToGrayScale,
599 ninePatch != nullptr, colorPalette.size(), alphaPalette.size());
600
601 if (context->verbose()) {
602 DiagMessage msg;
603 msg << "encoding PNG ";
604 if (ninePatch) {
605 msg << "(with 9-patch) as ";
Adam Lesinski21efb682016-09-14 17:35:43 -0700606 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700607 switch (newColorType) {
608 case PNG_COLOR_TYPE_GRAY:
609 msg << "GRAY";
610 break;
611 case PNG_COLOR_TYPE_GRAY_ALPHA:
612 msg << "GRAY + ALPHA";
613 break;
614 case PNG_COLOR_TYPE_RGB:
615 msg << "RGB";
616 break;
617 case PNG_COLOR_TYPE_RGB_ALPHA:
618 msg << "RGBA";
619 break;
620 case PNG_COLOR_TYPE_PALETTE:
621 msg << "PALETTE";
622 break;
623 default:
624 msg << "unknown type " << newColorType;
625 break;
Adam Lesinski21efb682016-09-14 17:35:43 -0700626 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700627 context->getDiagnostics()->note(msg);
628 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700629
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700630 png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8,
631 newColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
632 PNG_FILTER_TYPE_DEFAULT);
Adam Lesinski21efb682016-09-14 17:35:43 -0700633
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700634 if (newColorType & PNG_COLOR_MASK_PALETTE) {
635 // Assigns indices to the palette, and writes the encoded palette to the
636 // libpng writePtr.
637 writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette);
638 png_set_filter(writePtr, 0, PNG_NO_FILTERS);
639 } else {
640 png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
641 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700642
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700643 if (ninePatch) {
644 writeNinePatch(writePtr, writeInfoPtr, ninePatch);
645 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700646
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700647 // Flush our updates to the header.
648 png_write_info(writePtr, writeInfoPtr);
649
650 // Write out each row of image data according to its encoding.
651 if (newColorType == PNG_COLOR_TYPE_PALETTE) {
652 // 1 byte/pixel.
653 auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700654
655 for (int32_t y = 0; y < image->height; y++) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700656 png_const_bytep inRow = image->rows[y];
657 for (int32_t x = 0; x < image->width; x++) {
658 int rr = *inRow++;
659 int gg = *inRow++;
660 int bb = *inRow++;
661 int aa = *inRow++;
662 if (aa == 0) {
663 // Zero out color channels when transparent.
664 rr = gg = bb = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700665 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700666
667 const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa;
668 const int idx = colorPalette[color];
669 assert(idx != -1);
670 outRow[x] = static_cast<png_byte>(idx);
671 }
672 png_write_row(writePtr, outRow.get());
Adam Lesinski21efb682016-09-14 17:35:43 -0700673 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700674 } else if (newColorType == PNG_COLOR_TYPE_GRAY ||
675 newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
676 const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2;
677 auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
Adam Lesinski21efb682016-09-14 17:35:43 -0700678
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700679 for (int32_t y = 0; y < image->height; y++) {
680 png_const_bytep inRow = image->rows[y];
681 for (int32_t x = 0; x < image->width; x++) {
682 int rr = inRow[x * 4];
683 int gg = inRow[x * 4 + 1];
684 int bb = inRow[x * 4 + 2];
685 int aa = inRow[x * 4 + 3];
686 if (aa == 0) {
687 // Zero out the gray channel when transparent.
688 rr = gg = bb = 0;
Adam Lesinski21efb682016-09-14 17:35:43 -0700689 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700690
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700691 if (grayScale) {
692 // The image was already grayscale, red == green == blue.
693 outRow[x * bpp] = inRow[x * 4];
Adam Lesinski21efb682016-09-14 17:35:43 -0700694 } else {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700695 // The image is convertible to grayscale, use linear-luminance of
696 // sRGB colorspace:
697 // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
698 outRow[x * bpp] =
699 (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
Adam Lesinski21efb682016-09-14 17:35:43 -0700700 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700701
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700702 if (bpp == 2) {
703 // Write out alpha if we have it.
704 outRow[x * bpp + 1] = aa;
705 }
706 }
707 png_write_row(writePtr, outRow.get());
708 }
709 } else if (newColorType == PNG_COLOR_TYPE_RGB ||
710 newColorType == PNG_COLOR_TYPE_RGBA) {
711 const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4;
712 if (needsToZeroRGBChannelsOfTransparentPixels) {
713 // The source RGBA data can't be used as-is, because we need to zero out
714 // the RGB
715 // values of transparent pixels.
716 auto outRow =
717 std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
718
719 for (int32_t y = 0; y < image->height; y++) {
720 png_const_bytep inRow = image->rows[y];
721 for (int32_t x = 0; x < image->width; x++) {
722 int rr = *inRow++;
723 int gg = *inRow++;
724 int bb = *inRow++;
725 int aa = *inRow++;
726 if (aa == 0) {
727 // Zero out the RGB channels when transparent.
728 rr = gg = bb = 0;
729 }
730 outRow[x * bpp] = rr;
731 outRow[x * bpp + 1] = gg;
732 outRow[x * bpp + 2] = bb;
733 if (bpp == 4) {
734 outRow[x * bpp + 3] = aa;
735 }
736 }
737 png_write_row(writePtr, outRow.get());
738 }
739 } else {
740 // The source image can be used as-is, just tell libpng whether or not to
741 // ignore
742 // the alpha channel.
743 if (newColorType == PNG_COLOR_TYPE_RGB) {
744 // Delete the extraneous alpha values that we appended to our buffer
745 // when reading the original values.
746 png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
747 }
748 png_write_image(writePtr, image->rows.get());
749 }
750 } else {
751 assert(false && "unreachable");
752 }
753
754 png_write_end(writePtr, writeInfoPtr);
755 return true;
Adam Lesinski21efb682016-09-14 17:35:43 -0700756}
757
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700758} // namespace aapt