1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "util/BigBuffer.h" 18#include "Png.h" 19#include "Source.h" 20#include "util/Util.h" 21 22#include <androidfw/ResourceTypes.h> 23#include <iostream> 24#include <png.h> 25#include <sstream> 26#include <string> 27#include <vector> 28#include <zlib.h> 29 30namespace aapt { 31 32constexpr bool kDebug = false; 33constexpr size_t kPngSignatureSize = 8u; 34 35struct PngInfo { 36 ~PngInfo() { 37 for (png_bytep row : rows) { 38 if (row != nullptr) { 39 delete[] row; 40 } 41 } 42 43 delete[] xDivs; 44 delete[] yDivs; 45 } 46 47 void* serialize9Patch() { 48 void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs, 49 colors.data()); 50 reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile(); 51 return serialized; 52 } 53 54 uint32_t width = 0; 55 uint32_t height = 0; 56 std::vector<png_bytep> rows; 57 58 bool is9Patch = false; 59 android::Res_png_9patch info9Patch; 60 int32_t* xDivs = nullptr; 61 int32_t* yDivs = nullptr; 62 std::vector<uint32_t> colors; 63 64 // Layout padding. 65 bool haveLayoutBounds = false; 66 int32_t layoutBoundsLeft; 67 int32_t layoutBoundsTop; 68 int32_t layoutBoundsRight; 69 int32_t layoutBoundsBottom; 70 71 // Round rect outline description. 72 int32_t outlineInsetsLeft; 73 int32_t outlineInsetsTop; 74 int32_t outlineInsetsRight; 75 int32_t outlineInsetsBottom; 76 float outlineRadius; 77 uint8_t outlineAlpha; 78}; 79 80static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) { 81 std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr)); 82 if (!input->read(reinterpret_cast<char*>(data), length)) { 83 png_error(readPtr, strerror(errno)); 84 } 85} 86 87static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) { 88 BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); 89 png_bytep buf = outBuffer->nextBlock<png_byte>(length); 90 memcpy(buf, data, length); 91} 92 93static void flushDataToStream(png_structp /*writePtr*/) { 94} 95 96static void logWarning(png_structp readPtr, png_const_charp warningMessage) { 97 IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); 98 diag->warn(DiagMessage() << warningMessage); 99} 100 101 102static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) { 103 if (setjmp(png_jmpbuf(readPtr))) { 104 diag->error(DiagMessage() << "failed reading png"); 105 return false; 106 } 107 108 png_set_sig_bytes(readPtr, kPngSignatureSize); 109 png_read_info(readPtr, infoPtr); 110 111 int colorType, bitDepth, interlaceType, compressionType; 112 png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType, 113 &interlaceType, &compressionType, nullptr); 114 115 if (colorType == PNG_COLOR_TYPE_PALETTE) { 116 png_set_palette_to_rgb(readPtr); 117 } 118 119 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { 120 png_set_expand_gray_1_2_4_to_8(readPtr); 121 } 122 123 if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { 124 png_set_tRNS_to_alpha(readPtr); 125 } 126 127 if (bitDepth == 16) { 128 png_set_strip_16(readPtr); 129 } 130 131 if (!(colorType & PNG_COLOR_MASK_ALPHA)) { 132 png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); 133 } 134 135 if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { 136 png_set_gray_to_rgb(readPtr); 137 } 138 139 png_set_interlace_handling(readPtr); 140 png_read_update_info(readPtr, infoPtr); 141 142 const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr); 143 outInfo->rows.resize(outInfo->height); 144 for (size_t i = 0; i < outInfo->height; i++) { 145 outInfo->rows[i] = new png_byte[rowBytes]; 146 } 147 148 png_read_image(readPtr, outInfo->rows.data()); 149 png_read_end(readPtr, infoPtr); 150 return true; 151} 152 153static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) { 154 size_t patchSize = inPatch->serializedSize(); 155 void* newData = malloc(patchSize); 156 memcpy(newData, data, patchSize); 157 android::Res_png_9patch* outPatch = inPatch->deserialize(newData); 158 outPatch->fileToDevice(); 159 // deserialization is done in place, so outPatch == newData 160 assert(outPatch == newData); 161 assert(outPatch->numXDivs == inPatch->numXDivs); 162 assert(outPatch->numYDivs == inPatch->numYDivs); 163 assert(outPatch->paddingLeft == inPatch->paddingLeft); 164 assert(outPatch->paddingRight == inPatch->paddingRight); 165 assert(outPatch->paddingTop == inPatch->paddingTop); 166 assert(outPatch->paddingBottom == inPatch->paddingBottom); 167/* for (int i = 0; i < outPatch->numXDivs; i++) { 168 assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]); 169 } 170 for (int i = 0; i < outPatch->numYDivs; i++) { 171 assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]); 172 } 173 for (int i = 0; i < outPatch->numColors; i++) { 174 assert(outPatch->getColors()[i] == inPatch->getColors()[i]); 175 }*/ 176 free(newData); 177} 178 179/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) { 180 int i, j, rr, gg, bb, aa; 181 182 int bpp; 183 if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { 184 bpp = 1; 185 } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { 186 bpp = 2; 187 } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { 188 // We use a padding byte even when there is no alpha 189 bpp = 4; 190 } else { 191 printf("Unknown color type %d.\n", color_type); 192 } 193 194 for (j = 0; j < h; j++) { 195 const png_byte* row = rows[j]; 196 for (i = 0; i < w; i++) { 197 rr = row[0]; 198 gg = row[1]; 199 bb = row[2]; 200 aa = row[3]; 201 row += bpp; 202 203 if (i == 0) { 204 printf("Row %d:", j); 205 } 206 switch (bpp) { 207 case 1: 208 printf(" (%d)", rr); 209 break; 210 case 2: 211 printf(" (%d %d", rr, gg); 212 break; 213 case 3: 214 printf(" (%d %d %d)", rr, gg, bb); 215 break; 216 case 4: 217 printf(" (%d %d %d %d)", rr, gg, bb, aa); 218 break; 219 } 220 if (i == (w - 1)) { 221 printf("\n"); 222 } 223 } 224 } 225}*/ 226 227#define MAX(a,b) ((a)>(b)?(a):(b)) 228#define ABS(a) ((a)<0?-(a):(a)) 229 230static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance, 231 png_colorp rgbPalette, png_bytep alphaPalette, 232 int *paletteEntries, bool *hasTransparency, int *colorType, 233 png_bytepp outRows) { 234 int w = imageInfo.width; 235 int h = imageInfo.height; 236 int i, j, rr, gg, bb, aa, idx; 237 uint32_t colors[256], col; 238 int num_colors = 0; 239 int maxGrayDeviation = 0; 240 241 bool isOpaque = true; 242 bool isPalette = true; 243 bool isGrayscale = true; 244 245 // Scan the entire image and determine if: 246 // 1. Every pixel has R == G == B (grayscale) 247 // 2. Every pixel has A == 255 (opaque) 248 // 3. There are no more than 256 distinct RGBA colors 249 250 if (kDebug) { 251 printf("Initial image data:\n"); 252 //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA); 253 } 254 255 for (j = 0; j < h; j++) { 256 const png_byte* row = imageInfo.rows[j]; 257 png_bytep out = outRows[j]; 258 for (i = 0; i < w; i++) { 259 rr = *row++; 260 gg = *row++; 261 bb = *row++; 262 aa = *row++; 263 264 int odev = maxGrayDeviation; 265 maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); 266 maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); 267 maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); 268 if (maxGrayDeviation > odev) { 269 if (kDebug) { 270 printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", 271 maxGrayDeviation, i, j, rr, gg, bb, aa); 272 } 273 } 274 275 // Check if image is really grayscale 276 if (isGrayscale) { 277 if (rr != gg || rr != bb) { 278 if (kDebug) { 279 printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", 280 i, j, rr, gg, bb, aa); 281 } 282 isGrayscale = false; 283 } 284 } 285 286 // Check if image is really opaque 287 if (isOpaque) { 288 if (aa != 0xff) { 289 if (kDebug) { 290 printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", 291 i, j, rr, gg, bb, aa); 292 } 293 isOpaque = false; 294 } 295 } 296 297 // Check if image is really <= 256 colors 298 if (isPalette) { 299 col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa); 300 bool match = false; 301 for (idx = 0; idx < num_colors; idx++) { 302 if (colors[idx] == col) { 303 match = true; 304 break; 305 } 306 } 307 308 // Write the palette index for the pixel to outRows optimistically 309 // We might overwrite it later if we decide to encode as gray or 310 // gray + alpha 311 *out++ = idx; 312 if (!match) { 313 if (num_colors == 256) { 314 if (kDebug) { 315 printf("Found 257th color at %d, %d\n", i, j); 316 } 317 isPalette = false; 318 } else { 319 colors[num_colors++] = col; 320 } 321 } 322 } 323 } 324 } 325 326 *paletteEntries = 0; 327 *hasTransparency = !isOpaque; 328 int bpp = isOpaque ? 3 : 4; 329 int paletteSize = w * h + bpp * num_colors; 330 331 if (kDebug) { 332 printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"); 333 printf("isOpaque = %s\n", isOpaque ? "true" : "false"); 334 printf("isPalette = %s\n", isPalette ? "true" : "false"); 335 printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", 336 paletteSize, 2 * w * h, bpp * w * h); 337 printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance); 338 } 339 340 // Choose the best color type for the image. 341 // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel 342 // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations 343 // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA 344 // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently 345 // small, otherwise use COLOR_TYPE_RGB{_ALPHA} 346 if (isGrayscale) { 347 if (isOpaque) { 348 *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel 349 } else { 350 // Use a simple heuristic to determine whether using a palette will 351 // save space versus using gray + alpha for each pixel. 352 // This doesn't take into account chunk overhead, filtering, LZ 353 // compression, etc. 354 if (isPalette && (paletteSize < 2 * w * h)) { 355 *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color 356 } else { 357 *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel 358 } 359 } 360 } else if (isPalette && (paletteSize < bpp * w * h)) { 361 *colorType = PNG_COLOR_TYPE_PALETTE; 362 } else { 363 if (maxGrayDeviation <= grayscaleTolerance) { 364 diag->note(DiagMessage() 365 << "forcing image to gray (max deviation = " 366 << maxGrayDeviation << ")"); 367 *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; 368 } else { 369 *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; 370 } 371 } 372 373 // Perform postprocessing of the image or palette data based on the final 374 // color type chosen 375 376 if (*colorType == PNG_COLOR_TYPE_PALETTE) { 377 // Create separate RGB and Alpha palettes and set the number of colors 378 *paletteEntries = num_colors; 379 380 // Create the RGB and alpha palettes 381 for (int idx = 0; idx < num_colors; idx++) { 382 col = colors[idx]; 383 rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff); 384 rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff); 385 rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff); 386 alphaPalette[idx] = (png_byte) (col & 0xff); 387 } 388 } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { 389 // If the image is gray or gray + alpha, compact the pixels into outRows 390 for (j = 0; j < h; j++) { 391 const png_byte* row = imageInfo.rows[j]; 392 png_bytep out = outRows[j]; 393 for (i = 0; i < w; i++) { 394 rr = *row++; 395 gg = *row++; 396 bb = *row++; 397 aa = *row++; 398 399 if (isGrayscale) { 400 *out++ = rr; 401 } else { 402 *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); 403 } 404 if (!isOpaque) { 405 *out++ = aa; 406 } 407 } 408 } 409 } 410} 411 412static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info, 413 int grayScaleTolerance) { 414 if (setjmp(png_jmpbuf(writePtr))) { 415 diag->error(DiagMessage() << "failed to write png"); 416 return false; 417 } 418 419 uint32_t width, height; 420 int colorType, bitDepth, interlaceType, compressionType; 421 422 png_unknown_chunk unknowns[3]; 423 unknowns[0].data = nullptr; 424 unknowns[1].data = nullptr; 425 unknowns[2].data = nullptr; 426 427 png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep)); 428 if (outRows == (png_bytepp) 0) { 429 printf("Can't allocate output buffer!\n"); 430 exit(1); 431 } 432 for (uint32_t i = 0; i < info->height; i++) { 433 outRows[i] = (png_bytep) malloc(2 * (int) info->width); 434 if (outRows[i] == (png_bytep) 0) { 435 printf("Can't allocate output buffer!\n"); 436 exit(1); 437 } 438 } 439 440 png_set_compression_level(writePtr, Z_BEST_COMPRESSION); 441 442 if (kDebug) { 443 diag->note(DiagMessage() 444 << "writing image: w = " << info->width 445 << ", h = " << info->height); 446 } 447 448 png_color rgbPalette[256]; 449 png_byte alphaPalette[256]; 450 bool hasTransparency; 451 int paletteEntries; 452 453 analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, 454 &paletteEntries, &hasTransparency, &colorType, outRows); 455 456 // If the image is a 9-patch, we need to preserve it as a ARGB file to make 457 // sure the pixels will not be pre-dithered/clamped until we decide they are 458 if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB || 459 colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) { 460 colorType = PNG_COLOR_TYPE_RGB_ALPHA; 461 } 462 463 if (kDebug) { 464 switch (colorType) { 465 case PNG_COLOR_TYPE_PALETTE: 466 diag->note(DiagMessage() 467 << "has " << paletteEntries 468 << " colors" << (hasTransparency ? " (with alpha)" : "") 469 << ", using PNG_COLOR_TYPE_PALLETTE"); 470 break; 471 case PNG_COLOR_TYPE_GRAY: 472 diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); 473 break; 474 case PNG_COLOR_TYPE_GRAY_ALPHA: 475 diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); 476 break; 477 case PNG_COLOR_TYPE_RGB: 478 diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); 479 break; 480 case PNG_COLOR_TYPE_RGB_ALPHA: 481 diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); 482 break; 483 } 484 } 485 486 png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, 487 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); 488 489 if (colorType == PNG_COLOR_TYPE_PALETTE) { 490 png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries); 491 if (hasTransparency) { 492 png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0); 493 } 494 png_set_filter(writePtr, 0, PNG_NO_FILTERS); 495 } else { 496 png_set_filter(writePtr, 0, PNG_ALL_FILTERS); 497 } 498 499 if (info->is9Patch) { 500 int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0); 501 int pIndex = info->haveLayoutBounds ? 2 : 1; 502 int bIndex = 1; 503 int oIndex = 0; 504 505 // Chunks ordered thusly because older platforms depend on the base 9 patch data being last 506 png_bytep chunkNames = info->haveLayoutBounds 507 ? (png_bytep)"npOl\0npLb\0npTc\0" 508 : (png_bytep)"npOl\0npTc"; 509 510 // base 9 patch data 511 if (kDebug) { 512 diag->note(DiagMessage() << "adding 9-patch info.."); 513 } 514 strcpy((char*)unknowns[pIndex].name, "npTc"); 515 unknowns[pIndex].data = (png_byte*) info->serialize9Patch(); 516 unknowns[pIndex].size = info->info9Patch.serializedSize(); 517 // TODO: remove the check below when everything works 518 checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data); 519 520 // automatically generated 9 patch outline data 521 int chunkSize = sizeof(png_uint_32) * 6; 522 strcpy((char*)unknowns[oIndex].name, "npOl"); 523 unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1); 524 png_byte outputData[chunkSize]; 525 memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32)); 526 ((float*) outputData)[4] = info->outlineRadius; 527 ((png_uint_32*) outputData)[5] = info->outlineAlpha; 528 memcpy(unknowns[oIndex].data, &outputData, chunkSize); 529 unknowns[oIndex].size = chunkSize; 530 531 // optional optical inset / layout bounds data 532 if (info->haveLayoutBounds) { 533 int chunkSize = sizeof(png_uint_32) * 4; 534 strcpy((char*)unknowns[bIndex].name, "npLb"); 535 unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1); 536 memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize); 537 unknowns[bIndex].size = chunkSize; 538 } 539 540 for (int i = 0; i < chunkCount; i++) { 541 unknowns[i].location = PNG_HAVE_PLTE; 542 } 543 png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, 544 chunkNames, chunkCount); 545 png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount); 546 547#if PNG_LIBPNG_VER < 10600 548 // Deal with unknown chunk location bug in 1.5.x and earlier. 549 png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE); 550 if (info->haveLayoutBounds) { 551 png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE); 552 } 553#endif 554 } 555 556 png_write_info(writePtr, infoPtr); 557 558 png_bytepp rows; 559 if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) { 560 if (colorType == PNG_COLOR_TYPE_RGB) { 561 png_set_filler(writePtr, 0, PNG_FILLER_AFTER); 562 } 563 rows = info->rows.data(); 564 } else { 565 rows = outRows; 566 } 567 png_write_image(writePtr, rows); 568 569 if (kDebug) { 570 printf("Final image data:\n"); 571 //dump_image(info->width, info->height, rows, colorType); 572 } 573 574 png_write_end(writePtr, infoPtr); 575 576 for (uint32_t i = 0; i < info->height; i++) { 577 free(outRows[i]); 578 } 579 free(outRows); 580 free(unknowns[0].data); 581 free(unknowns[1].data); 582 free(unknowns[2].data); 583 584 png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType, 585 &compressionType, nullptr); 586 587 if (kDebug) { 588 diag->note(DiagMessage() 589 << "image written: w = " << width << ", h = " << height 590 << ", d = " << bitDepth << ", colors = " << colorType 591 << ", inter = " << interlaceType << ", comp = " << compressionType); 592 } 593 return true; 594} 595 596constexpr uint32_t kColorWhite = 0xffffffffu; 597constexpr uint32_t kColorTick = 0xff000000u; 598constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu; 599 600enum class TickType { 601 kNone, 602 kTick, 603 kLayoutBounds, 604 kBoth 605}; 606 607static TickType tickType(png_bytep p, bool transparent, const char** outError) { 608 png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); 609 610 if (transparent) { 611 if (p[3] == 0) { 612 return TickType::kNone; 613 } 614 if (color == kColorLayoutBoundsTick) { 615 return TickType::kLayoutBounds; 616 } 617 if (color == kColorTick) { 618 return TickType::kTick; 619 } 620 621 // Error cases 622 if (p[3] != 0xff) { 623 *outError = "Frame pixels must be either solid or transparent " 624 "(not intermediate alphas)"; 625 return TickType::kNone; 626 } 627 628 if (p[0] != 0 || p[1] != 0 || p[2] != 0) { 629 *outError = "Ticks in transparent frame must be black or red"; 630 } 631 return TickType::kTick; 632 } 633 634 if (p[3] != 0xFF) { 635 *outError = "White frame must be a solid color (no alpha)"; 636 } 637 if (color == kColorWhite) { 638 return TickType::kNone; 639 } 640 if (color == kColorTick) { 641 return TickType::kTick; 642 } 643 if (color == kColorLayoutBoundsTick) { 644 return TickType::kLayoutBounds; 645 } 646 647 if (p[0] != 0 || p[1] != 0 || p[2] != 0) { 648 *outError = "Ticks in white frame must be black or red"; 649 return TickType::kNone; 650 } 651 return TickType::kTick; 652} 653 654enum class TickState { 655 kStart, 656 kInside1, 657 kOutside1 658}; 659 660static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required, 661 int32_t* outLeft, int32_t* outRight, const char** outError, 662 uint8_t* outDivs, bool multipleAllowed) { 663 *outLeft = *outRight = -1; 664 TickState state = TickState::kStart; 665 bool found = false; 666 667 for (int i = 1; i < width - 1; i++) { 668 if (tickType(row+i*4, transparent, outError) == TickType::kTick) { 669 if (state == TickState::kStart || 670 (state == TickState::kOutside1 && multipleAllowed)) { 671 *outLeft = i-1; 672 *outRight = width-2; 673 found = true; 674 if (outDivs != NULL) { 675 *outDivs += 2; 676 } 677 state = TickState::kInside1; 678 } else if (state == TickState::kOutside1) { 679 *outError = "Can't have more than one marked region along edge"; 680 *outLeft = i; 681 return false; 682 } 683 } else if (!*outError) { 684 if (state == TickState::kInside1) { 685 // We're done with this div. Move on to the next. 686 *outRight = i-1; 687 outRight += 2; 688 outLeft += 2; 689 state = TickState::kOutside1; 690 } 691 } else { 692 *outLeft = i; 693 return false; 694 } 695 } 696 697 if (required && !found) { 698 *outError = "No marked region found along edge"; 699 *outLeft = -1; 700 return false; 701 } 702 return true; 703} 704 705static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent, 706 bool required, int32_t* outTop, int32_t* outBottom, 707 const char** outError, uint8_t* outDivs, bool multipleAllowed) { 708 *outTop = *outBottom = -1; 709 TickState state = TickState::kStart; 710 bool found = false; 711 712 for (int i = 1; i < height - 1; i++) { 713 if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) { 714 if (state == TickState::kStart || 715 (state == TickState::kOutside1 && multipleAllowed)) { 716 *outTop = i-1; 717 *outBottom = height-2; 718 found = true; 719 if (outDivs != NULL) { 720 *outDivs += 2; 721 } 722 state = TickState::kInside1; 723 } else if (state == TickState::kOutside1) { 724 *outError = "Can't have more than one marked region along edge"; 725 *outTop = i; 726 return false; 727 } 728 } else if (!*outError) { 729 if (state == TickState::kInside1) { 730 // We're done with this div. Move on to the next. 731 *outBottom = i-1; 732 outTop += 2; 733 outBottom += 2; 734 state = TickState::kOutside1; 735 } 736 } else { 737 *outTop = i; 738 return false; 739 } 740 } 741 742 if (required && !found) { 743 *outError = "No marked region found along edge"; 744 *outTop = -1; 745 return false; 746 } 747 return true; 748} 749 750static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent, 751 bool /* required */, int32_t* outLeft, 752 int32_t* outRight, const char** outError) { 753 *outLeft = *outRight = 0; 754 755 // Look for left tick 756 if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) { 757 // Starting with a layout padding tick 758 int i = 1; 759 while (i < width - 1) { 760 (*outLeft)++; 761 i++; 762 if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) { 763 break; 764 } 765 } 766 } 767 768 // Look for right tick 769 if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) { 770 // Ending with a layout padding tick 771 int i = width - 2; 772 while (i > 1) { 773 (*outRight)++; 774 i--; 775 if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) { 776 break; 777 } 778 } 779 } 780 return true; 781} 782 783static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent, 784 bool /* required */, int32_t* outTop, int32_t* outBottom, 785 const char** outError) { 786 *outTop = *outBottom = 0; 787 788 // Look for top tick 789 if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) { 790 // Starting with a layout padding tick 791 int i = 1; 792 while (i < height - 1) { 793 (*outTop)++; 794 i++; 795 if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) { 796 break; 797 } 798 } 799 } 800 801 // Look for bottom tick 802 if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) { 803 // Ending with a layout padding tick 804 int i = height - 2; 805 while (i > 1) { 806 (*outBottom)++; 807 i--; 808 if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) { 809 break; 810 } 811 } 812 } 813 return true; 814} 815 816static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY, 817 int dX, int dY, int* outInset) { 818 uint8_t maxOpacity = 0; 819 int inset = 0; 820 *outInset = 0; 821 for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) { 822 png_byte* color = rows[y] + x * 4; 823 uint8_t opacity = color[3]; 824 if (opacity > maxOpacity) { 825 maxOpacity = opacity; 826 *outInset = inset; 827 } 828 if (opacity == 0xff) return; 829 } 830} 831 832static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) { 833 uint8_t maxAlpha = 0; 834 for (int x = startX; x < endX; x++) { 835 uint8_t alpha = (row + x * 4)[3]; 836 if (alpha > maxAlpha) maxAlpha = alpha; 837 } 838 return maxAlpha; 839} 840 841static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) { 842 uint8_t maxAlpha = 0; 843 for (int y = startY; y < endY; y++) { 844 uint8_t alpha = (rows[y] + offsetX * 4)[3]; 845 if (alpha > maxAlpha) maxAlpha = alpha; 846 } 847 return maxAlpha; 848} 849 850static void getOutline(PngInfo* image) { 851 int midX = image->width / 2; 852 int midY = image->height / 2; 853 int endX = image->width - 2; 854 int endY = image->height - 2; 855 856 // find left and right extent of nine patch content on center row 857 if (image->width > 4) { 858 findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft); 859 findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, 860 &image->outlineInsetsRight); 861 } else { 862 image->outlineInsetsLeft = 0; 863 image->outlineInsetsRight = 0; 864 } 865 866 // find top and bottom extent of nine patch content on center column 867 if (image->height > 4) { 868 findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop); 869 findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, 870 &image->outlineInsetsBottom); 871 } else { 872 image->outlineInsetsTop = 0; 873 image->outlineInsetsBottom = 0; 874 } 875 876 int innerStartX = 1 + image->outlineInsetsLeft; 877 int innerStartY = 1 + image->outlineInsetsTop; 878 int innerEndX = endX - image->outlineInsetsRight; 879 int innerEndY = endY - image->outlineInsetsBottom; 880 int innerMidX = (innerEndX + innerStartX) / 2; 881 int innerMidY = (innerEndY + innerStartY) / 2; 882 883 // assuming the image is a round rect, compute the radius by marching 884 // diagonally from the top left corner towards the center 885 image->outlineAlpha = std::max( 886 maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX), 887 maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY)); 888 889 int diagonalInset = 0; 890 findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1, 891 &diagonalInset); 892 893 /* Determine source radius based upon inset: 894 * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r 895 * sqrt(2) * r = sqrt(2) * i + r 896 * (sqrt(2) - 1) * r = sqrt(2) * i 897 * r = sqrt(2) / (sqrt(2) - 1) * i 898 */ 899 image->outlineRadius = 3.4142f * diagonalInset; 900 901 if (kDebug) { 902 printf("outline insets %d %d %d %d, rad %f, alpha %x\n", 903 image->outlineInsetsLeft, 904 image->outlineInsetsTop, 905 image->outlineInsetsRight, 906 image->outlineInsetsBottom, 907 image->outlineRadius, 908 image->outlineAlpha); 909 } 910} 911 912static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) { 913 png_bytep color = rows[top] + left*4; 914 915 if (left > right || top > bottom) { 916 return android::Res_png_9patch::TRANSPARENT_COLOR; 917 } 918 919 while (top <= bottom) { 920 for (int i = left; i <= right; i++) { 921 png_bytep p = rows[top]+i*4; 922 if (color[3] == 0) { 923 if (p[3] != 0) { 924 return android::Res_png_9patch::NO_COLOR; 925 } 926 } else if (p[0] != color[0] || p[1] != color[1] || 927 p[2] != color[2] || p[3] != color[3]) { 928 return android::Res_png_9patch::NO_COLOR; 929 } 930 } 931 top++; 932 } 933 934 if (color[3] == 0) { 935 return android::Res_png_9patch::TRANSPARENT_COLOR; 936 } 937 return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2]; 938} 939 940static bool do9Patch(PngInfo* image, std::string* outError) { 941 image->is9Patch = true; 942 943 int W = image->width; 944 int H = image->height; 945 int i, j; 946 947 const int maxSizeXDivs = W * sizeof(int32_t); 948 const int maxSizeYDivs = H * sizeof(int32_t); 949 int32_t* xDivs = image->xDivs = new int32_t[W]; 950 int32_t* yDivs = image->yDivs = new int32_t[H]; 951 uint8_t numXDivs = 0; 952 uint8_t numYDivs = 0; 953 954 int8_t numColors; 955 int numRows; 956 int numCols; 957 int top; 958 int left; 959 int right; 960 int bottom; 961 memset(xDivs, -1, maxSizeXDivs); 962 memset(yDivs, -1, maxSizeYDivs); 963 image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1; 964 image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; 965 image->layoutBoundsLeft = image->layoutBoundsRight = 0; 966 image->layoutBoundsTop = image->layoutBoundsBottom = 0; 967 968 png_bytep p = image->rows[0]; 969 bool transparent = p[3] == 0; 970 bool hasColor = false; 971 972 const char* errorMsg = nullptr; 973 int errorPixel = -1; 974 const char* errorEdge = nullptr; 975 976 int colorIndex = 0; 977 std::vector<png_bytep> newRows; 978 979 // Validate size... 980 if (W < 3 || H < 3) { 981 errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels"; 982 goto getout; 983 } 984 985 // Validate frame... 986 if (!transparent && 987 (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) { 988 errorMsg = "Must have one-pixel frame that is either transparent or white"; 989 goto getout; 990 } 991 992 // Find left and right of sizing areas... 993 if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs, 994 true)) { 995 errorPixel = xDivs[0]; 996 errorEdge = "top"; 997 goto getout; 998 } 999 1000 // Find top and bottom of sizing areas... 1001 if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1], 1002 &errorMsg, &numYDivs, true)) { 1003 errorPixel = yDivs[0]; 1004 errorEdge = "left"; 1005 goto getout; 1006 } 1007 1008 // Copy patch size data into image... 1009 image->info9Patch.numXDivs = numXDivs; 1010 image->info9Patch.numYDivs = numYDivs; 1011 1012 // Find left and right of padding area... 1013 if (!getHorizontalTicks(image->rows[H-1], W, transparent, false, 1014 &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight, 1015 &errorMsg, nullptr, false)) { 1016 errorPixel = image->info9Patch.paddingLeft; 1017 errorEdge = "bottom"; 1018 goto getout; 1019 } 1020 1021 // Find top and bottom of padding area... 1022 if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false, 1023 &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom, 1024 &errorMsg, nullptr, false)) { 1025 errorPixel = image->info9Patch.paddingTop; 1026 errorEdge = "right"; 1027 goto getout; 1028 } 1029 1030 // Find left and right of layout padding... 1031 getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false, 1032 &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg); 1033 1034 getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false, 1035 &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg); 1036 1037 image->haveLayoutBounds = image->layoutBoundsLeft != 0 1038 || image->layoutBoundsRight != 0 1039 || image->layoutBoundsTop != 0 1040 || image->layoutBoundsBottom != 0; 1041 1042 if (image->haveLayoutBounds) { 1043 if (kDebug) { 1044 printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop, 1045 image->layoutBoundsRight, image->layoutBoundsBottom); 1046 } 1047 } 1048 1049 // use opacity of pixels to estimate the round rect outline 1050 getOutline(image); 1051 1052 // If padding is not yet specified, take values from size. 1053 if (image->info9Patch.paddingLeft < 0) { 1054 image->info9Patch.paddingLeft = xDivs[0]; 1055 image->info9Patch.paddingRight = W - 2 - xDivs[1]; 1056 } else { 1057 // Adjust value to be correct! 1058 image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight; 1059 } 1060 if (image->info9Patch.paddingTop < 0) { 1061 image->info9Patch.paddingTop = yDivs[0]; 1062 image->info9Patch.paddingBottom = H - 2 - yDivs[1]; 1063 } else { 1064 // Adjust value to be correct! 1065 image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom; 1066 } 1067 1068/* if (kDebug) { 1069 printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, 1070 xDivs[0], xDivs[1], 1071 yDivs[0], yDivs[1]); 1072 printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName, 1073 image->info9Patch.paddingLeft, image->info9Patch.paddingRight, 1074 image->info9Patch.paddingTop, image->info9Patch.paddingBottom); 1075 }*/ 1076 1077 // Remove frame from image. 1078 newRows.resize(H - 2); 1079 for (i = 0; i < H - 2; i++) { 1080 newRows[i] = image->rows[i + 1]; 1081 memmove(newRows[i], newRows[i] + 4, (W - 2) * 4); 1082 } 1083 image->rows.swap(newRows); 1084 1085 image->width -= 2; 1086 W = image->width; 1087 image->height -= 2; 1088 H = image->height; 1089 1090 // Figure out the number of rows and columns in the N-patch 1091 numCols = numXDivs + 1; 1092 if (xDivs[0] == 0) { // Column 1 is strechable 1093 numCols--; 1094 } 1095 if (xDivs[numXDivs - 1] == W) { 1096 numCols--; 1097 } 1098 numRows = numYDivs + 1; 1099 if (yDivs[0] == 0) { // Row 1 is strechable 1100 numRows--; 1101 } 1102 if (yDivs[numYDivs - 1] == H) { 1103 numRows--; 1104 } 1105 1106 // Make sure the amount of rows and columns will fit in the number of 1107 // colors we can use in the 9-patch format. 1108 if (numRows * numCols > 0x7F) { 1109 errorMsg = "Too many rows and columns in 9-patch perimeter"; 1110 goto getout; 1111 } 1112 1113 numColors = numRows * numCols; 1114 image->info9Patch.numColors = numColors; 1115 image->colors.resize(numColors); 1116 1117 // Fill in color information for each patch. 1118 1119 uint32_t c; 1120 top = 0; 1121 1122 // The first row always starts with the top being at y=0 and the bottom 1123 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case 1124 // the first row is stretchable along the Y axis, otherwise it is fixed. 1125 // The last row always ends with the bottom being bitmap.height and the top 1126 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or 1127 // yDivs[numYDivs-1]. In the former case the last row is stretchable along 1128 // the Y axis, otherwise it is fixed. 1129 // 1130 // The first and last columns are similarly treated with respect to the X 1131 // axis. 1132 // 1133 // The above is to help explain some of the special casing that goes on the 1134 // code below. 1135 1136 // The initial yDiv and whether the first row is considered stretchable or 1137 // not depends on whether yDiv[0] was zero or not. 1138 for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) { 1139 if (j == numYDivs) { 1140 bottom = H; 1141 } else { 1142 bottom = yDivs[j]; 1143 } 1144 left = 0; 1145 // The initial xDiv and whether the first column is considered 1146 // stretchable or not depends on whether xDiv[0] was zero or not. 1147 for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) { 1148 if (i == numXDivs) { 1149 right = W; 1150 } else { 1151 right = xDivs[i]; 1152 } 1153 c = getColor(image->rows.data(), left, top, right - 1, bottom - 1); 1154 image->colors[colorIndex++] = c; 1155 if (kDebug) { 1156 if (c != android::Res_png_9patch::NO_COLOR) { 1157 hasColor = true; 1158 } 1159 } 1160 left = right; 1161 } 1162 top = bottom; 1163 } 1164 1165 assert(colorIndex == numColors); 1166 1167 if (kDebug && hasColor) { 1168 for (i = 0; i < numColors; i++) { 1169 if (i == 0) printf("Colors:\n"); 1170 printf(" #%08x", image->colors[i]); 1171 if (i == numColors - 1) printf("\n"); 1172 } 1173 } 1174getout: 1175 if (errorMsg) { 1176 std::stringstream err; 1177 err << "9-patch malformed: " << errorMsg; 1178 if (errorEdge) { 1179 err << "." << std::endl; 1180 if (errorPixel >= 0) { 1181 err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge"; 1182 } else { 1183 err << "Found along " << errorEdge << " edge"; 1184 } 1185 } 1186 *outError = err.str(); 1187 return false; 1188 } 1189 return true; 1190} 1191 1192 1193bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer, 1194 const PngOptions& options) { 1195 png_byte signature[kPngSignatureSize]; 1196 1197 // Read the PNG signature first. 1198 if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { 1199 mDiag->error(DiagMessage() << strerror(errno)); 1200 return false; 1201 } 1202 1203 // If the PNG signature doesn't match, bail early. 1204 if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { 1205 mDiag->error(DiagMessage() << "not a valid png file"); 1206 return false; 1207 } 1208 1209 bool result = false; 1210 png_structp readPtr = nullptr; 1211 png_infop infoPtr = nullptr; 1212 png_structp writePtr = nullptr; 1213 png_infop writeInfoPtr = nullptr; 1214 PngInfo pngInfo = {}; 1215 1216 readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); 1217 if (!readPtr) { 1218 mDiag->error(DiagMessage() << "failed to allocate read ptr"); 1219 goto bail; 1220 } 1221 1222 infoPtr = png_create_info_struct(readPtr); 1223 if (!infoPtr) { 1224 mDiag->error(DiagMessage() << "failed to allocate info ptr"); 1225 goto bail; 1226 } 1227 1228 png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning); 1229 1230 // Set the read function to read from std::istream. 1231 png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream); 1232 1233 if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { 1234 goto bail; 1235 } 1236 1237 if (util::stringEndsWith<char>(source.path, ".9.png")) { 1238 std::string errorMsg; 1239 if (!do9Patch(&pngInfo, &errorMsg)) { 1240 mDiag->error(DiagMessage() << errorMsg); 1241 goto bail; 1242 } 1243 } 1244 1245 writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); 1246 if (!writePtr) { 1247 mDiag->error(DiagMessage() << "failed to allocate write ptr"); 1248 goto bail; 1249 } 1250 1251 writeInfoPtr = png_create_info_struct(writePtr); 1252 if (!writeInfoPtr) { 1253 mDiag->error(DiagMessage() << "failed to allocate write info ptr"); 1254 goto bail; 1255 } 1256 1257 png_set_error_fn(writePtr, nullptr, nullptr, logWarning); 1258 1259 // Set the write function to write to std::ostream. 1260 png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream); 1261 1262 if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) { 1263 goto bail; 1264 } 1265 1266 result = true; 1267bail: 1268 if (readPtr) { 1269 png_destroy_read_struct(&readPtr, &infoPtr, nullptr); 1270 } 1271 1272 if (writePtr) { 1273 png_destroy_write_struct(&writePtr, &writeInfoPtr); 1274 } 1275 return result; 1276} 1277 1278} // namespace aapt 1279