blob: 49415f8f9388fa9811a936354b652e5a272f3b6f [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 IIIda3e9ad2018-01-26 15:48:26 -050018#include "SkPixmapPriv.h"
Ben Wagner1ebeefe2018-03-02 16:59:53 -050019#include "SkRefCnt.h"
20#include "SkSize.h"
21#include "SkString.h"
22#include "SkTypes.h"
Leon Scroggins III07a722c2018-01-16 15:01:17 -050023#include "Test.h"
24
Ben Wagner1ebeefe2018-03-02 16:59:53 -050025#include <algorithm>
26#include <memory>
27
Leon Scroggins III07a722c2018-01-16 15:01:17 -050028static SkISize times(const SkISize& size, float factor) {
29 return { (int) (size.width() * factor), (int) (size.height() * factor) };
30}
31
32static SkISize plus(const SkISize& size, int term) {
33 return { size.width() + term, size.height() + term };
34}
35
36static bool invalid(const SkISize& size) {
37 return size.width() < 1 || size.height() < 1;
38}
39
40DEF_TEST(AndroidCodec_computeSampleSize, r) {
41 if (GetResourcePath().isEmpty()) {
42 return;
43 }
44 for (const char* file : { "images/color_wheel.webp",
45 "images/ship.png",
46 "images/dog.jpg",
47 "images/color_wheel.gif",
48 "images/rle.bmp",
49 "images/google_chrome.ico",
50 "images/mandrill.wbmp",
51#ifdef SK_CODEC_DECODES_RAW
52 "images/sample_1mp.dng",
53#endif
54 }) {
55 auto data = GetResourceAsData(file);
56 if (!data) {
57 ERRORF(r, "Could not get %s", file);
58 continue;
59 }
60
61 auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
62 if (!codec) {
63 ERRORF(r, "Could not create codec for %s", file);
64 continue;
65 }
66
67 const auto dims = codec->getInfo().dimensions();
68 const SkISize downscales[] = {
69 plus(dims, -1),
70 times(dims, .15f),
71 times(dims, .6f),
72 { (int32_t) (dims.width() * .25f), (int32_t) (dims.height() * .75f ) },
73 { 1, 1 },
74 { 1, 2 },
75 { 2, 1 },
76 { 0, -1 },
77 { dims.width(), dims.height() - 1 },
78 };
79 for (SkISize size : downscales) {
80 const auto requested = size;
81 const int computedSampleSize = codec->computeSampleSize(&size);
82 REPORTER_ASSERT(r, size.width() >= 1 && size.height() >= 1);
83 if (codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP) {
84 // WebP supports arbitrary down-scaling.
85 REPORTER_ASSERT(r, size == requested || invalid(requested));
86 } else if (computedSampleSize == 1) {
87 REPORTER_ASSERT(r, size == dims);
88 } else {
89 REPORTER_ASSERT(r, computedSampleSize > 1);
90 if (size.width() >= dims.width() || size.height() >= dims.height()) {
91 ERRORF(r, "File %s's computed sample size (%i) is bigger than"
92 " original? original: %i x %i\tsampled: %i x %i",
93 file, computedSampleSize, dims.width(), dims.height(),
94 size.width(), size.height());
95 }
96 REPORTER_ASSERT(r, size.width() >= requested.width() &&
97 size.height() >= requested.height());
98 REPORTER_ASSERT(r, size.width() < dims.width() &&
99 size.height() < dims.height());
100 }
101 }
102
103 const SkISize upscales[] = {
104 dims, plus(dims, 5), times(dims, 2),
105 };
106 for (SkISize size : upscales) {
107 const int computedSampleSize = codec->computeSampleSize(&size);
108 REPORTER_ASSERT(r, computedSampleSize == 1);
109 REPORTER_ASSERT(r, dims == size);
110 }
111
112 // This mimics how Android's ImageDecoder uses SkAndroidCodec. A client
113 // can choose their dimensions based on calling getSampledDimensions,
114 // but the ImageDecoder API takes an arbitrary size. It then uses
115 // computeSampleSize to determine the best dimensions and sampleSize.
116 // It should return the same dimensions. the sampleSize may be different
117 // due to integer division.
118 for (int sampleSize : { 1, 2, 3, 4, 8, 16, 32 }) {
119 const SkISize sampledDims = codec->getSampledDimensions(sampleSize);
120 SkISize size = sampledDims;
121 const int computedSampleSize = codec->computeSampleSize(&size);
122 if (sampledDims != size) {
123 ERRORF(r, "File '%s'->getSampledDimensions(%i) yields computed"
124 " sample size of %i\n\tsampledDimensions: %i x %i\t"
125 "computed dimensions: %i x %i",
126 file, sampleSize, computedSampleSize,
127 sampledDims.width(), sampledDims.height(),
128 size.width(), size.height());
129 }
130 }
131 }
132}
Leon Scroggins IIIda3e9ad2018-01-26 15:48:26 -0500133
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400134DEF_TEST(AndroidCodec_wide, r) {
135 if (GetResourcePath().isEmpty()) {
136 return;
137 }
138
139 const char* path = "images/wide-gamut.png";
140 auto data = GetResourceAsData(path);
141 if (!data) {
142 ERRORF(r, "Missing file %s", path);
143 return;
144 }
145
146 auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
147 if (!codec) {
148 ERRORF(r, "Failed to create codec from %s", path);
149 return;
150 }
151
152 auto info = codec->getInfo();
153 auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
154 if (!cs) {
155 ERRORF(r, "%s should have a color space", path);
156 return;
157 }
158
Brian Osman82ebe042019-01-04 17:03:00 -0500159 auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400160 REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
161}
162
163DEF_TEST(AndroidCodec_P3, r) {
164 if (GetResourcePath().isEmpty()) {
165 return;
166 }
167
168 const char* path = "images/purple-displayprofile.png";
169 auto data = GetResourceAsData(path);
170 if (!data) {
171 ERRORF(r, "Missing file %s", path);
172 return;
173 }
174
175 auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
176 if (!codec) {
177 ERRORF(r, "Failed to create codec from %s", path);
178 return;
179 }
180
181 auto info = codec->getInfo();
182 auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr);
183 if (!cs) {
184 ERRORF(r, "%s should have a color space", path);
185 return;
186 }
187
188 REPORTER_ASSERT(r, !cs->isSRGB());
189 REPORTER_ASSERT(r, cs->gammaCloseToSRGB());
190
Brian Osman82ebe042019-01-04 17:03:00 -0500191 skcms_Matrix3x3 matrix;
Mike Kleinae5e8642018-10-03 17:00:41 -0400192 cs->toXYZD50(&matrix);
193
Brian Osman82ebe042019-01-04 17:03:00 -0500194 static constexpr skcms_Matrix3x3 kExpected = {{
195 { 0.426254272f, 0.369018555f, 0.168914795f },
196 { 0.226013184f, 0.685974121f, 0.0880126953f },
197 { 0.0116729736f, 0.0950927734f, 0.71812439f },
198 }};
199 REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3)));
Leon Scroggins III36f7e322018-08-27 11:55:46 -0400200}
201
Leon Scroggins IIIda3e9ad2018-01-26 15:48:26 -0500202DEF_TEST(AndroidCodec_orientation, r) {
203 if (GetResourcePath().isEmpty()) {
204 return;
205 }
206
207 for (const char* ext : { "jpg", "webp" })
208 for (char i = '1'; i <= '8'; ++i) {
209 SkString path = SkStringPrintf("images/orientation/%c.%s", i, ext);
210 auto data = GetResourceAsData(path.c_str());
211 auto gen = SkCodecImageGenerator::MakeFromEncodedCodec(data);
212 if (!gen) {
213 ERRORF(r, "failed to decode %s", path.c_str());
214 return;
215 }
216
217 // Dimensions after adjusting for the origin.
218 const SkISize expectedDims = { 100, 80 };
219
220 // SkCodecImageGenerator automatically adjusts for the origin.
221 REPORTER_ASSERT(r, gen->getInfo().dimensions() == expectedDims);
222
223 auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data));
224 if (!androidCodec) {
225 ERRORF(r, "failed to decode %s", path.c_str());
226 return;
227 }
228
229 // SkAndroidCodec does not adjust for the origin by default. Dimensions may be reversed.
230 if (SkPixmapPriv::ShouldSwapWidthHeight(androidCodec->codec()->getOrigin())) {
231 auto swappedDims = SkPixmapPriv::SwapWidthHeight(androidCodec->getInfo()).dimensions();
232 REPORTER_ASSERT(r, expectedDims == swappedDims);
233 } else {
234 REPORTER_ASSERT(r, expectedDims == androidCodec->getInfo().dimensions());
235 }
236
237 // Passing kRespect adjusts for the origin.
238 androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)),
239 SkAndroidCodec::ExifOrientationBehavior::kRespect);
240 auto info = androidCodec->getInfo();
241 REPORTER_ASSERT(r, info.dimensions() == expectedDims);
242
243 SkBitmap fromGenerator;
244 fromGenerator.allocPixels(info);
245 REPORTER_ASSERT(r, gen->getPixels(info, fromGenerator.getPixels(),
246 fromGenerator.rowBytes()));
247
248 SkBitmap fromAndroidCodec;
249 fromAndroidCodec.allocPixels(info);
250 auto result = androidCodec->getPixels(info, fromAndroidCodec.getPixels(),
251 fromAndroidCodec.rowBytes());
252 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
253
254 for (int i = 0; i < info.width(); ++i)
255 for (int j = 0; j < info.height(); ++j) {
256 SkColor c1 = *fromGenerator .getAddr32(i, j);
257 SkColor c2 = *fromAndroidCodec.getAddr32(i, j);
258 if (c1 != c2) {
259 ERRORF(r, "Bitmaps for %s do not match starting at position %i, %i\n"
260 "\tfromGenerator: %x\tfromAndroidCodec: %x", path.c_str(), i, j,
261 c1, c2);
262 return;
263 }
264 }
265 }
266}