Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ffa96562 authored by Adam Lesinski's avatar Adam Lesinski Committed by Android (Google) Code Review
Browse files

Merge "AAPT2: Refactor PngCrunching"

parents 996b2083 21efb682
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -24,7 +24,10 @@ main := Main.cpp
sources := \
	compile/IdAssigner.cpp \
	compile/InlineXmlFormatParser.cpp \
	compile/NinePatch.cpp \
	compile/Png.cpp \
	compile/PngChunkFilter.cpp \
	compile/PngCrunch.cpp \
	compile/PseudolocaleGenerator.cpp \
	compile/Pseudolocalizer.cpp \
	compile/XmlIdCollector.cpp \
@@ -34,6 +37,7 @@ sources := \
	flatten/XmlFlattener.cpp \
	io/File.cpp \
	io/FileSystem.cpp \
	io/Io.cpp \
	io/ZipArchive.cpp \
	link/AutoVersioner.cpp \
	link/ManifestFixer.cpp \
@@ -84,6 +88,7 @@ sourcesJni :=
testSources := \
	compile/IdAssigner_test.cpp \
	compile/InlineXmlFormatParser_test.cpp \
	compile/NinePatch_test.cpp \
	compile/PseudolocaleGenerator_test.cpp \
	compile/Pseudolocalizer_test.cpp \
	compile/XmlIdCollector_test.cpp \
+122 −5
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <google/protobuf/io/coded_stream.h>

#include <android-base/errors.h>
#include <android-base/file.h>
#include <dirent.h>
#include <fstream>
#include <string>
@@ -359,6 +361,9 @@ static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outp
static bool compileXml(IAaptContext* context, const CompileOptions& options,
                       const ResourcePathData& pathData, IArchiveWriter* writer,
                       const std::string& outputPath) {
    if (context->verbose()) {
        context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling XML");
    }

    std::unique_ptr<xml::XmlResource> xmlRes;
    {
@@ -431,9 +436,43 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options,
    return true;
}

class BigBufferOutputStream : public io::OutputStream {
public:
    explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {
    }

    bool Next(void** data, int* len) override {
        size_t count;
        *data = mBuffer->nextBlock(&count);
        *len = static_cast<int>(count);
        return true;
    }

    void BackUp(int count) override {
        mBuffer->backUp(count);
    }

    int64_t ByteCount() const override {
        return mBuffer->size();
    }

    bool HadError() const override {
        return false;
    }

private:
    BigBuffer* mBuffer;

    DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
};

static bool compilePng(IAaptContext* context, const CompileOptions& options,
                       const ResourcePathData& pathData, IArchiveWriter* writer,
                       const std::string& outputPath) {
    if (context->verbose()) {
        context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling PNG");
    }

    BigBuffer buffer(4096);
    ResourceFile resFile;
    resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
@@ -441,16 +480,90 @@ static bool compilePng(IAaptContext* context, const CompileOptions& options,
    resFile.source = pathData.source;

    {
        std::ifstream fin(pathData.source.path, std::ifstream::binary);
        if (!fin) {
            context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
        std::string content;
        if (!android::base::ReadFileToString(pathData.source.path, &content)) {
            context->getDiagnostics()->error(DiagMessage(pathData.source)
                                             << android::base::SystemErrorCodeToString(errno));
            return false;
        }

        BigBuffer crunchedPngBuffer(4096);
        BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer);

        // Ensure that we only keep the chunks we care about if we end up
        // using the original PNG instead of the crunched one.
        PngChunkFilter pngChunkFilter(content);
        std::unique_ptr<Image> image = readPng(context, &pngChunkFilter);
        if (!image) {
            return false;
        }

        std::unique_ptr<NinePatch> ninePatch;
        if (pathData.extension == "9.png") {
            std::string err;
            ninePatch = NinePatch::create(image->rows.get(), image->width, image->height, &err);
            if (!ninePatch) {
                context->getDiagnostics()->error(DiagMessage() << err);
                return false;
            }

            // Remove the 1px border around the NinePatch.
            // Basically the row array is shifted up by 1, and the length is treated
            // as height - 2.
            // For each row, shift the array to the left by 1, and treat the length as width - 2.
            image->width -= 2;
            image->height -= 2;
            memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
            for (int32_t h = 0; h < image->height; h++) {
                memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
            }

            if (context->verbose()) {
                context->getDiagnostics()->note(DiagMessage(pathData.source)
                                                << "9-patch: " << *ninePatch);
            }
        }

        // Write the crunched PNG.
        if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut, {})) {
            return false;
        }

        if (ninePatch != nullptr
                || crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) {
            // No matter what, we must use the re-encoded PNG, even if it is larger.
            // 9-patch images must be re-encoded since their borders are stripped.
            buffer.appendBuffer(std::move(crunchedPngBuffer));
        } else {
            // The re-encoded PNG is larger than the original, and there is
            // no mandatory transformation. Use the original.
            if (context->verbose()) {
                context->getDiagnostics()->note(DiagMessage(pathData.source)
                                                << "original PNG is smaller than crunched PNG"
                                                << ", using original");
            }

            PngChunkFilter pngChunkFilterAgain(content);
            BigBuffer filteredPngBuffer(4096);
            BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer);
            io::copy(&filteredPngBufferOut, &pngChunkFilterAgain);
            buffer.appendBuffer(std::move(filteredPngBuffer));
        }

        if (context->verbose()) {
            // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
            // This will help catch exotic cases where the new code may generate larger PNGs.
            std::stringstream legacyStream(content);
            BigBuffer legacyBuffer(4096);
            Png png(context->getDiagnostics());
        if (!png.process(pathData.source, &fin, &buffer, {})) {
            if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) {
                return false;
            }

            context->getDiagnostics()->note(DiagMessage(pathData.source)
                                            << "legacy=" << legacyBuffer.size()
                                            << " new=" << buffer.size());
        }
    }

    if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
