• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/core/SkBitmap.h"
11 #include "include/core/SkColor.h"
12 #include "include/core/SkColorSpace.h"
13 #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"
21 #include "include/third_party/skcms/skcms.h"
22 #include "src/codec/SkCodecImageGenerator.h"
23 #include "src/core/SkPixmapPriv.h"
24 #include "tests/Test.h"
25 #include "tools/Resources.h"
26 
27 #include <string.h>
28 #include <initializer_list>
29 #include <memory>
30 #include <utility>
31 
times(const SkISize & size,float factor)32 static SkISize times(const SkISize& size, float factor) {
33     return { (int) (size.width() * factor), (int) (size.height() * factor) };
34 }
35 
plus(const SkISize & size,int term)36 static SkISize plus(const SkISize& size, int term) {
37     return { size.width() + term, size.height() + term };
38 }
39 
invalid(const SkISize & size)40 static bool invalid(const SkISize& size) {
41     return size.width() < 1 || size.height() < 1;
42 }
43 
DEF_TEST(AndroidCodec_computeSampleSize,r)44 DEF_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 }
137 
DEF_TEST(AndroidCodec_wide,r)138 DEF_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 
163     auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
164     REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
165 }
166 
DEF_TEST(AndroidCodec_P3,r)167 DEF_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 
195     skcms_Matrix3x3 matrix;
196     cs->toXYZD50(&matrix);
197 
198     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)));
204 }
205 
DEF_TEST(AndroidCodec_orientation,r)206 DEF_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 }
271 
DEF_TEST(AndroidCodec_sampledOrientation,r)272 DEF_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 }
302