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
171ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski#include "util/BigBuffer.h"
1898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include "Png.h"
1998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include "Source.h"
201ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski#include "util/Util.h"
2198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
2298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include <androidfw/ResourceTypes.h>
2398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include <iostream>
2498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include <png.h>
2598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include <sstream>
2698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include <string>
2798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include <vector>
2898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#include <zlib.h>
2998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
3098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskinamespace aapt {
3198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
3298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskiconstexpr bool kDebug = false;
3398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskiconstexpr size_t kPngSignatureSize = 8u;
3498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
3598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistruct PngInfo {
3698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    ~PngInfo() {
3798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (png_bytep row : rows) {
3898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (row != nullptr) {
3998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                delete[] row;
4098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
4198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
4298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
4398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        delete[] xDivs;
4498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        delete[] yDivs;
4598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
4698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
4798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    void* serialize9Patch() {
4898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
4998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                                              colors.data());
5098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
5198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return serialized;
5298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
5398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
5498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint32_t width = 0;
5598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint32_t height = 0;
5698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    std::vector<png_bytep> rows;
5798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
5898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool is9Patch = false;
5998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    android::Res_png_9patch info9Patch;
6098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t* xDivs = nullptr;
6198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t* yDivs = nullptr;
6298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    std::vector<uint32_t> colors;
6398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
6498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Layout padding.
6598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool haveLayoutBounds = false;
6698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t layoutBoundsLeft;
6798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t layoutBoundsTop;
6898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t layoutBoundsRight;
6998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t layoutBoundsBottom;
7098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
7198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Round rect outline description.
7298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t outlineInsetsLeft;
7398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t outlineInsetsTop;
7498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t outlineInsetsRight;
7598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t outlineInsetsBottom;
7698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    float outlineRadius;
7798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint8_t outlineAlpha;
7898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski};
7998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
8098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
8198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
8298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!input->read(reinterpret_cast<char*>(data), length)) {
8398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_error(readPtr, strerror(errno));
8498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
8598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
8698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
8798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
88769de98f2dd41bfe39a1c9f76aefd1ad58942733Adam Lesinski    BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
89769de98f2dd41bfe39a1c9f76aefd1ad58942733Adam Lesinski    png_bytep buf = outBuffer->nextBlock<png_byte>(length);
90769de98f2dd41bfe39a1c9f76aefd1ad58942733Adam Lesinski    memcpy(buf, data, length);
9198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
9298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
93769de98f2dd41bfe39a1c9f76aefd1ad58942733Adam Lesinskistatic void flushDataToStream(png_structp /*writePtr*/) {
9498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
9598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
9698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic void logWarning(png_structp readPtr, png_const_charp warningMessage) {
971ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
981ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    diag->warn(DiagMessage() << warningMessage);
9998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
10098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
10198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
1021ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinskistatic bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
10398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (setjmp(png_jmpbuf(readPtr))) {
1041ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        diag->error(DiagMessage() << "failed reading png");
10598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return false;
10698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
10798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
10898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_set_sig_bytes(readPtr, kPngSignatureSize);
10998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_read_info(readPtr, infoPtr);
11098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
11198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int colorType, bitDepth, interlaceType, compressionType;
11298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
11398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                 &interlaceType, &compressionType, nullptr);
11498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
11598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (colorType == PNG_COLOR_TYPE_PALETTE) {
11698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_palette_to_rgb(readPtr);
11798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
11898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
11998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
12098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_expand_gray_1_2_4_to_8(readPtr);
12198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
12298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
12398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
12498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_tRNS_to_alpha(readPtr);
12598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
12698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
12798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (bitDepth == 16) {
12898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_strip_16(readPtr);
12998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
13098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
13198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
13298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
13398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
13498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
13598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
13698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_gray_to_rgb(readPtr);
13798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
13898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
13998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_set_interlace_handling(readPtr);
14098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_read_update_info(readPtr, infoPtr);
14198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
14298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
14398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    outInfo->rows.resize(outInfo->height);
14498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (size_t i = 0; i < outInfo->height; i++) {
14598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        outInfo->rows[i] = new png_byte[rowBytes];
14698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
14798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
14898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_read_image(readPtr, outInfo->rows.data());
14998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_read_end(readPtr, infoPtr);
15098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return true;
15198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
15298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
15398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic void checkNinePatchSerialization(android::Res_png_9patch* inPatch,  void* data) {
15498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    size_t patchSize = inPatch->serializedSize();
15598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    void* newData = malloc(patchSize);
15698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    memcpy(newData, data, patchSize);
15798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
15898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    outPatch->fileToDevice();
15998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // deserialization is done in place, so outPatch == newData
16098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(outPatch == newData);
16198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(outPatch->numXDivs == inPatch->numXDivs);
16298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(outPatch->numYDivs == inPatch->numYDivs);
16398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(outPatch->paddingLeft == inPatch->paddingLeft);
16498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(outPatch->paddingRight == inPatch->paddingRight);
16598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(outPatch->paddingTop == inPatch->paddingTop);
16698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(outPatch->paddingBottom == inPatch->paddingBottom);
16798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski/*    for (int i = 0; i < outPatch->numXDivs; i++) {
16898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
16998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
17098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (int i = 0; i < outPatch->numYDivs; i++) {
17198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
17298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
17398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (int i = 0; i < outPatch->numColors; i++) {
17498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
17598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }*/
17698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    free(newData);
17798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
17898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
17998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
18098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int i, j, rr, gg, bb, aa;
18198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
18298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int bpp;
18398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
18498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        bpp = 1;
18598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
18698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        bpp = 2;
18798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
18898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // We use a padding byte even when there is no alpha
18998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        bpp = 4;
19098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
19198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("Unknown color type %d.\n", color_type);
19298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
19398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
19498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (j = 0; j < h; j++) {
19598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        const png_byte* row = rows[j];
19698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (i = 0; i < w; i++) {
19798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            rr = row[0];
19898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            gg = row[1];
19998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            bb = row[2];
20098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            aa = row[3];
20198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            row += bpp;
20298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
20398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (i == 0) {
20498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                printf("Row %d:", j);
20598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
20698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            switch (bpp) {
20798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            case 1:
20898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                printf(" (%d)", rr);
20998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
21098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            case 2:
21198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                printf(" (%d %d", rr, gg);
21298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
21398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            case 3:
21498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                printf(" (%d %d %d)", rr, gg, bb);
21598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
21698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            case 4:
21798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                printf(" (%d %d %d %d)", rr, gg, bb, aa);
21898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
21998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
22098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (i == (w - 1)) {
22198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                printf("\n");
22298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
22398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
22498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
22598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}*/
22698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
22798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#define MAX(a,b) ((a)>(b)?(a):(b))
22898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#define ABS(a)   ((a)<0?-(a):(a))
22998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
2301ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinskistatic void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
23198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                          png_colorp rgbPalette, png_bytep alphaPalette,
23298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                          int *paletteEntries, bool *hasTransparency, int *colorType,
23398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                          png_bytepp outRows) {
23498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int w = imageInfo.width;
23598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int h = imageInfo.height;
23698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int i, j, rr, gg, bb, aa, idx;
23798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint32_t colors[256], col;
23898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int num_colors = 0;
23998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int maxGrayDeviation = 0;
24098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
24198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool isOpaque = true;
24298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool isPalette = true;
24398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool isGrayscale = true;
24498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
24598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Scan the entire image and determine if:
24698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // 1. Every pixel has R == G == B (grayscale)
24798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // 2. Every pixel has A == 255 (opaque)
24898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // 3. There are no more than 256 distinct RGBA colors
24998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
25098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug) {
25198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("Initial image data:\n");
25298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
25398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
25498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
25598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (j = 0; j < h; j++) {
25698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        const png_byte* row = imageInfo.rows[j];
25798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_bytep out = outRows[j];
25898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (i = 0; i < w; i++) {
25998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            rr = *row++;
26098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            gg = *row++;
26198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            bb = *row++;
26298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            aa = *row++;
26398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
26498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            int odev = maxGrayDeviation;
26598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
26698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
26798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
26898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (maxGrayDeviation > odev) {
26998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (kDebug) {
27098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
27198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                            maxGrayDeviation, i, j, rr, gg, bb, aa);
27298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
27398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
27498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
27598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            // Check if image is really grayscale
27698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (isGrayscale) {
27798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (rr != gg || rr != bb) {
27898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    if (kDebug) {
27998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
28098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                i, j, rr, gg, bb, aa);
28198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    }
28298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    isGrayscale = false;
28398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
28498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
28598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
28698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            // Check if image is really opaque
28798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (isOpaque) {
28898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (aa != 0xff) {
28998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    if (kDebug) {
29098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
29198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                i, j, rr, gg, bb, aa);
29298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    }
29398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    isOpaque = false;
29498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
29598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
29698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
29798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            // Check if image is really <= 256 colors
29898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (isPalette) {
29998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
30098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                bool match = false;
30198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                for (idx = 0; idx < num_colors; idx++) {
30298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    if (colors[idx] == col) {
30398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        match = true;
30498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        break;
30598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    }
30698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
30798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
30898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                // Write the palette index for the pixel to outRows optimistically
30998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                // We might overwrite it later if we decide to encode as gray or
31098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                // gray + alpha
31198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *out++ = idx;
31298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (!match) {
31398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    if (num_colors == 256) {
31498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        if (kDebug) {
31598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                            printf("Found 257th color at %d, %d\n", i, j);
31698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        }
31798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        isPalette = false;
31898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    } else {
31998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        colors[num_colors++] = col;
32098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    }
32198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
32298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
32398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
32498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
32598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
32698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    *paletteEntries = 0;
32798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    *hasTransparency = !isOpaque;
32898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int bpp = isOpaque ? 3 : 4;
32998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int paletteSize = w * h + bpp * num_colors;
33098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
33198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug) {
33298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
33398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("isOpaque = %s\n", isOpaque ? "true" : "false");
33498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("isPalette = %s\n", isPalette ? "true" : "false");
33598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
33698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                paletteSize, 2 * w * h, bpp * w * h);
33798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
33898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
33998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
34098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Choose the best color type for the image.
34198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
34298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
34398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    //     is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
34498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
34598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    //     small, otherwise use COLOR_TYPE_RGB{_ALPHA}
34698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (isGrayscale) {
34798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (isOpaque) {
34898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
34998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        } else {
35098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            // Use a simple heuristic to determine whether using a palette will
35198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            // save space versus using gray + alpha for each pixel.
35298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            // This doesn't take into account chunk overhead, filtering, LZ
35398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            // compression, etc.
35498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (isPalette && (paletteSize < 2 * w * h)) {
35598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
35698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            } else {
35798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
35898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
35998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
36098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else if (isPalette && (paletteSize < bpp * w * h)) {
36198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *colorType = PNG_COLOR_TYPE_PALETTE;
36298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
36398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (maxGrayDeviation <= grayscaleTolerance) {
3641ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            diag->note(DiagMessage()
3651ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                       << "forcing image to gray (max deviation = "
3661ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                       << maxGrayDeviation << ")");
36798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
36898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        } else {
36998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
37098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
37198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
37298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
37398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Perform postprocessing of the image or palette data based on the final
37498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // color type chosen
37598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
37698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (*colorType == PNG_COLOR_TYPE_PALETTE) {
37798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Create separate RGB and Alpha palettes and set the number of colors
37898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *paletteEntries = num_colors;
37998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
38098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Create the RGB and alpha palettes
38198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (int idx = 0; idx < num_colors; idx++) {
38298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            col = colors[idx];
38398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            rgbPalette[idx].red   = (png_byte) ((col >> 24) & 0xff);
38498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
38598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            rgbPalette[idx].blue  = (png_byte) ((col >>  8) & 0xff);
38698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            alphaPalette[idx]     = (png_byte)  (col        & 0xff);
38798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
38898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
38998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // If the image is gray or gray + alpha, compact the pixels into outRows
39098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (j = 0; j < h; j++) {
39198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            const png_byte* row = imageInfo.rows[j];
39298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            png_bytep out = outRows[j];
39398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            for (i = 0; i < w; i++) {
39498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                rr = *row++;
39598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                gg = *row++;
39698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                bb = *row++;
39798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                aa = *row++;
39898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
39998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (isGrayscale) {
40098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    *out++ = rr;
40198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                } else {
40298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
40398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
40498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (!isOpaque) {
40598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    *out++ = aa;
40698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
40798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski           }
40898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
40998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
41098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
41198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
4121ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinskistatic bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
4131ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                     int grayScaleTolerance) {
41498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (setjmp(png_jmpbuf(writePtr))) {
4151ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        diag->error(DiagMessage() << "failed to write png");
41698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return false;
41798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
41898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
41998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint32_t width, height;
42098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int colorType, bitDepth, interlaceType, compressionType;
42198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
42298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_unknown_chunk unknowns[3];
42398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    unknowns[0].data = nullptr;
42498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    unknowns[1].data = nullptr;
42598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    unknowns[2].data = nullptr;
42698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
42798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
42898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (outRows == (png_bytepp) 0) {
42998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("Can't allocate output buffer!\n");
43098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        exit(1);
43198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
43298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (uint32_t i = 0; i < info->height; i++) {
43398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        outRows[i] = (png_bytep) malloc(2 * (int) info->width);
43498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (outRows[i] == (png_bytep) 0) {
43598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            printf("Can't allocate output buffer!\n");
43698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            exit(1);
43798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
43898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
43998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
44098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
44198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
44298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug) {
4431ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        diag->note(DiagMessage()
4441ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                   << "writing image: w = " << info->width
4451ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                   << ", h = " << info->height);
44698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
44798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
44898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_color rgbPalette[256];
44998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_byte alphaPalette[256];
45098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool hasTransparency;
45198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int paletteEntries;
45298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
4531ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
45498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                  &paletteEntries, &hasTransparency, &colorType, outRows);
45598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
45698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // If the image is a 9-patch, we need to preserve it as a ARGB file to make
45798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // sure the pixels will not be pre-dithered/clamped until we decide they are
45898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
45998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
46098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        colorType = PNG_COLOR_TYPE_RGB_ALPHA;
46198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
46298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
46398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug) {
46498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        switch (colorType) {
46598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        case PNG_COLOR_TYPE_PALETTE:
4661ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            diag->note(DiagMessage()
4671ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                       << "has " << paletteEntries
4681ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                       << " colors" << (hasTransparency ? " (with alpha)" : "")
4691ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                       << ", using PNG_COLOR_TYPE_PALLETTE");
47098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            break;
47198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        case PNG_COLOR_TYPE_GRAY:
4721ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
47398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            break;
47498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        case PNG_COLOR_TYPE_GRAY_ALPHA:
4751ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
47698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            break;
47798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        case PNG_COLOR_TYPE_RGB:
4781ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
47998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            break;
48098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        case PNG_COLOR_TYPE_RGB_ALPHA:
4811ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
48298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            break;
48398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
48498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
48598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
48698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
48798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
48898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
48998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (colorType == PNG_COLOR_TYPE_PALETTE) {
49098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
49198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (hasTransparency) {
49298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
49398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
49498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_filter(writePtr, 0, PNG_NO_FILTERS);
49598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
49698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
49798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
49898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
49998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (info->is9Patch) {
50098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
50198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int pIndex = info->haveLayoutBounds ? 2 : 1;
50298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int bIndex = 1;
50398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int oIndex = 0;
50498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
50598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
50698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_bytep chunkNames = info->haveLayoutBounds
50798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                ? (png_bytep)"npOl\0npLb\0npTc\0"
50898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                : (png_bytep)"npOl\0npTc";
50998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
51098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // base 9 patch data
51198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (kDebug) {
5121ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            diag->note(DiagMessage() << "adding 9-patch info..");
51398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
51498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        strcpy((char*)unknowns[pIndex].name, "npTc");
51598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
51698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        unknowns[pIndex].size = info->info9Patch.serializedSize();
51798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // TODO: remove the check below when everything works
51898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
51998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
52098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // automatically generated 9 patch outline data
52198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int chunkSize = sizeof(png_uint_32) * 6;
52298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        strcpy((char*)unknowns[oIndex].name, "npOl");
52398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
52498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_byte outputData[chunkSize];
52598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
52698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        ((float*) outputData)[4] = info->outlineRadius;
52798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        ((png_uint_32*) outputData)[5] = info->outlineAlpha;
52898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        memcpy(unknowns[oIndex].data, &outputData, chunkSize);
52998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        unknowns[oIndex].size = chunkSize;
53098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
53198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // optional optical inset / layout bounds data
53298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (info->haveLayoutBounds) {
53398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            int chunkSize = sizeof(png_uint_32) * 4;
53498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            strcpy((char*)unknowns[bIndex].name, "npLb");
53598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
53698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
53798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            unknowns[bIndex].size = chunkSize;
53898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
53998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
54098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (int i = 0; i < chunkCount; i++) {
54198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            unknowns[i].location = PNG_HAVE_PLTE;
54298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
54398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
54498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                    chunkNames, chunkCount);
54598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
54698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
54798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#if PNG_LIBPNG_VER < 10600
54898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Deal with unknown chunk location bug in 1.5.x and earlier.
54998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
55098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (info->haveLayoutBounds) {
55198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
55298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
55398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski#endif
55498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
55598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
55698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_write_info(writePtr, infoPtr);
55798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
55898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_bytepp rows;
55998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
56098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (colorType == PNG_COLOR_TYPE_RGB) {
56198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
56298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
56398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        rows = info->rows.data();
56498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
56598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        rows = outRows;
56698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
56798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_write_image(writePtr, rows);
56898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
56998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug) {
57098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("Final image data:\n");
57198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        //dump_image(info->width, info->height, rows, colorType);
57298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
57398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
57498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_write_end(writePtr, infoPtr);
57598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
57698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (uint32_t i = 0; i < info->height; i++) {
57798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        free(outRows[i]);
57898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
57998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    free(outRows);
58098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    free(unknowns[0].data);
58198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    free(unknowns[1].data);
58298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    free(unknowns[2].data);
58398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
58498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
58598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                 &compressionType, nullptr);
58698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
58798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug) {
5881ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        diag->note(DiagMessage()
5891ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                   << "image written: w = " << width << ", h = " << height
5901ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                   << ", d = " << bitDepth << ", colors = " << colorType
5911ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                   << ", inter = " << interlaceType << ", comp = " << compressionType);
59298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
59398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return true;
59498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
59598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
59698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskiconstexpr uint32_t kColorWhite = 0xffffffffu;
59798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskiconstexpr uint32_t kColorTick = 0xff000000u;
59898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskiconstexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
59998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
60098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskienum class TickType {
60198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    kNone,
60298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    kTick,
60398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    kLayoutBounds,
60498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    kBoth
60598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski};
60698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
60798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic TickType tickType(png_bytep p, bool transparent, const char** outError) {
60898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
60998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
61098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (transparent) {
61198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (p[3] == 0) {
61298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            return TickType::kNone;
61398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
61498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (color == kColorLayoutBoundsTick) {
61598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            return TickType::kLayoutBounds;
61698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
61798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (color == kColorTick) {
61898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            return TickType::kTick;
61998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
62098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
62198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Error cases
62298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (p[3] != 0xff) {
62398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *outError = "Frame pixels must be either solid or transparent "
62498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                        "(not intermediate alphas)";
62598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            return TickType::kNone;
62698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
62798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
62898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
62998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *outError = "Ticks in transparent frame must be black or red";
63098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
63198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return TickType::kTick;
63298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
63398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
63498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (p[3] != 0xFF) {
63598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *outError = "White frame must be a solid color (no alpha)";
63698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
63798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (color == kColorWhite) {
63898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return TickType::kNone;
63998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
64098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (color == kColorTick) {
64198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return TickType::kTick;
64298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
64398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (color == kColorLayoutBoundsTick) {
64498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return TickType::kLayoutBounds;
64598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
64698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
64798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
64898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *outError = "Ticks in white frame must be black or red";
64998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return TickType::kNone;
65098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
65198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return TickType::kTick;
65298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
65398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
65498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskienum class TickState {
65598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    kStart,
65698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    kInside1,
65798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    kOutside1
65898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski};
65998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
66098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
66198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                               int32_t* outLeft, int32_t* outRight, const char** outError,
66298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                               uint8_t* outDivs, bool multipleAllowed) {
66398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    *outLeft = *outRight = -1;
66498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    TickState state = TickState::kStart;
66598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool found = false;
66698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
66798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (int i = 1; i < width - 1; i++) {
66898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
66998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (state == TickState::kStart ||
67098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                (state == TickState::kOutside1 && multipleAllowed)) {
67198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outLeft = i-1;
67298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outRight = width-2;
67398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                found = true;
67498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (outDivs != NULL) {
67598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    *outDivs += 2;
67698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
67798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                state = TickState::kInside1;
67898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            } else if (state == TickState::kOutside1) {
67998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outError = "Can't have more than one marked region along edge";
68098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outLeft = i;
68198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                return false;
68298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
68398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        } else if (!*outError) {
68498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (state == TickState::kInside1) {
68598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                // We're done with this div.  Move on to the next.
68698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outRight = i-1;
68798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                outRight += 2;
68898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                outLeft += 2;
68998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                state = TickState::kOutside1;
69098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
69198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        } else {
69298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *outLeft = i;
69398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            return false;
69498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
69598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
69698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
69798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (required && !found) {
69898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *outError = "No marked region found along edge";
69998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *outLeft = -1;
70098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return false;
70198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
70298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return true;
70398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
70498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
70598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
70698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                             bool required, int32_t* outTop, int32_t* outBottom,
70798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                             const char** outError, uint8_t* outDivs, bool multipleAllowed) {
70898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    *outTop = *outBottom = -1;
70998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    TickState state = TickState::kStart;
71098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool found = false;
71198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
71298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (int i = 1; i < height - 1; i++) {
71398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
71498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (state == TickState::kStart ||
71598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                (state == TickState::kOutside1 && multipleAllowed)) {
71698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outTop = i-1;
71798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outBottom = height-2;
71898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                found = true;
71998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (outDivs != NULL) {
72098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    *outDivs += 2;
72198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
72298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                state = TickState::kInside1;
72398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            } else if (state == TickState::kOutside1) {
72498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outError = "Can't have more than one marked region along edge";
72598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outTop = i;
72698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                return false;
72798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
72898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        } else if (!*outError) {
72998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (state == TickState::kInside1) {
73098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                // We're done with this div.  Move on to the next.
73198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                *outBottom = i-1;
73298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                outTop += 2;
73398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                outBottom += 2;
73498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                state = TickState::kOutside1;
73598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
73698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        } else {
73798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *outTop = i;
73898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            return false;
73998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
74098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
74198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
74298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (required && !found) {
74398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *outError = "No marked region found along edge";
74498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *outTop = -1;
74598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return false;
74698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
74798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return true;
74898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
74998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
75098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
75198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                           bool /* required */, int32_t* outLeft,
75298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                           int32_t* outRight, const char** outError) {
75398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    *outLeft = *outRight = 0;
75498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
75598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Look for left tick
75698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
75798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Starting with a layout padding tick
75898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int i = 1;
75998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        while (i < width - 1) {
76098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            (*outLeft)++;
76198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            i++;
76298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
76398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
76498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
76598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
76698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
76798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
76898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Look for right tick
76998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
77098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Ending with a layout padding tick
77198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int i = width - 2;
77298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        while (i > 1) {
77398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            (*outRight)++;
77498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            i--;
77598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
77698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
77798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
77898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
77998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
78098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return true;
78198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
78298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
78398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
78498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                         bool /* required */, int32_t* outTop, int32_t* outBottom,
78598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                         const char** outError) {
78698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    *outTop = *outBottom = 0;
78798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
78898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Look for top tick
78998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
79098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Starting with a layout padding tick
79198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int i = 1;
79298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        while (i < height - 1) {
79398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            (*outTop)++;
79498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            i++;
79598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
79698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
79798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
79898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
79998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
80098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
80198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Look for bottom tick
80298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
80398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Ending with a layout padding tick
80498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        int i = height - 2;
80598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        while (i > 1) {
80698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            (*outBottom)++;
80798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            i--;
80898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
80998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                break;
81098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
81198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
81298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
81398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return true;
81498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
81598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
81698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
81798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                           int dX, int dY, int* outInset) {
81898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint8_t maxOpacity = 0;
81998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int inset = 0;
82098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    *outInset = 0;
82198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
82298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_byte* color = rows[y] + x * 4;
82398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        uint8_t opacity = color[3];
82498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (opacity > maxOpacity) {
82598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            maxOpacity = opacity;
82698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            *outInset = inset;
82798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
82898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (opacity == 0xff) return;
82998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
83098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
83198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
83298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
83398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint8_t maxAlpha = 0;
83498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (int x = startX; x < endX; x++) {
83598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        uint8_t alpha = (row + x * 4)[3];
83698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (alpha > maxAlpha) maxAlpha = alpha;
83798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
83898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return maxAlpha;
83998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
84098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
84198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
84298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint8_t maxAlpha = 0;
84398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (int y = startY; y < endY; y++) {
84498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        uint8_t alpha = (rows[y] + offsetX * 4)[3];
84598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (alpha > maxAlpha) maxAlpha = alpha;
84698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
84798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return maxAlpha;
84898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
84998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
85098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic void getOutline(PngInfo* image) {
85198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int midX = image->width / 2;
85298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int midY = image->height / 2;
85398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int endX = image->width - 2;
85498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int endY = image->height - 2;
85598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
85698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // find left and right extent of nine patch content on center row
85798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (image->width > 4) {
85898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
85998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
86098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                       &image->outlineInsetsRight);
86198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
86298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->outlineInsetsLeft = 0;
86398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->outlineInsetsRight = 0;
86498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
86598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
86698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // find top and bottom extent of nine patch content on center column
86798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (image->height > 4) {
86898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
86998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
87098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                       &image->outlineInsetsBottom);
87198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
87298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->outlineInsetsTop = 0;
87398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->outlineInsetsBottom = 0;
87498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
87598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
87698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int innerStartX = 1 + image->outlineInsetsLeft;
87798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int innerStartY = 1 + image->outlineInsetsTop;
87898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int innerEndX = endX - image->outlineInsetsRight;
87998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int innerEndY = endY - image->outlineInsetsBottom;
88098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int innerMidX = (innerEndX + innerStartX) / 2;
88198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int innerMidY = (innerEndY + innerStartY) / 2;
88298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
88398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // assuming the image is a round rect, compute the radius by marching
88498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // diagonally from the top left corner towards the center
88598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->outlineAlpha = std::max(
88698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
88798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
88898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
88998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int diagonalInset = 0;
89098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
89198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                   &diagonalInset);
89298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
89398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    /* Determine source radius based upon inset:
89498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski     *     sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
89598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski     *     sqrt(2) * r = sqrt(2) * i + r
89698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski     *     (sqrt(2) - 1) * r = sqrt(2) * i
89798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski     *     r = sqrt(2) / (sqrt(2) - 1) * i
89898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski     */
89998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->outlineRadius = 3.4142f * diagonalInset;
90098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
90198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug) {
90298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
90398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->outlineInsetsLeft,
90498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->outlineInsetsTop,
90598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->outlineInsetsRight,
90698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->outlineInsetsBottom,
90798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->outlineRadius,
90898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->outlineAlpha);
90998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
91098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
91198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
91298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
91398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_bytep color = rows[top] + left*4;
91498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
91598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (left > right || top > bottom) {
91698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return android::Res_png_9patch::TRANSPARENT_COLOR;
91798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
91898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
91998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    while (top <= bottom) {
92098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (int i = left; i <= right; i++) {
92198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            png_bytep p = rows[top]+i*4;
92298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (color[3] == 0) {
92398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (p[3] != 0) {
92498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    return android::Res_png_9patch::NO_COLOR;
92598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
92698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            } else if (p[0] != color[0] || p[1] != color[1] ||
92798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    p[2] != color[2] || p[3] != color[3]) {
92898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                return android::Res_png_9patch::NO_COLOR;
92998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
93098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
93198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        top++;
93298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
93398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
93498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (color[3] == 0) {
93598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return android::Res_png_9patch::TRANSPARENT_COLOR;
93698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
93798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
93898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
93998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
94098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskistatic bool do9Patch(PngInfo* image, std::string* outError) {
94198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->is9Patch = true;
94298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
94398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int W = image->width;
94498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int H = image->height;
94598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int i, j;
94698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
94798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    const int maxSizeXDivs = W * sizeof(int32_t);
94898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    const int maxSizeYDivs = H * sizeof(int32_t);
94998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t* xDivs = image->xDivs = new int32_t[W];
95098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int32_t* yDivs = image->yDivs = new int32_t[H];
95198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint8_t numXDivs = 0;
95298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint8_t numYDivs = 0;
95398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
95498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int8_t numColors;
95598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int numRows;
95698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int numCols;
95798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int top;
95898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int left;
95998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int right;
96098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int bottom;
96198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    memset(xDivs, -1, maxSizeXDivs);
96298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    memset(yDivs, -1, maxSizeYDivs);
96398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
96498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
96598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->layoutBoundsLeft = image->layoutBoundsRight = 0;
96698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->layoutBoundsTop = image->layoutBoundsBottom = 0;
96798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
96898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_bytep p = image->rows[0];
96998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool transparent = p[3] == 0;
97098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool hasColor = false;
97198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
97298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    const char* errorMsg = nullptr;
97398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int errorPixel = -1;
97498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    const char* errorEdge = nullptr;
97598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
97698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    int colorIndex = 0;
97798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    std::vector<png_bytep> newRows;
97898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
97998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Validate size...
98098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (W < 3 || H < 3) {
98198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
98298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto getout;
98398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
98498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
98598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Validate frame...
98698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!transparent &&
98798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
98898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorMsg = "Must have one-pixel frame that is either transparent or white";
98998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto getout;
99098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
99198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
99298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Find left and right of sizing areas...
99398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
99498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                            true)) {
99598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorPixel = xDivs[0];
99698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorEdge = "top";
99798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto getout;
99898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
99998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
100098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Find top and bottom of sizing areas...
100198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
100298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                          &errorMsg, &numYDivs, true)) {
100398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorPixel = yDivs[0];
100498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorEdge = "left";
100598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto getout;
100698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
100798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
100898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Copy patch size data into image...
100998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->info9Patch.numXDivs = numXDivs;
101098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->info9Patch.numYDivs = numYDivs;
101198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
101298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Find left and right of padding area...
101398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
101498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                            &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
101598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                            &errorMsg, nullptr, false)) {
101698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorPixel = image->info9Patch.paddingLeft;
101798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorEdge = "bottom";
101898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto getout;
101998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
102098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
102198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Find top and bottom of padding area...
102298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
102398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                          &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
102498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                          &errorMsg, nullptr, false)) {
102598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorPixel = image->info9Patch.paddingTop;
102698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorEdge = "right";
102798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto getout;
102898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
102998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
103098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Find left and right of layout padding...
103198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
103298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                   &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
103398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
103498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
103598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                                 &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
103698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
103798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->haveLayoutBounds = image->layoutBoundsLeft != 0
103898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                               || image->layoutBoundsRight != 0
103998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                               || image->layoutBoundsTop != 0
104098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                               || image->layoutBoundsBottom != 0;
104198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
104298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (image->haveLayoutBounds) {
104398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (kDebug) {
104498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
104598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    image->layoutBoundsRight, image->layoutBoundsBottom);
104698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
104798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
104898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
104998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // use opacity of pixels to estimate the round rect outline
105098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    getOutline(image);
105198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
105298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // If padding is not yet specified, take values from size.
105398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (image->info9Patch.paddingLeft < 0) {
105498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->info9Patch.paddingLeft = xDivs[0];
105598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->info9Patch.paddingRight = W - 2 - xDivs[1];
105698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
105798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Adjust value to be correct!
105898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
105998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
106098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (image->info9Patch.paddingTop < 0) {
106198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->info9Patch.paddingTop = yDivs[0];
106298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->info9Patch.paddingBottom = H - 2 - yDivs[1];
106398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    } else {
106498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // Adjust value to be correct!
106598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
106698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
106798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
106898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski/*    if (kDebug) {
106998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
107098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                xDivs[0], xDivs[1],
107198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                yDivs[0], yDivs[1]);
107298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
107398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
107498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
107598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }*/
107698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
107798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Remove frame from image.
107898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    newRows.resize(H - 2);
107998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (i = 0; i < H - 2; i++) {
108098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        newRows[i] = image->rows[i + 1];
108198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
108298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
108398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->rows.swap(newRows);
108498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
108598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->width -= 2;
108698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    W = image->width;
108798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->height -= 2;
108898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    H = image->height;
108998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
109098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Figure out the number of rows and columns in the N-patch
109198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    numCols = numXDivs + 1;
109298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (xDivs[0] == 0) {  // Column 1 is strechable
109398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        numCols--;
109498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
109598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (xDivs[numXDivs - 1] == W) {
109698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        numCols--;
109798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
109898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    numRows = numYDivs + 1;
109998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (yDivs[0] == 0) {  // Row 1 is strechable
110098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        numRows--;
110198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
110298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (yDivs[numYDivs - 1] == H) {
110398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        numRows--;
110498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
110598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
110698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Make sure the amount of rows and columns will fit in the number of
110798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // colors we can use in the 9-patch format.
110898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (numRows * numCols > 0x7F) {
110998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        errorMsg = "Too many rows and columns in 9-patch perimeter";
111098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto getout;
111198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
111298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
111398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    numColors = numRows * numCols;
111498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->info9Patch.numColors = numColors;
111598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    image->colors.resize(numColors);
111698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
111798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Fill in color information for each patch.
111898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
111998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    uint32_t c;
112098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    top = 0;
112198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
112298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // The first row always starts with the top being at y=0 and the bottom
112398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // being either yDivs[1] (if yDivs[0]=0) of yDivs[0].  In the former case
112498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // the first row is stretchable along the Y axis, otherwise it is fixed.
112598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // The last row always ends with the bottom being bitmap.height and the top
112698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
112798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // yDivs[numYDivs-1]. In the former case the last row is stretchable along
112898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // the Y axis, otherwise it is fixed.
112998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    //
113098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // The first and last columns are similarly treated with respect to the X
113198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // axis.
113298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    //
113398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // The above is to help explain some of the special casing that goes on the
113498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // code below.
113598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
113698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // The initial yDiv and whether the first row is considered stretchable or
113798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // not depends on whether yDiv[0] was zero or not.
113898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
113998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        if (j == numYDivs) {
114098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            bottom = H;
114198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        } else {
114298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            bottom = yDivs[j];
114398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
114498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        left = 0;
114598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // The initial xDiv and whether the first column is considered
114698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        // stretchable or not depends on whether xDiv[0] was zero or not.
114798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
114898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (i == numXDivs) {
114998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                right = W;
115098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            } else {
115198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                right = xDivs[i];
115298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
115398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
115498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            image->colors[colorIndex++] = c;
115598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (kDebug) {
115698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                if (c != android::Res_png_9patch::NO_COLOR) {
115798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                    hasColor = true;
115898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                }
115998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
116098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            left = right;
116198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
116298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        top = bottom;
116398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
116498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
116598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    assert(colorIndex == numColors);
116698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
116798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (kDebug && hasColor) {
116898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        for (i = 0; i < numColors; i++) {
116998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (i == 0) printf("Colors:\n");
117098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            printf(" #%08x", image->colors[i]);
117198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (i == numColors - 1) printf("\n");
117298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
117398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
117498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskigetout:
117598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (errorMsg) {
117698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        std::stringstream err;
117798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        err << "9-patch malformed: " << errorMsg;
1178144c5eabe52f61e9afc08c461142878d10f5e443Adam Lesinski        if (errorEdge) {
117998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            err << "." << std::endl;
118098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            if (errorPixel >= 0) {
118198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
118298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            } else {
118398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski                err << "Found along " << errorEdge << " edge";
118498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            }
118598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
118698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        *outError = err.str();
118798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return false;
118898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
118998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return true;
119098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
119198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
119298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
11931ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinskibool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
11941ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski                  const PngOptions& options) {
119598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_byte signature[kPngSignatureSize];
119698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
119798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Read the PNG signature first.
11981ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
11991ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        mDiag->error(DiagMessage() << strerror(errno));
120098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return false;
120198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
120298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
120398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // If the PNG signature doesn't match, bail early.
120498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
12051ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        mDiag->error(DiagMessage() << "not a valid png file");
120698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        return false;
120798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
120898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
120998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    bool result = false;
121098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_structp readPtr = nullptr;
121198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_infop infoPtr = nullptr;
121298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_structp writePtr = nullptr;
121398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_infop writeInfoPtr = nullptr;
121498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    PngInfo pngInfo = {};
121598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
121698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
121798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!readPtr) {
12181ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        mDiag->error(DiagMessage() << "failed to allocate read ptr");
121998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto bail;
122098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
122198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
122298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    infoPtr = png_create_info_struct(readPtr);
122398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!infoPtr) {
12241ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        mDiag->error(DiagMessage() << "failed to allocate info ptr");
122598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto bail;
122698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
122798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
12281ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
122998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
123098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Set the read function to read from std::istream.
12311ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream);
123298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
12331ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) {
123498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto bail;
123598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
123698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
12374d3a987694f6f6b95d8a0f1542618223ce253e6dAdam Lesinski    if (util::stringEndsWith<char>(source.path, ".9.png")) {
12381ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        std::string errorMsg;
12391ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        if (!do9Patch(&pngInfo, &errorMsg)) {
12401ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski            mDiag->error(DiagMessage() << errorMsg);
124198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski            goto bail;
124298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        }
124398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
124498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
124598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
124698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!writePtr) {
12471ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        mDiag->error(DiagMessage() << "failed to allocate write ptr");
124898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto bail;
124998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
125098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
125198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    writeInfoPtr = png_create_info_struct(writePtr);
125298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (!writeInfoPtr) {
12531ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski        mDiag->error(DiagMessage() << "failed to allocate write info ptr");
125498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto bail;
125598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
125698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
125798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
125898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
125998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    // Set the write function to write to std::ostream.
1260769de98f2dd41bfe39a1c9f76aefd1ad58942733Adam Lesinski    png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
126198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
12621ab598f46c3ff520a67f9d80194847741f3467abAdam Lesinski    if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) {
126398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        goto bail;
126498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
126598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
126698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    result = true;
126798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinskibail:
126898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (readPtr) {
126998aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
127098aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
127198aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
127298aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    if (writePtr) {
127398aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski        png_destroy_write_struct(&writePtr, &writeInfoPtr);
127498aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    }
127598aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski    return result;
127698aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski}
127798aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski
127898aa3ad6e46e3b0270785c8b3f9798e37e8af140Adam Lesinski} // namespace aapt
1279