2 // Copyright 2006 The Android Open Source Project
4 // Build resource files from raw assets.
11 #include <utils/ResourceTypes.h>
12 #include <utils/ByteOrder.h>
19 png_write_aapt_file(png_structp png_ptr
, png_bytep data
, png_size_t length
)
21 status_t err
= ((AaptFile
*)png_ptr
->io_ptr
)->writeData(data
, length
);
22 if (err
!= NO_ERROR
) {
23 png_error(png_ptr
, "Write Error");
29 png_flush_aapt_file(png_structp png_ptr
)
33 // This holds an image as 8bpp RGBA.
36 image_info() : rows(NULL
), is9Patch(false), allocRows(NULL
) { }
38 if (rows
&& rows
!= allocRows
) {
42 for (int i
=0; i
<(int)allocHeight
; i
++) {
47 free(info9Patch
.xDivs
);
48 free(info9Patch
.yDivs
);
49 free(info9Patch
.colors
);
58 Res_png_9patch info9Patch
;
60 png_uint_32 allocHeight
;
64 static void read_png(const char* imageName
,
65 png_structp read_ptr
, png_infop read_info
,
66 image_info
* outImageInfo
)
69 int bit_depth
, interlace_type
, compression_type
;
72 png_read_info(read_ptr
, read_info
);
74 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
75 &outImageInfo
->height
, &bit_depth
, &color_type
,
76 &interlace_type
, &compression_type
, NULL
);
78 //printf("Image %s:\n", imageName);
79 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
80 // color_type, bit_depth, interlace_type, compression_type);
82 if (color_type
== PNG_COLOR_TYPE_PALETTE
)
83 png_set_palette_to_rgb(read_ptr
);
85 if (color_type
== PNG_COLOR_TYPE_GRAY
&& bit_depth
< 8)
86 png_set_gray_1_2_4_to_8(read_ptr
);
88 if (png_get_valid(read_ptr
, read_info
, PNG_INFO_tRNS
)) {
89 //printf("Has PNG_INFO_tRNS!\n");
90 png_set_tRNS_to_alpha(read_ptr
);
94 png_set_strip_16(read_ptr
);
96 if ((color_type
&PNG_COLOR_MASK_ALPHA
) == 0)
97 png_set_add_alpha(read_ptr
, 0xFF, PNG_FILLER_AFTER
);
99 if (color_type
== PNG_COLOR_TYPE_GRAY
|| color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
)
100 png_set_gray_to_rgb(read_ptr
);
102 png_read_update_info(read_ptr
, read_info
);
104 outImageInfo
->rows
= (png_bytepp
)malloc(
105 outImageInfo
->height
* png_sizeof(png_bytep
));
106 outImageInfo
->allocHeight
= outImageInfo
->height
;
107 outImageInfo
->allocRows
= outImageInfo
->rows
;
109 png_set_rows(read_ptr
, read_info
, outImageInfo
->rows
);
111 for (i
= 0; i
< (int)outImageInfo
->height
; i
++)
113 outImageInfo
->rows
[i
] = (png_bytep
)
114 malloc(png_get_rowbytes(read_ptr
, read_info
));
117 png_read_image(read_ptr
, outImageInfo
->rows
);
119 png_read_end(read_ptr
, read_info
);
121 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
123 (int)outImageInfo
->width
, (int)outImageInfo
->height
,
124 bit_depth
, color_type
,
125 interlace_type
, compression_type
));
127 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
128 &outImageInfo
->height
, &bit_depth
, &color_type
,
129 &interlace_type
, &compression_type
, NULL
);
132 static bool is_tick(png_bytep p
, bool transparent
, const char** outError
)
139 *outError
= "Frame pixels must be either solid or transparent (not intermediate alphas)";
142 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
143 *outError
= "Ticks in transparent frame must be black";
149 *outError
= "White frame must be a solid color (no alpha)";
151 if (p
[0] == 0xFF && p
[1] == 0xFF && p
[2] == 0xFF) {
154 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
155 *outError
= "Ticks in white frame must be black";
167 static status_t
get_horizontal_ticks(
168 png_bytep row
, int width
, bool transparent
, bool required
,
169 int32_t* outLeft
, int32_t* outRight
, const char** outError
,
170 uint8_t* outDivs
, bool multipleAllowed
)
173 *outLeft
= *outRight
= -1;
174 int state
= TICK_START
;
177 for (i
=1; i
<width
-1; i
++) {
178 if (is_tick(row
+i
*4, transparent
, outError
)) {
179 if (state
== TICK_START
||
180 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
184 if (outDivs
!= NULL
) {
187 state
= TICK_INSIDE_1
;
188 } else if (state
== TICK_OUTSIDE_1
) {
189 *outError
= "Can't have more than one marked region along edge";
191 return UNKNOWN_ERROR
;
193 } else if (*outError
== NULL
) {
194 if (state
== TICK_INSIDE_1
) {
195 // We're done with this div. Move on to the next.
199 state
= TICK_OUTSIDE_1
;
203 return UNKNOWN_ERROR
;
207 if (required
&& !found
) {
208 *outError
= "No marked region found along edge";
210 return UNKNOWN_ERROR
;
216 static status_t
get_vertical_ticks(
217 png_bytepp rows
, int offset
, int height
, bool transparent
, bool required
,
218 int32_t* outTop
, int32_t* outBottom
, const char** outError
,
219 uint8_t* outDivs
, bool multipleAllowed
)
222 *outTop
= *outBottom
= -1;
223 int state
= TICK_START
;
226 for (i
=1; i
<height
-1; i
++) {
227 if (is_tick(rows
[i
]+offset
, transparent
, outError
)) {
228 if (state
== TICK_START
||
229 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
231 *outBottom
= height
-2;
233 if (outDivs
!= NULL
) {
236 state
= TICK_INSIDE_1
;
237 } else if (state
== TICK_OUTSIDE_1
) {
238 *outError
= "Can't have more than one marked region along edge";
240 return UNKNOWN_ERROR
;
242 } else if (*outError
== NULL
) {
243 if (state
== TICK_INSIDE_1
) {
244 // We're done with this div. Move on to the next.
248 state
= TICK_OUTSIDE_1
;
252 return UNKNOWN_ERROR
;
256 if (required
&& !found
) {
257 *outError
= "No marked region found along edge";
259 return UNKNOWN_ERROR
;
265 static uint32_t get_color(
266 png_bytepp rows
, int left
, int top
, int right
, int bottom
)
268 png_bytep color
= rows
[top
] + left
*4;
270 if (left
> right
|| top
> bottom
) {
271 return Res_png_9patch::TRANSPARENT_COLOR
;
274 while (top
<= bottom
) {
275 for (int i
= left
; i
<= right
; i
++) {
276 png_bytep p
= rows
[top
]+i
*4;
279 return Res_png_9patch::NO_COLOR
;
281 } else if (p
[0] != color
[0] || p
[1] != color
[1]
282 || p
[2] != color
[2] || p
[3] != color
[3]) {
283 return Res_png_9patch::NO_COLOR
;
290 return Res_png_9patch::TRANSPARENT_COLOR
;
292 return (color
[3]<<24) | (color
[0]<<16) | (color
[1]<<8) | color
[2];
295 static void select_patch(
296 int which
, int front
, int back
, int size
, int* start
, int* end
)
314 static uint32_t get_color(image_info
* image
, int hpatch
, int vpatch
)
316 int left
, right
, top
, bottom
;
318 hpatch
, image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
319 image
->width
, &left
, &right
);
321 vpatch
, image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1],
322 image
->height
, &top
, &bottom
);
323 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
324 // hpatch, vpatch, left, top, right, bottom);
325 const uint32_t c
= get_color(image
->rows
, left
, top
, right
, bottom
);
326 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left
, top
, right
, bottom
, c
));
330 static status_t
do_9patch(const char* imageName
, image_info
* image
)
332 image
->is9Patch
= true;
334 int W
= image
->width
;
335 int H
= image
->height
;
338 int maxSizeXDivs
= W
* sizeof(int32_t);
339 int maxSizeYDivs
= H
* sizeof(int32_t);
340 int32_t* xDivs
= (int32_t*) malloc(maxSizeXDivs
);
341 int32_t* yDivs
= (int32_t*) malloc(maxSizeYDivs
);
342 uint8_t numXDivs
= 0;
343 uint8_t numYDivs
= 0;
351 memset(xDivs
, -1, maxSizeXDivs
);
352 memset(yDivs
, -1, maxSizeYDivs
);
353 image
->info9Patch
.paddingLeft
= image
->info9Patch
.paddingRight
=
354 image
->info9Patch
.paddingTop
= image
->info9Patch
.paddingBottom
= -1;
356 png_bytep p
= image
->rows
[0];
357 bool transparent
= p
[3] == 0;
358 bool hasColor
= false;
360 const char* errorMsg
= NULL
;
362 const char* errorEdge
= NULL
;
367 if (W
< 3 || H
< 3) {
368 errorMsg
= "Image must be at least 3x3 (1x1 without frame) pixels";
374 (p
[0] != 0xFF || p
[1] != 0xFF || p
[2] != 0xFF || p
[3] != 0xFF)) {
375 errorMsg
= "Must have one-pixel frame that is either transparent or white";
379 // Find left and right of sizing areas...
380 if (get_horizontal_ticks(p
, W
, transparent
, true, &xDivs
[0],
381 &xDivs
[1], &errorMsg
, &numXDivs
, true) != NO_ERROR
) {
382 errorPixel
= xDivs
[0];
387 // Find top and bottom of sizing areas...
388 if (get_vertical_ticks(image
->rows
, 0, H
, transparent
, true, &yDivs
[0],
389 &yDivs
[1], &errorMsg
, &numYDivs
, true) != NO_ERROR
) {
390 errorPixel
= yDivs
[0];
395 // Find left and right of padding area...
396 if (get_horizontal_ticks(image
->rows
[H
-1], W
, transparent
, false, &image
->info9Patch
.paddingLeft
,
397 &image
->info9Patch
.paddingRight
, &errorMsg
, NULL
, false) != NO_ERROR
) {
398 errorPixel
= image
->info9Patch
.paddingLeft
;
399 errorEdge
= "bottom";
403 // Find top and bottom of padding area...
404 if (get_vertical_ticks(image
->rows
, (W
-1)*4, H
, transparent
, false, &image
->info9Patch
.paddingTop
,
405 &image
->info9Patch
.paddingBottom
, &errorMsg
, NULL
, false) != NO_ERROR
) {
406 errorPixel
= image
->info9Patch
.paddingTop
;
411 // Copy patch data into image
412 image
->info9Patch
.numXDivs
= numXDivs
;
413 image
->info9Patch
.numYDivs
= numYDivs
;
414 image
->info9Patch
.xDivs
= xDivs
;
415 image
->info9Patch
.yDivs
= yDivs
;
417 // If padding is not yet specified, take values from size.
418 if (image
->info9Patch
.paddingLeft
< 0) {
419 image
->info9Patch
.paddingLeft
= xDivs
[0];
420 image
->info9Patch
.paddingRight
= W
- 2 - xDivs
[1];
422 // Adjust value to be correct!
423 image
->info9Patch
.paddingRight
= W
- 2 - image
->info9Patch
.paddingRight
;
425 if (image
->info9Patch
.paddingTop
< 0) {
426 image
->info9Patch
.paddingTop
= yDivs
[0];
427 image
->info9Patch
.paddingBottom
= H
- 2 - yDivs
[1];
429 // Adjust value to be correct!
430 image
->info9Patch
.paddingBottom
= H
- 2 - image
->info9Patch
.paddingBottom
;
433 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName
,
434 image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
435 image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1]));
436 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName
,
437 image
->info9Patch
.paddingLeft
, image
->info9Patch
.paddingRight
,
438 image
->info9Patch
.paddingTop
, image
->info9Patch
.paddingBottom
));
440 // Remove frame from image.
441 image
->rows
= (png_bytepp
)malloc((H
-2) * png_sizeof(png_bytep
));
442 for (i
=0; i
<(H
-2); i
++) {
443 image
->rows
[i
] = image
->allocRows
[i
+1];
444 memmove(image
->rows
[i
], image
->rows
[i
]+4, (W
-2)*4);
451 // Figure out the number of rows and columns in the N-patch
452 numCols
= numXDivs
+ 1;
453 if (xDivs
[0] == 0) { // Column 1 is strechable
456 if (xDivs
[numXDivs
- 1] == W
) {
459 numRows
= numYDivs
+ 1;
460 if (yDivs
[0] == 0) { // Row 1 is strechable
463 if (yDivs
[numYDivs
- 1] == H
) {
467 // Make sure the amount of rows and columns will fit in the number of
468 // colors we can use in the 9-patch format.
469 if (numRows
* numCols
> 0x7F) {
470 errorMsg
= "Too many rows and columns in 9-patch perimeter";
474 numColors
= numRows
* numCols
;
475 image
->info9Patch
.numColors
= numColors
;
476 image
->info9Patch
.colors
= (uint32_t*)malloc(numColors
* sizeof(uint32_t));
478 // Fill in color information for each patch.
483 // The first row always starts with the top being at y=0 and the bottom
484 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
485 // the first row is stretchable along the Y axis, otherwise it is fixed.
486 // The last row always ends with the bottom being bitmap.height and the top
487 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
488 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
489 // the Y axis, otherwise it is fixed.
491 // The first and last columns are similarly treated with respect to the X
494 // The above is to help explain some of the special casing that goes on the
497 // The initial yDiv and whether the first row is considered stretchable or
498 // not depends on whether yDiv[0] was zero or not.
499 for (j
= (yDivs
[0] == 0 ? 1 : 0);
500 j
<= numYDivs
&& top
< H
;
508 // The initial xDiv and whether the first column is considered
509 // stretchable or not depends on whether xDiv[0] was zero or not.
510 for (i
= xDivs
[0] == 0 ? 1 : 0;
511 i
<= numXDivs
&& left
< W
;
518 c
= get_color(image
->rows
, left
, top
, right
- 1, bottom
- 1);
519 image
->info9Patch
.colors
[colorIndex
++] = c
;
520 NOISY(if (c
!= Res_png_9patch::NO_COLOR
) hasColor
= true);
526 assert(colorIndex
== numColors
);
528 for (i
=0; i
<numColors
; i
++) {
530 if (i
== 0) printf("Colors in %s:\n ", imageName
);
531 printf(" #%08x", image
->info9Patch
.colors
[i
]);
532 if (i
== numColors
- 1) printf("\n");
536 image
->is9Patch
= true;
537 image
->info9Patch
.deviceToFile();
542 "ERROR: 9-patch image %s malformed.\n"
543 " %s.\n", imageName
, errorMsg
);
544 if (errorEdge
!= NULL
) {
545 if (errorPixel
>= 0) {
547 " Found at pixel #%d along %s edge.\n", errorPixel
, errorEdge
);
550 " Found along %s edge.\n", errorEdge
);
553 return UNKNOWN_ERROR
;
558 static void checkNinePatchSerialization(Res_png_9patch
* inPatch
, void * data
)
560 if (sizeof(void*) != sizeof(int32_t)) {
561 // can't deserialize on a non-32 bit system
564 size_t patchSize
= inPatch
->serializedSize();
565 void * newData
= malloc(patchSize
);
566 memcpy(newData
, data
, patchSize
);
567 Res_png_9patch
* outPatch
= inPatch
->deserialize(newData
);
568 // deserialization is done in place, so outPatch == newData
569 assert(outPatch
== newData
);
570 assert(outPatch
->numXDivs
== inPatch
->numXDivs
);
571 assert(outPatch
->numYDivs
== inPatch
->numYDivs
);
572 assert(outPatch
->paddingLeft
== inPatch
->paddingLeft
);
573 assert(outPatch
->paddingRight
== inPatch
->paddingRight
);
574 assert(outPatch
->paddingTop
== inPatch
->paddingTop
);
575 assert(outPatch
->paddingBottom
== inPatch
->paddingBottom
);
576 for (int i
= 0; i
< outPatch
->numXDivs
; i
++) {
577 assert(outPatch
->xDivs
[i
] == inPatch
->xDivs
[i
]);
579 for (int i
= 0; i
< outPatch
->numYDivs
; i
++) {
580 assert(outPatch
->yDivs
[i
] == inPatch
->yDivs
[i
]);
582 for (int i
= 0; i
< outPatch
->numColors
; i
++) {
583 assert(outPatch
->colors
[i
] == inPatch
->colors
[i
]);
588 static bool patch_equals(Res_png_9patch
& patch1
, Res_png_9patch
& patch2
) {
589 if (!(patch1
.numXDivs
== patch2
.numXDivs
&&
590 patch1
.numYDivs
== patch2
.numYDivs
&&
591 patch1
.numColors
== patch2
.numColors
&&
592 patch1
.paddingLeft
== patch2
.paddingLeft
&&
593 patch1
.paddingRight
== patch2
.paddingRight
&&
594 patch1
.paddingTop
== patch2
.paddingTop
&&
595 patch1
.paddingBottom
== patch2
.paddingBottom
)) {
598 for (int i
= 0; i
< patch1
.numColors
; i
++) {
599 if (patch1
.colors
[i
] != patch2
.colors
[i
]) {
603 for (int i
= 0; i
< patch1
.numXDivs
; i
++) {
604 if (patch1
.xDivs
[i
] != patch2
.xDivs
[i
]) {
608 for (int i
= 0; i
< patch1
.numYDivs
; i
++) {
609 if (patch1
.yDivs
[i
] != patch2
.yDivs
[i
]) {
616 static void dump_image(int w
, int h
, png_bytepp rows
, int color_type
)
618 int i
, j
, rr
, gg
, bb
, aa
;
621 if (color_type
== PNG_COLOR_TYPE_PALETTE
|| color_type
== PNG_COLOR_TYPE_GRAY
) {
623 } else if (color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
) {
625 } else if (color_type
== PNG_COLOR_TYPE_RGB
|| color_type
== PNG_COLOR_TYPE_RGB_ALPHA
) {
626 // We use a padding byte even when there is no alpha
629 printf("Unknown color type %d.\n", color_type
);
632 for (j
= 0; j
< h
; j
++) {
633 png_bytep row
= rows
[j
];
634 for (i
= 0; i
< w
; i
++) {
642 printf("Row %d:", j
);
649 printf(" (%d %d", rr
, gg
);
652 printf(" (%d %d %d)", rr
, gg
, bb
);
655 printf(" (%d %d %d %d)", rr
, gg
, bb
, aa
);
665 #define MAX(a,b) ((a)>(b)?(a):(b))
666 #define ABS(a) ((a)<0?-(a):(a))
668 static void analyze_image(const char *imageName
, image_info
&imageInfo
, int grayscaleTolerance
,
669 png_colorp rgbPalette
, png_bytep alphaPalette
,
670 int *paletteEntries
, bool *hasTransparency
, int *colorType
,
673 int w
= imageInfo
.width
;
674 int h
= imageInfo
.height
;
675 int i
, j
, rr
, gg
, bb
, aa
, idx
;
676 uint32_t colors
[256], col
;
678 int maxGrayDeviation
= 0;
680 bool isOpaque
= true;
681 bool isPalette
= true;
682 bool isGrayscale
= true;
684 // Scan the entire image and determine if:
685 // 1. Every pixel has R == G == B (grayscale)
686 // 2. Every pixel has A == 255 (opaque)
687 // 3. There are no more than 256 distinct RGBA colors
689 // NOISY(printf("Initial image data:\n"));
690 // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
692 for (j
= 0; j
< h
; j
++) {
693 png_bytep row
= imageInfo
.rows
[j
];
694 png_bytep out
= outRows
[j
];
695 for (i
= 0; i
< w
; i
++) {
701 int odev
= maxGrayDeviation
;
702 maxGrayDeviation
= MAX(ABS(rr
- gg
), maxGrayDeviation
);
703 maxGrayDeviation
= MAX(ABS(gg
- bb
), maxGrayDeviation
);
704 maxGrayDeviation
= MAX(ABS(bb
- rr
), maxGrayDeviation
);
705 if (maxGrayDeviation
> odev
) {
706 NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
707 maxGrayDeviation
, i
, j
, rr
, gg
, bb
, aa
));
710 // Check if image is really grayscale
712 if (rr
!= gg
|| rr
!= bb
) {
713 NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
714 i
, j
, rr
, gg
, bb
, aa
));
719 // Check if image is really opaque
722 NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
723 i
, j
, rr
, gg
, bb
, aa
));
728 // Check if image is really <= 256 colors
730 col
= (uint32_t) ((rr
<< 24) | (gg
<< 16) | (bb
<< 8) | aa
);
732 for (idx
= 0; idx
< num_colors
; idx
++) {
733 if (colors
[idx
] == col
) {
739 // Write the palette index for the pixel to outRows optimistically
740 // We might overwrite it later if we decide to encode as gray or
744 if (num_colors
== 256) {
745 NOISY(printf("Found 257th color at %d, %d\n", i
, j
));
748 colors
[num_colors
++] = col
;
756 *hasTransparency
= !isOpaque
;
757 int bpp
= isOpaque
? 3 : 4;
758 int paletteSize
= w
* h
+ bpp
* num_colors
;
760 NOISY(printf("isGrayscale = %s\n", isGrayscale
? "true" : "false"));
761 NOISY(printf("isOpaque = %s\n", isOpaque
? "true" : "false"));
762 NOISY(printf("isPalette = %s\n", isPalette
? "true" : "false"));
763 NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
764 paletteSize
, 2 * w
* h
, bpp
* w
* h
));
765 NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation
, grayscaleTolerance
));
767 // Choose the best color type for the image.
768 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
769 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
770 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
771 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
772 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
775 *colorType
= PNG_COLOR_TYPE_GRAY
; // 1 byte/pixel
777 // Use a simple heuristic to determine whether using a palette will
778 // save space versus using gray + alpha for each pixel.
779 // This doesn't take into account chunk overhead, filtering, LZ
781 if (isPalette
&& (paletteSize
< 2 * w
* h
)) {
782 *colorType
= PNG_COLOR_TYPE_PALETTE
; // 1 byte/pixel + 4 bytes/color
784 *colorType
= PNG_COLOR_TYPE_GRAY_ALPHA
; // 2 bytes per pixel
787 } else if (isPalette
&& (paletteSize
< bpp
* w
* h
)) {
788 *colorType
= PNG_COLOR_TYPE_PALETTE
;
790 if (maxGrayDeviation
<= grayscaleTolerance
) {
791 printf("%s: forcing image to gray (max deviation = %d)\n", imageName
, maxGrayDeviation
);
792 *colorType
= isOpaque
? PNG_COLOR_TYPE_GRAY
: PNG_COLOR_TYPE_GRAY_ALPHA
;
794 *colorType
= isOpaque
? PNG_COLOR_TYPE_RGB
: PNG_COLOR_TYPE_RGB_ALPHA
;
798 // Perform postprocessing of the image or palette data based on the final
801 if (*colorType
== PNG_COLOR_TYPE_PALETTE
) {
802 // Create separate RGB and Alpha palettes and set the number of colors
803 *paletteEntries
= num_colors
;
805 // Create the RGB and alpha palettes
806 for (int idx
= 0; idx
< num_colors
; idx
++) {
808 rgbPalette
[idx
].red
= (png_byte
) ((col
>> 24) & 0xff);
809 rgbPalette
[idx
].green
= (png_byte
) ((col
>> 16) & 0xff);
810 rgbPalette
[idx
].blue
= (png_byte
) ((col
>> 8) & 0xff);
811 alphaPalette
[idx
] = (png_byte
) (col
& 0xff);
813 } else if (*colorType
== PNG_COLOR_TYPE_GRAY
|| *colorType
== PNG_COLOR_TYPE_GRAY_ALPHA
) {
814 // If the image is gray or gray + alpha, compact the pixels into outRows
815 for (j
= 0; j
< h
; j
++) {
816 png_bytep row
= imageInfo
.rows
[j
];
817 png_bytep out
= outRows
[j
];
818 for (i
= 0; i
< w
; i
++) {
827 *out
++ = (png_byte
) (rr
* 0.2126f
+ gg
* 0.7152f
+ bb
* 0.0722f
);
838 static void write_png(const char* imageName
,
839 png_structp write_ptr
, png_infop write_info
,
840 image_info
& imageInfo
, int grayscaleTolerance
)
842 bool optimize
= true;
843 png_uint_32 width
, height
;
845 int bit_depth
, interlace_type
, compression_type
;
848 png_unknown_chunk unknowns
[1];
849 unknowns
[0].data
= NULL
;
851 png_bytepp outRows
= (png_bytepp
) malloc((int) imageInfo
.height
* png_sizeof(png_bytep
));
852 if (outRows
== (png_bytepp
) 0) {
853 printf("Can't allocate output buffer!\n");
856 for (i
= 0; i
< (int) imageInfo
.height
; i
++) {
857 outRows
[i
] = (png_bytep
) malloc(2 * (int) imageInfo
.width
);
858 if (outRows
[i
] == (png_bytep
) 0) {
859 printf("Can't allocate output buffer!\n");
864 png_set_compression_level(write_ptr
, Z_BEST_COMPRESSION
);
866 NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName
,
867 (int) imageInfo
.width
, (int) imageInfo
.height
));
869 png_color rgbPalette
[256];
870 png_byte alphaPalette
[256];
871 bool hasTransparency
;
874 analyze_image(imageName
, imageInfo
, grayscaleTolerance
, rgbPalette
, alphaPalette
,
875 &paletteEntries
, &hasTransparency
, &color_type
, outRows
);
877 // If the image is a 9-patch, we need to preserve it as a ARGB file to make
878 // sure the pixels will not be pre-dithered/clamped until we decide they are
879 if (imageInfo
.is9Patch
&& (color_type
== PNG_COLOR_TYPE_RGB
||
880 color_type
== PNG_COLOR_TYPE_GRAY
|| color_type
== PNG_COLOR_TYPE_PALETTE
)) {
881 color_type
= PNG_COLOR_TYPE_RGB_ALPHA
;
884 switch (color_type
) {
885 case PNG_COLOR_TYPE_PALETTE
:
886 NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
887 imageName
, paletteEntries
,
888 hasTransparency
? " (with alpha)" : ""));
890 case PNG_COLOR_TYPE_GRAY
:
891 NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName
));
893 case PNG_COLOR_TYPE_GRAY_ALPHA
:
894 NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName
));
896 case PNG_COLOR_TYPE_RGB
:
897 NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName
));
899 case PNG_COLOR_TYPE_RGB_ALPHA
:
900 NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName
));
904 png_set_IHDR(write_ptr
, write_info
, imageInfo
.width
, imageInfo
.height
,
905 8, color_type
, PNG_INTERLACE_NONE
,
906 PNG_COMPRESSION_TYPE_DEFAULT
, PNG_FILTER_TYPE_DEFAULT
);
908 if (color_type
== PNG_COLOR_TYPE_PALETTE
) {
909 png_set_PLTE(write_ptr
, write_info
, rgbPalette
, paletteEntries
);
910 if (hasTransparency
) {
911 png_set_tRNS(write_ptr
, write_info
, alphaPalette
, paletteEntries
, (png_color_16p
) 0);
913 png_set_filter(write_ptr
, 0, PNG_NO_FILTERS
);
915 png_set_filter(write_ptr
, 0, PNG_ALL_FILTERS
);
918 if (imageInfo
.is9Patch
) {
919 NOISY(printf("Adding 9-patch info...\n"));
920 strcpy((char*)unknowns
[0].name
, "npTc");
921 unknowns
[0].data
= (png_byte
*)imageInfo
.info9Patch
.serialize();
922 unknowns
[0].size
= imageInfo
.info9Patch
.serializedSize();
923 // TODO: remove the check below when everything works
924 checkNinePatchSerialization(&imageInfo
.info9Patch
, unknowns
[0].data
);
925 png_set_keep_unknown_chunks(write_ptr
, PNG_HANDLE_CHUNK_ALWAYS
,
926 (png_byte
*)"npTc", 1);
927 png_set_unknown_chunks(write_ptr
, write_info
, unknowns
, 1);
928 // XXX I can't get this to work without forcibly changing
929 // the location to what I want... which apparently is supposed
930 // to be a private API, but everything else I have tried results
931 // in the location being set to what I -last- wrote so I never
933 png_set_unknown_chunk_location(write_ptr
, write_info
, 0, PNG_HAVE_PLTE
);
936 png_write_info(write_ptr
, write_info
);
939 if (color_type
== PNG_COLOR_TYPE_RGB
|| color_type
== PNG_COLOR_TYPE_RGB_ALPHA
) {
940 png_set_filler(write_ptr
, 0, PNG_FILLER_AFTER
);
941 rows
= imageInfo
.rows
;
945 png_write_image(write_ptr
, rows
);
947 // NOISY(printf("Final image data:\n"));
948 // dump_image(imageInfo.width, imageInfo.height, rows, color_type);
950 png_write_end(write_ptr
, write_info
);
952 for (i
= 0; i
< (int) imageInfo
.height
; i
++) {
956 free(unknowns
[0].data
);
958 png_get_IHDR(write_ptr
, write_info
, &width
, &height
,
959 &bit_depth
, &color_type
, &interlace_type
,
960 &compression_type
, NULL
);
962 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
963 (int)width
, (int)height
, bit_depth
, color_type
, interlace_type
,
967 status_t
preProcessImage(Bundle
* bundle
, const sp
<AaptAssets
>& assets
,
968 const sp
<AaptFile
>& file
, String8
* outNewLeafName
)
970 String8
ext(file
->getPath().getPathExtension());
972 // We currently only process PNG images.
973 if (strcmp(ext
.string(), ".png") != 0) {
977 // Example of renaming a file:
978 //*outNewLeafName = file->getPath().getBasePath().getFileName();
979 //outNewLeafName->append(".nupng");
981 String8
printableName(file
->getPrintableSource());
983 png_structp read_ptr
= NULL
;
984 png_infop read_info
= NULL
;
987 image_info imageInfo
;
989 png_structp write_ptr
= NULL
;
990 png_infop write_info
= NULL
;
992 status_t error
= UNKNOWN_ERROR
;
994 const size_t nameLen
= file
->getPath().length();
996 fp
= fopen(file
->getSourceFile().string(), "rb");
998 fprintf(stderr
, "%s: ERROR: Unable to open PNG file\n", printableName
.string());
1002 read_ptr
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
1003 (png_error_ptr
)NULL
);
1008 read_info
= png_create_info_struct(read_ptr
);
1013 if (setjmp(png_jmpbuf(read_ptr
))) {
1017 png_init_io(read_ptr
, fp
);
1019 read_png(printableName
.string(), read_ptr
, read_info
, &imageInfo
);
1022 const char* name
= file
->getPath().string();
1023 if (name
[nameLen
-5] == '9' && name
[nameLen
-6] == '.') {
1024 if (do_9patch(printableName
.string(), &imageInfo
) != NO_ERROR
) {
1030 write_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
1031 (png_error_ptr
)NULL
);
1037 write_info
= png_create_info_struct(write_ptr
);
1043 png_set_write_fn(write_ptr
, (void*)file
.get(),
1044 png_write_aapt_file
, png_flush_aapt_file
);
1046 if (setjmp(png_jmpbuf(write_ptr
)))
1051 write_png(printableName
.string(), write_ptr
, write_info
, imageInfo
,
1052 bundle
->getGrayscaleTolerance());
1056 if (bundle
->getVerbose()) {
1057 fseek(fp
, 0, SEEK_END
);
1058 size_t oldSize
= (size_t)ftell(fp
);
1059 size_t newSize
= file
->getSize();
1060 float factor
= ((float)newSize
)/oldSize
;
1061 int percent
= (int)(factor
*100);
1062 printf(" (processed image %s: %d%% size of source)\n", printableName
.string(), percent
);
1067 png_destroy_read_struct(&read_ptr
, &read_info
, (png_infopp
)NULL
);
1073 png_destroy_write_struct(&write_ptr
, &write_info
);
1076 if (error
!= NO_ERROR
) {
1077 fprintf(stderr
, "ERROR: Failure processing PNG image %s\n",
1078 file
->getPrintableSource().string());
1083 status_t
preProcessImageToCache(Bundle
* bundle
, String8 source
, String8 dest
)
1085 png_structp read_ptr
= NULL
;
1086 png_infop read_info
= NULL
;
1090 image_info imageInfo
;
1092 png_structp write_ptr
= NULL
;
1093 png_infop write_info
= NULL
;
1095 status_t error
= UNKNOWN_ERROR
;
1097 // Get a file handler to read from
1098 fp
= fopen(source
.string(),"rb");
1100 fprintf(stderr
, "%s ERROR: Unable to open PNG file\n", source
.string());
1104 // Call libpng to get a struct to read image data into
1105 read_ptr
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
1108 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1112 // Call libpng to get a struct to read image info into
1113 read_info
= png_create_info_struct(read_ptr
);
1116 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1120 // Set a jump point for libpng to long jump back to on error
1121 if (setjmp(png_jmpbuf(read_ptr
))) {
1123 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1127 // Set up libpng to read from our file.
1128 png_init_io(read_ptr
,fp
);
1130 // Actually read data from the file
1131 read_png(source
.string(), read_ptr
, read_info
, &imageInfo
);
1133 // We're done reading so we can clean up
1134 // Find old file size before releasing handle
1135 fseek(fp
, 0, SEEK_END
);
1136 size_t oldSize
= (size_t)ftell(fp
);
1138 png_destroy_read_struct(&read_ptr
, &read_info
,NULL
);
1140 // Check to see if we're dealing with a 9-patch
1141 // If we are, process appropriately
1142 if (source
.getBasePath().getPathExtension() == ".9") {
1143 if (do_9patch(source
.string(), &imageInfo
) != NO_ERROR
) {
1148 // Call libpng to create a structure to hold the processed image data
1149 // that can be written to disk
1150 write_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, NULL
, NULL
, NULL
);
1152 png_destroy_write_struct(&write_ptr
, &write_info
);
1156 // Call libpng to create a structure to hold processed image info that can
1157 // be written to disk
1158 write_info
= png_create_info_struct(write_ptr
);
1160 png_destroy_write_struct(&write_ptr
, &write_info
);
1164 // Open up our destination file for writing
1165 fp
= fopen(dest
.string(), "wb");
1167 fprintf(stderr
, "%s ERROR: Unable to open PNG file\n", dest
.string());
1168 png_destroy_write_struct(&write_ptr
, &write_info
);
1172 // Set up libpng to write to our file
1173 png_init_io(write_ptr
, fp
);
1175 // Set up a jump for libpng to long jump back on on errors
1176 if (setjmp(png_jmpbuf(write_ptr
))) {
1178 png_destroy_write_struct(&write_ptr
, &write_info
);
1182 // Actually write out to the new png
1183 write_png(dest
.string(), write_ptr
, write_info
, imageInfo
,
1184 bundle
->getGrayscaleTolerance());
1186 if (bundle
->getVerbose()) {
1187 // Find the size of our new file
1188 FILE* reader
= fopen(dest
.string(), "rb");
1189 fseek(reader
, 0, SEEK_END
);
1190 size_t newSize
= (size_t)ftell(reader
);
1193 float factor
= ((float)newSize
)/oldSize
;
1194 int percent
= (int)(factor
*100);
1195 printf(" (processed image to cache entry %s: %d%% size of source)\n",
1196 dest
.string(), percent
);
1201 png_destroy_write_struct(&write_ptr
, &write_info
);
1206 status_t
postProcessImage(const sp
<AaptAssets
>& assets
,
1207 ResourceTable
* table
, const sp
<AaptFile
>& file
)
1209 String8
ext(file
->getPath().getPathExtension());
1211 // At this point, now that we have all the resource data, all we need to
1212 // do is compile XML files.
1213 if (strcmp(ext
.string(), ".xml") == 0) {
1214 return compileXmlFile(assets
, file
, table
);