]> git.saurik.com Git - android/aapt.git/blob - Images.cpp
auto import from //branches/cupcake/...@125939
[android/aapt.git] / Images.cpp
1 //
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 <utils/ResourceTypes.h>
12 #include <utils/ByteOrder.h>
13
14 #include <png.h>
15
16 #define NOISY(x) //x
17
18 static void
19 png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
20 {
21 status_t err = ((AaptFile*)png_ptr->io_ptr)->writeData(data, length);
22 if (err != NO_ERROR) {
23 png_error(png_ptr, "Write Error");
24 }
25 }
26
27
28 static void
29 png_flush_aapt_file(png_structp png_ptr)
30 {
31 }
32
33 // This holds an image as 8bpp RGBA.
34 struct image_info
35 {
36 image_info() : rows(NULL), hasTransparency(true), is9Patch(false), allocRows(NULL) { }
37 ~image_info() {
38 if (rows && rows != allocRows) {
39 free(rows);
40 }
41 if (allocRows) {
42 for (int i=0; i<(int)allocHeight; i++) {
43 free(allocRows[i]);
44 }
45 free(allocRows);
46 }
47 }
48
49 png_uint_32 width;
50 png_uint_32 height;
51 png_bytepp rows;
52
53 bool hasTransparency;
54
55 // 9-patch info.
56 bool is9Patch;
57 Res_png_9patch info9Patch;
58
59 png_uint_32 allocHeight;
60 png_bytepp allocRows;
61 };
62
63 static void read_png(const char* imageName,
64 png_structp read_ptr, png_infop read_info,
65 image_info* outImageInfo)
66 {
67 int color_type;
68 int bit_depth, interlace_type, compression_type;
69 int i;
70
71 png_read_info(read_ptr, read_info);
72
73 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
74 &outImageInfo->height, &bit_depth, &color_type,
75 &interlace_type, &compression_type, NULL);
76
77 //printf("Image %s:\n", imageName);
78 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
79 // color_type, bit_depth, interlace_type, compression_type);
80
81 if (color_type == PNG_COLOR_TYPE_PALETTE)
82 png_set_palette_to_rgb(read_ptr);
83
84 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
85 png_set_gray_1_2_4_to_8(read_ptr);
86
87 if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) {
88 //printf("Has PNG_INFO_tRNS!\n");
89 png_set_tRNS_to_alpha(read_ptr);
90 }
91
92 if (bit_depth == 16)
93 png_set_strip_16(read_ptr);
94
95 if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
96 png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
97
98 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
99 png_set_gray_to_rgb(read_ptr);
100
101 png_read_update_info(read_ptr, read_info);
102
103 outImageInfo->rows = (png_bytepp)malloc(
104 outImageInfo->height * png_sizeof(png_bytep));
105 outImageInfo->allocHeight = outImageInfo->height;
106 outImageInfo->allocRows = outImageInfo->rows;
107
108 png_set_rows(read_ptr, read_info, outImageInfo->rows);
109
110 for (i = 0; i < (int)outImageInfo->height; i++)
111 {
112 outImageInfo->rows[i] = (png_bytep)
113 malloc(png_get_rowbytes(read_ptr, read_info));
114 }
115
116 png_read_image(read_ptr, outImageInfo->rows);
117
118 png_read_end(read_ptr, read_info);
119
120 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
121 imageName,
122 (int)outImageInfo->width, (int)outImageInfo->height,
123 bit_depth, color_type,
124 interlace_type, compression_type));
125
126 png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
127 &outImageInfo->height, &bit_depth, &color_type,
128 &interlace_type, &compression_type, NULL);
129 }
130
131 static bool is_tick(png_bytep p, bool transparent, const char** outError)
132 {
133 if (transparent) {
134 if (p[3] == 0) {
135 return false;
136 }
137 if (p[3] != 0xff) {
138 *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
139 return false;
140 }
141 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
142 *outError = "Ticks in transparent frame must be black";
143 }
144 return true;
145 }
146
147 if (p[3] != 0xFF) {
148 *outError = "White frame must be a solid color (no alpha)";
149 }
150 if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) {
151 return false;
152 }
153 if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
154 *outError = "Ticks in white frame must be black";
155 return false;
156 }
157 return true;
158 }
159
160 enum {
161 TICK_START,
162 TICK_INSIDE_1,
163 TICK_OUTSIDE_1
164 };
165
166 static status_t get_horizontal_ticks(
167 png_bytep row, int width, bool transparent, bool required,
168 int32_t* outLeft, int32_t* outRight, const char** outError,
169 uint8_t* outDivs, bool multipleAllowed)
170 {
171 int i;
172 *outLeft = *outRight = -1;
173 int state = TICK_START;
174 bool found = false;
175
176 for (i=1; i<width-1; i++) {
177 if (is_tick(row+i*4, transparent, outError)) {
178 if (state == TICK_START ||
179 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
180 *outLeft = i-1;
181 *outRight = width-2;
182 found = true;
183 if (outDivs != NULL) {
184 *outDivs += 2;
185 }
186 state = TICK_INSIDE_1;
187 } else if (state == TICK_OUTSIDE_1) {
188 *outError = "Can't have more than one marked region along edge";
189 *outLeft = i;
190 return UNKNOWN_ERROR;
191 }
192 } else if (*outError == NULL) {
193 if (state == TICK_INSIDE_1) {
194 // We're done with this div. Move on to the next.
195 *outRight = i-1;
196 outRight += 2;
197 outLeft += 2;
198 state = TICK_OUTSIDE_1;
199 }
200 } else {
201 *outLeft = i;
202 return UNKNOWN_ERROR;
203 }
204 }
205
206 if (required && !found) {
207 *outError = "No marked region found along edge";
208 *outLeft = -1;
209 return UNKNOWN_ERROR;
210 }
211
212 return NO_ERROR;
213 }
214
215 static status_t get_vertical_ticks(
216 png_bytepp rows, int offset, int height, bool transparent, bool required,
217 int32_t* outTop, int32_t* outBottom, const char** outError,
218 uint8_t* outDivs, bool multipleAllowed)
219 {
220 int i;
221 *outTop = *outBottom = -1;
222 int state = TICK_START;
223 bool found = false;
224
225 for (i=1; i<height-1; i++) {
226 if (is_tick(rows[i]+offset, transparent, outError)) {
227 if (state == TICK_START ||
228 (state == TICK_OUTSIDE_1 && multipleAllowed)) {
229 *outTop = i-1;
230 *outBottom = height-2;
231 found = true;
232 if (outDivs != NULL) {
233 *outDivs += 2;
234 }
235 state = TICK_INSIDE_1;
236 } else if (state == TICK_OUTSIDE_1) {
237 *outError = "Can't have more than one marked region along edge";
238 *outTop = i;
239 return UNKNOWN_ERROR;
240 }
241 } else if (*outError == NULL) {
242 if (state == TICK_INSIDE_1) {
243 // We're done with this div. Move on to the next.
244 *outBottom = i-1;
245 outTop += 2;
246 outBottom += 2;
247 state = TICK_OUTSIDE_1;
248 }
249 } else {
250 *outTop = i;
251 return UNKNOWN_ERROR;
252 }
253 }
254
255 if (required && !found) {
256 *outError = "No marked region found along edge";
257 *outTop = -1;
258 return UNKNOWN_ERROR;
259 }
260
261 return NO_ERROR;
262 }
263
264 static uint32_t get_color(
265 png_bytepp rows, int left, int top, int right, int bottom)
266 {
267 png_bytep color = rows[top] + left*4;
268
269 if (left > right || top > bottom) {
270 return Res_png_9patch::TRANSPARENT_COLOR;
271 }
272
273 while (top <= bottom) {
274 for (int i = left; i <= right; i++) {
275 png_bytep p = rows[top]+i*4;
276 if (color[3] == 0) {
277 if (p[3] != 0) {
278 return Res_png_9patch::NO_COLOR;
279 }
280 } else if (p[0] != color[0] || p[1] != color[1]
281 || p[2] != color[2] || p[3] != color[3]) {
282 return Res_png_9patch::NO_COLOR;
283 }
284 }
285 top++;
286 }
287
288 if (color[3] == 0) {
289 return Res_png_9patch::TRANSPARENT_COLOR;
290 }
291 return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
292 }
293
294 static void select_patch(
295 int which, int front, int back, int size, int* start, int* end)
296 {
297 switch (which) {
298 case 0:
299 *start = 0;
300 *end = front-1;
301 break;
302 case 1:
303 *start = front;
304 *end = back-1;
305 break;
306 case 2:
307 *start = back;
308 *end = size-1;
309 break;
310 }
311 }
312
313 static uint32_t get_color(image_info* image, int hpatch, int vpatch)
314 {
315 int left, right, top, bottom;
316 select_patch(
317 hpatch, image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
318 image->width, &left, &right);
319 select_patch(
320 vpatch, image->info9Patch.yDivs[0], image->info9Patch.yDivs[1],
321 image->height, &top, &bottom);
322 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
323 // hpatch, vpatch, left, top, right, bottom);
324 const uint32_t c = get_color(image->rows, left, top, right, bottom);
325 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c));
326 return c;
327 }
328
329 static void examine_image(image_info* image)
330 {
331 bool hasTrans = false;
332 for (int i=0; i<(int)image->height && !hasTrans; i++) {
333 png_bytep p = image->rows[i];
334 for (int j=0; j<(int)image->width; j++) {
335 if (p[(j*4)+3] != 0xFF) {
336 hasTrans = true;
337 break;
338 }
339 }
340 }
341
342 image->hasTransparency = hasTrans;
343 }
344
345 static status_t do_9patch(const char* imageName, image_info* image)
346 {
347 image->is9Patch = true;
348
349 int W = image->width;
350 int H = image->height;
351 int i, j;
352
353 int maxSizeXDivs = (W / 2 + 1) * sizeof(int32_t);
354 int maxSizeYDivs = (H / 2 + 1) * sizeof(int32_t);
355 int32_t* xDivs = (int32_t*) malloc(maxSizeXDivs);
356 int32_t* yDivs = (int32_t*) malloc(maxSizeYDivs);
357 uint8_t numXDivs = 0;
358 uint8_t numYDivs = 0;
359 int8_t numColors;
360 int numRows;
361 int numCols;
362 int top;
363 int left;
364 int right;
365 int bottom;
366 memset(xDivs, -1, maxSizeXDivs);
367 memset(yDivs, -1, maxSizeYDivs);
368 image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
369 image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
370
371 png_bytep p = image->rows[0];
372 bool transparent = p[3] == 0;
373 bool hasColor = false;
374
375 const char* errorMsg = NULL;
376 int errorPixel = -1;
377 const char* errorEdge = "";
378
379 int colorIndex = 0;
380
381 // Validate size...
382 if (W < 3 || H < 3) {
383 errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
384 goto getout;
385 }
386
387 // Validate frame...
388 if (!transparent &&
389 (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
390 errorMsg = "Must have one-pixel frame that is either transparent or white";
391 goto getout;
392 }
393
394 // Find left and right of sizing areas...
395 if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0],
396 &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) {
397 errorPixel = xDivs[0];
398 errorEdge = "top";
399 goto getout;
400 }
401
402 // Find top and bottom of sizing areas...
403 if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0],
404 &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) {
405 errorPixel = yDivs[0];
406 errorEdge = "left";
407 goto getout;
408 }
409
410 // Find left and right of padding area...
411 if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft,
412 &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) {
413 errorPixel = image->info9Patch.paddingLeft;
414 errorEdge = "bottom";
415 goto getout;
416 }
417
418 // Find top and bottom of padding area...
419 if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop,
420 &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) {
421 errorPixel = image->info9Patch.paddingTop;
422 errorEdge = "right";
423 goto getout;
424 }
425
426 // Copy patch data into image
427 image->info9Patch.numXDivs = numXDivs;
428 image->info9Patch.numYDivs = numYDivs;
429 image->info9Patch.xDivs = xDivs;
430 image->info9Patch.yDivs = yDivs;
431
432 // If padding is not yet specified, take values from size.
433 if (image->info9Patch.paddingLeft < 0) {
434 image->info9Patch.paddingLeft = xDivs[0];
435 image->info9Patch.paddingRight = W - 2 - xDivs[1];
436 } else {
437 // Adjust value to be correct!
438 image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
439 }
440 if (image->info9Patch.paddingTop < 0) {
441 image->info9Patch.paddingTop = yDivs[0];
442 image->info9Patch.paddingBottom = H - 2 - yDivs[1];
443 } else {
444 // Adjust value to be correct!
445 image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
446 }
447
448 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
449 image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
450 image->info9Patch.yDivs[0], image->info9Patch.yDivs[1]));
451 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
452 image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
453 image->info9Patch.paddingTop, image->info9Patch.paddingBottom));
454
455 // Remove frame from image.
456 image->rows = (png_bytepp)malloc((H-2) * png_sizeof(png_bytep));
457 for (i=0; i<(H-2); i++) {
458 image->rows[i] = image->allocRows[i+1];
459 memmove(image->rows[i], image->rows[i]+4, (W-2)*4);
460 }
461 image->width -= 2;
462 W = image->width;
463 image->height -= 2;
464 H = image->height;
465
466 // Figure out the number of rows and columns in the N-patch
467 numCols = numXDivs + 1;
468 if (xDivs[0] == 0) { // Column 1 is strechable
469 numCols--;
470 }
471 if (xDivs[numXDivs - 1] == W) {
472 numCols--;
473 }
474 numRows = numYDivs + 1;
475 if (yDivs[0] == 0) { // Row 1 is strechable
476 numRows--;
477 }
478 if (yDivs[numYDivs - 1] == H) {
479 numRows--;
480 }
481 numColors = numRows * numCols;
482 image->info9Patch.numColors = numColors;
483 image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
484
485 // Fill in color information for each patch.
486
487 uint32_t c;
488 top = 0;
489
490 // The first row always starts with the top being at y=0 and the bottom
491 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
492 // the first row is stretchable along the Y axis, otherwise it is fixed.
493 // The last row always ends with the bottom being bitmap.height and the top
494 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
495 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
496 // the Y axis, otherwise it is fixed.
497 //
498 // The first and last columns are similarly treated with respect to the X
499 // axis.
500 //
501 // The above is to help explain some of the special casing that goes on the
502 // code below.
503
504 // The initial yDiv and whether the first row is considered stretchable or
505 // not depends on whether yDiv[0] was zero or not.
506 for (j = (yDivs[0] == 0 ? 1 : 0);
507 j <= numYDivs && top < H;
508 j++) {
509 if (j == numYDivs) {
510 bottom = H;
511 } else {
512 bottom = yDivs[j];
513 }
514 left = 0;
515 // The initial xDiv and whether the first column is considered
516 // stretchable or not depends on whether xDiv[0] was zero or not.
517 for (i = xDivs[0] == 0 ? 1 : 0;
518 i <= numXDivs && left < W;
519 i++) {
520 if (i == numXDivs) {
521 right = W;
522 } else {
523 right = xDivs[i];
524 }
525 c = get_color(image->rows, left, top, right - 1, bottom - 1);
526 image->info9Patch.colors[colorIndex++] = c;
527 NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true);
528 left = right;
529 }
530 top = bottom;
531 }
532
533 assert(colorIndex == numColors);
534
535 for (i=0; i<numColors; i++) {
536 if (hasColor) {
537 if (i == 0) printf("Colors in %s:\n ", imageName);
538 printf(" #%08x", image->info9Patch.colors[i]);
539 if (i == numColors - 1) printf("\n");
540 }
541 }
542
543 image->is9Patch = true;
544 image->info9Patch.deviceToFile();
545
546 getout:
547 if (errorMsg) {
548 fprintf(stderr,
549 "ERROR: 9-patch image %s malformed.\n"
550 " %s.\n", imageName, errorMsg);
551 if (errorPixel >= 0) {
552 fprintf(stderr,
553 " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
554 } else {
555 fprintf(stderr,
556 " Found along %s edge.\n", errorEdge);
557 }
558 return UNKNOWN_ERROR;
559 }
560 return NO_ERROR;
561 }
562
563 static void checkNinePatchSerialization(Res_png_9patch* inPatch, void * data)
564 {
565 if (sizeof(void*) != sizeof(int32_t)) {
566 // can't deserialize on a non-32 bit system
567 return;
568 }
569 size_t patchSize = inPatch->serializedSize();
570 void * newData = malloc(patchSize);
571 memcpy(newData, data, patchSize);
572 Res_png_9patch* outPatch = inPatch->deserialize(newData);
573 // deserialization is done in place, so outPatch == newData
574 assert(outPatch == newData);
575 assert(outPatch->numXDivs == inPatch->numXDivs);
576 assert(outPatch->numYDivs == inPatch->numYDivs);
577 assert(outPatch->paddingLeft == inPatch->paddingLeft);
578 assert(outPatch->paddingRight == inPatch->paddingRight);
579 assert(outPatch->paddingTop == inPatch->paddingTop);
580 assert(outPatch->paddingBottom == inPatch->paddingBottom);
581 for (int i = 0; i < outPatch->numXDivs; i++) {
582 assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
583 }
584 for (int i = 0; i < outPatch->numYDivs; i++) {
585 assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
586 }
587 for (int i = 0; i < outPatch->numColors; i++) {
588 assert(outPatch->colors[i] == inPatch->colors[i]);
589 }
590 free(newData);
591 }
592
593 static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) {
594 if (!(patch1.numXDivs == patch2.numXDivs &&
595 patch1.numYDivs == patch2.numYDivs &&
596 patch1.numColors == patch2.numColors &&
597 patch1.paddingLeft == patch2.paddingLeft &&
598 patch1.paddingRight == patch2.paddingRight &&
599 patch1.paddingTop == patch2.paddingTop &&
600 patch1.paddingBottom == patch2.paddingBottom)) {
601 return false;
602 }
603 for (int i = 0; i < patch1.numColors; i++) {
604 if (patch1.colors[i] != patch2.colors[i]) {
605 return false;
606 }
607 }
608 for (int i = 0; i < patch1.numXDivs; i++) {
609 if (patch1.xDivs[i] != patch2.xDivs[i]) {
610 return false;
611 }
612 }
613 for (int i = 0; i < patch1.numYDivs; i++) {
614 if (patch1.yDivs[i] != patch2.yDivs[i]) {
615 return false;
616 }
617 }
618 return true;
619 }
620
621
622 static void analyze_image(image_info &imageInfo, png_colorp rgbPalette, png_bytep alphaPalette,
623 int *paletteEntries, bool *hasTransparency, int *colorType,
624 png_bytepp outRows)
625 {
626 int w = imageInfo.width;
627 int h = imageInfo.height;
628 bool trans = imageInfo.hasTransparency;
629 int i, j, rr, gg, bb, aa, idx;
630 uint32_t colors[256], col;
631 int num_colors = 0;
632
633 bool isOpaque = true;
634 bool isPalette = true;
635 bool isGrayscale = true;
636
637 // Scan the entire image and determine if:
638 // 1. Every pixel has R == G == B (grayscale)
639 // 2. Every pixel has A == 255 (opaque)
640 // 3. There are no more than 256 distinct RGBA colors
641 for (j = 0; j < h; j++) {
642 png_bytep row = imageInfo.rows[j];
643 png_bytep out = outRows[j];
644 for (i = 0; i < w; i++) {
645 rr = *row++;
646 gg = *row++;
647 bb = *row++;
648 aa = *row++;
649 if (!trans) {
650 // Ignore the actually byte and assume alpha == 255
651 aa = 0xff;
652 }
653
654 // Check if image is really grayscale
655 if (isGrayscale) {
656 if (rr != gg || rr != bb) {
657 isGrayscale = false;
658 }
659 }
660
661 // Check if image is really opaque
662 if (isOpaque) {
663 if (aa != 0xff) {
664 isOpaque = false;
665 }
666 }
667
668 // Check if image is really <= 256 colors
669 if (isPalette) {
670 col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
671 bool match = false;
672 for (idx = 0; idx < num_colors; idx++) {
673 if (colors[idx] == col) {
674 match = true;
675 break;
676 }
677 }
678
679 // Write the palette index for the pixel to outRows optimistically
680 // We might overwrite it later if we decide to encode as gray or
681 // gray + alpha
682 *out++ = idx;
683 if (!match) {
684 if (num_colors == 256) {
685 isPalette = false;
686 } else {
687 colors[num_colors++] = col;
688 }
689 }
690 }
691 }
692 }
693
694 *paletteEntries = 0;
695 *hasTransparency = !isOpaque;
696 int bpp = isOpaque ? 3 : 4;
697 int paletteSize = w * h + bpp * num_colors;
698
699 // Choose the best color type for the image.
700 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
701 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
702 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
703 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
704 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
705 if (isGrayscale) {
706 if (isOpaque) {
707 *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
708 } else {
709 // Use a simple heuristic to determine whether using a palette will
710 // save space versus using gray + alpha for each pixel.
711 // This doesn't take into account chunk overhead, filtering, LZ
712 // compression, etc.
713 if (isPalette && (paletteSize < 2 * w * h)) {
714 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
715 } else {
716 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
717 }
718 }
719 } else if (isPalette && (paletteSize < bpp * w * h)) {
720 *colorType = PNG_COLOR_TYPE_PALETTE;
721 } else {
722 *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
723 }
724
725 // Perform postprocessing of the image or palette data based on the final
726 // color type chosen
727
728 if (*colorType == PNG_COLOR_TYPE_PALETTE) {
729 // Create separate RGB and Alpha palettes and set the number of colors
730 *paletteEntries = num_colors;
731
732 // Create the RGB and alpha palettes
733 for (int idx = 0; idx < num_colors; idx++) {
734 col = colors[idx];
735 rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
736 rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
737 rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
738 alphaPalette[idx] = (png_byte) (col & 0xff);
739 }
740 } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
741 // If the image is gray or gray + alpha, compact the pixels into outRows
742 for (j = 0; j < h; j++) {
743 png_bytep row = imageInfo.rows[j];
744 png_bytep out = outRows[j];
745 for (i = 0; i < w; i++) {
746 rr = *row++;
747 gg = *row++;
748 bb = *row++;
749 aa = *row++;
750
751 *out++ = rr;
752 if (!isOpaque) {
753 *out++ = aa;
754 }
755 }
756 }
757 }
758 }
759
760
761 static void write_png(const char* imageName,
762 png_structp write_ptr, png_infop write_info,
763 image_info& imageInfo)
764 {
765 bool optimize = true;
766 png_uint_32 width, height;
767 int color_type;
768 int bit_depth, interlace_type, compression_type;
769 int i;
770
771 png_unknown_chunk unknowns[1];
772
773 png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep));
774 if (outRows == (png_bytepp) 0) {
775 printf("Can't allocate output buffer!\n");
776 exit(1);
777 }
778 for (i = 0; i < (int) imageInfo.height; i++) {
779 outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
780 if (outRows[i] == (png_bytep) 0) {
781 printf("Can't allocate output buffer!\n");
782 exit(1);
783 }
784 }
785
786 png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
787
788 NOISY(printf("Writing image %s: w = %d, h = %d, trans = %s\n", imageName,
789 (int) imageInfo.width, (int) imageInfo.height,
790 imageInfo.hasTransparency ? "true" : "false"));
791
792 png_color rgbPalette[256];
793 png_byte alphaPalette[256];
794 bool hasTransparency;
795 int paletteEntries;
796
797 if (optimize) {
798 analyze_image(imageInfo, rgbPalette, alphaPalette, &paletteEntries, &hasTransparency,
799 &color_type, outRows);
800 switch (color_type) {
801 case PNG_COLOR_TYPE_PALETTE:
802 NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
803 imageName, paletteEntries,
804 hasTransparency ? " (with alpha)" : ""));
805 break;
806 case PNG_COLOR_TYPE_GRAY:
807 NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
808 break;
809 case PNG_COLOR_TYPE_GRAY_ALPHA:
810 NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
811 break;
812 case PNG_COLOR_TYPE_RGB:
813 NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
814 break;
815 case PNG_COLOR_TYPE_RGB_ALPHA:
816 NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
817 break;
818 }
819 } else {
820 // Force RGB or RGB_ALPHA color type, copy transparency from input
821 paletteEntries = 0;
822 hasTransparency = imageInfo.hasTransparency;
823 color_type = hasTransparency ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
824 }
825
826 png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
827 8, color_type, PNG_INTERLACE_NONE,
828 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
829
830 if (color_type == PNG_COLOR_TYPE_PALETTE) {
831 png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
832 if (hasTransparency) {
833 png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
834 }
835 png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
836 } else {
837 png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
838 }
839
840 if (imageInfo.is9Patch) {
841 NOISY(printf("Adding 9-patch info...\n"));
842 strcpy((char*)unknowns[0].name, "npTc");
843 unknowns[0].data = (png_byte*)imageInfo.info9Patch.serialize();
844 unknowns[0].size = imageInfo.info9Patch.serializedSize();
845 // TODO: remove the check below when everything works
846 checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[0].data);
847 png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
848 (png_byte*)"npTc", 1);
849 png_set_unknown_chunks(write_ptr, write_info, unknowns, 1);
850 // XXX I can't get this to work without forcibly changing
851 // the location to what I want... which apparently is supposed
852 // to be a private API, but everything else I have tried results
853 // in the location being set to what I -last- wrote so I never
854 // get written. :p
855 png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
856 }
857
858 png_write_info(write_ptr, write_info);
859
860 if (!imageInfo.hasTransparency) {
861 png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
862 }
863
864 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
865 png_write_image(write_ptr, imageInfo.rows);
866 } else {
867 png_write_image(write_ptr, outRows);
868 }
869
870 png_write_end(write_ptr, write_info);
871
872 for (i = 0; i < (int) imageInfo.height; i++) {
873 free(outRows[i]);
874 }
875 free(outRows);
876
877 png_get_IHDR(write_ptr, write_info, &width, &height,
878 &bit_depth, &color_type, &interlace_type,
879 &compression_type, NULL);
880
881 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
882 (int)width, (int)height, bit_depth, color_type, interlace_type,
883 compression_type));
884 }
885
886 status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets,
887 const sp<AaptFile>& file, String8* outNewLeafName)
888 {
889 String8 ext(file->getPath().getPathExtension());
890
891 // We currently only process PNG images.
892 if (strcmp(ext.string(), ".png") != 0) {
893 return NO_ERROR;
894 }
895
896 // Example of renaming a file:
897 //*outNewLeafName = file->getPath().getBasePath().getFileName();
898 //outNewLeafName->append(".nupng");
899
900 String8 printableName(file->getPrintableSource());
901
902 png_structp read_ptr = NULL;
903 png_infop read_info = NULL;
904 FILE* fp;
905
906 image_info imageInfo;
907
908 png_structp write_ptr = NULL;
909 png_infop write_info = NULL;
910
911 status_t error = UNKNOWN_ERROR;
912
913 const size_t nameLen = file->getPath().length();
914
915 fp = fopen(file->getSourceFile().string(), "rb");
916 if (fp == NULL) {
917 fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
918 goto bail;
919 }
920
921 read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
922 (png_error_ptr)NULL);
923 if (!read_ptr) {
924 goto bail;
925 }
926
927 read_info = png_create_info_struct(read_ptr);
928 if (!read_info) {
929 goto bail;
930 }
931
932 if (setjmp(png_jmpbuf(read_ptr))) {
933 goto bail;
934 }
935
936 png_init_io(read_ptr, fp);
937
938 read_png(printableName.string(), read_ptr, read_info, &imageInfo);
939
940 examine_image(&imageInfo);
941
942 if (nameLen > 6) {
943 const char* name = file->getPath().string();
944 if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
945 if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
946 goto bail;
947 }
948 }
949 }
950
951 write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
952 (png_error_ptr)NULL);
953 if (!write_ptr)
954 {
955 goto bail;
956 }
957
958 write_info = png_create_info_struct(write_ptr);
959 if (!write_info)
960 {
961 goto bail;
962 }
963
964 png_set_write_fn(write_ptr, (void*)file.get(),
965 png_write_aapt_file, png_flush_aapt_file);
966
967 if (setjmp(png_jmpbuf(write_ptr)))
968 {
969 goto bail;
970 }
971
972 write_png(printableName.string(), write_ptr, write_info, imageInfo);
973
974 error = NO_ERROR;
975
976 if (bundle->getVerbose()) {
977 fseek(fp, 0, SEEK_END);
978 size_t oldSize = (size_t)ftell(fp);
979 size_t newSize = file->getSize();
980 float factor = ((float)newSize)/oldSize;
981 int percent = (int)(factor*100);
982 printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent);
983 }
984
985 bail:
986 if (read_ptr) {
987 png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
988 }
989 if (fp) {
990 fclose(fp);
991 }
992 if (write_ptr) {
993 png_destroy_write_struct(&write_ptr, &write_info);
994 }
995
996 if (error != NO_ERROR) {
997 fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
998 file->getPrintableSource().string());
999 }
1000 return error;
1001 }
1002
1003
1004
1005 status_t postProcessImage(const sp<AaptAssets>& assets,
1006 ResourceTable* table, const sp<AaptFile>& file)
1007 {
1008 String8 ext(file->getPath().getPathExtension());
1009
1010 // At this point, now that we have all the resource data, all we need to
1011 // do is compile XML files.
1012 if (strcmp(ext.string(), ".xml") == 0) {
1013 return compileXmlFile(assets, file, table);
1014 }
1015
1016 return NO_ERROR;
1017 }