blob: 3381d4f5cef5f58d963a15e8cb9b31a3828fe427 [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
Ben Wagner1ebeefe2018-03-02 16:59:53 -05008#include "Resources.h"
Leon Scroggins III07a722c2018-01-16 15:01:17 -05009#include "SkAndroidCodec.h"
Ben Wagner1ebeefe2018-03-02 16:59:53 -050010#include "SkBitmap.h"
Leon Scroggins III07a722c2018-01-16 15:01:17 -050011#include "SkCodec.h"
Leon Scroggins IIIda3e9ad2018-01-26 15:48:26 -050012#include "SkCodecImageGenerator.h"
Ben Wagner1ebeefe2018-03-02 16:59:53 -050013#include "SkColor.h"
14#include "SkData.h"
Leon Scroggins III07a722c2018-01-16 15:01:17 -050015#include "SkEncodedImageFormat.h"
Ben Wagner1ebeefe2018-03-02 16:59:53 -050016#include "SkImageGenerator.h"
17#include "SkImageInfo.h"
Leon Scroggins III36f7e322018-08-27 11:55:46 -040018#include "SkMatrix44.h"
Leon Scroggins IIIda3e9ad2018-01-26 15:48:26 -050019#include "SkPixmapPriv.h"
Ben Wagner1ebeefe2018-03-02 16:59:53 -050020#include "SkRefCnt.h"
21#include "SkSize.h"
22#include "SkString.h"
23#include "SkTypes.h"
Leon Scroggins III07a722c2018-01-16 15:01:17 -050024#include "Test.h"
25
Ben Wagner1ebeefe2018-03-02 16:59:53 -050026#include <algorithm>
27#include <memory>
28
Leon Scroggins III07a722c2018-01-16 15:01:17 -050029static SkISize times(const SkISize& size, float factor) {
30 return { (int) (size.width() * factor), (int) (size.height() * factor) };
31}
32
33static SkISize plus(const SkISize& size, int term) {
34 return { size.width() + term, size.height() + term };
35}
36
37static bool invalid(const SkISize& size) {
38 return size.width() < 1 || size.height() < 1;
39}
40
41DEF_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}
Leon Scroggins IIIda3e9ad2018-01-26 15:48:26 -0500134
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400135DEF_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 auto expected = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
161 SkColorSpace::kDCIP3_D65_Gamut);
162 REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
163}
164
165DEF_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
Mike Klein4429a4f2018-10-04 09:06:00 -0400193 SkMatrix44 matrix;
Mike Kleinae5e8642018-10-03 17:00:41 -0400194 cs->toXYZD50(&matrix);
195
Mike Klein4429a4f2018-10-04 09:06:00 -0400196 SkMatrix44 expected;
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400197 static constexpr float kExpected[] = {
198 0.426254272f, 0.369018555f, 0.168914795f,
199 0.226013184f, 0.685974121f, 0.0880126953f,
200 0.0116729736f, 0.0950927734f, 0.71812439f,
201 };
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400202 expected.set3x3RowMajorf(kExpected);
Mike Kleinae5e8642018-10-03 17:00:41 -0400203 REPORTER_ASSERT(r, matrix == expected);
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}