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 "Resources.h"
9 #include "SkAndroidCodec.h"
10 #include "SkBitmap.h"
11 #include "SkCodec.h"
12 #include "SkCodecImageGenerator.h"
13 #include "SkColor.h"
14 #include "SkData.h"
15 #include "SkEncodedImageFormat.h"
16 #include "SkImageGenerator.h"
17 #include "SkImageInfo.h"
18 #include "SkPixmapPriv.h"
19 #include "SkRefCnt.h"
20 #include "SkSize.h"
21 #include "SkString.h"
22 #include "SkTypes.h"
23 #include "Test.h"
24 
25 #include <algorithm>
26 #include <memory>
27 
times(const SkISize & size,float factor)28 static SkISize times(const SkISize& size, float factor) {
29     return { (int) (size.width() * factor), (int) (size.height() * factor) };
30 }
31 
plus(const SkISize & size,int term)32 static SkISize plus(const SkISize& size, int term) {
33     return { size.width() + term, size.height() + term };
34 }
35 
invalid(const SkISize & size)36 static bool invalid(const SkISize& size) {
37     return size.width() < 1 || size.height() < 1;
38 }
39 
DEF_TEST(AndroidCodec_computeSampleSize,r)40 DEF_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 }
133 
DEF_TEST(AndroidCodec_wide,r)134 DEF_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 
159     auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
160     REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
161 }
162 
DEF_TEST(AndroidCodec_P3,r)163 DEF_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 
191     skcms_Matrix3x3 matrix;
192     cs->toXYZD50(&matrix);
193 
194     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)));
200 }
201 
DEF_TEST(AndroidCodec_orientation,r)202 DEF_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 }
267 
DEF_TEST(AndroidCodec_sampledOrientation,r)268 DEF_TEST(AndroidCodec_sampledOrientation, r) {
269     if (GetResourcePath().isEmpty()) {
270         return;
271     }
272 
273     // kRightTop_SkEncodedOrigin    = 6, // Rotated 90 CW
274     auto path = "images/orientation/6.jpg";
275     auto data = GetResourceAsData(path);
276     if (!data) {
277         ERRORF(r, "Failed to get resource %s", path);
278         return;
279     }
280 
281     auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)),
282                 SkAndroidCodec::ExifOrientationBehavior::kRespect);
283     constexpr int sampleSize = 7;
284     auto sampledDims = androidCodec->getSampledDimensions(sampleSize);
285 
286     SkAndroidCodec::AndroidOptions options;
287     options.fSampleSize = sampleSize;
288 
289     SkBitmap bm;
290     auto info = androidCodec->getInfo().makeWH(sampledDims.width(), sampledDims.height());
291     bm.allocPixels(info);
292 
293     auto result = androidCodec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &options);
294     if (result != SkCodec::kSuccess) {
295         ERRORF(r, "got result \"%s\"\n", SkCodec::ResultToString(result));
296     }
297 }
298