blob: 28de933a315eafc7641da46fb6d148fa21774fd3 [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4// Build resource files from raw assets.
5//
6
7#define PNG_INTERNAL
8
9#include "Images.h"
10
11#include <androidfw/ResourceTypes.h>
12#include <utils/ByteOrder.h>
13
14#include <png.h>
15#include <zlib.h>
16
17#define NOISY(x) //x
18
19static void
20png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
21{
22 AaptFile* aaptfile = (AaptFile*) png_get_io_ptr(png_ptr);
23 status_t err = aaptfile->writeData(data, length);
24 if (err != NO_ERROR) {
25 png_error(png_ptr, "Write Error");
26 }
27}
28
29
30static void
31png_flush_aapt_file(png_structp png_ptr)
32{
33}
34
35// This holds an image as 8bpp RGBA.
36struct image_info
37{
Narayan Kamath6381dd42014-03-03 17:12:03 +000038 image_info() : rows(NULL), is9Patch(false),
39 xDivs(NULL), yDivs(NULL), colors(NULL), allocRows(NULL) { }
40
Adam Lesinski282e1812014-01-23 18:17:42 -080041 ~image_info() {
42 if (rows && rows != allocRows) {
43 free(rows);
44 }
45 if (allocRows) {
46 for (int i=0; i<(int)allocHeight; i++) {
47 free(allocRows[i]);
48 }
49 free(allocRows);
50 }
Narayan Kamath6381dd42014-03-03 17:12:03 +000051 free(xDivs);
52 free(yDivs);
53 free(colors);
54 }
55
56 void* serialize9patch() {
57 void* serialized = Res_png_9patch::serialize(info9Patch, xDivs, yDivs, colors);
58 reinterpret_cast<Res_png_9patch*>(serialized)->deviceToFile();
59 return serialized;
Adam Lesinski282e1812014-01-23 18:17:42 -080060 }
61
62 png_uint_32 width;
63 png_uint_32 height;
64 png_bytepp rows;
65
66 // 9-patch info.
67 bool is9Patch;
68 Res_png_9patch info9Patch;
Narayan Kamath6381dd42014-03-03 17:12:03 +000069 int32_t* xDivs;
70 int32_t* yDivs;
71 uint32_t* colors;
Adam Lesinski282e1812014-01-23 18:17:42 -080072
73 // Layout padding, if relevant
74 bool haveLayoutBounds;
75 int32_t layoutBoundsLeft;
76 int32_t layoutBoundsTop;
77 int32_t layoutBoundsRight;
78 int32_t layoutBoundsBottom;
79
Chris Craik47cd8e92014-07-08 17:13:08 -070080 // Round rect outline description
81 int32_t outlineInsetsLeft;
82 int32_t outlineInsetsTop;
83 int32_t outlineInsetsRight;
84 int32_t outlineInsetsBottom;
85 float outlineRadius;
86 bool outlineFilled;
87
Adam Lesinski282e1812014-01-23 18:17:42 -080088 png_uint_32 allocHeight;
89 png_bytepp allocRows;
90};
91
John Reck859e19f2013-09-05 16:26:04 -070092static void log_warning(png_structp png_ptr, png_const_charp warning_message)
93{
94 const char* imageName = (const char*) png_get_error_ptr(png_ptr);
95 fprintf(stderr, "%s: libpng warning: %s\n", imageName, warning_message);
96}
97
Adam Lesinski282e1812014-01-23 18:17:42 -080098static void read_png(const char* imageName,
99 png_structp read_ptr, png_infop read_info,
100 image_info* outImageInfo)
101{
102 int color_type;
103 int bit_depth, interlace_type, compression_type;
104 int i;
105
John Reck859e19f2013-09-05 16:26:04 -0700106 png_set_error_fn(read_ptr, const_cast<char*>(imageName),
107 NULL /* use default errorfn */, log_warning);
Adam Lesinski282e1812014-01-23 18:17:42 -0800108 png_read_info(read_ptr, read_info);
109
110 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
111 &outImageInfo->height, &bit_depth, &color_type,
112 &interlace_type, &compression_type, NULL);
113
114 //printf("Image %s:\n", imageName);
115 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
116 // color_type, bit_depth, interlace_type, compression_type);
117
118 if (color_type == PNG_COLOR_TYPE_PALETTE)
119 png_set_palette_to_rgb(read_ptr);
120
121 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
122 png_set_expand_gray_1_2_4_to_8(read_ptr);
123
124 if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) {
125 //printf("Has PNG_INFO_tRNS!\n");
126 png_set_tRNS_to_alpha(read_ptr);
127 }
128
129 if (bit_depth == 16)
130 png_set_strip_16(read_ptr);
131
132 if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
133 png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
134
135 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
136 png_set_gray_to_rgb(read_ptr);
137
John Reck859e19f2013-09-05 16:26:04 -0700138 png_set_interlace_handling(read_ptr);
139
Adam Lesinski282e1812014-01-23 18:17:42 -0800140 png_read_update_info(read_ptr, read_info);
141
142 outImageInfo->rows = (png_bytepp)malloc(
143 outImageInfo->height * sizeof(png_bytep));
144 outImageInfo->allocHeight = outImageInfo->height;
145 outImageInfo->allocRows = outImageInfo->rows;
146
147 png_set_rows(read_ptr, read_info, outImageInfo->rows);
148
149 for (i = 0; i < (int)outImageInfo->height; i++)
150 {
151 outImageInfo->rows[i] = (png_bytep)
152 malloc(png_get_rowbytes(read_ptr, read_info));
153 }
154
155 png_read_image(read_ptr, outImageInfo->rows);
156
157 png_read_end(read_ptr, read_info);
158
159 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
160 imageName,
161 (int)outImageInfo->width, (int)outImageInfo->height,
162 bit_depth, color_type,
163 interlace_type, compression_type));
164
165 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
166 &outImageInfo->height, &bit_depth, &color_type,
167 &interlace_type, &compression_type, NULL);
168}
169
170#define COLOR_TRANSPARENT 0
171#define COLOR_WHITE 0xFFFFFFFF
172#define COLOR_TICK 0xFF000000
173#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF
174
175enum {
176 TICK_TYPE_NONE,
177 TICK_TYPE_TICK,
178 TICK_TYPE_LAYOUT_BOUNDS,
179 TICK_TYPE_BOTH
180};
181
182static int tick_type(png_bytep p, bool transparent, const char** outError)
183{
184 png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
185
186 if (transparent) {
187 if (p[3] == 0) {
188 return TICK_TYPE_NONE;
189 }
190 if (color == COLOR_LAYOUT_BOUNDS_TICK) {
191 return TICK_TYPE_LAYOUT_BOUNDS;
192 }
193 if (color == COLOR_TICK) {
194 return TICK_TYPE_TICK;
195 }
196
197 // Error cases
198 if (p[3] != 0xff) {
199 *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
200 return TICK_TYPE_NONE;
201 }
202 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
203 *outError = "Ticks in transparent frame must be black or red";
204 }
205 return TICK_TYPE_TICK;
206 }
207
208 if (p[3] != 0xFF) {
209 *outError = "White frame must be a solid color (no alpha)";
210 }
211 if (color == COLOR_WHITE) {
212 return TICK_TYPE_NONE;
213 }
214 if (color == COLOR_TICK) {
215 return TICK_TYPE_TICK;
216 }
217 if (color == COLOR_LAYOUT_BOUNDS_TICK) {
218 return TICK_TYPE_LAYOUT_BOUNDS;
219 }
220
221 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
222 *outError = "Ticks in white frame must be black or red";
223 return TICK_TYPE_NONE;
224 }
225 return TICK_TYPE_TICK;
226}
227
228enum {
229 TICK_START,
230 TICK_INSIDE_1,
231 TICK_OUTSIDE_1
232};
233
234static status_t get_horizontal_ticks(
235 png_bytep row, int width, bool transparent, bool required,
236 int32_t* outLeft, int32_t* outRight, const char** outError,
237 uint8_t* outDivs, bool multipleAllowed)
238{
239 int i;
240 *outLeft = *outRight = -1;
241 int state = TICK_START;
242 bool found = false;
243
244 for (i=1; i<width-1; i++) {
245 if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) {
246 if (state == TICK_START ||
247 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
248 *outLeft = i-1;
249 *outRight = width-2;
250 found = true;
251 if (outDivs != NULL) {
252 *outDivs += 2;
253 }
254 state = TICK_INSIDE_1;
255 } else if (state == TICK_OUTSIDE_1) {
256 *outError = "Can't have more than one marked region along edge";
257 *outLeft = i;
258 return UNKNOWN_ERROR;
259 }
260 } else if (*outError == NULL) {
261 if (state == TICK_INSIDE_1) {
262 // We're done with this div. Move on to the next.
263 *outRight = i-1;
264 outRight += 2;
265 outLeft += 2;
266 state = TICK_OUTSIDE_1;
267 }
268 } else {
269 *outLeft = i;
270 return UNKNOWN_ERROR;
271 }
272 }
273
274 if (required && !found) {
275 *outError = "No marked region found along edge";
276 *outLeft = -1;
277 return UNKNOWN_ERROR;
278 }
279
280 return NO_ERROR;
281}
282
283static status_t get_vertical_ticks(
284 png_bytepp rows, int offset, int height, bool transparent, bool required,
285 int32_t* outTop, int32_t* outBottom, const char** outError,
286 uint8_t* outDivs, bool multipleAllowed)
287{
288 int i;
289 *outTop = *outBottom = -1;
290 int state = TICK_START;
291 bool found = false;
292
293 for (i=1; i<height-1; i++) {
294 if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) {
295 if (state == TICK_START ||
296 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
297 *outTop = i-1;
298 *outBottom = height-2;
299 found = true;
300 if (outDivs != NULL) {
301 *outDivs += 2;
302 }
303 state = TICK_INSIDE_1;
304 } else if (state == TICK_OUTSIDE_1) {
305 *outError = "Can't have more than one marked region along edge";
306 *outTop = i;
307 return UNKNOWN_ERROR;
308 }
309 } else if (*outError == NULL) {
310 if (state == TICK_INSIDE_1) {
311 // We're done with this div. Move on to the next.
312 *outBottom = i-1;
313 outTop += 2;
314 outBottom += 2;
315 state = TICK_OUTSIDE_1;
316 }
317 } else {
318 *outTop = i;
319 return UNKNOWN_ERROR;
320 }
321 }
322
323 if (required && !found) {
324 *outError = "No marked region found along edge";
325 *outTop = -1;
326 return UNKNOWN_ERROR;
327 }
328
329 return NO_ERROR;
330}
331
332static status_t get_horizontal_layout_bounds_ticks(
333 png_bytep row, int width, bool transparent, bool required,
334 int32_t* outLeft, int32_t* outRight, const char** outError)
335{
336 int i;
337 *outLeft = *outRight = 0;
338
339 // Look for left tick
340 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) {
341 // Starting with a layout padding tick
342 i = 1;
343 while (i < width - 1) {
344 (*outLeft)++;
345 i++;
346 int tick = tick_type(row + i * 4, transparent, outError);
347 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
348 break;
349 }
350 }
351 }
352
353 // Look for right tick
354 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) {
355 // Ending with a layout padding tick
356 i = width - 2;
357 while (i > 1) {
358 (*outRight)++;
359 i--;
360 int tick = tick_type(row+i*4, transparent, outError);
361 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
362 break;
363 }
364 }
365 }
366
367 return NO_ERROR;
368}
369
370static status_t get_vertical_layout_bounds_ticks(
371 png_bytepp rows, int offset, int height, bool transparent, bool required,
372 int32_t* outTop, int32_t* outBottom, const char** outError)
373{
374 int i;
375 *outTop = *outBottom = 0;
376
377 // Look for top tick
378 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) {
379 // Starting with a layout padding tick
380 i = 1;
381 while (i < height - 1) {
382 (*outTop)++;
383 i++;
384 int tick = tick_type(rows[i] + offset, transparent, outError);
385 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
386 break;
387 }
388 }
389 }
390
391 // Look for bottom tick
392 if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) {
393 // Ending with a layout padding tick
394 i = height - 2;
395 while (i > 1) {
396 (*outBottom)++;
397 i--;
398 int tick = tick_type(rows[i] + offset, transparent, outError);
399 if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
400 break;
401 }
402 }
403 }
404
405 return NO_ERROR;
406}
407
Chris Craik47cd8e92014-07-08 17:13:08 -0700408static void find_max_opacity(png_byte** rows,
409 int startX, int startY, int endX, int endY, int dX, int dY,
410 int* out_inset)
411{
412 bool opaque_within_inset = true;
413 unsigned char max_opacity = 0;
414 int inset = 0;
415 *out_inset = 0;
416 for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
417 png_byte* color = rows[y] + x * 4;
418 unsigned char opacity = color[3];
419 if (opacity > max_opacity) {
420 max_opacity = opacity;
421 *out_inset = inset;
422 }
423 if (opacity == 0xff) return;
424 }
425}
426
427static bool is_opaque_over_row(png_byte* row, int startX, int endX)
428{
429 for (int x = startX; x < endX; x++) {
430 png_byte* color = row + x * 4;
431 if (color[3] != 0xff) return false;
432 }
433 return true;
434}
435
436static bool is_opaque_over_col(png_byte** rows, int offsetX, int startY, int endY)
437{
438 for (int y = startY; y < endY; y++) {
439 png_byte* color = rows[y] + offsetX * 4;
440 if (color[3] != 0xff) return false;
441 }
442 return true;
443}
444
445static void get_outline(image_info* image)
446{
447 int midX = image->width / 2;
448 int midY = image->height / 2;
449 int endX = image->width - 2;
450 int endY = image->height - 2;
451
452 // find left and right extent of nine patch content on center row
453 if (image->width > 4) {
454 find_max_opacity(image->rows, 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
455 find_max_opacity(image->rows, endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
456 } else {
457 image->outlineInsetsLeft = 0;
458 image->outlineInsetsRight = 0;
459 }
460
461 // find top and bottom extent of nine patch content on center column
462 if (image->height > 4) {
463 find_max_opacity(image->rows, midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
464 find_max_opacity(image->rows, midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
465 } else {
466 image->outlineInsetsTop = 0;
467 image->outlineInsetsBottom = 0;
468 }
469
470 int innerStartX = 1 + image->outlineInsetsLeft;
471 int innerStartY = 1 + image->outlineInsetsTop;
472 int innerEndX = endX - image->outlineInsetsRight;
473 int innerEndY = endY - image->outlineInsetsBottom;
474 int innerMidX = (innerEndX + innerStartX) / 2;
475 int innerMidY = (innerEndY + innerStartY) / 2;
476
477 // assuming the image is a round rect, compute the radius by marching
478 // diagonally from the top left corner towards the center
479 image->outlineFilled = is_opaque_over_row(image->rows[innerMidY], innerStartX, innerEndX)
480 && is_opaque_over_col(image->rows, innerMidX, innerStartY, innerStartY);
481
482 int diagonalInset = 0;
483 find_max_opacity(image->rows, innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
484 &diagonalInset);
485
486 // Determine source radius based upon inset
487 // radius = 1 / (sqrt(2) - 1) * inset
488 image->outlineRadius = 2.4142f * diagonalInset;
489
490 NOISY(printf("outline insets %d %d %d %d, rad %f, filled %d\n",
491 image->outlineFilled,
492 image->outlineInsetsLeft,
493 image->outlineInsetsTop,
494 image->outlineInsetsRight,
495 image->outlineInsetsBottom,
496 image->outlineRadius,
497 image->outlineFilled));
498}
499
Adam Lesinski282e1812014-01-23 18:17:42 -0800500
501static uint32_t get_color(
502 png_bytepp rows, int left, int top, int right, int bottom)
503{
504 png_bytep color = rows[top] + left*4;
505
506 if (left > right || top > bottom) {
507 return Res_png_9patch::TRANSPARENT_COLOR;
508 }
509
510 while (top <= bottom) {
511 for (int i = left; i <= right; i++) {
512 png_bytep p = rows[top]+i*4;
513 if (color[3] == 0) {
514 if (p[3] != 0) {
515 return Res_png_9patch::NO_COLOR;
516 }
517 } else if (p[0] != color[0] || p[1] != color[1]
518 || p[2] != color[2] || p[3] != color[3]) {
519 return Res_png_9patch::NO_COLOR;
520 }
521 }
522 top++;
523 }
524
525 if (color[3] == 0) {
526 return Res_png_9patch::TRANSPARENT_COLOR;
527 }
528 return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
529}
530
531static void select_patch(
532 int which, int front, int back, int size, int* start, int* end)
533{
534 switch (which) {
535 case 0:
536 *start = 0;
537 *end = front-1;
538 break;
539 case 1:
540 *start = front;
541 *end = back-1;
542 break;
543 case 2:
544 *start = back;
545 *end = size-1;
546 break;
547 }
548}
549
550static uint32_t get_color(image_info* image, int hpatch, int vpatch)
551{
552 int left, right, top, bottom;
553 select_patch(
Narayan Kamath6381dd42014-03-03 17:12:03 +0000554 hpatch, image->xDivs[0], image->xDivs[1],
Adam Lesinski282e1812014-01-23 18:17:42 -0800555 image->width, &left, &right);
556 select_patch(
Narayan Kamath6381dd42014-03-03 17:12:03 +0000557 vpatch, image->yDivs[0], image->yDivs[1],
Adam Lesinski282e1812014-01-23 18:17:42 -0800558 image->height, &top, &bottom);
559 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
560 // hpatch, vpatch, left, top, right, bottom);
561 const uint32_t c = get_color(image->rows, left, top, right, bottom);
562 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c));
563 return c;
564}
565
566static status_t do_9patch(const char* imageName, image_info* image)
567{
568 image->is9Patch = true;
569
570 int W = image->width;
571 int H = image->height;
572 int i, j;
573
574 int maxSizeXDivs = W * sizeof(int32_t);
575 int maxSizeYDivs = H * sizeof(int32_t);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000576 int32_t* xDivs = image->xDivs = (int32_t*) malloc(maxSizeXDivs);
577 int32_t* yDivs = image->yDivs = (int32_t*) malloc(maxSizeYDivs);
Elliott Hughesc367d482013-10-29 13:12:55 -0700578 uint8_t numXDivs = 0;
579 uint8_t numYDivs = 0;
580
Adam Lesinski282e1812014-01-23 18:17:42 -0800581 int8_t numColors;
582 int numRows;
583 int numCols;
584 int top;
585 int left;
586 int right;
587 int bottom;
588 memset(xDivs, -1, maxSizeXDivs);
589 memset(yDivs, -1, maxSizeYDivs);
590 image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
591 image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
592
593 image->layoutBoundsLeft = image->layoutBoundsRight =
594 image->layoutBoundsTop = image->layoutBoundsBottom = 0;
595
596 png_bytep p = image->rows[0];
597 bool transparent = p[3] == 0;
598 bool hasColor = false;
599
600 const char* errorMsg = NULL;
601 int errorPixel = -1;
602 const char* errorEdge = NULL;
603
604 int colorIndex = 0;
605
606 // Validate size...
607 if (W < 3 || H < 3) {
608 errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
609 goto getout;
610 }
611
612 // Validate frame...
613 if (!transparent &&
614 (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
615 errorMsg = "Must have one-pixel frame that is either transparent or white";
616 goto getout;
617 }
618
619 // Find left and right of sizing areas...
620 if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0],
621 &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) {
622 errorPixel = xDivs[0];
623 errorEdge = "top";
624 goto getout;
625 }
626
627 // Find top and bottom of sizing areas...
628 if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0],
629 &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) {
630 errorPixel = yDivs[0];
631 errorEdge = "left";
632 goto getout;
633 }
634
Elliott Hughesb30296b2013-10-29 15:25:52 -0700635 // Copy patch size data into image...
636 image->info9Patch.numXDivs = numXDivs;
637 image->info9Patch.numYDivs = numYDivs;
638
Adam Lesinski282e1812014-01-23 18:17:42 -0800639 // Find left and right of padding area...
640 if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft,
641 &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) {
642 errorPixel = image->info9Patch.paddingLeft;
643 errorEdge = "bottom";
644 goto getout;
645 }
646
647 // Find top and bottom of padding area...
648 if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop,
649 &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) {
650 errorPixel = image->info9Patch.paddingTop;
651 errorEdge = "right";
652 goto getout;
653 }
654
655 // Find left and right of layout padding...
656 get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false,
657 &image->layoutBoundsLeft,
658 &image->layoutBoundsRight, &errorMsg);
659
660 get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false,
661 &image->layoutBoundsTop,
662 &image->layoutBoundsBottom, &errorMsg);
663
664 image->haveLayoutBounds = image->layoutBoundsLeft != 0
665 || image->layoutBoundsRight != 0
666 || image->layoutBoundsTop != 0
667 || image->layoutBoundsBottom != 0;
668
669 if (image->haveLayoutBounds) {
670 NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
671 image->layoutBoundsRight, image->layoutBoundsBottom));
672 }
673
Chris Craik47cd8e92014-07-08 17:13:08 -0700674 // use opacity of pixels to estimate the round rect outline
675 get_outline(image);
676
Adam Lesinski282e1812014-01-23 18:17:42 -0800677 // If padding is not yet specified, take values from size.
678 if (image->info9Patch.paddingLeft < 0) {
679 image->info9Patch.paddingLeft = xDivs[0];
680 image->info9Patch.paddingRight = W - 2 - xDivs[1];
681 } else {
682 // Adjust value to be correct!
683 image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
684 }
685 if (image->info9Patch.paddingTop < 0) {
686 image->info9Patch.paddingTop = yDivs[0];
687 image->info9Patch.paddingBottom = H - 2 - yDivs[1];
688 } else {
689 // Adjust value to be correct!
690 image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
691 }
692
693 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
694 image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
695 image->info9Patch.yDivs[0], image->info9Patch.yDivs[1]));
696 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
697 image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
698 image->info9Patch.paddingTop, image->info9Patch.paddingBottom));
699
700 // Remove frame from image.
701 image->rows = (png_bytepp)malloc((H-2) * sizeof(png_bytep));
702 for (i=0; i<(H-2); i++) {
703 image->rows[i] = image->allocRows[i+1];
704 memmove(image->rows[i], image->rows[i]+4, (W-2)*4);
705 }
706 image->width -= 2;
707 W = image->width;
708 image->height -= 2;
709 H = image->height;
710
711 // Figure out the number of rows and columns in the N-patch
712 numCols = numXDivs + 1;
713 if (xDivs[0] == 0) { // Column 1 is strechable
714 numCols--;
715 }
716 if (xDivs[numXDivs - 1] == W) {
717 numCols--;
718 }
719 numRows = numYDivs + 1;
720 if (yDivs[0] == 0) { // Row 1 is strechable
721 numRows--;
722 }
723 if (yDivs[numYDivs - 1] == H) {
724 numRows--;
725 }
726
727 // Make sure the amount of rows and columns will fit in the number of
728 // colors we can use in the 9-patch format.
729 if (numRows * numCols > 0x7F) {
730 errorMsg = "Too many rows and columns in 9-patch perimeter";
731 goto getout;
732 }
733
734 numColors = numRows * numCols;
735 image->info9Patch.numColors = numColors;
Narayan Kamath6381dd42014-03-03 17:12:03 +0000736 image->colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
Adam Lesinski282e1812014-01-23 18:17:42 -0800737
738 // Fill in color information for each patch.
739
740 uint32_t c;
741 top = 0;
742
743 // The first row always starts with the top being at y=0 and the bottom
744 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
745 // the first row is stretchable along the Y axis, otherwise it is fixed.
746 // The last row always ends with the bottom being bitmap.height and the top
747 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
748 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
749 // the Y axis, otherwise it is fixed.
750 //
751 // The first and last columns are similarly treated with respect to the X
752 // axis.
753 //
754 // The above is to help explain some of the special casing that goes on the
755 // code below.
756
757 // The initial yDiv and whether the first row is considered stretchable or
758 // not depends on whether yDiv[0] was zero or not.
759 for (j = (yDivs[0] == 0 ? 1 : 0);
760 j <= numYDivs && top < H;
761 j++) {
762 if (j == numYDivs) {
763 bottom = H;
764 } else {
765 bottom = yDivs[j];
766 }
767 left = 0;
768 // The initial xDiv and whether the first column is considered
769 // stretchable or not depends on whether xDiv[0] was zero or not.
770 for (i = xDivs[0] == 0 ? 1 : 0;
771 i <= numXDivs && left < W;
772 i++) {
773 if (i == numXDivs) {
774 right = W;
775 } else {
776 right = xDivs[i];
777 }
778 c = get_color(image->rows, left, top, right - 1, bottom - 1);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000779 image->colors[colorIndex++] = c;
Adam Lesinski282e1812014-01-23 18:17:42 -0800780 NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true);
781 left = right;
782 }
783 top = bottom;
784 }
785
786 assert(colorIndex == numColors);
787
788 for (i=0; i<numColors; i++) {
789 if (hasColor) {
790 if (i == 0) printf("Colors in %s:\n ", imageName);
Narayan Kamath6381dd42014-03-03 17:12:03 +0000791 printf(" #%08x", image->colors[i]);
Adam Lesinski282e1812014-01-23 18:17:42 -0800792 if (i == numColors - 1) printf("\n");
793 }
794 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800795getout:
796 if (errorMsg) {
797 fprintf(stderr,
798 "ERROR: 9-patch image %s malformed.\n"
799 " %s.\n", imageName, errorMsg);
800 if (errorEdge != NULL) {
801 if (errorPixel >= 0) {
802 fprintf(stderr,
803 " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
804 } else {
805 fprintf(stderr,
806 " Found along %s edge.\n", errorEdge);
807 }
808 }
809 return UNKNOWN_ERROR;
810 }
811 return NO_ERROR;
812}
813
Narayan Kamath6381dd42014-03-03 17:12:03 +0000814static void checkNinePatchSerialization(Res_png_9patch* inPatch, void* data)
Adam Lesinski282e1812014-01-23 18:17:42 -0800815{
Adam Lesinski282e1812014-01-23 18:17:42 -0800816 size_t patchSize = inPatch->serializedSize();
Narayan Kamath6381dd42014-03-03 17:12:03 +0000817 void* newData = malloc(patchSize);
Adam Lesinski282e1812014-01-23 18:17:42 -0800818 memcpy(newData, data, patchSize);
819 Res_png_9patch* outPatch = inPatch->deserialize(newData);
820 // deserialization is done in place, so outPatch == newData
821 assert(outPatch == newData);
822 assert(outPatch->numXDivs == inPatch->numXDivs);
823 assert(outPatch->numYDivs == inPatch->numYDivs);
824 assert(outPatch->paddingLeft == inPatch->paddingLeft);
825 assert(outPatch->paddingRight == inPatch->paddingRight);
826 assert(outPatch->paddingTop == inPatch->paddingTop);
827 assert(outPatch->paddingBottom == inPatch->paddingBottom);
828 for (int i = 0; i < outPatch->numXDivs; i++) {
829 assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
830 }
831 for (int i = 0; i < outPatch->numYDivs; i++) {
832 assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
833 }
834 for (int i = 0; i < outPatch->numColors; i++) {
835 assert(outPatch->colors[i] == inPatch->colors[i]);
836 }
837 free(newData);
838}
839
Adam Lesinski282e1812014-01-23 18:17:42 -0800840static void dump_image(int w, int h, png_bytepp rows, int color_type)
841{
842 int i, j, rr, gg, bb, aa;
843
844 int bpp;
845 if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
846 bpp = 1;
847 } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
848 bpp = 2;
849 } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
850 // We use a padding byte even when there is no alpha
851 bpp = 4;
852 } else {
853 printf("Unknown color type %d.\n", color_type);
854 }
855
856 for (j = 0; j < h; j++) {
857 png_bytep row = rows[j];
858 for (i = 0; i < w; i++) {
859 rr = row[0];
860 gg = row[1];
861 bb = row[2];
862 aa = row[3];
863 row += bpp;
864
865 if (i == 0) {
866 printf("Row %d:", j);
867 }
868 switch (bpp) {
869 case 1:
870 printf(" (%d)", rr);
871 break;
872 case 2:
873 printf(" (%d %d", rr, gg);
874 break;
875 case 3:
876 printf(" (%d %d %d)", rr, gg, bb);
877 break;
878 case 4:
879 printf(" (%d %d %d %d)", rr, gg, bb, aa);
880 break;
881 }
882 if (i == (w - 1)) {
883 NOISY(printf("\n"));
884 }
885 }
886 }
887}
888
889#define MAX(a,b) ((a)>(b)?(a):(b))
890#define ABS(a) ((a)<0?-(a):(a))
891
892static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance,
893 png_colorp rgbPalette, png_bytep alphaPalette,
894 int *paletteEntries, bool *hasTransparency, int *colorType,
895 png_bytepp outRows)
896{
897 int w = imageInfo.width;
898 int h = imageInfo.height;
899 int i, j, rr, gg, bb, aa, idx;
900 uint32_t colors[256], col;
901 int num_colors = 0;
902 int maxGrayDeviation = 0;
903
904 bool isOpaque = true;
905 bool isPalette = true;
906 bool isGrayscale = true;
907
908 // Scan the entire image and determine if:
909 // 1. Every pixel has R == G == B (grayscale)
910 // 2. Every pixel has A == 255 (opaque)
911 // 3. There are no more than 256 distinct RGBA colors
912
913 // NOISY(printf("Initial image data:\n"));
914 // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
915
916 for (j = 0; j < h; j++) {
917 png_bytep row = imageInfo.rows[j];
918 png_bytep out = outRows[j];
919 for (i = 0; i < w; i++) {
920 rr = *row++;
921 gg = *row++;
922 bb = *row++;
923 aa = *row++;
924
925 int odev = maxGrayDeviation;
926 maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
927 maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
928 maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
929 if (maxGrayDeviation > odev) {
930 NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
931 maxGrayDeviation, i, j, rr, gg, bb, aa));
932 }
933
934 // Check if image is really grayscale
935 if (isGrayscale) {
936 if (rr != gg || rr != bb) {
937 NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
938 i, j, rr, gg, bb, aa));
939 isGrayscale = false;
940 }
941 }
942
943 // Check if image is really opaque
944 if (isOpaque) {
945 if (aa != 0xff) {
946 NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
947 i, j, rr, gg, bb, aa));
948 isOpaque = false;
949 }
950 }
951
952 // Check if image is really <= 256 colors
953 if (isPalette) {
954 col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
955 bool match = false;
956 for (idx = 0; idx < num_colors; idx++) {
957 if (colors[idx] == col) {
958 match = true;
959 break;
960 }
961 }
962
963 // Write the palette index for the pixel to outRows optimistically
964 // We might overwrite it later if we decide to encode as gray or
965 // gray + alpha
966 *out++ = idx;
967 if (!match) {
968 if (num_colors == 256) {
969 NOISY(printf("Found 257th color at %d, %d\n", i, j));
970 isPalette = false;
971 } else {
972 colors[num_colors++] = col;
973 }
974 }
975 }
976 }
977 }
978
979 *paletteEntries = 0;
980 *hasTransparency = !isOpaque;
981 int bpp = isOpaque ? 3 : 4;
982 int paletteSize = w * h + bpp * num_colors;
983
984 NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"));
985 NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false"));
986 NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false"));
987 NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
988 paletteSize, 2 * w * h, bpp * w * h));
989 NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance));
990
991 // Choose the best color type for the image.
992 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
993 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
994 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
995 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
996 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
997 if (isGrayscale) {
998 if (isOpaque) {
999 *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
1000 } else {
1001 // Use a simple heuristic to determine whether using a palette will
1002 // save space versus using gray + alpha for each pixel.
1003 // This doesn't take into account chunk overhead, filtering, LZ
1004 // compression, etc.
1005 if (isPalette && (paletteSize < 2 * w * h)) {
1006 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
1007 } else {
1008 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
1009 }
1010 }
1011 } else if (isPalette && (paletteSize < bpp * w * h)) {
1012 *colorType = PNG_COLOR_TYPE_PALETTE;
1013 } else {
1014 if (maxGrayDeviation <= grayscaleTolerance) {
1015 printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation);
1016 *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
1017 } else {
1018 *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
1019 }
1020 }
1021
1022 // Perform postprocessing of the image or palette data based on the final
1023 // color type chosen
1024
1025 if (*colorType == PNG_COLOR_TYPE_PALETTE) {
1026 // Create separate RGB and Alpha palettes and set the number of colors
1027 *paletteEntries = num_colors;
1028
1029 // Create the RGB and alpha palettes
1030 for (int idx = 0; idx < num_colors; idx++) {
1031 col = colors[idx];
1032 rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
1033 rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
1034 rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
1035 alphaPalette[idx] = (png_byte) (col & 0xff);
1036 }
1037 } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
1038 // If the image is gray or gray + alpha, compact the pixels into outRows
1039 for (j = 0; j < h; j++) {
1040 png_bytep row = imageInfo.rows[j];
1041 png_bytep out = outRows[j];
1042 for (i = 0; i < w; i++) {
1043 rr = *row++;
1044 gg = *row++;
1045 bb = *row++;
1046 aa = *row++;
1047
1048 if (isGrayscale) {
1049 *out++ = rr;
1050 } else {
1051 *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
1052 }
1053 if (!isOpaque) {
1054 *out++ = aa;
1055 }
1056 }
1057 }
1058 }
1059}
1060
1061
1062static void write_png(const char* imageName,
1063 png_structp write_ptr, png_infop write_info,
1064 image_info& imageInfo, int grayscaleTolerance)
1065{
1066 bool optimize = true;
1067 png_uint_32 width, height;
1068 int color_type;
1069 int bit_depth, interlace_type, compression_type;
1070 int i;
1071
Chris Craik47cd8e92014-07-08 17:13:08 -07001072 png_unknown_chunk unknowns[3];
Adam Lesinski282e1812014-01-23 18:17:42 -08001073 unknowns[0].data = NULL;
1074 unknowns[1].data = NULL;
Chris Craik47cd8e92014-07-08 17:13:08 -07001075 unknowns[2].data = NULL;
Adam Lesinski282e1812014-01-23 18:17:42 -08001076
1077 png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
1078 if (outRows == (png_bytepp) 0) {
1079 printf("Can't allocate output buffer!\n");
1080 exit(1);
1081 }
1082 for (i = 0; i < (int) imageInfo.height; i++) {
1083 outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
1084 if (outRows[i] == (png_bytep) 0) {
1085 printf("Can't allocate output buffer!\n");
1086 exit(1);
1087 }
1088 }
1089
1090 png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
1091
1092 NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
1093 (int) imageInfo.width, (int) imageInfo.height));
1094
1095 png_color rgbPalette[256];
1096 png_byte alphaPalette[256];
1097 bool hasTransparency;
1098 int paletteEntries;
1099
1100 analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
1101 &paletteEntries, &hasTransparency, &color_type, outRows);
1102
1103 // If the image is a 9-patch, we need to preserve it as a ARGB file to make
1104 // sure the pixels will not be pre-dithered/clamped until we decide they are
1105 if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
1106 color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
1107 color_type = PNG_COLOR_TYPE_RGB_ALPHA;
1108 }
1109
1110 switch (color_type) {
1111 case PNG_COLOR_TYPE_PALETTE:
1112 NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
1113 imageName, paletteEntries,
1114 hasTransparency ? " (with alpha)" : ""));
1115 break;
1116 case PNG_COLOR_TYPE_GRAY:
1117 NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
1118 break;
1119 case PNG_COLOR_TYPE_GRAY_ALPHA:
1120 NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
1121 break;
1122 case PNG_COLOR_TYPE_RGB:
1123 NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
1124 break;
1125 case PNG_COLOR_TYPE_RGB_ALPHA:
1126 NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
1127 break;
1128 }
1129
1130 png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
1131 8, color_type, PNG_INTERLACE_NONE,
1132 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1133
1134 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1135 png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
1136 if (hasTransparency) {
1137 png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
1138 }
1139 png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
1140 } else {
1141 png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
1142 }
1143
1144 if (imageInfo.is9Patch) {
Chris Craik47cd8e92014-07-08 17:13:08 -07001145 int chunk_count = 2 + (imageInfo.haveLayoutBounds ? 1 : 0);
1146 int p_index = imageInfo.haveLayoutBounds ? 2 : 1;
1147 int b_index = 1;
1148 int o_index = 0;
1149
1150 // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
Adam Lesinski282e1812014-01-23 18:17:42 -08001151 png_byte *chunk_names = imageInfo.haveLayoutBounds
Chris Craik47cd8e92014-07-08 17:13:08 -07001152 ? (png_byte*)"npOl\0npLb\0npTc\0"
1153 : (png_byte*)"npOl\0npTc";
1154
1155 // base 9 patch data
Adam Lesinski282e1812014-01-23 18:17:42 -08001156 NOISY(printf("Adding 9-patch info...\n"));
1157 strcpy((char*)unknowns[p_index].name, "npTc");
Narayan Kamath6381dd42014-03-03 17:12:03 +00001158 unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
Adam Lesinski282e1812014-01-23 18:17:42 -08001159 unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
1160 // TODO: remove the check below when everything works
1161 checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
1162
Chris Craik47cd8e92014-07-08 17:13:08 -07001163 // automatically generated 9 patch outline data
1164 int chunk_size = sizeof(png_uint_32) * 6;
1165 strcpy((char*)unknowns[o_index].name, "npOl");
1166 unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
1167 png_byte outputData[chunk_size];
1168 memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
1169 ((float*) outputData)[4] = imageInfo.outlineRadius;
1170 ((png_uint_32*) outputData)[5] = imageInfo.outlineFilled ? 1 : 0;
1171 memcpy(unknowns[o_index].data, &outputData, chunk_size);
1172 unknowns[o_index].size = chunk_size;
1173
1174 // optional optical inset / layout bounds data
Adam Lesinski282e1812014-01-23 18:17:42 -08001175 if (imageInfo.haveLayoutBounds) {
1176 int chunk_size = sizeof(png_uint_32) * 4;
1177 strcpy((char*)unknowns[b_index].name, "npLb");
1178 unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
1179 memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
1180 unknowns[b_index].size = chunk_size;
1181 }
1182
1183 for (int i = 0; i < chunk_count; i++) {
1184 unknowns[i].location = PNG_HAVE_PLTE;
1185 }
1186 png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
1187 chunk_names, chunk_count);
1188 png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
1189#if PNG_LIBPNG_VER < 10600
1190 /* Deal with unknown chunk location bug in 1.5.x and earlier */
1191 png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
1192 if (imageInfo.haveLayoutBounds) {
1193 png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
1194 }
1195#endif
1196 }
1197
1198
1199 png_write_info(write_ptr, write_info);
1200
1201 png_bytepp rows;
1202 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
1203 if (color_type == PNG_COLOR_TYPE_RGB) {
1204 png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
1205 }
1206 rows = imageInfo.rows;
1207 } else {
1208 rows = outRows;
1209 }
1210 png_write_image(write_ptr, rows);
1211
1212// NOISY(printf("Final image data:\n"));
1213// dump_image(imageInfo.width, imageInfo.height, rows, color_type);
1214
1215 png_write_end(write_ptr, write_info);
1216
1217 for (i = 0; i < (int) imageInfo.height; i++) {
1218 free(outRows[i]);
1219 }
1220 free(outRows);
1221 free(unknowns[0].data);
1222 free(unknowns[1].data);
Chris Craik47cd8e92014-07-08 17:13:08 -07001223 free(unknowns[2].data);
Adam Lesinski282e1812014-01-23 18:17:42 -08001224
1225 png_get_IHDR(write_ptr, write_info, &width, &height,
1226 &bit_depth, &color_type, &interlace_type,
1227 &compression_type, NULL);
1228
1229 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
1230 (int)width, (int)height, bit_depth, color_type, interlace_type,
1231 compression_type));
1232}
1233
1234status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
1235 const sp<AaptFile>& file, String8* outNewLeafName)
1236{
1237 String8 ext(file->getPath().getPathExtension());
1238
1239 // We currently only process PNG images.
1240 if (strcmp(ext.string(), ".png") != 0) {
1241 return NO_ERROR;
1242 }
1243
1244 // Example of renaming a file:
1245 //*outNewLeafName = file->getPath().getBasePath().getFileName();
1246 //outNewLeafName->append(".nupng");
1247
1248 String8 printableName(file->getPrintableSource());
1249
1250 if (bundle->getVerbose()) {
1251 printf("Processing image: %s\n", printableName.string());
1252 }
1253
1254 png_structp read_ptr = NULL;
1255 png_infop read_info = NULL;
1256 FILE* fp;
1257
1258 image_info imageInfo;
1259
1260 png_structp write_ptr = NULL;
1261 png_infop write_info = NULL;
1262
1263 status_t error = UNKNOWN_ERROR;
1264
1265 const size_t nameLen = file->getPath().length();
1266
1267 fp = fopen(file->getSourceFile().string(), "rb");
1268 if (fp == NULL) {
1269 fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
1270 goto bail;
1271 }
1272
1273 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
1274 (png_error_ptr)NULL);
1275 if (!read_ptr) {
1276 goto bail;
1277 }
1278
1279 read_info = png_create_info_struct(read_ptr);
1280 if (!read_info) {
1281 goto bail;
1282 }
1283
1284 if (setjmp(png_jmpbuf(read_ptr))) {
1285 goto bail;
1286 }
1287
1288 png_init_io(read_ptr, fp);
1289
1290 read_png(printableName.string(), read_ptr, read_info, &imageInfo);
1291
1292 if (nameLen > 6) {
1293 const char* name = file->getPath().string();
1294 if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
1295 if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
1296 goto bail;
1297 }
1298 }
1299 }
1300
1301 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
1302 (png_error_ptr)NULL);
1303 if (!write_ptr)
1304 {
1305 goto bail;
1306 }
1307
1308 write_info = png_create_info_struct(write_ptr);
1309 if (!write_info)
1310 {
1311 goto bail;
1312 }
1313
1314 png_set_write_fn(write_ptr, (void*)file.get(),
1315 png_write_aapt_file, png_flush_aapt_file);
1316
1317 if (setjmp(png_jmpbuf(write_ptr)))
1318 {
1319 goto bail;
1320 }
1321
1322 write_png(printableName.string(), write_ptr, write_info, imageInfo,
1323 bundle->getGrayscaleTolerance());
1324
1325 error = NO_ERROR;
1326
1327 if (bundle->getVerbose()) {
1328 fseek(fp, 0, SEEK_END);
1329 size_t oldSize = (size_t)ftell(fp);
1330 size_t newSize = file->getSize();
1331 float factor = ((float)newSize)/oldSize;
1332 int percent = (int)(factor*100);
1333 printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent);
1334 }
1335
1336bail:
1337 if (read_ptr) {
1338 png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
1339 }
1340 if (fp) {
1341 fclose(fp);
1342 }
1343 if (write_ptr) {
1344 png_destroy_write_struct(&write_ptr, &write_info);
1345 }
1346
1347 if (error != NO_ERROR) {
1348 fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
1349 file->getPrintableSource().string());
1350 }
1351 return error;
1352}
1353
1354status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest)
1355{
1356 png_structp read_ptr = NULL;
1357 png_infop read_info = NULL;
1358
1359 FILE* fp;
1360
1361 image_info imageInfo;
1362
1363 png_structp write_ptr = NULL;
1364 png_infop write_info = NULL;
1365
1366 status_t error = UNKNOWN_ERROR;
1367
1368 if (bundle->getVerbose()) {
1369 printf("Processing image to cache: %s => %s\n", source.string(), dest.string());
1370 }
1371
1372 // Get a file handler to read from
1373 fp = fopen(source.string(),"rb");
1374 if (fp == NULL) {
1375 fprintf(stderr, "%s ERROR: Unable to open PNG file\n", source.string());
1376 return error;
1377 }
1378
1379 // Call libpng to get a struct to read image data into
1380 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1381 if (!read_ptr) {
1382 fclose(fp);
1383 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1384 return error;
1385 }
1386
1387 // Call libpng to get a struct to read image info into
1388 read_info = png_create_info_struct(read_ptr);
1389 if (!read_info) {
1390 fclose(fp);
1391 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1392 return error;
1393 }
1394
1395 // Set a jump point for libpng to long jump back to on error
1396 if (setjmp(png_jmpbuf(read_ptr))) {
1397 fclose(fp);
1398 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1399 return error;
1400 }
1401
1402 // Set up libpng to read from our file.
1403 png_init_io(read_ptr,fp);
1404
1405 // Actually read data from the file
1406 read_png(source.string(), read_ptr, read_info, &imageInfo);
1407
1408 // We're done reading so we can clean up
1409 // Find old file size before releasing handle
1410 fseek(fp, 0, SEEK_END);
1411 size_t oldSize = (size_t)ftell(fp);
1412 fclose(fp);
1413 png_destroy_read_struct(&read_ptr, &read_info,NULL);
1414
1415 // Check to see if we're dealing with a 9-patch
1416 // If we are, process appropriately
1417 if (source.getBasePath().getPathExtension() == ".9") {
1418 if (do_9patch(source.string(), &imageInfo) != NO_ERROR) {
1419 return error;
1420 }
1421 }
1422
1423 // Call libpng to create a structure to hold the processed image data
1424 // that can be written to disk
1425 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
1426 if (!write_ptr) {
1427 png_destroy_write_struct(&write_ptr, &write_info);
1428 return error;
1429 }
1430
1431 // Call libpng to create a structure to hold processed image info that can
1432 // be written to disk
1433 write_info = png_create_info_struct(write_ptr);
1434 if (!write_info) {
1435 png_destroy_write_struct(&write_ptr, &write_info);
1436 return error;
1437 }
1438
1439 // Open up our destination file for writing
1440 fp = fopen(dest.string(), "wb");
1441 if (!fp) {
1442 fprintf(stderr, "%s ERROR: Unable to open PNG file\n", dest.string());
1443 png_destroy_write_struct(&write_ptr, &write_info);
1444 return error;
1445 }
1446
1447 // Set up libpng to write to our file
1448 png_init_io(write_ptr, fp);
1449
1450 // Set up a jump for libpng to long jump back on on errors
1451 if (setjmp(png_jmpbuf(write_ptr))) {
1452 fclose(fp);
1453 png_destroy_write_struct(&write_ptr, &write_info);
1454 return error;
1455 }
1456
1457 // Actually write out to the new png
1458 write_png(dest.string(), write_ptr, write_info, imageInfo,
1459 bundle->getGrayscaleTolerance());
1460
1461 if (bundle->getVerbose()) {
1462 // Find the size of our new file
1463 FILE* reader = fopen(dest.string(), "rb");
1464 fseek(reader, 0, SEEK_END);
1465 size_t newSize = (size_t)ftell(reader);
1466 fclose(reader);
1467
1468 float factor = ((float)newSize)/oldSize;
1469 int percent = (int)(factor*100);
1470 printf(" (processed image to cache entry %s: %d%% size of source)\n",
1471 dest.string(), percent);
1472 }
1473
1474 //Clean up
1475 fclose(fp);
1476 png_destroy_write_struct(&write_ptr, &write_info);
1477
1478 return NO_ERROR;
1479}
1480
1481status_t postProcessImage(const sp<AaptAssets>& assets,
1482 ResourceTable* table, const sp<AaptFile>& file)
1483{
1484 String8 ext(file->getPath().getPathExtension());
1485
1486 // At this point, now that we have all the resource data, all we need to
1487 // do is compile XML files.
1488 if (strcmp(ext.string(), ".xml") == 0) {
1489 return compileXmlFile(assets, file, table);
1490 }
1491
1492 return NO_ERROR;
1493}