xref: /aosp_15_r20/external/skia/tests/AndroidCodecTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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/codec/SkEncodedImageFormat.h"
11 #include "include/core/SkColorSpace.h"
12 #include "include/core/SkData.h"
13 #include "include/core/SkImageInfo.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkSize.h"
16 #include "include/core/SkString.h"
17 #include "include/core/SkTypes.h"
18 #include "include/private/SkGainmapInfo.h"  // IWYU pragma: keep
19 #include "modules/skcms/skcms.h"
20 #include "tests/Test.h"
21 #include "tools/Resources.h"
22 
23 #include <cstdint>
24 #include <cstring>
25 #include <initializer_list>
26 #include <memory>
27 #include <utility>
28 
times(const SkISize & size,float factor)29 static SkISize times(const SkISize& size, float factor) {
30     return { (int) (size.width() * factor), (int) (size.height() * factor) };
31 }
32 
plus(const SkISize & size,int term)33 static SkISize plus(const SkISize& size, int term) {
34     return { size.width() + term, size.height() + term };
35 }
36 
invalid(const SkISize & size)37 static bool invalid(const SkISize& size) {
38     return size.width() < 1 || size.height() < 1;
39 }
40 
DEF_TEST(AndroidCodec_computeSampleSize,r)41 DEF_TEST(AndroidCodec_computeSampleSize, r) {
42     if (GetResourcePath().isEmpty()) {
43         return;
44     }
45     for (const char* file : { "images/color_wheel.webp",
46                               "images/ship.png",
47                               "images/dog.jpg",
48                               "images/color_wheel.gif",
49                               "images/rle.bmp",
50                               "images/google_chrome.ico",
51                               "images/mandrill.wbmp",
52 #ifdef SK_CODEC_DECODES_RAW
53                               "images/sample_1mp.dng",
54 #endif
55                               }) {
56         auto data = GetResourceAsData(file);
57         if (!data) {
58             ERRORF(r, "Could not get %s", file);
59             continue;
60         }
61 
62         auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
63         if (!codec) {
64             ERRORF(r, "Could not create codec for %s", file);
65             continue;
66         }
67 
68         const auto dims = codec->getInfo().dimensions();
69         const SkISize downscales[] = {
70             plus(dims, -1),
71             times(dims, .15f),
72             times(dims, .6f),
73             { (int32_t) (dims.width() * .25f), (int32_t) (dims.height() * .75f ) },
74             { 1,  1 },
75             { 1,  2 },
76             { 2,  1 },
77             { 0, -1 },
78             { dims.width(), dims.height() - 1 },
79         };
80         for (SkISize size : downscales) {
81             const auto requested = size;
82             const int computedSampleSize = codec->computeSampleSize(&size);
83             REPORTER_ASSERT(r, size.width() >= 1 && size.height() >= 1);
84             if (codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP) {
85                 // WebP supports arbitrary down-scaling.
86                 REPORTER_ASSERT(r, size == requested || invalid(requested));
87             } else if (computedSampleSize == 1) {
88                 REPORTER_ASSERT(r, size == dims);
89             } else {
90                 REPORTER_ASSERT(r, computedSampleSize > 1);
91                 if (size.width() >= dims.width() || size.height() >= dims.height()) {
92                     ERRORF(r, "File %s's computed sample size (%i) is bigger than"
93                               " original? original: %i x %i\tsampled: %i x %i",
94                               file, computedSampleSize, dims.width(), dims.height(),
95                               size.width(), size.height());
96                 }
97                 REPORTER_ASSERT(r, size.width()  >= requested.width() &&
98                                    size.height() >= requested.height());
99                 REPORTER_ASSERT(r, size.width()  <  dims.width() &&
100                                    size.height() <  dims.height());
101             }
102         }
103 
104         const SkISize upscales[] = {
105             dims, plus(dims, 5), times(dims, 2),
106         };
107         for (SkISize size : upscales) {
108             const int computedSampleSize = codec->computeSampleSize(&size);
109             REPORTER_ASSERT(r, computedSampleSize == 1);
110             REPORTER_ASSERT(r, dims == size);
111         }
112 
113         // This mimics how Android's ImageDecoder uses SkAndroidCodec. A client
114         // can choose their dimensions based on calling getSampledDimensions,
115         // but the ImageDecoder API takes an arbitrary size. It then uses
116         // computeSampleSize to determine the best dimensions and sampleSize.
117         // It should return the same dimensions. the sampleSize may be different
118         // due to integer division.
119         for (int sampleSize : { 1, 2, 3, 4, 8, 16, 32 }) {
120             const SkISize sampledDims = codec->getSampledDimensions(sampleSize);
121             SkISize size = sampledDims;
122             const int computedSampleSize = codec->computeSampleSize(&size);
123             if (sampledDims != size) {
124                 ERRORF(r, "File '%s'->getSampledDimensions(%i) yields computed"
125                           " sample size of %i\n\tsampledDimensions: %i x %i\t"
126                           "computed dimensions: %i x %i",
127                           file, sampleSize, computedSampleSize,
128                           sampledDims.width(), sampledDims.height(),
129                           size.width(), size.height());
130             }
131         }
132     }
133 }
134 
DEF_TEST(AndroidCodec_wide,r)135 DEF_TEST(AndroidCodec_wide, r) {
136     if (GetResourcePath().isEmpty()) {
137         return;
138     }
139 
140     const char* path = "images/wide-gamut.png";
141     auto data = GetResourceAsData(path);
142     if (!data) {
143         ERRORF(r, "Missing file %s", path);
144         return;
145     }
146 
147     auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
148     if (!codec) {
149         ERRORF(r, "Failed to create codec from %s", path);
150         return;
151     }
152 
153     auto info = codec->getInfo();
154     auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
155     if (!cs) {
156         ERRORF(r, "%s should have a color space", path);
157         return;
158     }
159 
160     // This image has a gamut that is VERY close to sRGB, so SkColorSpace::MakeRGB snaps to sRGB.
161     auto expected = SkColorSpace::MakeSRGB();
162     REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
163 }
164 
DEF_TEST(AndroidCodec_P3,r)165 DEF_TEST(AndroidCodec_P3, r) {
166     if (GetResourcePath().isEmpty()) {
167         return;
168     }
169 
170     const char* path = "images/purple-displayprofile.png";
171     auto data = GetResourceAsData(path);
172     if (!data) {
173         ERRORF(r, "Missing file %s", path);
174         return;
175     }
176 
177     auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
178     if (!codec) {
179         ERRORF(r, "Failed to create codec from %s", path);
180         return;
181     }
182 
183     auto info = codec->getInfo();
184     auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
185     if (!cs) {
186         ERRORF(r, "%s should have a color space", path);
187         return;
188     }
189 
190     REPORTER_ASSERT(r, !cs->isSRGB());
191     REPORTER_ASSERT(r, cs->gammaCloseToSRGB());
192 
193     skcms_Matrix3x3 matrix;
194     cs->toXYZD50(&matrix);
195 
196     static constexpr skcms_Matrix3x3 kExpected = {{
197         { 0.426254272f,  0.369018555f,  0.168914795f  },
198         { 0.226013184f,  0.685974121f,  0.0880126953f },
199         { 0.0116729736f, 0.0950927734f, 0.71812439f   },
200     }};
201     REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3)));
202 }
203 
DEF_TEST(AndroidCodec_HLG,r)204 DEF_TEST(AndroidCodec_HLG, r) {
205     if (GetResourcePath().isEmpty()) {
206         return;
207     }
208 
209     const char* path = "images/red-hlg-profile.png";
210     auto data = GetResourceAsData(path);
211     if (!data) {
212         ERRORF(r, "Missing file %s", path);
213         return;
214     }
215 
216     auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
217     if (!codec) {
218         ERRORF(r, "Failed to create codec from %s", path);
219         return;
220     }
221 
222     auto info = codec->getInfo();
223     auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
224     if (!cs) {
225         ERRORF(r, "%s should have a color space", path);
226         return;
227     }
228 
229     skcms_TransferFunction tf;
230     cs->transferFn(&tf);
231     REPORTER_ASSERT(r, skcms_TransferFunction_isHLGish(&tf));
232 
233     skcms_Matrix3x3 matrix;
234     cs->toXYZD50(&matrix);
235 
236     static constexpr skcms_Matrix3x3 kExpected = SkNamedGamut::kRec2020;
237     REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3)));
238 }
239 
DEF_TEST(AndroidCodec_PQ,r)240 DEF_TEST(AndroidCodec_PQ, r) {
241     if (GetResourcePath().isEmpty()) {
242         return;
243     }
244 
245     const char* path = "images/red-pq-profile.png";
246     auto data = GetResourceAsData(path);
247     if (!data) {
248         ERRORF(r, "Missing file %s", path);
249         return;
250     }
251 
252     auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
253     if (!codec) {
254         ERRORF(r, "Failed to create codec from %s", path);
255         return;
256     }
257 
258     auto info = codec->getInfo();
259     auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
260     if (!cs) {
261         ERRORF(r, "%s should have a color space", path);
262         return;
263     }
264 
265     skcms_TransferFunction tf;
266     cs->transferFn(&tf);
267     REPORTER_ASSERT(r, skcms_TransferFunction_isPQish(&tf));
268 
269     skcms_Matrix3x3 matrix;
270     cs->toXYZD50(&matrix);
271 
272     static constexpr skcms_Matrix3x3 kExpected = SkNamedGamut::kRec2020;
273     REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3)));
274 }
275