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