@@ -463,6 +576,10 @@ static bool compilePng(IAaptContext* context, const CompileOptions& options,
static bool compileFile(IAaptContext* context, const CompileOptions& options,
                        const ResourcePathData& pathData, IArchiveWriter* writer,
                        const std::string& outputPath) {
    if (context->verbose()) {
        context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling file");
    }

    BigBuffer buffer(256);
    ResourceFile resFile;
    resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
+201 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef AAPT_COMPILE_IMAGE_H
#define AAPT_COMPILE_IMAGE_H

#include <android-base/macros.h>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace aapt {

/**
 * An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
 */
class Image {
public:
    explicit Image() = default;

    /**
     * A `height` sized array of pointers, where each element points to a
     * `width` sized row of RGBA_8888 pixels.
     */
    std::unique_ptr<uint8_t*[]> rows;

    /**
     * The width of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
     * format limitations.
     */
    int32_t width = 0;

    /**
     * The height of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data
     * format limitations.
     */
    int32_t height = 0;

    /**
     * Buffer to the raw image data stored sequentially.
     * Use `rows` to access the data on a row-by-row basis.
     */
    std::unique_ptr<uint8_t[]> data;

private:
    DISALLOW_COPY_AND_ASSIGN(Image);
};

/**
 * A range of pixel values, starting at 'start' and ending before 'end' exclusive. Or rather [a, b).
 */
struct Range {
    int32_t start = 0;
    int32_t end = 0;

    explicit Range() = default;
    inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
    }
};

inline bool operator==(const Range& left, const Range& right) {
    return left.start == right.start && left.end == right.end;
}

/**
 * Inset lengths from all edges of a rectangle. `left` and `top` are measured from the left and top
 * edges, while `right` and `bottom` are measured from the right and bottom edges, respectively.
 */
struct Bounds {
    int32_t left = 0;
    int32_t top = 0;
    int32_t right = 0;
    int32_t bottom = 0;

    explicit Bounds() = default;
    inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b) :
            left(l), top(t), right(r), bottom(b) {
    }

    bool nonZero() const;
};

inline bool Bounds::nonZero() const {
    return left != 0 || top != 0 || right != 0 || bottom != 0;
}

inline bool operator==(const Bounds& left, const Bounds& right) {
    return left.left == right.left && left.top == right.top &&
            left.right == right.right && left.bottom == right.bottom;
}

/**
 * Contains 9-patch data from a source image. All measurements exclude the 1px border of the
 * source 9-patch image.
 */
class NinePatch {
public:
    static std::unique_ptr<NinePatch> create(uint8_t** rows,
                                             const int32_t width, const int32_t height,
                                             std::string* errOut);

    /**
     * Packs the RGBA_8888 data pointed to by pixel into a uint32_t
     * with format 0xAARRGGBB (the way 9-patch expects it).
     */
    static uint32_t packRGBA(const uint8_t* pixel);

    /**
     * 9-patch content padding/insets. All positions are relative to the 9-patch
     * NOT including the 1px thick source border.
     */
    Bounds padding;

    /**
     * Optical layout bounds/insets. This overrides the padding for
     * layout purposes. All positions are relative to the 9-patch
     * NOT including the 1px thick source border.
     * See https://developer.android.com/about/versions/android-4.3.html#OpticalBounds
     */
    Bounds layoutBounds;

    /**
     * Outline of the image, calculated based on opacity.
     */
    Bounds outline;

    /**
     * The computed radius of the outline. If non-zero, the outline is a rounded-rect.
     */
    float outlineRadius = 0.0f;

    /**
     * The largest alpha value within the outline.
     */
    uint32_t outlineAlpha = 0x000000ffu;

    /**
     * Horizontal regions of the image that are stretchable.
     * All positions are relative to the 9-patch
     * NOT including the 1px thick source border.
     */
    std::vector<Range> horizontalStretchRegions;

    /**
     * Vertical regions of the image that are stretchable.
     * All positions are relative to the 9-patch
     * NOT including the 1px thick source border.
     */
    std::vector<Range> verticalStretchRegions;

    /**
     * The colors within each region, fixed or stretchable.
     * For w*h regions, the color of region (x,y) is addressable
     * via index y*w + x.
     */
    std::vector<uint32_t> regionColors;

    /**
     * Returns serialized data containing the original basic 9-patch meta data.
     * Optical layout bounds and round rect outline data must be serialized
     * separately using serializeOpticalLayoutBounds() and serializeRoundedRectOutline().
     */
    std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const;

    /**
     * Serializes the layout bounds.
     */
    std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const;

    /**
     * Serializes the rounded-rect outline.
     */
    std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const;

private:
    explicit NinePatch() = default;

    DISALLOW_COPY_AND_ASSIGN(NinePatch);
};

::std::ostream& operator<<(::std::ostream& out, const Range& range);
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch);

} // namespace aapt

#endif /* AAPT_COMPILE_IMAGE_H */
+671 −0

File added.

Preview size limit exceeded, changes collapsed.

+322 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading