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