1/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/codec/SkAndroidCodec.h"
9#include "include/codec/SkCodec.h"
10#include "include/core/SkBitmap.h"
11#include "include/core/SkColor.h"
12#include "include/core/SkColorSpace.h"
13#include "include/core/SkData.h"
14#include "include/core/SkEncodedImageFormat.h"
15#include "include/core/SkImageGenerator.h"
16#include "include/core/SkImageInfo.h"
17#include "include/core/SkRefCnt.h"
18#include "include/core/SkSize.h"
19#include "include/core/SkString.h"
20#include "include/core/SkTypes.h"
21#include "include/third_party/skcms/skcms.h"
22#include "src/codec/SkCodecImageGenerator.h"
23#include "src/core/SkPixmapPriv.h"
24#include "tests/Test.h"
25#include "tools/Resources.h"
26
27#include <string.h>
28#include <initializer_list>
29#include <memory>
30#include <utility>
31
32static SkISize times(const SkISize& size, float factor) {
33    return { (int) (size.width() * factor), (int) (size.height() * factor) };
34}
35
36static SkISize plus(const SkISize& size, int term) {
37    return { size.width() + term, size.height() + term };
38}
39
40static bool invalid(const SkISize& size) {
41    return size.width() < 1 || size.height() < 1;
42}
43
44DEF_TEST(AndroidCodec_computeSampleSize, r) {
45    if (GetResourcePath().isEmpty()) {
46        return;
47    }
48    for (const char* file : { "images/color_wheel.webp",
49                              "images/ship.png",
50                              "images/dog.jpg",
51                              "images/color_wheel.gif",
52                              "images/rle.bmp",
53                              "images/google_chrome.ico",
54                              "images/mandrill.wbmp",
55#ifdef SK_CODEC_DECODES_RAW
56                              "images/sample_1mp.dng",
57#endif
58                              }) {
59        auto data = GetResourceAsData(file);
60        if (!data) {
61            ERRORF(r, "Could not get %s", file);
62            continue;
63        }
64
65        auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
66        if (!codec) {
67            ERRORF(r, "Could not create codec for %s", file);
68            continue;
69        }
70
71        const auto dims = codec->getInfo().dimensions();
72        const SkISize downscales[] = {
73            plus(dims, -1),
74            times(dims, .15f),
75            times(dims, .6f),
76            { (int32_t) (dims.width() * .25f), (int32_t) (dims.height() * .75f ) },
77            { 1,  1 },
78            { 1,  2 },
79            { 2,  1 },
80            { 0, -1 },
81            { dims.width(), dims.height() - 1 },
82        };
83        for (SkISize size : downscales) {
84            const auto requested = size;
85            const int computedSampleSize = codec->computeSampleSize(&size);
86            REPORTER_ASSERT(r, size.width() >= 1 && size.height() >= 1);
87            if (codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP) {
88                // WebP supports arbitrary down-scaling.
89                REPORTER_ASSERT(r, size == requested || invalid(requested));
90            } else if (computedSampleSize == 1) {
91                REPORTER_ASSERT(r, size == dims);
92            } else {
93                REPORTER_ASSERT(r, computedSampleSize > 1);
94                if (size.width() >= dims.width() || size.height() >= dims.height()) {
95                    ERRORF(r, "File %s's computed sample size (%i) is bigger than"
96                              " original? original: %i x %i\tsampled: %i x %i",
97                              file, computedSampleSize, dims.width(), dims.height(),
98                              size.width(), size.height());
99                }
100                REPORTER_ASSERT(r, size.width()  >= requested.width() &&
101                                   size.height() >= requested.height());
102                REPORTER_ASSERT(r, size.width()  <  dims.width() &&
103                                   size.height() <  dims.height());
104            }
105        }
106
107        const SkISize upscales[] = {
108            dims, plus(dims, 5), times(dims, 2),
109        };
110        for (SkISize size : upscales) {
111            const int computedSampleSize = codec->computeSampleSize(&size);
112            REPORTER_ASSERT(r, computedSampleSize == 1);
113            REPORTER_ASSERT(r, dims == size);
114        }
115
116        // This mimics how Android's ImageDecoder uses SkAndroidCodec. A client
117        // can choose their dimensions based on calling getSampledDimensions,
118        // but the ImageDecoder API takes an arbitrary size. It then uses
119        // computeSampleSize to determine the best dimensions and sampleSize.
120        // It should return the same dimensions. the sampleSize may be different
121        // due to integer division.
122        for (int sampleSize : { 1, 2, 3, 4, 8, 16, 32 }) {
123            const SkISize sampledDims = codec->getSampledDimensions(sampleSize);
124            SkISize size = sampledDims;
125            const int computedSampleSize = codec->computeSampleSize(&size);
126            if (sampledDims != size) {
127                ERRORF(r, "File '%s'->getSampledDimensions(%i) yields computed"
128                          " sample size of %i\n\tsampledDimensions: %i x %i\t"
129                          "computed dimensions: %i x %i",
130                          file, sampleSize, computedSampleSize,
131                          sampledDims.width(), sampledDims.height(),
132                          size.width(), size.height());
133            }
134        }
135    }
136}
137
138DEF_TEST(AndroidCodec_wide, r) {
139    if (GetResourcePath().isEmpty()) {
140        return;
141    }
142
143    const char* path = "images/wide-gamut.png";
144    auto data = GetResourceAsData(path);
145    if (!data) {
146        ERRORF(r, "Missing file %s", path);
147        return;
148    }
149
150    auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
151    if (!codec) {
152        ERRORF(r, "Failed to create codec from %s", path);
153        return;
154    }
155
156    auto info = codec->getInfo();
157    auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
158    if (!cs) {
159        ERRORF(r, "%s should have a color space", path);
160        return;
161    }
162
163    auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3);
164    REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
165}
166
167DEF_TEST(AndroidCodec_P3, r) {
168    if (GetResourcePath().isEmpty()) {
169        return;
170    }
171
172    const char* path = "images/purple-displayprofile.png";
173    auto data = GetResourceAsData(path);
174    if (!data) {
175        ERRORF(r, "Missing file %s", path);
176        return;
177    }
178
179    auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
180    if (!codec) {
181        ERRORF(r, "Failed to create codec from %s", path);
182        return;
183    }
184
185    auto info = codec->getInfo();
186    auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
187    if (!cs) {
188        ERRORF(r, "%s should have a color space", path);
189        return;
190    }
191
192    REPORTER_ASSERT(r, !cs->isSRGB());
193    REPORTER_ASSERT(r, cs->gammaCloseToSRGB());
194
195    skcms_Matrix3x3 matrix;
196    cs->toXYZD50(&matrix);
197
198    static constexpr skcms_Matrix3x3 kExpected = {{
199        { 0.426254272f,  0.369018555f,  0.168914795f  },
200        { 0.226013184f,  0.685974121f,  0.0880126953f },
201        { 0.0116729736f, 0.0950927734f, 0.71812439f   },
202    }};
203    REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3)));
204}
205