blob: f8496918d15abd11d81580a586d2ba9e26f7d4e4 [file] [log] [blame]
Leon Scroggins III07a722c2018-01-16 15:01:17 -05001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/codec/SkAndroidCodec.h"
9#include "include/codec/SkCodec.h"
10#include "include/core/SkBitmap.h"
11#include "include/core/SkColor.h"
Ben Wagner9707a7e2019-05-06 17:17:19 -040012#include "include/core/SkColorSpace.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#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"
Ben Wagner9707a7e2019-05-06 17:17:19 -040021#include "include/third_party/skcms/skcms.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050022#include "src/codec/SkCodecImageGenerator.h"
23#include "src/core/SkPixmapPriv.h"
24#include "tests/Test.h"
25#include "tools/Resources.h"
Leon Scroggins III07a722c2018-01-16 15:01:17 -050026
Ben Wagner9707a7e2019-05-06 17:17:19 -040027#include <string.h>
28#include <initializer_list>
Ben Wagner1ebeefe2018-03-02 16:59:53 -050029#include <memory>
Ben Wagner9707a7e2019-05-06 17:17:19 -040030#include <utility>
Ben Wagner1ebeefe2018-03-02 16:59:53 -050031
Leon Scroggins III07a722c2018-01-16 15:01:17 -050032static 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}
Leon Scroggins IIIda3e9ad2018-01-26 15:48:26 -0500137
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400138DEF_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
Brian Osman82ebe042019-01-04 17:03:00 -0500163 auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400164 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
Brian Osman82ebe042019-01-04 17:03:00 -0500195 skcms_Matrix3x3 matrix;
Mike Kleinae5e8642018-10-03 17:00:41 -0400196 cs->toXYZD50(&matrix);
197
Brian Osman82ebe042019-01-04 17:03:00 -0500198 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)));
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400204}
205
Leon Scroggins IIIda3e9ad2018-01-26 15:48:26 -0500206DEF_TEST(AndroidCodec_orientation, r) {
207 if (GetResourcePath().isEmpty()) {
208 return;
209 }
210
211 for (const char* ext : { "jpg", "webp" })
212 for (char i = '1'; i <= '8'; ++i) {
213 SkString path = SkStringPrintf("images/orientation/%c.%s", i, ext);
214 auto data = GetResourceAsData(path.c_str());
215 auto gen = SkCodecImageGenerator::MakeFromEncodedCodec(data);
216 if (!gen) {
217 ERRORF(r, "failed to decode %s", path.c_str());
218 return;
219 }
220
221 // Dimensions after adjusting for the origin.
222 const SkISize expectedDims = { 100, 80 };
223
224 // SkCodecImageGenerator automatically adjusts for the origin.
225 REPORTER_ASSERT(r, gen->getInfo().dimensions() == expectedDims);
226
227 auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data));
228 if (!androidCodec) {
229 ERRORF(r, "failed to decode %s", path.c_str());
230 return;
231 }
232
233 // SkAndroidCodec does not adjust for the origin by default. Dimensions may be reversed.
234 if (SkPixmapPriv::ShouldSwapWidthHeight(androidCodec->codec()->getOrigin())) {
235 auto swappedDims = SkPixmapPriv::SwapWidthHeight(androidCodec->getInfo()).dimensions();
236 REPORTER_ASSERT(r, expectedDims == swappedDims);
237 } else {
238 REPORTER_ASSERT(r, expectedDims == androidCodec->getInfo().dimensions());
239 }
240
241 // Passing kRespect adjusts for the origin.
242 androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)),
243 SkAndroidCodec::ExifOrientationBehavior::kRespect);
244 auto info = androidCodec->getInfo();
245 REPORTER_ASSERT(r, info.dimensions() == expectedDims);
246
247 SkBitmap fromGenerator;
248 fromGenerator.allocPixels(info);
249 REPORTER_ASSERT(r, gen->getPixels(info, fromGenerator.getPixels(),
250 fromGenerator.rowBytes()));
251
252 SkBitmap fromAndroidCodec;
253 fromAndroidCodec.allocPixels(info);
254 auto result = androidCodec->getPixels(info, fromAndroidCodec.getPixels(),
255 fromAndroidCodec.rowBytes());
256 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
257
258 for (int i = 0; i < info.width(); ++i)
259 for (int j = 0; j < info.height(); ++j) {
260 SkColor c1 = *fromGenerator .getAddr32(i, j);
261 SkColor c2 = *fromAndroidCodec.getAddr32(i, j);
262 if (c1 != c2) {
263 ERRORF(r, "Bitmaps for %s do not match starting at position %i, %i\n"
264 "\tfromGenerator: %x\tfromAndroidCodec: %x", path.c_str(), i, j,
265 c1, c2);
266 return;
267 }
268 }
269 }
270}
Leon Scroggins III95d0c8d2019-01-30 15:33:09 -0500271
272DEF_TEST(AndroidCodec_sampledOrientation, r) {
273 if (GetResourcePath().isEmpty()) {
274 return;
275 }
276
277 // kRightTop_SkEncodedOrigin = 6, // Rotated 90 CW
278 auto path = "images/orientation/6.jpg";
279 auto data = GetResourceAsData(path);
280 if (!data) {
281 ERRORF(r, "Failed to get resource %s", path);
282 return;
283 }
284
285 auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)),
286 SkAndroidCodec::ExifOrientationBehavior::kRespect);
287 constexpr int sampleSize = 7;
288 auto sampledDims = androidCodec->getSampledDimensions(sampleSize);
289
290 SkAndroidCodec::AndroidOptions options;
291 options.fSampleSize = sampleSize;
292
293 SkBitmap bm;
294 auto info = androidCodec->getInfo().makeWH(sampledDims.width(), sampledDims.height());
295 bm.allocPixels(info);
296
297 auto result = androidCodec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &options);
298 if (result != SkCodec::kSuccess) {
299 ERRORF(r, "got result \"%s\"\n", SkCodec::ResultToString(result));
300 }
301}