Home | History | Annotate | Download | only in aapt
      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), 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         free(info9Patch.xDivs);
     48         free(info9Patch.yDivs);
     49         free(info9Patch.colors);
     50     }
     51 
     52     png_uint_32 width;
     53     png_uint_32 height;
     54     png_bytepp rows;
     55 
     56     // 9-patch info.
     57     bool is9Patch;
     58     Res_png_9patch info9Patch;
     59 
     60     png_uint_32 allocHeight;
     61     png_bytepp allocRows;
     62 };
     63 
     64 static void read_png(const char* imageName,
     65                      png_structp read_ptr, png_infop read_info,
     66                      image_info* outImageInfo)
     67 {
     68     int color_type;
     69     int bit_depth, interlace_type, compression_type;
     70     int i;
     71 
     72     png_read_info(read_ptr, read_info);
     73 
     74     png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
     75        &outImageInfo->height, &bit_depth, &color_type,
     76        &interlace_type, &compression_type, NULL);
     77 
     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);
     81 
     82     if (color_type == PNG_COLOR_TYPE_PALETTE)
     83         png_set_palette_to_rgb(read_ptr);
     84 
     85     if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
     86         png_set_gray_1_2_4_to_8(read_ptr);
     87 
     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);
     91     }
     92 
     93     if (bit_depth == 16)
     94         png_set_strip_16(read_ptr);
     95 
     96     if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
     97         png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
     98 
     99     if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
    100         png_set_gray_to_rgb(read_ptr);
    101 
    102     png_read_update_info(read_ptr, read_info);
    103 
    104     outImageInfo->rows = (png_bytepp)malloc(
    105         outImageInfo->height * png_sizeof(png_bytep));
    106     outImageInfo->allocHeight = outImageInfo->height;
    107     outImageInfo->allocRows = outImageInfo->rows;
    108 
    109     png_set_rows(read_ptr, read_info, outImageInfo->rows);
    110 
    111     for (i = 0; i < (int)outImageInfo->height; i++)
    112     {
    113         outImageInfo->rows[i] = (png_bytep)
    114             malloc(png_get_rowbytes(read_ptr, read_info));
    115     }
    116 
    117     png_read_image(read_ptr, outImageInfo->rows);
    118 
    119     png_read_end(read_ptr, read_info);
    120 
    121     NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
    122                  imageName,
    123                  (int)outImageInfo->width, (int)outImageInfo->height,
    124                  bit_depth, color_type,
    125                  interlace_type, compression_type));
    126 
    127     png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
    128        &outImageInfo->height, &bit_depth, &color_type,
    129        &interlace_type, &compression_type, NULL);
    130 }
    131 
    132 static bool is_tick(png_bytep p, bool transparent, const char** outError)
    133 {
    134     if (transparent) {
    135         if (p[3] == 0) {
    136             return false;
    137         }
    138         if (p[3] != 0xff) {
    139             *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
    140             return false;
    141         }
    142         if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
    143             *outError = "Ticks in transparent frame must be black";
    144         }
    145         return true;
    146     }
    147 
    148     if (p[3] != 0xFF) {
    149         *outError = "White frame must be a solid color (no alpha)";
    150     }
    151     if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) {
    152         return false;
    153     }
    154     if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
    155         *outError = "Ticks in white frame must be black";
    156         return false;
    157     }
    158     return true;
    159 }
    160 
    161 enum {
    162     TICK_START,
    163     TICK_INSIDE_1,
    164     TICK_OUTSIDE_1
    165 };
    166 
    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)
    171 {
    172     int i;
    173     *outLeft = *outRight = -1;
    174     int state = TICK_START;
    175     bool found = false;
    176 
    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)) {
    181                 *outLeft = i-1;
    182                 *outRight = width-2;
    183                 found = true;
    184                 if (outDivs != NULL) {
    185                     *outDivs += 2;
    186                 }
    187                 state = TICK_INSIDE_1;
    188             } else if (state == TICK_OUTSIDE_1) {
    189                 *outError = "Can't have more than one marked region along edge";
    190                 *outLeft = i;
    191                 return UNKNOWN_ERROR;
    192             }
    193         } else if (*outError == NULL) {
    194             if (state == TICK_INSIDE_1) {
    195                 // We're done with this div.  Move on to the next.
    196                 *outRight = i-1;
    197                 outRight += 2;
    198                 outLeft += 2;
    199                 state = TICK_OUTSIDE_1;
    200             }
    201         } else {
    202             *outLeft = i;
    203             return UNKNOWN_ERROR;
    204         }
    205     }
    206 
    207     if (required && !found) {
    208         *outError = "No marked region found along edge";
    209         *outLeft = -1;
    210         return UNKNOWN_ERROR;
    211     }
    212 
    213     return NO_ERROR;
    214 }
    215 
    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)
    220 {
    221     int i;
    222     *outTop = *outBottom = -1;
    223     int state = TICK_START;
    224     bool found = false;
    225 
    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)) {
    230                 *outTop = i-1;
    231                 *outBottom = height-2;
    232                 found = true;
    233                 if (outDivs != NULL) {
    234                     *outDivs += 2;
    235                 }
    236                 state = TICK_INSIDE_1;
    237             } else if (state == TICK_OUTSIDE_1) {
    238                 *outError = "Can't have more than one marked region along edge";
    239                 *outTop = i;
    240                 return UNKNOWN_ERROR;
    241             }
    242         } else if (*outError == NULL) {
    243             if (state == TICK_INSIDE_1) {
    244                 // We're done with this div.  Move on to the next.
    245                 *outBottom = i-1;
    246                 outTop += 2;
    247                 outBottom += 2;
    248                 state = TICK_OUTSIDE_1;
    249             }
    250         } else {
    251             *outTop = i;
    252             return UNKNOWN_ERROR;
    253         }
    254     }
    255 
    256     if (required && !found) {
    257         *outError = "No marked region found along edge";
    258         *outTop = -1;
    259         return UNKNOWN_ERROR;
    260     }
    261 
    262     return NO_ERROR;
    263 }
    264 
    265 static uint32_t get_color(
    266     png_bytepp rows, int left, int top, int right, int bottom)
    267 {
    268     png_bytep color = rows[top] + left*4;
    269 
    270     if (left > right || top > bottom) {
    271         return Res_png_9patch::TRANSPARENT_COLOR;
    272     }
    273 
    274     while (top <= bottom) {
    275         for (int i = left; i <= right; i++) {
    276             png_bytep p = rows[top]+i*4;
    277             if (color[3] == 0) {
    278                 if (p[3] != 0) {
    279                     return Res_png_9patch::NO_COLOR;
    280                 }
    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;
    284             }
    285         }
    286         top++;
    287     }
    288 
    289     if (color[3] == 0) {
    290         return Res_png_9patch::TRANSPARENT_COLOR;
    291     }
    292     return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
    293 }
    294 
    295 static void select_patch(
    296     int which, int front, int back, int size, int* start, int* end)
    297 {
    298     switch (which) {
    299     case 0:
    300         *start = 0;
    301         *end = front-1;
    302         break;
    303     case 1:
    304         *start = front;
    305         *end = back-1;
    306         break;
    307     case 2:
    308         *start = back;
    309         *end = size-1;
    310         break;
    311     }
    312 }
    313 
    314 static uint32_t get_color(image_info* image, int hpatch, int vpatch)
    315 {
    316     int left, right, top, bottom;
    317     select_patch(
    318         hpatch, image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
    319         image->width, &left, &right);
    320     select_patch(
    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));
    327     return c;
    328 }
    329 
    330 static status_t do_9patch(const char* imageName, image_info* image)
    331 {
    332     image->is9Patch = true;
    333 
    334     int W = image->width;
    335     int H = image->height;
    336     int i, j;
    337 
    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;
    344     int8_t numColors;
    345     int numRows;
    346     int numCols;
    347     int top;
    348     int left;
    349     int right;
    350     int bottom;
    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;
    355 
    356     png_bytep p = image->rows[0];
    357     bool transparent = p[3] == 0;
    358     bool hasColor = false;
    359 
    360     const char* errorMsg = NULL;
    361     int errorPixel = -1;
    362     const char* errorEdge = NULL;
    363 
    364     int colorIndex = 0;
    365 
    366     // Validate size...
    367     if (W < 3 || H < 3) {
    368         errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
    369         goto getout;
    370     }
    371 
    372     // Validate frame...
    373     if (!transparent &&
    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";
    376         goto getout;
    377     }
    378 
    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];
    383         errorEdge = "top";
    384         goto getout;
    385     }
    386 
    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];
    391         errorEdge = "left";
    392         goto getout;
    393     }
    394 
    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";
    400         goto getout;
    401     }
    402 
    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;
    407         errorEdge = "right";
    408         goto getout;
    409     }
    410 
    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;
    416 
    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];
    421     } else {
    422         // Adjust value to be correct!
    423         image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
    424     }
    425     if (image->info9Patch.paddingTop < 0) {
    426         image->info9Patch.paddingTop = yDivs[0];
    427         image->info9Patch.paddingBottom = H - 2 - yDivs[1];
    428     } else {
    429         // Adjust value to be correct!
    430         image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
    431     }
    432 
    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));
    439 
    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);
    445     }
    446     image->width -= 2;
    447     W = image->width;
    448     image->height -= 2;
    449     H = image->height;
    450 
    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
    454         numCols--;
    455     }
    456     if (xDivs[numXDivs - 1] == W) {
    457         numCols--;
    458     }
    459     numRows = numYDivs + 1;
    460     if (yDivs[0] == 0) {  // Row 1 is strechable
    461         numRows--;
    462     }
    463     if (yDivs[numYDivs - 1] == H) {
    464         numRows--;
    465     }
    466 
    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";
    471         goto getout;
    472     }
    473 
    474     numColors = numRows * numCols;
    475     image->info9Patch.numColors = numColors;
    476     image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
    477 
    478     // Fill in color information for each patch.
    479 
    480     uint32_t c;
    481     top = 0;
    482 
    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.
    490     //
    491     // The first and last columns are similarly treated with respect to the X
    492     // axis.
    493     //
    494     // The above is to help explain some of the special casing that goes on the
    495     // code below.
    496 
    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;
    501           j++) {
    502         if (j == numYDivs) {
    503             bottom = H;
    504         } else {
    505             bottom = yDivs[j];
    506         }
    507         left = 0;
    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;
    512               i++) {
    513             if (i == numXDivs) {
    514                 right = W;
    515             } else {
    516                 right = xDivs[i];
    517             }
    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);
    521             left = right;
    522         }
    523         top = bottom;
    524     }
    525 
    526     assert(colorIndex == numColors);
    527 
    528     for (i=0; i<numColors; i++) {
    529         if (hasColor) {
    530             if (i == 0) printf("Colors in %s:\n ", imageName);
    531             printf(" #%08x", image->info9Patch.colors[i]);
    532             if (i == numColors - 1) printf("\n");
    533         }
    534     }
    535 
    536     image->is9Patch = true;
    537     image->info9Patch.deviceToFile();
    538 
    539 getout:
    540     if (errorMsg) {
    541         fprintf(stderr,
    542             "ERROR: 9-patch image %s malformed.\n"
    543             "       %s.\n", imageName, errorMsg);
    544         if (errorEdge != NULL) {
    545             if (errorPixel >= 0) {
    546                 fprintf(stderr,
    547                     "       Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
    548             } else {
    549                 fprintf(stderr,
    550                     "       Found along %s edge.\n", errorEdge);
    551             }
    552         }
    553         return UNKNOWN_ERROR;
    554     }
    555     return NO_ERROR;
    556 }
    557 
    558 static void checkNinePatchSerialization(Res_png_9patch* inPatch,  void * data)
    559 {
    560     if (sizeof(void*) != sizeof(int32_t)) {
    561         // can't deserialize on a non-32 bit system
    562         return;
    563     }
    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]);
    578     }
    579     for (int i = 0; i < outPatch->numYDivs; i++) {
    580         assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
    581     }
    582     for (int i = 0; i < outPatch->numColors; i++) {
    583         assert(outPatch->colors[i] == inPatch->colors[i]);
    584     }
    585     free(newData);
    586 }
    587 
    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)) {
    596             return false;
    597     }
    598     for (int i = 0; i < patch1.numColors; i++) {
    599         if (patch1.colors[i] != patch2.colors[i]) {
    600             return false;
    601         }
    602     }
    603     for (int i = 0; i < patch1.numXDivs; i++) {
    604         if (patch1.xDivs[i] != patch2.xDivs[i]) {
    605             return false;
    606         }
    607     }
    608     for (int i = 0; i < patch1.numYDivs; i++) {
    609         if (patch1.yDivs[i] != patch2.yDivs[i]) {
    610             return false;
    611         }
    612     }
    613     return true;
    614 }
    615 
    616 static void dump_image(int w, int h, png_bytepp rows, int color_type)
    617 {
    618     int i, j, rr, gg, bb, aa;
    619 
    620     int bpp;
    621     if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
    622         bpp = 1;
    623     } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
    624         bpp = 2;
    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
    627         bpp = 4;
    628     } else {
    629         printf("Unknown color type %d.\n", color_type);
    630     }
    631 
    632     for (j = 0; j < h; j++) {
    633         png_bytep row = rows[j];
    634         for (i = 0; i < w; i++) {
    635             rr = row[0];
    636             gg = row[1];
    637             bb = row[2];
    638             aa = row[3];
    639             row += bpp;
    640 
    641             if (i == 0) {
    642                 printf("Row %d:", j);
    643             }
    644             switch (bpp) {
    645             case 1:
    646                 printf(" (%d)", rr);
    647                 break;
    648             case 2:
    649                 printf(" (%d %d", rr, gg);
    650                 break;
    651             case 3:
    652                 printf(" (%d %d %d)", rr, gg, bb);
    653                 break;
    654             case 4:
    655                 printf(" (%d %d %d %d)", rr, gg, bb, aa);
    656                 break;
    657             }
    658             if (i == (w - 1)) {
    659                 NOISY(printf("\n"));
    660             }
    661         }
    662     }
    663 }
    664 
    665 #define MAX(a,b) ((a)>(b)?(a):(b))
    666 #define ABS(a)   ((a)<0?-(a):(a))
    667 
    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,
    671                           png_bytepp outRows)
    672 {
    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;
    677     int num_colors = 0;
    678     int maxGrayDeviation = 0;
    679 
    680     bool isOpaque = true;
    681     bool isPalette = true;
    682     bool isGrayscale = true;
    683 
    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
    688 
    689     // NOISY(printf("Initial image data:\n"));
    690     // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
    691 
    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++) {
    696             rr = *row++;
    697             gg = *row++;
    698             bb = *row++;
    699             aa = *row++;
    700 
    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));
    708             }
    709 
    710             // Check if image is really grayscale
    711             if (isGrayscale) {
    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));
    715                     isGrayscale = false;
    716                 }
    717             }
    718 
    719             // Check if image is really opaque
    720             if (isOpaque) {
    721                 if (aa != 0xff) {
    722                     NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
    723                                  i, j, rr, gg, bb, aa));
    724                     isOpaque = false;
    725                 }
    726             }
    727 
    728             // Check if image is really <= 256 colors
    729             if (isPalette) {
    730                 col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
    731                 bool match = false;
    732                 for (idx = 0; idx < num_colors; idx++) {
    733                     if (colors[idx] == col) {
    734                         match = true;
    735                         break;
    736                     }
    737                 }
    738 
    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
    741                 // gray + alpha
    742                 *out++ = idx;
    743                 if (!match) {
    744                     if (num_colors == 256) {
    745                         NOISY(printf("Found 257th color at %d, %d\n", i, j));
    746                         isPalette = false;
    747                     } else {
    748                         colors[num_colors++] = col;
    749                     }
    750                 }
    751             }
    752         }
    753     }
    754 
    755     *paletteEntries = 0;
    756     *hasTransparency = !isOpaque;
    757     int bpp = isOpaque ? 3 : 4;
    758     int paletteSize = w * h + bpp * num_colors;
    759 
    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));
    766 
    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}
    773     if (isGrayscale) {
    774         if (isOpaque) {
    775             *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
    776         } else {
    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
    780             // compression, etc.
    781             if (isPalette && (paletteSize < 2 * w * h)) {
    782                 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
    783             } else {
    784                 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
    785             }
    786         }
    787     } else if (isPalette && (paletteSize < bpp * w * h)) {
    788         *colorType = PNG_COLOR_TYPE_PALETTE;
    789     } else {
    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;
    793         } else {
    794             *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
    795         }
    796     }
    797 
    798     // Perform postprocessing of the image or palette data based on the final
    799     // color type chosen
    800 
    801     if (*colorType == PNG_COLOR_TYPE_PALETTE) {
    802         // Create separate RGB and Alpha palettes and set the number of colors
    803         *paletteEntries = num_colors;
    804 
    805         // Create the RGB and alpha palettes
    806         for (int idx = 0; idx < num_colors; idx++) {
    807             col = 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);
    812         }
    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++) {
    819                 rr = *row++;
    820                 gg = *row++;
    821                 bb = *row++;
    822                 aa = *row++;
    823 
    824                 if (isGrayscale) {
    825                     *out++ = rr;
    826                 } else {
    827                     *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
    828                 }
    829                 if (!isOpaque) {
    830                     *out++ = aa;
    831                 }
    832            }
    833         }
    834     }
    835 }
    836 
    837 
    838 static void write_png(const char* imageName,
    839                       png_structp write_ptr, png_infop write_info,
    840                       image_info& imageInfo, int grayscaleTolerance)
    841 {
    842     bool optimize = true;
    843     png_uint_32 width, height;
    844     int color_type;
    845     int bit_depth, interlace_type, compression_type;
    846     int i;
    847 
    848     png_unknown_chunk unknowns[1];
    849     unknowns[0].data = NULL;
    850 
    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");
    854         exit(1);
    855     }
    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");
    860             exit(1);
    861         }
    862     }
    863 
    864     png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
    865 
    866     NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
    867           (int) imageInfo.width, (int) imageInfo.height));
    868 
    869     png_color rgbPalette[256];
    870     png_byte alphaPalette[256];
    871     bool hasTransparency;
    872     int paletteEntries;
    873 
    874     analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
    875                   &paletteEntries, &hasTransparency, &color_type, outRows);
    876 
    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;
    882     }
    883 
    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)" : ""));
    889         break;
    890     case PNG_COLOR_TYPE_GRAY:
    891         NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
    892         break;
    893     case PNG_COLOR_TYPE_GRAY_ALPHA:
    894         NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
    895         break;
    896     case PNG_COLOR_TYPE_RGB:
    897         NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
    898         break;
    899     case PNG_COLOR_TYPE_RGB_ALPHA:
    900         NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
    901         break;
    902     }
    903 
    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);
    907 
    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);
    912         }
    913        png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
    914     } else {
    915        png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
    916     }
    917 
    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
    932         // get written. :p
    933         png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
    934     }
    935 
    936     png_write_info(write_ptr, write_info);
    937 
    938     png_bytepp rows;
    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;
    942     } else {
    943         rows = outRows;
    944     }
    945     png_write_image(write_ptr, rows);
    946 
    947 //     NOISY(printf("Final image data:\n"));
    948 //     dump_image(imageInfo.width, imageInfo.height, rows, color_type);
    949 
    950     png_write_end(write_ptr, write_info);
    951 
    952     for (i = 0; i < (int) imageInfo.height; i++) {
    953         free(outRows[i]);
    954     }
    955     free(outRows);
    956     free(unknowns[0].data);
    957 
    958     png_get_IHDR(write_ptr, write_info, &width, &height,
    959        &bit_depth, &color_type, &interlace_type,
    960        &compression_type, NULL);
    961 
    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,
    964                  compression_type));
    965 }
    966 
    967 status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets,
    968                          const sp<AaptFile>& file, String8* outNewLeafName)
    969 {
    970     String8 ext(file->getPath().getPathExtension());
    971 
    972     // We currently only process PNG images.
    973     if (strcmp(ext.string(), ".png") != 0) {
    974         return NO_ERROR;
    975     }
    976 
    977     // Example of renaming a file:
    978     //*outNewLeafName = file->getPath().getBasePath().getFileName();
    979     //outNewLeafName->append(".nupng");
    980 
    981     String8 printableName(file->getPrintableSource());
    982 
    983     png_structp read_ptr = NULL;
    984     png_infop read_info = NULL;
    985     FILE* fp;
    986 
    987     image_info imageInfo;
    988 
    989     png_structp write_ptr = NULL;
    990     png_infop write_info = NULL;
    991 
    992     status_t error = UNKNOWN_ERROR;
    993 
    994     const size_t nameLen = file->getPath().length();
    995 
    996     fp = fopen(file->getSourceFile().string(), "rb");
    997     if (fp == NULL) {
    998         fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
    999         goto bail;
   1000     }
   1001 
   1002     read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
   1003                                         (png_error_ptr)NULL);
   1004     if (!read_ptr) {
   1005         goto bail;
   1006     }
   1007 
   1008     read_info = png_create_info_struct(read_ptr);
   1009     if (!read_info) {
   1010         goto bail;
   1011     }
   1012 
   1013     if (setjmp(png_jmpbuf(read_ptr))) {
   1014         goto bail;
   1015     }
   1016 
   1017     png_init_io(read_ptr, fp);
   1018 
   1019     read_png(printableName.string(), read_ptr, read_info, &imageInfo);
   1020 
   1021     if (nameLen > 6) {
   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) {
   1025                 goto bail;
   1026             }
   1027         }
   1028     }
   1029 
   1030     write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
   1031                                         (png_error_ptr)NULL);
   1032     if (!write_ptr)
   1033     {
   1034         goto bail;
   1035     }
   1036 
   1037     write_info = png_create_info_struct(write_ptr);
   1038     if (!write_info)
   1039     {
   1040         goto bail;
   1041     }
   1042 
   1043     png_set_write_fn(write_ptr, (void*)file.get(),
   1044                      png_write_aapt_file, png_flush_aapt_file);
   1045 
   1046     if (setjmp(png_jmpbuf(write_ptr)))
   1047     {
   1048         goto bail;
   1049     }
   1050 
   1051     write_png(printableName.string(), write_ptr, write_info, imageInfo,
   1052               bundle->getGrayscaleTolerance());
   1053 
   1054     error = NO_ERROR;
   1055 
   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);
   1063     }
   1064 
   1065 bail:
   1066     if (read_ptr) {
   1067         png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
   1068     }
   1069     if (fp) {
   1070         fclose(fp);
   1071     }
   1072     if (write_ptr) {
   1073         png_destroy_write_struct(&write_ptr, &write_info);
   1074     }
   1075 
   1076     if (error != NO_ERROR) {
   1077         fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
   1078                 file->getPrintableSource().string());
   1079     }
   1080     return error;
   1081 }
   1082 
   1083 
   1084 
   1085 status_t postProcessImage(const sp<AaptAssets>& assets,
   1086                           ResourceTable* table, const sp<AaptFile>& file)
   1087 {
   1088     String8 ext(file->getPath().getPathExtension());
   1089 
   1090     // At this point, now that we have all the resource data, all we need to
   1091     // do is compile XML files.
   1092     if (strcmp(ext.string(), ".xml") == 0) {
   1093         return compileXmlFile(assets, file, table);
   1094     }
   1095 
   1096     return NO_ERROR;
   1097 }
   1098