blob: 8bf2a492decbd6f4fc3bfbca3ab97ddc6879dd07 [file] [log] [blame]
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Adam Lesinski1ab598f2015-08-14 14:26:04 -070017#include "util/BigBuffer.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070018#include "Png.h"
19#include "Source.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070020#include "util/Util.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070021
22#include <androidfw/ResourceTypes.h>
23#include <iostream>
24#include <png.h>
25#include <sstream>
26#include <string>
27#include <vector>
28#include <zlib.h>
29
30namespace aapt {
31
32constexpr bool kDebug = false;
33constexpr size_t kPngSignatureSize = 8u;
34
35struct PngInfo {
36 ~PngInfo() {
37 for (png_bytep row : rows) {
38 if (row != nullptr) {
39 delete[] row;
40 }
41 }
42
43 delete[] xDivs;
44 delete[] yDivs;
45 }
46
47 void* serialize9Patch() {
48 void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
49 colors.data());
50 reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
51 return serialized;
52 }
53
54 uint32_t width = 0;
55 uint32_t height = 0;
56 std::vector<png_bytep> rows;
57
58 bool is9Patch = false;
59 android::Res_png_9patch info9Patch;
60 int32_t* xDivs = nullptr;
61 int32_t* yDivs = nullptr;
62 std::vector<uint32_t> colors;
63
64 // Layout padding.
65 bool haveLayoutBounds = false;
66 int32_t layoutBoundsLeft;
67 int32_t layoutBoundsTop;
68 int32_t layoutBoundsRight;
69 int32_t layoutBoundsBottom;
70
71 // Round rect outline description.
72 int32_t outlineInsetsLeft;
73 int32_t outlineInsetsTop;
74 int32_t outlineInsetsRight;
75 int32_t outlineInsetsBottom;
76 float outlineRadius;
77 uint8_t outlineAlpha;
78};
79
80static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
81 std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
82 if (!input->read(reinterpret_cast<char*>(data), length)) {
83 png_error(readPtr, strerror(errno));
84 }
85}
86
87static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
Adam Lesinski769de982015-04-10 19:43:55 -070088 BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
89 png_bytep buf = outBuffer->nextBlock<png_byte>(length);
90 memcpy(buf, data, length);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070091}
92
Adam Lesinski769de982015-04-10 19:43:55 -070093static void flushDataToStream(png_structp /*writePtr*/) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070094}
95
96static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070097 IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
98 diag->warn(DiagMessage() << warningMessage);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070099}
100
101
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700102static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700103 if (setjmp(png_jmpbuf(readPtr))) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700104 diag->error(DiagMessage() << "failed reading png");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700105 return false;
106 }
107
108 png_set_sig_bytes(readPtr, kPngSignatureSize);
109 png_read_info(readPtr, infoPtr);
110
111 int colorType, bitDepth, interlaceType, compressionType;
112 png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
113 &interlaceType, &compressionType, nullptr);
114
115 if (colorType == PNG_COLOR_TYPE_PALETTE) {
116 png_set_palette_to_rgb(readPtr);
117 }
118
119 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
120 png_set_expand_gray_1_2_4_to_8(readPtr);
121 }
122
123 if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
124 png_set_tRNS_to_alpha(readPtr);
125 }
126
127 if (bitDepth == 16) {
128 png_set_strip_16(readPtr);
129 }
130
131 if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
132 png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
133 }
134
135 if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
136 png_set_gray_to_rgb(readPtr);
137 }
138
139 png_set_interlace_handling(readPtr);
140 png_read_update_info(readPtr, infoPtr);
141
142 const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
143 outInfo->rows.resize(outInfo->height);
144 for (size_t i = 0; i < outInfo->height; i++) {
145 outInfo->rows[i] = new png_byte[rowBytes];
146 }
147
148 png_read_image(readPtr, outInfo->rows.data());
149 png_read_end(readPtr, infoPtr);
150 return true;
151}
152
153static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) {
154 size_t patchSize = inPatch->serializedSize();
155 void* newData = malloc(patchSize);
156 memcpy(newData, data, patchSize);
157 android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
158 outPatch->fileToDevice();
159 // deserialization is done in place, so outPatch == newData
160 assert(outPatch == newData);
161 assert(outPatch->numXDivs == inPatch->numXDivs);
162 assert(outPatch->numYDivs == inPatch->numYDivs);
163 assert(outPatch->paddingLeft == inPatch->paddingLeft);
164 assert(outPatch->paddingRight == inPatch->paddingRight);
165 assert(outPatch->paddingTop == inPatch->paddingTop);
166 assert(outPatch->paddingBottom == inPatch->paddingBottom);
167/* for (int i = 0; i < outPatch->numXDivs; i++) {
168 assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
169 }
170 for (int i = 0; i < outPatch->numYDivs; i++) {
171 assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
172 }
173 for (int i = 0; i < outPatch->numColors; i++) {
174 assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
175 }*/
176 free(newData);
177}
178
179/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
180 int i, j, rr, gg, bb, aa;
181
182 int bpp;
183 if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
184 bpp = 1;
185 } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
186 bpp = 2;
187 } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
188 // We use a padding byte even when there is no alpha
189 bpp = 4;
190 } else {
191 printf("Unknown color type %d.\n", color_type);
192 }
193
194 for (j = 0; j < h; j++) {
195 const png_byte* row = rows[j];
196 for (i = 0; i < w; i++) {
197 rr = row[0];
198 gg = row[1];
199 bb = row[2];
200 aa = row[3];
201 row += bpp;
202
203 if (i == 0) {
204 printf("Row %d:", j);
205 }
206 switch (bpp) {
207 case 1:
208 printf(" (%d)", rr);
209 break;
210 case 2:
211 printf(" (%d %d", rr, gg);
212 break;
213 case 3:
214 printf(" (%d %d %d)", rr, gg, bb);
215 break;
216 case 4:
217 printf(" (%d %d %d %d)", rr, gg, bb, aa);
218 break;
219 }
220 if (i == (w - 1)) {
221 printf("\n");
222 }
223 }
224 }
225}*/
226
Tamas Berghammercbd3f0c2016-06-22 15:21:38 +0100227#ifdef MAX
228#undef MAX
229#endif
230#ifdef ABS
231#undef ABS
232#endif
233
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700234#define MAX(a,b) ((a)>(b)?(a):(b))
235#define ABS(a) ((a)<0?-(a):(a))
236
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700237static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700238 png_colorp rgbPalette, png_bytep alphaPalette,
239 int *paletteEntries, bool *hasTransparency, int *colorType,
240 png_bytepp outRows) {
241 int w = imageInfo.width;
242 int h = imageInfo.height;
243 int i, j, rr, gg, bb, aa, idx;
244 uint32_t colors[256], col;
245 int num_colors = 0;
246 int maxGrayDeviation = 0;
247
248 bool isOpaque = true;
249 bool isPalette = true;
250 bool isGrayscale = true;
251
252 // Scan the entire image and determine if:
253 // 1. Every pixel has R == G == B (grayscale)
254 // 2. Every pixel has A == 255 (opaque)
255 // 3. There are no more than 256 distinct RGBA colors
256
257 if (kDebug) {
258 printf("Initial image data:\n");
259 //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
260 }
261
262 for (j = 0; j < h; j++) {
263 const png_byte* row = imageInfo.rows[j];
264 png_bytep out = outRows[j];
265 for (i = 0; i < w; i++) {
266 rr = *row++;
267 gg = *row++;
268 bb = *row++;
269 aa = *row++;
270
271 int odev = maxGrayDeviation;
272 maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
273 maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
274 maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
275 if (maxGrayDeviation > odev) {
276 if (kDebug) {
277 printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
278 maxGrayDeviation, i, j, rr, gg, bb, aa);
279 }
280 }
281
282 // Check if image is really grayscale
283 if (isGrayscale) {
284 if (rr != gg || rr != bb) {
285 if (kDebug) {
286 printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
287 i, j, rr, gg, bb, aa);
288 }
289 isGrayscale = false;
290 }
291 }
292
293 // Check if image is really opaque
294 if (isOpaque) {
295 if (aa != 0xff) {
296 if (kDebug) {
297 printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
298 i, j, rr, gg, bb, aa);
299 }
300 isOpaque = false;
301 }
302 }
303
304 // Check if image is really <= 256 colors
305 if (isPalette) {
306 col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
307 bool match = false;
308 for (idx = 0; idx < num_colors; idx++) {
309 if (colors[idx] == col) {
310 match = true;
311 break;
312 }
313 }
314
315 // Write the palette index for the pixel to outRows optimistically
316 // We might overwrite it later if we decide to encode as gray or
317 // gray + alpha
318 *out++ = idx;
319 if (!match) {
320 if (num_colors == 256) {
321 if (kDebug) {
322 printf("Found 257th color at %d, %d\n", i, j);
323 }
324 isPalette = false;
325 } else {
326 colors[num_colors++] = col;
327 }
328 }
329 }
330 }
331 }
332
333 *paletteEntries = 0;
334 *hasTransparency = !isOpaque;
335 int bpp = isOpaque ? 3 : 4;
336 int paletteSize = w * h + bpp * num_colors;
337
338 if (kDebug) {
339 printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
340 printf("isOpaque = %s\n", isOpaque ? "true" : "false");
341 printf("isPalette = %s\n", isPalette ? "true" : "false");
342 printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
343 paletteSize, 2 * w * h, bpp * w * h);
344 printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
345 }
346
347 // Choose the best color type for the image.
348 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
349 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
350 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
351 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
352 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
353 if (isGrayscale) {
354 if (isOpaque) {
355 *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
356 } else {
357 // Use a simple heuristic to determine whether using a palette will
358 // save space versus using gray + alpha for each pixel.
359 // This doesn't take into account chunk overhead, filtering, LZ
360 // compression, etc.
361 if (isPalette && (paletteSize < 2 * w * h)) {
362 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
363 } else {
364 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
365 }
366 }
367 } else if (isPalette && (paletteSize < bpp * w * h)) {
368 *colorType = PNG_COLOR_TYPE_PALETTE;
369 } else {
370 if (maxGrayDeviation <= grayscaleTolerance) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700371 diag->note(DiagMessage()
372 << "forcing image to gray (max deviation = "
373 << maxGrayDeviation << ")");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700374 *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
375 } else {
376 *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
377 }
378 }
379
380 // Perform postprocessing of the image or palette data based on the final
381 // color type chosen
382
383 if (*colorType == PNG_COLOR_TYPE_PALETTE) {
384 // Create separate RGB and Alpha palettes and set the number of colors
385 *paletteEntries = num_colors;
386
387 // Create the RGB and alpha palettes
388 for (int idx = 0; idx < num_colors; idx++) {
389 col = colors[idx];
390 rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
391 rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
392 rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
393 alphaPalette[idx] = (png_byte) (col & 0xff);
394 }
395 } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
396 // If the image is gray or gray + alpha, compact the pixels into outRows
397 for (j = 0; j < h; j++) {
398 const png_byte* row = imageInfo.rows[j];
399 png_bytep out = outRows[j];
400 for (i = 0; i < w; i++) {
401 rr = *row++;
402 gg = *row++;
403 bb = *row++;
404 aa = *row++;
405
406 if (isGrayscale) {
407 *out++ = rr;
408 } else {
409 *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
410 }
411 if (!isOpaque) {
412 *out++ = aa;
413 }
414 }
415 }
416 }
417}
418
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700419static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
420 int grayScaleTolerance) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700421 if (setjmp(png_jmpbuf(writePtr))) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700422 diag->error(DiagMessage() << "failed to write png");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700423 return false;
424 }
425
426 uint32_t width, height;
427 int colorType, bitDepth, interlaceType, compressionType;
428
429 png_unknown_chunk unknowns[3];
430 unknowns[0].data = nullptr;
431 unknowns[1].data = nullptr;
432 unknowns[2].data = nullptr;
433
434 png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
435 if (outRows == (png_bytepp) 0) {
436 printf("Can't allocate output buffer!\n");
437 exit(1);
438 }
439 for (uint32_t i = 0; i < info->height; i++) {
440 outRows[i] = (png_bytep) malloc(2 * (int) info->width);
441 if (outRows[i] == (png_bytep) 0) {
442 printf("Can't allocate output buffer!\n");
443 exit(1);
444 }
445 }
446
447 png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
448
449 if (kDebug) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700450 diag->note(DiagMessage()
451 << "writing image: w = " << info->width
452 << ", h = " << info->height);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700453 }
454
455 png_color rgbPalette[256];
456 png_byte alphaPalette[256];
457 bool hasTransparency;
458 int paletteEntries;
459
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700460 analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700461 &paletteEntries, &hasTransparency, &colorType, outRows);
462
463 // If the image is a 9-patch, we need to preserve it as a ARGB file to make
464 // sure the pixels will not be pre-dithered/clamped until we decide they are
465 if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
466 colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
467 colorType = PNG_COLOR_TYPE_RGB_ALPHA;
468 }
469
470 if (kDebug) {
471 switch (colorType) {
472 case PNG_COLOR_TYPE_PALETTE:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700473 diag->note(DiagMessage()
474 << "has " << paletteEntries
475 << " colors" << (hasTransparency ? " (with alpha)" : "")
476 << ", using PNG_COLOR_TYPE_PALLETTE");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700477 break;
478 case PNG_COLOR_TYPE_GRAY:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700479 diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700480 break;
481 case PNG_COLOR_TYPE_GRAY_ALPHA:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700482 diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700483 break;
484 case PNG_COLOR_TYPE_RGB:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700485 diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700486 break;
487 case PNG_COLOR_TYPE_RGB_ALPHA:
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700488 diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700489 break;
490 }
491 }
492
493 png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
494 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
495
496 if (colorType == PNG_COLOR_TYPE_PALETTE) {
497 png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
498 if (hasTransparency) {
499 png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
500 }
501 png_set_filter(writePtr, 0, PNG_NO_FILTERS);
502 } else {
503 png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
504 }
505
506 if (info->is9Patch) {
507 int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
508 int pIndex = info->haveLayoutBounds ? 2 : 1;
509 int bIndex = 1;
510 int oIndex = 0;
511
512 // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
513 png_bytep chunkNames = info->haveLayoutBounds
514 ? (png_bytep)"npOl\0npLb\0npTc\0"
515 : (png_bytep)"npOl\0npTc";
516
517 // base 9 patch data
518 if (kDebug) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700519 diag->note(DiagMessage() << "adding 9-patch info..");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700520 }
521 strcpy((char*)unknowns[pIndex].name, "npTc");
522 unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
523 unknowns[pIndex].size = info->info9Patch.serializedSize();
524 // TODO: remove the check below when everything works
525 checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
526
527 // automatically generated 9 patch outline data
528 int chunkSize = sizeof(png_uint_32) * 6;
529 strcpy((char*)unknowns[oIndex].name, "npOl");
530 unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
531 png_byte outputData[chunkSize];
532 memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
533 ((float*) outputData)[4] = info->outlineRadius;
534 ((png_uint_32*) outputData)[5] = info->outlineAlpha;
535 memcpy(unknowns[oIndex].data, &outputData, chunkSize);
536 unknowns[oIndex].size = chunkSize;
537
538 // optional optical inset / layout bounds data
539 if (info->haveLayoutBounds) {
540 int chunkSize = sizeof(png_uint_32) * 4;
541 strcpy((char*)unknowns[bIndex].name, "npLb");
542 unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
543 memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
544 unknowns[bIndex].size = chunkSize;
545 }
546
547 for (int i = 0; i < chunkCount; i++) {
548 unknowns[i].location = PNG_HAVE_PLTE;
549 }
550 png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
551 chunkNames, chunkCount);
552 png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
553
554#if PNG_LIBPNG_VER < 10600
555 // Deal with unknown chunk location bug in 1.5.x and earlier.
556 png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
557 if (info->haveLayoutBounds) {
558 png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
559 }
560#endif
561 }
562
563 png_write_info(writePtr, infoPtr);
564
565 png_bytepp rows;
566 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
567 if (colorType == PNG_COLOR_TYPE_RGB) {
568 png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
569 }
570 rows = info->rows.data();
571 } else {
572 rows = outRows;
573 }
574 png_write_image(writePtr, rows);
575
576 if (kDebug) {
577 printf("Final image data:\n");
578 //dump_image(info->width, info->height, rows, colorType);
579 }
580
581 png_write_end(writePtr, infoPtr);
582
583 for (uint32_t i = 0; i < info->height; i++) {
584 free(outRows[i]);
585 }
586 free(outRows);
587 free(unknowns[0].data);
588 free(unknowns[1].data);
589 free(unknowns[2].data);
590
591 png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
592 &compressionType, nullptr);
593
594 if (kDebug) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700595 diag->note(DiagMessage()
596 << "image written: w = " << width << ", h = " << height
597 << ", d = " << bitDepth << ", colors = " << colorType
598 << ", inter = " << interlaceType << ", comp = " << compressionType);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700599 }
600 return true;
601}
602
603constexpr uint32_t kColorWhite = 0xffffffffu;
604constexpr uint32_t kColorTick = 0xff000000u;
605constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
606
607enum class TickType {
608 kNone,
609 kTick,
610 kLayoutBounds,
611 kBoth
612};
613
614static TickType tickType(png_bytep p, bool transparent, const char** outError) {
615 png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
616
617 if (transparent) {
618 if (p[3] == 0) {
619 return TickType::kNone;
620 }
621 if (color == kColorLayoutBoundsTick) {
622 return TickType::kLayoutBounds;
623 }
624 if (color == kColorTick) {
625 return TickType::kTick;
626 }
627
628 // Error cases
629 if (p[3] != 0xff) {
630 *outError = "Frame pixels must be either solid or transparent "
631 "(not intermediate alphas)";
632 return TickType::kNone;
633 }
634
635 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
636 *outError = "Ticks in transparent frame must be black or red";
637 }
638 return TickType::kTick;
639 }
640
641 if (p[3] != 0xFF) {
642 *outError = "White frame must be a solid color (no alpha)";
643 }
644 if (color == kColorWhite) {
645 return TickType::kNone;
646 }
647 if (color == kColorTick) {
648 return TickType::kTick;
649 }
650 if (color == kColorLayoutBoundsTick) {
651 return TickType::kLayoutBounds;
652 }
653
654 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
655 *outError = "Ticks in white frame must be black or red";
656 return TickType::kNone;
657 }
658 return TickType::kTick;
659}
660
661enum class TickState {
662 kStart,
663 kInside1,
664 kOutside1
665};
666
667static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
668 int32_t* outLeft, int32_t* outRight, const char** outError,
669 uint8_t* outDivs, bool multipleAllowed) {
670 *outLeft = *outRight = -1;
671 TickState state = TickState::kStart;
672 bool found = false;
673
674 for (int i = 1; i < width - 1; i++) {
675 if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
676 if (state == TickState::kStart ||
677 (state == TickState::kOutside1 && multipleAllowed)) {
678 *outLeft = i-1;
679 *outRight = width-2;
680 found = true;
681 if (outDivs != NULL) {
682 *outDivs += 2;
683 }
684 state = TickState::kInside1;
685 } else if (state == TickState::kOutside1) {
686 *outError = "Can't have more than one marked region along edge";
687 *outLeft = i;
688 return false;
689 }
690 } else if (!*outError) {
691 if (state == TickState::kInside1) {
692 // We're done with this div. Move on to the next.
693 *outRight = i-1;
694 outRight += 2;
695 outLeft += 2;
696 state = TickState::kOutside1;
697 }
698 } else {
699 *outLeft = i;
700 return false;
701 }
702 }
703
704 if (required && !found) {
705 *outError = "No marked region found along edge";
706 *outLeft = -1;
707 return false;
708 }
709 return true;
710}
711
712static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
713 bool required, int32_t* outTop, int32_t* outBottom,
714 const char** outError, uint8_t* outDivs, bool multipleAllowed) {
715 *outTop = *outBottom = -1;
716 TickState state = TickState::kStart;
717 bool found = false;
718
719 for (int i = 1; i < height - 1; i++) {
720 if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
721 if (state == TickState::kStart ||
722 (state == TickState::kOutside1 && multipleAllowed)) {
723 *outTop = i-1;
724 *outBottom = height-2;
725 found = true;
726 if (outDivs != NULL) {
727 *outDivs += 2;
728 }
729 state = TickState::kInside1;
730 } else if (state == TickState::kOutside1) {
731 *outError = "Can't have more than one marked region along edge";
732 *outTop = i;
733 return false;
734 }
735 } else if (!*outError) {
736 if (state == TickState::kInside1) {
737 // We're done with this div. Move on to the next.
738 *outBottom = i-1;
739 outTop += 2;
740 outBottom += 2;
741 state = TickState::kOutside1;
742 }
743 } else {
744 *outTop = i;
745 return false;
746 }
747 }
748
749 if (required && !found) {
750 *outError = "No marked region found along edge";
751 *outTop = -1;
752 return false;
753 }
754 return true;
755}
756
757static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
758 bool /* required */, int32_t* outLeft,
759 int32_t* outRight, const char** outError) {
760 *outLeft = *outRight = 0;
761
762 // Look for left tick
763 if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
764 // Starting with a layout padding tick
765 int i = 1;
766 while (i < width - 1) {
767 (*outLeft)++;
768 i++;
769 if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
770 break;
771 }
772 }
773 }
774
775 // Look for right tick
776 if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
777 // Ending with a layout padding tick
778 int i = width - 2;
779 while (i > 1) {
780 (*outRight)++;
781 i--;
782 if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
783 break;
784 }
785 }
786 }
787 return true;
788}
789
790static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
791 bool /* required */, int32_t* outTop, int32_t* outBottom,
792 const char** outError) {
793 *outTop = *outBottom = 0;
794
795 // Look for top tick
796 if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
797 // Starting with a layout padding tick
798 int i = 1;
799 while (i < height - 1) {
800 (*outTop)++;
801 i++;
802 if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
803 break;
804 }
805 }
806 }
807
808 // Look for bottom tick
809 if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
810 // Ending with a layout padding tick
811 int i = height - 2;
812 while (i > 1) {
813 (*outBottom)++;
814 i--;
815 if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
816 break;
817 }
818 }
819 }
820 return true;
821}
822
823static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
824 int dX, int dY, int* outInset) {
825 uint8_t maxOpacity = 0;
826 int inset = 0;
827 *outInset = 0;
828 for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
829 png_byte* color = rows[y] + x * 4;
830 uint8_t opacity = color[3];
831 if (opacity > maxOpacity) {
832 maxOpacity = opacity;
833 *outInset = inset;
834 }
835 if (opacity == 0xff) return;
836 }
837}
838
839static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
840 uint8_t maxAlpha = 0;
841 for (int x = startX; x < endX; x++) {
842 uint8_t alpha = (row + x * 4)[3];
843 if (alpha > maxAlpha) maxAlpha = alpha;
844 }
845 return maxAlpha;
846}
847
848static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
849 uint8_t maxAlpha = 0;
850 for (int y = startY; y < endY; y++) {
851 uint8_t alpha = (rows[y] + offsetX * 4)[3];
852 if (alpha > maxAlpha) maxAlpha = alpha;
853 }
854 return maxAlpha;
855}
856
857static void getOutline(PngInfo* image) {
858 int midX = image->width / 2;
859 int midY = image->height / 2;
860 int endX = image->width - 2;
861 int endY = image->height - 2;
862
863 // find left and right extent of nine patch content on center row
864 if (image->width > 4) {
865 findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
866 findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
867 &image->outlineInsetsRight);
868 } else {
869 image->outlineInsetsLeft = 0;
870 image->outlineInsetsRight = 0;
871 }
872
873 // find top and bottom extent of nine patch content on center column
874 if (image->height > 4) {
875 findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
876 findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
877 &image->outlineInsetsBottom);
878 } else {
879 image->outlineInsetsTop = 0;
880 image->outlineInsetsBottom = 0;
881 }
882
883 int innerStartX = 1 + image->outlineInsetsLeft;
884 int innerStartY = 1 + image->outlineInsetsTop;
885 int innerEndX = endX - image->outlineInsetsRight;
886 int innerEndY = endY - image->outlineInsetsBottom;
887 int innerMidX = (innerEndX + innerStartX) / 2;
888 int innerMidY = (innerEndY + innerStartY) / 2;
889
890 // assuming the image is a round rect, compute the radius by marching
891 // diagonally from the top left corner towards the center
892 image->outlineAlpha = std::max(
893 maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
894 maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
895
896 int diagonalInset = 0;
897 findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
898 &diagonalInset);
899
900 /* Determine source radius based upon inset:
901 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
902 * sqrt(2) * r = sqrt(2) * i + r
903 * (sqrt(2) - 1) * r = sqrt(2) * i
904 * r = sqrt(2) / (sqrt(2) - 1) * i
905 */
906 image->outlineRadius = 3.4142f * diagonalInset;
907
908 if (kDebug) {
909 printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
910 image->outlineInsetsLeft,
911 image->outlineInsetsTop,
912 image->outlineInsetsRight,
913 image->outlineInsetsBottom,
914 image->outlineRadius,
915 image->outlineAlpha);
916 }
917}
918
919static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
920 png_bytep color = rows[top] + left*4;
921
922 if (left > right || top > bottom) {
923 return android::Res_png_9patch::TRANSPARENT_COLOR;
924 }
925
926 while (top <= bottom) {
927 for (int i = left; i <= right; i++) {
928 png_bytep p = rows[top]+i*4;
929 if (color[3] == 0) {
930 if (p[3] != 0) {
931 return android::Res_png_9patch::NO_COLOR;
932 }
933 } else if (p[0] != color[0] || p[1] != color[1] ||
934 p[2] != color[2] || p[3] != color[3]) {
935 return android::Res_png_9patch::NO_COLOR;
936 }
937 }
938 top++;
939 }
940
941 if (color[3] == 0) {
942 return android::Res_png_9patch::TRANSPARENT_COLOR;
943 }
944 return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
945}
946
947static bool do9Patch(PngInfo* image, std::string* outError) {
948 image->is9Patch = true;
949
950 int W = image->width;
951 int H = image->height;
952 int i, j;
953
954 const int maxSizeXDivs = W * sizeof(int32_t);
955 const int maxSizeYDivs = H * sizeof(int32_t);
956 int32_t* xDivs = image->xDivs = new int32_t[W];
957 int32_t* yDivs = image->yDivs = new int32_t[H];
958 uint8_t numXDivs = 0;
959 uint8_t numYDivs = 0;
960
961 int8_t numColors;
962 int numRows;
963 int numCols;
964 int top;
965 int left;
966 int right;
967 int bottom;
968 memset(xDivs, -1, maxSizeXDivs);
969 memset(yDivs, -1, maxSizeYDivs);
970 image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
971 image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
972 image->layoutBoundsLeft = image->layoutBoundsRight = 0;
973 image->layoutBoundsTop = image->layoutBoundsBottom = 0;
974
975 png_bytep p = image->rows[0];
976 bool transparent = p[3] == 0;
977 bool hasColor = false;
978
979 const char* errorMsg = nullptr;
980 int errorPixel = -1;
981 const char* errorEdge = nullptr;
982
983 int colorIndex = 0;
984 std::vector<png_bytep> newRows;
985
986 // Validate size...
987 if (W < 3 || H < 3) {
988 errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
989 goto getout;
990 }
991
992 // Validate frame...
993 if (!transparent &&
994 (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
995 errorMsg = "Must have one-pixel frame that is either transparent or white";
996 goto getout;
997 }
998
999 // Find left and right of sizing areas...
1000 if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
1001 true)) {
1002 errorPixel = xDivs[0];
1003 errorEdge = "top";
1004 goto getout;
1005 }
1006
1007 // Find top and bottom of sizing areas...
1008 if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
1009 &errorMsg, &numYDivs, true)) {
1010 errorPixel = yDivs[0];
1011 errorEdge = "left";
1012 goto getout;
1013 }
1014
1015 // Copy patch size data into image...
1016 image->info9Patch.numXDivs = numXDivs;
1017 image->info9Patch.numYDivs = numYDivs;
1018
1019 // Find left and right of padding area...
1020 if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
1021 &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
1022 &errorMsg, nullptr, false)) {
1023 errorPixel = image->info9Patch.paddingLeft;
1024 errorEdge = "bottom";
1025 goto getout;
1026 }
1027
1028 // Find top and bottom of padding area...
1029 if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
1030 &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
1031 &errorMsg, nullptr, false)) {
1032 errorPixel = image->info9Patch.paddingTop;
1033 errorEdge = "right";
1034 goto getout;
1035 }
1036
1037 // Find left and right of layout padding...
1038 getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
1039 &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
1040
1041 getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
1042 &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
1043
1044 image->haveLayoutBounds = image->layoutBoundsLeft != 0
1045 || image->layoutBoundsRight != 0
1046 || image->layoutBoundsTop != 0
1047 || image->layoutBoundsBottom != 0;
1048
1049 if (image->haveLayoutBounds) {
1050 if (kDebug) {
1051 printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
1052 image->layoutBoundsRight, image->layoutBoundsBottom);
1053 }
1054 }
1055
1056 // use opacity of pixels to estimate the round rect outline
1057 getOutline(image);
1058
1059 // If padding is not yet specified, take values from size.
1060 if (image->info9Patch.paddingLeft < 0) {
1061 image->info9Patch.paddingLeft = xDivs[0];
1062 image->info9Patch.paddingRight = W - 2 - xDivs[1];
1063 } else {
1064 // Adjust value to be correct!
1065 image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
1066 }
1067 if (image->info9Patch.paddingTop < 0) {
1068 image->info9Patch.paddingTop = yDivs[0];
1069 image->info9Patch.paddingBottom = H - 2 - yDivs[1];
1070 } else {
1071 // Adjust value to be correct!
1072 image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
1073 }
1074
1075/* if (kDebug) {
1076 printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
1077 xDivs[0], xDivs[1],
1078 yDivs[0], yDivs[1]);
1079 printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
1080 image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
1081 image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
1082 }*/
1083
1084 // Remove frame from image.
1085 newRows.resize(H - 2);
1086 for (i = 0; i < H - 2; i++) {
1087 newRows[i] = image->rows[i + 1];
1088 memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
1089 }
1090 image->rows.swap(newRows);
1091
1092 image->width -= 2;
1093 W = image->width;
1094 image->height -= 2;
1095 H = image->height;
1096
1097 // Figure out the number of rows and columns in the N-patch
1098 numCols = numXDivs + 1;
1099 if (xDivs[0] == 0) { // Column 1 is strechable
1100 numCols--;
1101 }
1102 if (xDivs[numXDivs - 1] == W) {
1103 numCols--;
1104 }
1105 numRows = numYDivs + 1;
1106 if (yDivs[0] == 0) { // Row 1 is strechable
1107 numRows--;
1108 }
1109 if (yDivs[numYDivs - 1] == H) {
1110 numRows--;
1111 }
1112
1113 // Make sure the amount of rows and columns will fit in the number of
1114 // colors we can use in the 9-patch format.
1115 if (numRows * numCols > 0x7F) {
1116 errorMsg = "Too many rows and columns in 9-patch perimeter";
1117 goto getout;
1118 }
1119
1120 numColors = numRows * numCols;
1121 image->info9Patch.numColors = numColors;
1122 image->colors.resize(numColors);
1123
1124 // Fill in color information for each patch.
1125
1126 uint32_t c;
1127 top = 0;
1128
1129 // The first row always starts with the top being at y=0 and the bottom
1130 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
1131 // the first row is stretchable along the Y axis, otherwise it is fixed.
1132 // The last row always ends with the bottom being bitmap.height and the top
1133 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
1134 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
1135 // the Y axis, otherwise it is fixed.
1136 //
1137 // The first and last columns are similarly treated with respect to the X
1138 // axis.
1139 //
1140 // The above is to help explain some of the special casing that goes on the
1141 // code below.
1142
1143 // The initial yDiv and whether the first row is considered stretchable or
1144 // not depends on whether yDiv[0] was zero or not.
1145 for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
1146 if (j == numYDivs) {
1147 bottom = H;
1148 } else {
1149 bottom = yDivs[j];
1150 }
1151 left = 0;
1152 // The initial xDiv and whether the first column is considered
1153 // stretchable or not depends on whether xDiv[0] was zero or not.
1154 for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
1155 if (i == numXDivs) {
1156 right = W;
1157 } else {
1158 right = xDivs[i];
1159 }
1160 c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
1161 image->colors[colorIndex++] = c;
1162 if (kDebug) {
1163 if (c != android::Res_png_9patch::NO_COLOR) {
1164 hasColor = true;
1165 }
1166 }
1167 left = right;
1168 }
1169 top = bottom;
1170 }
1171
1172 assert(colorIndex == numColors);
1173
1174 if (kDebug && hasColor) {
1175 for (i = 0; i < numColors; i++) {
1176 if (i == 0) printf("Colors:\n");
1177 printf(" #%08x", image->colors[i]);
1178 if (i == numColors - 1) printf("\n");
1179 }
1180 }
1181getout:
1182 if (errorMsg) {
1183 std::stringstream err;
1184 err << "9-patch malformed: " << errorMsg;
Adam Lesinski144c5ea2016-03-01 09:05:11 -08001185 if (errorEdge) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001186 err << "." << std::endl;
1187 if (errorPixel >= 0) {
1188 err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
1189 } else {
1190 err << "Found along " << errorEdge << " edge";
1191 }
1192 }
1193 *outError = err.str();
1194 return false;
1195 }
1196 return true;
1197}
1198
1199
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001200bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
1201 const PngOptions& options) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001202 png_byte signature[kPngSignatureSize];
1203
1204 // Read the PNG signature first.
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001205 if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
1206 mDiag->error(DiagMessage() << strerror(errno));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001207 return false;
1208 }
1209
1210 // If the PNG signature doesn't match, bail early.
1211 if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001212 mDiag->error(DiagMessage() << "not a valid png file");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001213 return false;
1214 }
1215
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001216 bool result = false;
1217 png_structp readPtr = nullptr;
1218 png_infop infoPtr = nullptr;
1219 png_structp writePtr = nullptr;
1220 png_infop writeInfoPtr = nullptr;
1221 PngInfo pngInfo = {};
1222
1223 readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
1224 if (!readPtr) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001225 mDiag->error(DiagMessage() << "failed to allocate read ptr");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001226 goto bail;
1227 }
1228
1229 infoPtr = png_create_info_struct(readPtr);
1230 if (!infoPtr) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001231 mDiag->error(DiagMessage() << "failed to allocate info ptr");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001232 goto bail;
1233 }
1234
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001235 png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001236
1237 // Set the read function to read from std::istream.
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001238 png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001239
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001240 if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001241 goto bail;
1242 }
1243
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001244 if (util::stringEndsWith<char>(source.path, ".9.png")) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001245 std::string errorMsg;
1246 if (!do9Patch(&pngInfo, &errorMsg)) {
1247 mDiag->error(DiagMessage() << errorMsg);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001248 goto bail;
1249 }
1250 }
1251
1252 writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
1253 if (!writePtr) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001254 mDiag->error(DiagMessage() << "failed to allocate write ptr");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001255 goto bail;
1256 }
1257
1258 writeInfoPtr = png_create_info_struct(writePtr);
1259 if (!writeInfoPtr) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001260 mDiag->error(DiagMessage() << "failed to allocate write info ptr");
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001261 goto bail;
1262 }
1263
1264 png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
1265
1266 // Set the write function to write to std::ostream.
Adam Lesinski769de982015-04-10 19:43:55 -07001267 png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001268
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001269 if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001270 goto bail;
1271 }
1272
1273 result = true;
1274bail:
1275 if (readPtr) {
1276 png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
1277 }
1278
1279 if (writePtr) {
1280 png_destroy_write_struct(&writePtr, &writeInfoPtr);
1281 }
1282 return result;
1283}
1284
1285} // namespace aapt