X-Git-Url: https://git.saurik.com/android/aapt.git/blobdiff_plain/3c80e5c0dc993f14ed11bca00e023ce8c073a7f4..b5a0aec21ed839862f44bbd6e0c24c0e3a3433a4:/Images.cpp?ds=sidebyside diff --git a/Images.cpp b/Images.cpp index 9d5937c..f2414dd 100644 --- a/Images.cpp +++ b/Images.cpp @@ -33,7 +33,7 @@ png_flush_aapt_file(png_structp png_ptr) // This holds an image as 8bpp RGBA. struct image_info { - image_info() : rows(NULL), hasTransparency(true), is9Patch(false), allocRows(NULL) { } + image_info() : rows(NULL), is9Patch(false), allocRows(NULL) { } ~image_info() { if (rows && rows != allocRows) { free(rows); @@ -44,14 +44,15 @@ struct image_info } free(allocRows); } + free(info9Patch.xDivs); + free(info9Patch.yDivs); + free(info9Patch.colors); } png_uint_32 width; png_uint_32 height; png_bytepp rows; - bool hasTransparency; - // 9-patch info. bool is9Patch; Res_png_9patch info9Patch; @@ -326,22 +327,6 @@ static uint32_t get_color(image_info* image, int hpatch, int vpatch) return c; } -static void examine_image(image_info* image) -{ - bool hasTrans = false; - for (int i=0; i<(int)image->height && !hasTrans; i++) { - png_bytep p = image->rows[i]; - for (int j=0; j<(int)image->width; j++) { - if (p[(j*4)+3] != 0xFF) { - hasTrans = true; - break; - } - } - } - - image->hasTransparency = hasTrans; -} - static status_t do_9patch(const char* imageName, image_info* image) { image->is9Patch = true; @@ -350,8 +335,8 @@ static status_t do_9patch(const char* imageName, image_info* image) int H = image->height; int i, j; - int maxSizeXDivs = (W / 2 + 1) * sizeof(int32_t); - int maxSizeYDivs = (H / 2 + 1) * sizeof(int32_t); + int maxSizeXDivs = W * sizeof(int32_t); + int maxSizeYDivs = H * sizeof(int32_t); int32_t* xDivs = (int32_t*) malloc(maxSizeXDivs); int32_t* yDivs = (int32_t*) malloc(maxSizeYDivs); uint8_t numXDivs = 0; @@ -618,28 +603,308 @@ static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) { return true; } +static void dump_image(int w, int h, png_bytepp rows, int color_type) +{ + int i, j, rr, gg, bb, aa; + + int bpp; + if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { + bpp = 1; + } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + bpp = 2; + } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + // We use a padding byte even when there is no alpha + bpp = 4; + } else { + printf("Unknown color type %d.\n", color_type); + } + + for (j = 0; j < h; j++) { + png_bytep row = rows[j]; + for (i = 0; i < w; i++) { + rr = row[0]; + gg = row[1]; + bb = row[2]; + aa = row[3]; + row += bpp; + + if (i == 0) { + printf("Row %d:", j); + } + switch (bpp) { + case 1: + printf(" (%d)", rr); + break; + case 2: + printf(" (%d %d", rr, gg); + break; + case 3: + printf(" (%d %d %d)", rr, gg, bb); + break; + case 4: + printf(" (%d %d %d %d)", rr, gg, bb, aa); + break; + } + if (i == (w - 1)) { + NOISY(printf("\n")); + } + } + } +} + +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define ABS(a) ((a)<0?-(a):(a)) + +static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance, + png_colorp rgbPalette, png_bytep alphaPalette, + int *paletteEntries, bool *hasTransparency, int *colorType, + png_bytepp outRows) +{ + int w = imageInfo.width; + int h = imageInfo.height; + int i, j, rr, gg, bb, aa, idx; + uint32_t colors[256], col; + int num_colors = 0; + int maxGrayDeviation = 0; + + bool isOpaque = true; + bool isPalette = true; + bool isGrayscale = true; + + // Scan the entire image and determine if: + // 1. Every pixel has R == G == B (grayscale) + // 2. Every pixel has A == 255 (opaque) + // 3. There are no more than 256 distinct RGBA colors + + // NOISY(printf("Initial image data:\n")); + // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA); + + for (j = 0; j < h; j++) { + png_bytep row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + int odev = maxGrayDeviation; + maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); + if (maxGrayDeviation > odev) { + NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", + maxGrayDeviation, i, j, rr, gg, bb, aa)); + } + + // Check if image is really grayscale + if (isGrayscale) { + if (rr != gg || rr != bb) { + NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", + i, j, rr, gg, bb, aa)); + isGrayscale = false; + } + } + + // Check if image is really opaque + if (isOpaque) { + if (aa != 0xff) { + NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", + i, j, rr, gg, bb, aa)); + isOpaque = false; + } + } + + // Check if image is really <= 256 colors + if (isPalette) { + col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa); + bool match = false; + for (idx = 0; idx < num_colors; idx++) { + if (colors[idx] == col) { + match = true; + break; + } + } + + // Write the palette index for the pixel to outRows optimistically + // We might overwrite it later if we decide to encode as gray or + // gray + alpha + *out++ = idx; + if (!match) { + if (num_colors == 256) { + NOISY(printf("Found 257th color at %d, %d\n", i, j)); + isPalette = false; + } else { + colors[num_colors++] = col; + } + } + } + } + } + + *paletteEntries = 0; + *hasTransparency = !isOpaque; + int bpp = isOpaque ? 3 : 4; + int paletteSize = w * h + bpp * num_colors; + + NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false")); + NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false")); + NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false")); + NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", + paletteSize, 2 * w * h, bpp * w * h)); + NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance)); + + // Choose the best color type for the image. + // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel + // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations + // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA + // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently + // small, otherwise use COLOR_TYPE_RGB{_ALPHA} + if (isGrayscale) { + if (isOpaque) { + *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel + } else { + // Use a simple heuristic to determine whether using a palette will + // save space versus using gray + alpha for each pixel. + // This doesn't take into account chunk overhead, filtering, LZ + // compression, etc. + if (isPalette && (paletteSize < 2 * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color + } else { + *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel + } + } + } else if (isPalette && (paletteSize < bpp * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; + } else { + if (maxGrayDeviation <= grayscaleTolerance) { + printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation); + *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; + } else { + *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + } + } + + // Perform postprocessing of the image or palette data based on the final + // color type chosen + + if (*colorType == PNG_COLOR_TYPE_PALETTE) { + // Create separate RGB and Alpha palettes and set the number of colors + *paletteEntries = num_colors; + + // Create the RGB and alpha palettes + for (int idx = 0; idx < num_colors; idx++) { + col = colors[idx]; + rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff); + rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff); + rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff); + alphaPalette[idx] = (png_byte) (col & 0xff); + } + } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + // If the image is gray or gray + alpha, compact the pixels into outRows + for (j = 0; j < h; j++) { + png_bytep row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + if (isGrayscale) { + *out++ = rr; + } else { + *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); + } + if (!isOpaque) { + *out++ = aa; + } + } + } + } +} + + static void write_png(const char* imageName, png_structp write_ptr, png_infop write_info, - image_info& imageInfo) + image_info& imageInfo, int grayscaleTolerance) { + bool optimize = true; png_uint_32 width, height; int color_type; int bit_depth, interlace_type, compression_type; int i; png_unknown_chunk unknowns[1]; + unknowns[0].data = NULL; + + png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep)); + if (outRows == (png_bytepp) 0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + for (i = 0; i < (int) imageInfo.height; i++) { + outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width); + if (outRows[i] == (png_bytep) 0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + } png_set_compression_level(write_ptr, Z_BEST_COMPRESSION); - color_type = PNG_COLOR_MASK_COLOR; - if (imageInfo.hasTransparency) { - color_type |= PNG_COLOR_MASK_ALPHA; + NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName, + (int) imageInfo.width, (int) imageInfo.height)); + + png_color rgbPalette[256]; + png_byte alphaPalette[256]; + bool hasTransparency; + int paletteEntries; + + analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette, + &paletteEntries, &hasTransparency, &color_type, outRows); + + // If the image is a 9-patch, we need to preserve it as a ARGB file to make + // sure the pixels will not be pre-dithered/clamped until we decide they are + if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) { + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + + switch (color_type) { + case PNG_COLOR_TYPE_PALETTE: + NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n", + imageName, paletteEntries, + hasTransparency ? " (with alpha)" : "")); + break; + case PNG_COLOR_TYPE_GRAY: + NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName)); + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName)); + break; + case PNG_COLOR_TYPE_RGB: + NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName)); + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName)); + break; } png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height, 8, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries); + if (hasTransparency) { + png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0); + } + png_set_filter(write_ptr, 0, PNG_NO_FILTERS); + } else { + png_set_filter(write_ptr, 0, PNG_ALL_FILTERS); + } + if (imageInfo.is9Patch) { NOISY(printf("Adding 9-patch info...\n")); strcpy((char*)unknowns[0].name, "npTc"); @@ -660,14 +925,26 @@ static void write_png(const char* imageName, png_write_info(write_ptr, write_info); - if (!imageInfo.hasTransparency) { + png_bytepp rows; + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { png_set_filler(write_ptr, 0, PNG_FILLER_AFTER); + rows = imageInfo.rows; + } else { + rows = outRows; } + png_write_image(write_ptr, rows); - png_write_image(write_ptr, imageInfo.rows); +// NOISY(printf("Final image data:\n")); +// dump_image(imageInfo.width, imageInfo.height, rows, color_type); png_write_end(write_ptr, write_info); + for (i = 0; i < (int) imageInfo.height; i++) { + free(outRows[i]); + } + free(outRows); + free(unknowns[0].data); + png_get_IHDR(write_ptr, write_info, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, NULL); @@ -687,9 +964,9 @@ status_t preProcessImage(Bundle* bundle, const sp& assets, return NO_ERROR; } - // Example of renaming a file: - //*outNewLeafName = file->getPath().getBasePath().getFileName(); - //outNewLeafName->append(".nupng"); + // Example of renaming a file: + //*outNewLeafName = file->getPath().getBasePath().getFileName(); + //outNewLeafName->append(".nupng"); String8 printableName(file->getPrintableSource()); @@ -731,8 +1008,6 @@ status_t preProcessImage(Bundle* bundle, const sp& assets, read_png(printableName.string(), read_ptr, read_info, &imageInfo); - examine_image(&imageInfo); - if (nameLen > 6) { const char* name = file->getPath().string(); if (name[nameLen-5] == '9' && name[nameLen-6] == '.') { @@ -763,7 +1038,8 @@ status_t preProcessImage(Bundle* bundle, const sp& assets, goto bail; } - write_png(printableName.string(), write_ptr, write_info, imageInfo); + write_png(printableName.string(), write_ptr, write_info, imageInfo, + bundle->getGrayscaleTolerance()); error = NO_ERROR;