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
++) {
55 Res_png_9patch info9Patch
;
57 png_uint_32 allocHeight
;
61 static void read_png(const char* imageName
,
62 png_structp read_ptr
, png_infop read_info
,
63 image_info
* outImageInfo
)
66 int bit_depth
, interlace_type
, compression_type
;
69 png_read_info(read_ptr
, read_info
);
71 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
72 &outImageInfo
->height
, &bit_depth
, &color_type
,
73 &interlace_type
, &compression_type
, NULL
);
75 //printf("Image %s:\n", imageName);
76 //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
77 // color_type, bit_depth, interlace_type, compression_type);
79 if (color_type
== PNG_COLOR_TYPE_PALETTE
)
80 png_set_palette_to_rgb(read_ptr
);
82 if (color_type
== PNG_COLOR_TYPE_GRAY
&& bit_depth
< 8)
83 png_set_gray_1_2_4_to_8(read_ptr
);
85 if (png_get_valid(read_ptr
, read_info
, PNG_INFO_tRNS
)) {
86 //printf("Has PNG_INFO_tRNS!\n");
87 png_set_tRNS_to_alpha(read_ptr
);
91 png_set_strip_16(read_ptr
);
93 if ((color_type
&PNG_COLOR_MASK_ALPHA
) == 0)
94 png_set_add_alpha(read_ptr
, 0xFF, PNG_FILLER_AFTER
);
96 if (color_type
== PNG_COLOR_TYPE_GRAY
|| color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
)
97 png_set_gray_to_rgb(read_ptr
);
99 png_read_update_info(read_ptr
, read_info
);
101 outImageInfo
->rows
= (png_bytepp
)malloc(
102 outImageInfo
->height
* png_sizeof(png_bytep
));
103 outImageInfo
->allocHeight
= outImageInfo
->height
;
104 outImageInfo
->allocRows
= outImageInfo
->rows
;
106 png_set_rows(read_ptr
, read_info
, outImageInfo
->rows
);
108 for (i
= 0; i
< (int)outImageInfo
->height
; i
++)
110 outImageInfo
->rows
[i
] = (png_bytep
)
111 malloc(png_get_rowbytes(read_ptr
, read_info
));
114 png_read_image(read_ptr
, outImageInfo
->rows
);
116 png_read_end(read_ptr
, read_info
);
118 NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
120 (int)outImageInfo
->width
, (int)outImageInfo
->height
,
121 bit_depth
, color_type
,
122 interlace_type
, compression_type
));
124 png_get_IHDR(read_ptr
, read_info
, &outImageInfo
->width
,
125 &outImageInfo
->height
, &bit_depth
, &color_type
,
126 &interlace_type
, &compression_type
, NULL
);
129 static bool is_tick(png_bytep p
, bool transparent
, const char** outError
)
136 *outError
= "Frame pixels must be either solid or transparent (not intermediate alphas)";
139 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
140 *outError
= "Ticks in transparent frame must be black";
146 *outError
= "White frame must be a solid color (no alpha)";
148 if (p
[0] == 0xFF && p
[1] == 0xFF && p
[2] == 0xFF) {
151 if (p
[0] != 0 || p
[1] != 0 || p
[2] != 0) {
152 *outError
= "Ticks in white frame must be black";
164 static status_t
get_horizontal_ticks(
165 png_bytep row
, int width
, bool transparent
, bool required
,
166 int32_t* outLeft
, int32_t* outRight
, const char** outError
,
167 uint8_t* outDivs
, bool multipleAllowed
)
170 *outLeft
= *outRight
= -1;
171 int state
= TICK_START
;
174 for (i
=1; i
<width
-1; i
++) {
175 if (is_tick(row
+i
*4, transparent
, outError
)) {
176 if (state
== TICK_START
||
177 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
181 if (outDivs
!= NULL
) {
184 state
= TICK_INSIDE_1
;
185 } else if (state
== TICK_OUTSIDE_1
) {
186 *outError
= "Can't have more than one marked region along edge";
188 return UNKNOWN_ERROR
;
190 } else if (*outError
== NULL
) {
191 if (state
== TICK_INSIDE_1
) {
192 // We're done with this div. Move on to the next.
196 state
= TICK_OUTSIDE_1
;
200 return UNKNOWN_ERROR
;
204 if (required
&& !found
) {
205 *outError
= "No marked region found along edge";
207 return UNKNOWN_ERROR
;
213 static status_t
get_vertical_ticks(
214 png_bytepp rows
, int offset
, int height
, bool transparent
, bool required
,
215 int32_t* outTop
, int32_t* outBottom
, const char** outError
,
216 uint8_t* outDivs
, bool multipleAllowed
)
219 *outTop
= *outBottom
= -1;
220 int state
= TICK_START
;
223 for (i
=1; i
<height
-1; i
++) {
224 if (is_tick(rows
[i
]+offset
, transparent
, outError
)) {
225 if (state
== TICK_START
||
226 (state
== TICK_OUTSIDE_1
&& multipleAllowed
)) {
228 *outBottom
= height
-2;
230 if (outDivs
!= NULL
) {
233 state
= TICK_INSIDE_1
;
234 } else if (state
== TICK_OUTSIDE_1
) {
235 *outError
= "Can't have more than one marked region along edge";
237 return UNKNOWN_ERROR
;
239 } else if (*outError
== NULL
) {
240 if (state
== TICK_INSIDE_1
) {
241 // We're done with this div. Move on to the next.
245 state
= TICK_OUTSIDE_1
;
249 return UNKNOWN_ERROR
;
253 if (required
&& !found
) {
254 *outError
= "No marked region found along edge";
256 return UNKNOWN_ERROR
;
262 static uint32_t get_color(
263 png_bytepp rows
, int left
, int top
, int right
, int bottom
)
265 png_bytep color
= rows
[top
] + left
*4;
267 if (left
> right
|| top
> bottom
) {
268 return Res_png_9patch::TRANSPARENT_COLOR
;
271 while (top
<= bottom
) {
272 for (int i
= left
; i
<= right
; i
++) {
273 png_bytep p
= rows
[top
]+i
*4;
276 return Res_png_9patch::NO_COLOR
;
278 } else if (p
[0] != color
[0] || p
[1] != color
[1]
279 || p
[2] != color
[2] || p
[3] != color
[3]) {
280 return Res_png_9patch::NO_COLOR
;
287 return Res_png_9patch::TRANSPARENT_COLOR
;
289 return (color
[3]<<24) | (color
[0]<<16) | (color
[1]<<8) | color
[2];
292 static void select_patch(
293 int which
, int front
, int back
, int size
, int* start
, int* end
)
311 static uint32_t get_color(image_info
* image
, int hpatch
, int vpatch
)
313 int left
, right
, top
, bottom
;
315 hpatch
, image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
316 image
->width
, &left
, &right
);
318 vpatch
, image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1],
319 image
->height
, &top
, &bottom
);
320 //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
321 // hpatch, vpatch, left, top, right, bottom);
322 const uint32_t c
= get_color(image
->rows
, left
, top
, right
, bottom
);
323 NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left
, top
, right
, bottom
, c
));
327 static status_t
do_9patch(const char* imageName
, image_info
* image
)
329 image
->is9Patch
= true;
331 int W
= image
->width
;
332 int H
= image
->height
;
335 int maxSizeXDivs
= (W
/ 2 + 1) * sizeof(int32_t);
336 int maxSizeYDivs
= (H
/ 2 + 1) * sizeof(int32_t);
337 int32_t* xDivs
= (int32_t*) malloc(maxSizeXDivs
);
338 int32_t* yDivs
= (int32_t*) malloc(maxSizeYDivs
);
339 uint8_t numXDivs
= 0;
340 uint8_t numYDivs
= 0;
348 memset(xDivs
, -1, maxSizeXDivs
);
349 memset(yDivs
, -1, maxSizeYDivs
);
350 image
->info9Patch
.paddingLeft
= image
->info9Patch
.paddingRight
=
351 image
->info9Patch
.paddingTop
= image
->info9Patch
.paddingBottom
= -1;
353 png_bytep p
= image
->rows
[0];
354 bool transparent
= p
[3] == 0;
355 bool hasColor
= false;
357 const char* errorMsg
= NULL
;
359 const char* errorEdge
= "";
364 if (W
< 3 || H
< 3) {
365 errorMsg
= "Image must be at least 3x3 (1x1 without frame) pixels";
371 (p
[0] != 0xFF || p
[1] != 0xFF || p
[2] != 0xFF || p
[3] != 0xFF)) {
372 errorMsg
= "Must have one-pixel frame that is either transparent or white";
376 // Find left and right of sizing areas...
377 if (get_horizontal_ticks(p
, W
, transparent
, true, &xDivs
[0],
378 &xDivs
[1], &errorMsg
, &numXDivs
, true) != NO_ERROR
) {
379 errorPixel
= xDivs
[0];
384 // Find top and bottom of sizing areas...
385 if (get_vertical_ticks(image
->rows
, 0, H
, transparent
, true, &yDivs
[0],
386 &yDivs
[1], &errorMsg
, &numYDivs
, true) != NO_ERROR
) {
387 errorPixel
= yDivs
[0];
392 // Find left and right of padding area...
393 if (get_horizontal_ticks(image
->rows
[H
-1], W
, transparent
, false, &image
->info9Patch
.paddingLeft
,
394 &image
->info9Patch
.paddingRight
, &errorMsg
, NULL
, false) != NO_ERROR
) {
395 errorPixel
= image
->info9Patch
.paddingLeft
;
396 errorEdge
= "bottom";
400 // Find top and bottom of padding area...
401 if (get_vertical_ticks(image
->rows
, (W
-1)*4, H
, transparent
, false, &image
->info9Patch
.paddingTop
,
402 &image
->info9Patch
.paddingBottom
, &errorMsg
, NULL
, false) != NO_ERROR
) {
403 errorPixel
= image
->info9Patch
.paddingTop
;
408 // Copy patch data into image
409 image
->info9Patch
.numXDivs
= numXDivs
;
410 image
->info9Patch
.numYDivs
= numYDivs
;
411 image
->info9Patch
.xDivs
= xDivs
;
412 image
->info9Patch
.yDivs
= yDivs
;
414 // If padding is not yet specified, take values from size.
415 if (image
->info9Patch
.paddingLeft
< 0) {
416 image
->info9Patch
.paddingLeft
= xDivs
[0];
417 image
->info9Patch
.paddingRight
= W
- 2 - xDivs
[1];
419 // Adjust value to be correct!
420 image
->info9Patch
.paddingRight
= W
- 2 - image
->info9Patch
.paddingRight
;
422 if (image
->info9Patch
.paddingTop
< 0) {
423 image
->info9Patch
.paddingTop
= yDivs
[0];
424 image
->info9Patch
.paddingBottom
= H
- 2 - yDivs
[1];
426 // Adjust value to be correct!
427 image
->info9Patch
.paddingBottom
= H
- 2 - image
->info9Patch
.paddingBottom
;
430 NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName
,
431 image
->info9Patch
.xDivs
[0], image
->info9Patch
.xDivs
[1],
432 image
->info9Patch
.yDivs
[0], image
->info9Patch
.yDivs
[1]));
433 NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName
,
434 image
->info9Patch
.paddingLeft
, image
->info9Patch
.paddingRight
,
435 image
->info9Patch
.paddingTop
, image
->info9Patch
.paddingBottom
));
437 // Remove frame from image.
438 image
->rows
= (png_bytepp
)malloc((H
-2) * png_sizeof(png_bytep
));
439 for (i
=0; i
<(H
-2); i
++) {
440 image
->rows
[i
] = image
->allocRows
[i
+1];
441 memmove(image
->rows
[i
], image
->rows
[i
]+4, (W
-2)*4);
448 // Figure out the number of rows and columns in the N-patch
449 numCols
= numXDivs
+ 1;
450 if (xDivs
[0] == 0) { // Column 1 is strechable
453 if (xDivs
[numXDivs
- 1] == W
) {
456 numRows
= numYDivs
+ 1;
457 if (yDivs
[0] == 0) { // Row 1 is strechable
460 if (yDivs
[numYDivs
- 1] == H
) {
463 numColors
= numRows
* numCols
;
464 image
->info9Patch
.numColors
= numColors
;
465 image
->info9Patch
.colors
= (uint32_t*)malloc(numColors
* sizeof(uint32_t));
467 // Fill in color information for each patch.
472 // The first row always starts with the top being at y=0 and the bottom
473 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
474 // the first row is stretchable along the Y axis, otherwise it is fixed.
475 // The last row always ends with the bottom being bitmap.height and the top
476 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
477 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
478 // the Y axis, otherwise it is fixed.
480 // The first and last columns are similarly treated with respect to the X
483 // The above is to help explain some of the special casing that goes on the
486 // The initial yDiv and whether the first row is considered stretchable or
487 // not depends on whether yDiv[0] was zero or not.
488 for (j
= (yDivs
[0] == 0 ? 1 : 0);
489 j
<= numYDivs
&& top
< H
;
497 // The initial xDiv and whether the first column is considered
498 // stretchable or not depends on whether xDiv[0] was zero or not.
499 for (i
= xDivs
[0] == 0 ? 1 : 0;
500 i
<= numXDivs
&& left
< W
;
507 c
= get_color(image
->rows
, left
, top
, right
- 1, bottom
- 1);
508 image
->info9Patch
.colors
[colorIndex
++] = c
;
509 NOISY(if (c
!= Res_png_9patch::NO_COLOR
) hasColor
= true);
515 assert(colorIndex
== numColors
);
517 for (i
=0; i
<numColors
; i
++) {
519 if (i
== 0) printf("Colors in %s:\n ", imageName
);
520 printf(" #%08x", image
->info9Patch
.colors
[i
]);
521 if (i
== numColors
- 1) printf("\n");
525 image
->is9Patch
= true;
526 image
->info9Patch
.deviceToFile();
531 "ERROR: 9-patch image %s malformed.\n"
532 " %s.\n", imageName
, errorMsg
);
533 if (errorPixel
>= 0) {
535 " Found at pixel #%d along %s edge.\n", errorPixel
, errorEdge
);
538 " Found along %s edge.\n", errorEdge
);
540 return UNKNOWN_ERROR
;
545 static void checkNinePatchSerialization(Res_png_9patch
* inPatch
, void * data
)
547 if (sizeof(void*) != sizeof(int32_t)) {
548 // can't deserialize on a non-32 bit system
551 size_t patchSize
= inPatch
->serializedSize();
552 void * newData
= malloc(patchSize
);
553 memcpy(newData
, data
, patchSize
);
554 Res_png_9patch
* outPatch
= inPatch
->deserialize(newData
);
555 // deserialization is done in place, so outPatch == newData
556 assert(outPatch
== newData
);
557 assert(outPatch
->numXDivs
== inPatch
->numXDivs
);
558 assert(outPatch
->numYDivs
== inPatch
->numYDivs
);
559 assert(outPatch
->paddingLeft
== inPatch
->paddingLeft
);
560 assert(outPatch
->paddingRight
== inPatch
->paddingRight
);
561 assert(outPatch
->paddingTop
== inPatch
->paddingTop
);
562 assert(outPatch
->paddingBottom
== inPatch
->paddingBottom
);
563 for (int i
= 0; i
< outPatch
->numXDivs
; i
++) {
564 assert(outPatch
->xDivs
[i
] == inPatch
->xDivs
[i
]);
566 for (int i
= 0; i
< outPatch
->numYDivs
; i
++) {
567 assert(outPatch
->yDivs
[i
] == inPatch
->yDivs
[i
]);
569 for (int i
= 0; i
< outPatch
->numColors
; i
++) {
570 assert(outPatch
->colors
[i
] == inPatch
->colors
[i
]);
575 static bool patch_equals(Res_png_9patch
& patch1
, Res_png_9patch
& patch2
) {
576 if (!(patch1
.numXDivs
== patch2
.numXDivs
&&
577 patch1
.numYDivs
== patch2
.numYDivs
&&
578 patch1
.numColors
== patch2
.numColors
&&
579 patch1
.paddingLeft
== patch2
.paddingLeft
&&
580 patch1
.paddingRight
== patch2
.paddingRight
&&
581 patch1
.paddingTop
== patch2
.paddingTop
&&
582 patch1
.paddingBottom
== patch2
.paddingBottom
)) {
585 for (int i
= 0; i
< patch1
.numColors
; i
++) {
586 if (patch1
.colors
[i
] != patch2
.colors
[i
]) {
590 for (int i
= 0; i
< patch1
.numXDivs
; i
++) {
591 if (patch1
.xDivs
[i
] != patch2
.xDivs
[i
]) {
595 for (int i
= 0; i
< patch1
.numYDivs
; i
++) {
596 if (patch1
.yDivs
[i
] != patch2
.yDivs
[i
]) {
603 static void dump_image(int w
, int h
, png_bytepp rows
, int color_type
)
605 int i
, j
, rr
, gg
, bb
, aa
;
608 if (color_type
== PNG_COLOR_TYPE_PALETTE
|| color_type
== PNG_COLOR_TYPE_GRAY
) {
610 } else if (color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
) {
612 } else if (color_type
== PNG_COLOR_TYPE_RGB
|| color_type
== PNG_COLOR_TYPE_RGB_ALPHA
) {
613 // We use a padding byte even when there is no alpha
616 printf("Unknown color type %d.\n", color_type
);
619 for (j
= 0; j
< h
; j
++) {
620 png_bytep row
= rows
[j
];
621 for (i
= 0; i
< w
; i
++) {
629 printf("Row %d:", j
);
636 printf(" (%d %d", rr
, gg
);
639 printf(" (%d %d %d)", rr
, gg
, bb
);
642 printf(" (%d %d %d %d)", rr
, gg
, bb
, aa
);
652 #define MAX(a,b) ((a)>(b)?(a):(b))
653 #define ABS(a) ((a)<0?-(a):(a))
655 static void analyze_image(const char *imageName
, image_info
&imageInfo
, int grayscaleTolerance
,
656 png_colorp rgbPalette
, png_bytep alphaPalette
,
657 int *paletteEntries
, bool *hasTransparency
, int *colorType
,
660 int w
= imageInfo
.width
;
661 int h
= imageInfo
.height
;
662 int i
, j
, rr
, gg
, bb
, aa
, idx
;
663 uint32_t colors
[256], col
;
665 int maxGrayDeviation
= 0;
667 bool isOpaque
= true;
668 bool isPalette
= true;
669 bool isGrayscale
= true;
671 // Scan the entire image and determine if:
672 // 1. Every pixel has R == G == B (grayscale)
673 // 2. Every pixel has A == 255 (opaque)
674 // 3. There are no more than 256 distinct RGBA colors
676 // NOISY(printf("Initial image data:\n"));
677 // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
679 for (j
= 0; j
< h
; j
++) {
680 png_bytep row
= imageInfo
.rows
[j
];
681 png_bytep out
= outRows
[j
];
682 for (i
= 0; i
< w
; i
++) {
688 int odev
= maxGrayDeviation
;
689 maxGrayDeviation
= MAX(ABS(rr
- gg
), maxGrayDeviation
);
690 maxGrayDeviation
= MAX(ABS(gg
- bb
), maxGrayDeviation
);
691 maxGrayDeviation
= MAX(ABS(bb
- rr
), maxGrayDeviation
);
692 if (maxGrayDeviation
> odev
) {
693 NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
694 maxGrayDeviation
, i
, j
, rr
, gg
, bb
, aa
));
697 // Check if image is really grayscale
699 if (rr
!= gg
|| rr
!= bb
) {
700 NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
701 i
, j
, rr
, gg
, bb
, aa
));
706 // Check if image is really opaque
709 NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
710 i
, j
, rr
, gg
, bb
, aa
));
715 // Check if image is really <= 256 colors
717 col
= (uint32_t) ((rr
<< 24) | (gg
<< 16) | (bb
<< 8) | aa
);
719 for (idx
= 0; idx
< num_colors
; idx
++) {
720 if (colors
[idx
] == col
) {
726 // Write the palette index for the pixel to outRows optimistically
727 // We might overwrite it later if we decide to encode as gray or
731 if (num_colors
== 256) {
732 NOISY(printf("Found 257th color at %d, %d\n", i
, j
));
735 colors
[num_colors
++] = col
;
743 *hasTransparency
= !isOpaque
;
744 int bpp
= isOpaque
? 3 : 4;
745 int paletteSize
= w
* h
+ bpp
* num_colors
;
747 NOISY(printf("isGrayscale = %s\n", isGrayscale
? "true" : "false"));
748 NOISY(printf("isOpaque = %s\n", isOpaque
? "true" : "false"));
749 NOISY(printf("isPalette = %s\n", isPalette
? "true" : "false"));
750 NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
751 paletteSize
, 2 * w
* h
, bpp
* w
* h
));
752 NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation
, grayscaleTolerance
));
754 // Choose the best color type for the image.
755 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
756 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
757 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
758 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
759 // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
762 *colorType
= PNG_COLOR_TYPE_GRAY
; // 1 byte/pixel
764 // Use a simple heuristic to determine whether using a palette will
765 // save space versus using gray + alpha for each pixel.
766 // This doesn't take into account chunk overhead, filtering, LZ
768 if (isPalette
&& (paletteSize
< 2 * w
* h
)) {
769 *colorType
= PNG_COLOR_TYPE_PALETTE
; // 1 byte/pixel + 4 bytes/color
771 *colorType
= PNG_COLOR_TYPE_GRAY_ALPHA
; // 2 bytes per pixel
774 } else if (isPalette
&& (paletteSize
< bpp
* w
* h
)) {
775 *colorType
= PNG_COLOR_TYPE_PALETTE
;
777 if (maxGrayDeviation
<= grayscaleTolerance
) {
778 printf("%s: forcing image to gray (max deviation = %d)\n", imageName
, maxGrayDeviation
);
779 *colorType
= isOpaque
? PNG_COLOR_TYPE_GRAY
: PNG_COLOR_TYPE_GRAY_ALPHA
;
781 *colorType
= isOpaque
? PNG_COLOR_TYPE_RGB
: PNG_COLOR_TYPE_RGB_ALPHA
;
785 // Perform postprocessing of the image or palette data based on the final
788 if (*colorType
== PNG_COLOR_TYPE_PALETTE
) {
789 // Create separate RGB and Alpha palettes and set the number of colors
790 *paletteEntries
= num_colors
;
792 // Create the RGB and alpha palettes
793 for (int idx
= 0; idx
< num_colors
; idx
++) {
795 rgbPalette
[idx
].red
= (png_byte
) ((col
>> 24) & 0xff);
796 rgbPalette
[idx
].green
= (png_byte
) ((col
>> 16) & 0xff);
797 rgbPalette
[idx
].blue
= (png_byte
) ((col
>> 8) & 0xff);
798 alphaPalette
[idx
] = (png_byte
) (col
& 0xff);
800 } else if (*colorType
== PNG_COLOR_TYPE_GRAY
|| *colorType
== PNG_COLOR_TYPE_GRAY_ALPHA
) {
801 // If the image is gray or gray + alpha, compact the pixels into outRows
802 for (j
= 0; j
< h
; j
++) {
803 png_bytep row
= imageInfo
.rows
[j
];
804 png_bytep out
= outRows
[j
];
805 for (i
= 0; i
< w
; i
++) {
814 *out
++ = (png_byte
) (rr
* 0.2126f
+ gg
* 0.7152f
+ bb
* 0.0722f
);
825 static void write_png(const char* imageName
,
826 png_structp write_ptr
, png_infop write_info
,
827 image_info
& imageInfo
, int grayscaleTolerance
)
829 bool optimize
= true;
830 png_uint_32 width
, height
;
832 int bit_depth
, interlace_type
, compression_type
;
835 png_unknown_chunk unknowns
[1];
837 png_bytepp outRows
= (png_bytepp
) malloc((int) imageInfo
.height
* png_sizeof(png_bytep
));
838 if (outRows
== (png_bytepp
) 0) {
839 printf("Can't allocate output buffer!\n");
842 for (i
= 0; i
< (int) imageInfo
.height
; i
++) {
843 outRows
[i
] = (png_bytep
) malloc(2 * (int) imageInfo
.width
);
844 if (outRows
[i
] == (png_bytep
) 0) {
845 printf("Can't allocate output buffer!\n");
850 png_set_compression_level(write_ptr
, Z_BEST_COMPRESSION
);
852 NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName
,
853 (int) imageInfo
.width
, (int) imageInfo
.height
));
855 png_color rgbPalette
[256];
856 png_byte alphaPalette
[256];
857 bool hasTransparency
;
860 analyze_image(imageName
, imageInfo
, grayscaleTolerance
, rgbPalette
, alphaPalette
,
861 &paletteEntries
, &hasTransparency
, &color_type
, outRows
);
862 switch (color_type
) {
863 case PNG_COLOR_TYPE_PALETTE
:
864 NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
865 imageName
, paletteEntries
,
866 hasTransparency
? " (with alpha)" : ""));
868 case PNG_COLOR_TYPE_GRAY
:
869 NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName
));
871 case PNG_COLOR_TYPE_GRAY_ALPHA
:
872 NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName
));
874 case PNG_COLOR_TYPE_RGB
:
875 NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName
));
877 case PNG_COLOR_TYPE_RGB_ALPHA
:
878 NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName
));
882 png_set_IHDR(write_ptr
, write_info
, imageInfo
.width
, imageInfo
.height
,
883 8, color_type
, PNG_INTERLACE_NONE
,
884 PNG_COMPRESSION_TYPE_DEFAULT
, PNG_FILTER_TYPE_DEFAULT
);
886 if (color_type
== PNG_COLOR_TYPE_PALETTE
) {
887 png_set_PLTE(write_ptr
, write_info
, rgbPalette
, paletteEntries
);
888 if (hasTransparency
) {
889 png_set_tRNS(write_ptr
, write_info
, alphaPalette
, paletteEntries
, (png_color_16p
) 0);
891 png_set_filter(write_ptr
, 0, PNG_NO_FILTERS
);
893 png_set_filter(write_ptr
, 0, PNG_ALL_FILTERS
);
896 if (imageInfo
.is9Patch
) {
897 NOISY(printf("Adding 9-patch info...\n"));
898 strcpy((char*)unknowns
[0].name
, "npTc");
899 unknowns
[0].data
= (png_byte
*)imageInfo
.info9Patch
.serialize();
900 unknowns
[0].size
= imageInfo
.info9Patch
.serializedSize();
901 // TODO: remove the check below when everything works
902 checkNinePatchSerialization(&imageInfo
.info9Patch
, unknowns
[0].data
);
903 png_set_keep_unknown_chunks(write_ptr
, PNG_HANDLE_CHUNK_ALWAYS
,
904 (png_byte
*)"npTc", 1);
905 png_set_unknown_chunks(write_ptr
, write_info
, unknowns
, 1);
906 // XXX I can't get this to work without forcibly changing
907 // the location to what I want... which apparently is supposed
908 // to be a private API, but everything else I have tried results
909 // in the location being set to what I -last- wrote so I never
911 png_set_unknown_chunk_location(write_ptr
, write_info
, 0, PNG_HAVE_PLTE
);
914 png_write_info(write_ptr
, write_info
);
917 if (color_type
== PNG_COLOR_TYPE_RGB
|| color_type
== PNG_COLOR_TYPE_RGB_ALPHA
) {
918 png_set_filler(write_ptr
, 0, PNG_FILLER_AFTER
);
919 rows
= imageInfo
.rows
;
923 png_write_image(write_ptr
, rows
);
925 // NOISY(printf("Final image data:\n"));
926 // dump_image(imageInfo.width, imageInfo.height, rows, color_type);
928 png_write_end(write_ptr
, write_info
);
930 for (i
= 0; i
< (int) imageInfo
.height
; i
++) {
935 png_get_IHDR(write_ptr
, write_info
, &width
, &height
,
936 &bit_depth
, &color_type
, &interlace_type
,
937 &compression_type
, NULL
);
939 NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
940 (int)width
, (int)height
, bit_depth
, color_type
, interlace_type
,
944 status_t
preProcessImage(Bundle
* bundle
, const sp
<AaptAssets
>& assets
,
945 const sp
<AaptFile
>& file
, String8
* outNewLeafName
)
947 String8
ext(file
->getPath().getPathExtension());
949 // We currently only process PNG images.
950 if (strcmp(ext
.string(), ".png") != 0) {
954 // Example of renaming a file:
955 //*outNewLeafName = file->getPath().getBasePath().getFileName();
956 //outNewLeafName->append(".nupng");
958 String8
printableName(file
->getPrintableSource());
960 png_structp read_ptr
= NULL
;
961 png_infop read_info
= NULL
;
964 image_info imageInfo
;
966 png_structp write_ptr
= NULL
;
967 png_infop write_info
= NULL
;
969 status_t error
= UNKNOWN_ERROR
;
971 const size_t nameLen
= file
->getPath().length();
973 fp
= fopen(file
->getSourceFile().string(), "rb");
975 fprintf(stderr
, "%s: ERROR: Unable to open PNG file\n", printableName
.string());
979 read_ptr
= png_create_read_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
980 (png_error_ptr
)NULL
);
985 read_info
= png_create_info_struct(read_ptr
);
990 if (setjmp(png_jmpbuf(read_ptr
))) {
994 png_init_io(read_ptr
, fp
);
996 read_png(printableName
.string(), read_ptr
, read_info
, &imageInfo
);
999 const char* name
= file
->getPath().string();
1000 if (name
[nameLen
-5] == '9' && name
[nameLen
-6] == '.') {
1001 if (do_9patch(printableName
.string(), &imageInfo
) != NO_ERROR
) {
1007 write_ptr
= png_create_write_struct(PNG_LIBPNG_VER_STRING
, 0, (png_error_ptr
)NULL
,
1008 (png_error_ptr
)NULL
);
1014 write_info
= png_create_info_struct(write_ptr
);
1020 png_set_write_fn(write_ptr
, (void*)file
.get(),
1021 png_write_aapt_file
, png_flush_aapt_file
);
1023 if (setjmp(png_jmpbuf(write_ptr
)))
1028 write_png(printableName
.string(), write_ptr
, write_info
, imageInfo
,
1029 bundle
->getGrayscaleTolerance());
1033 if (bundle
->getVerbose()) {
1034 fseek(fp
, 0, SEEK_END
);
1035 size_t oldSize
= (size_t)ftell(fp
);
1036 size_t newSize
= file
->getSize();
1037 float factor
= ((float)newSize
)/oldSize
;
1038 int percent
= (int)(factor
*100);
1039 printf(" (processed image %s: %d%% size of source)\n", printableName
.string(), percent
);
1044 png_destroy_read_struct(&read_ptr
, &read_info
, (png_infopp
)NULL
);
1050 png_destroy_write_struct(&write_ptr
, &write_info
);
1053 if (error
!= NO_ERROR
) {
1054 fprintf(stderr
, "ERROR: Failure processing PNG image %s\n",
1055 file
->getPrintableSource().string());
1062 status_t
postProcessImage(const sp
<AaptAssets
>& assets
,
1063 ResourceTable
* table
, const sp
<AaptFile
>& file
)
1065 String8
ext(file
->getPath().getPathExtension());
1067 // At this point, now that we have all the resource data, all we need to
1068 // do is compile XML files.
1069 if (strcmp(ext
.string(), ".xml") == 0) {
1070 return compileXmlFile(assets
, file
, table
);