blob: 86b96ae0f9327be1472c59b19109fc6c6a71f9e0 [file] [log] [blame]
scroggo@google.com2bbc2c92013-06-14 15:33:20 +00001/*
2 * Copyright 2013 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 "SkBitmap.h"
scroggo@google.com826d63a2013-07-18 20:06:28 +00009#include "SkCanvas.h"
10#include "SkColor.h"
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000011#include "SkColorPriv.h"
scroggo@google.com826d63a2013-07-18 20:06:28 +000012#include "SkData.h"
halcanary@google.comdedd44a2013-12-20 16:35:22 +000013#include "SkDecodingImageGenerator.h"
halcanary@google.com3d50ea12014-01-02 13:15:13 +000014#include "SkDiscardableMemoryPool.h"
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000015#include "SkForceLinking.h"
scroggo@google.com826d63a2013-07-18 20:06:28 +000016#include "SkGradientShader.h"
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000017#include "SkImageDecoder.h"
scroggo@google.com826d63a2013-07-18 20:06:28 +000018#include "SkImageEncoder.h"
halcanary@google.com3d50ea12014-01-02 13:15:13 +000019#include "SkImageGenerator.h"
20#include "SkImagePriv.h"
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000021#include "SkOSFile.h"
scroggo@google.com826d63a2013-07-18 20:06:28 +000022#include "SkPoint.h"
23#include "SkShader.h"
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000024#include "SkStream.h"
25#include "SkString.h"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000026#include "Test.h"
scroggo@google.com2bbc2c92013-06-14 15:33:20 +000027
28__SK_FORCE_IMAGE_DECODER_LINKING;
29
30/**
31 * Interprets c as an unpremultiplied color, and returns the
32 * premultiplied equivalent.
33 */
34static SkPMColor premultiply_unpmcolor(SkPMColor c) {
35 U8CPU a = SkGetPackedA32(c);
36 U8CPU r = SkGetPackedR32(c);
37 U8CPU g = SkGetPackedG32(c);
38 U8CPU b = SkGetPackedB32(c);
39 return SkPreMultiplyARGB(a, r, g, b);
40}
41
42/**
43 * Return true if this stream format should be skipped, due
44 * to do being an opaque format or not a valid format.
45 */
46static bool skip_image_format(SkImageDecoder::Format format) {
47 switch (format) {
48 case SkImageDecoder::kPNG_Format:
49 case SkImageDecoder::kWEBP_Format:
50 return false;
51 // Skip unknown since it will not be decoded anyway.
52 case SkImageDecoder::kUnknown_Format:
53 // Technically ICO and BMP supports alpha channels, but our image
54 // decoders do not, so skip them as well.
55 case SkImageDecoder::kICO_Format:
56 case SkImageDecoder::kBMP_Format:
57 // The rest of these are opaque.
58 case SkImageDecoder::kWBMP_Format:
59 case SkImageDecoder::kGIF_Format:
60 case SkImageDecoder::kJPEG_Format:
61 return true;
62 }
63 SkASSERT(false);
64 return true;
65}
66
67/**
68 * Test decoding an image in premultiplied mode and unpremultiplied mode and compare
69 * them.
70 */
71static void compare_unpremul(skiatest::Reporter* reporter, const SkString& filename) {
72 // Decode a resource:
73 SkBitmap bm8888;
74 SkBitmap bm8888Unpremul;
75
76 SkFILEStream stream(filename.c_str());
77
78 SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&stream);
79 if (skip_image_format(format)) {
80 return;
81 }
82
83 SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
84 if (NULL == decoder.get()) {
85 SkDebugf("couldn't decode %s\n", filename.c_str());
86 return;
87 }
88
89 bool success = decoder->decode(&stream, &bm8888, SkBitmap::kARGB_8888_Config,
90 SkImageDecoder::kDecodePixels_Mode);
91 if (!success) {
92 return;
93 }
94
95 success = stream.rewind();
96 REPORTER_ASSERT(reporter, success);
97 if (!success) {
98 return;
99 }
100
101 decoder->setRequireUnpremultipliedColors(true);
102 success = decoder->decode(&stream, &bm8888Unpremul, SkBitmap::kARGB_8888_Config,
103 SkImageDecoder::kDecodePixels_Mode);
104 if (!success) {
105 return;
106 }
107
108 bool dimensionsMatch = bm8888.width() == bm8888Unpremul.width()
109 && bm8888.height() == bm8888Unpremul.height();
110 REPORTER_ASSERT(reporter, dimensionsMatch);
111 if (!dimensionsMatch) {
112 return;
113 }
114
115 // Only do the comparison if the two bitmaps are both 8888.
116 if (bm8888.config() != SkBitmap::kARGB_8888_Config
117 || bm8888Unpremul.config() != SkBitmap::kARGB_8888_Config) {
118 return;
119 }
120
121 // Now compare the two bitmaps.
122 for (int i = 0; i < bm8888.width(); ++i) {
123 for (int j = 0; j < bm8888.height(); ++j) {
124 // "c0" is the color of the premultiplied bitmap at (i, j).
125 const SkPMColor c0 = *bm8888.getAddr32(i, j);
126 // "c1" is the result of premultiplying the color of the unpremultiplied
127 // bitmap at (i, j).
128 const SkPMColor c1 = premultiply_unpmcolor(*bm8888Unpremul.getAddr32(i, j));
129 // Compute the difference for each component.
130 int da = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1));
131 int dr = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1));
132 int dg = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1));
133 int db = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1));
134
135 // Alpha component must be exactly the same.
136 REPORTER_ASSERT(reporter, 0 == da);
scroggo@google.comdaaea2d2013-06-14 20:39:48 +0000137
138 // Color components may not match exactly due to rounding error.
139 REPORTER_ASSERT(reporter, dr <= 1);
140 REPORTER_ASSERT(reporter, dg <= 1);
141 REPORTER_ASSERT(reporter, db <= 1);
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000142 }
143 }
144}
145
scroggo@google.comf698c822013-07-18 19:34:49 +0000146static void test_unpremul(skiatest::Reporter* reporter) {
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000147 // This test cannot run if there is no resource path.
148 SkString resourcePath = skiatest::Test::GetResourcePath();
149 if (resourcePath.isEmpty()) {
bungeman@google.com3c996f82013-10-24 21:39:35 +0000150 SkDebugf("Could not run unpremul test because resourcePath not specified.");
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000151 return;
152 }
153 SkOSFile::Iter iter(resourcePath.c_str());
154 SkString basename;
155 if (iter.next(&basename)) {
156 do {
157 SkString filename = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str());
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000158 // SkDebugf("about to decode \"%s\"\n", filename.c_str());
scroggo@google.com2bbc2c92013-06-14 15:33:20 +0000159 compare_unpremul(reporter, filename);
160 } while (iter.next(&basename));
161 } else {
162 SkDebugf("Failed to find any files :(\n");
163 }
164}
165
commit-bot@chromium.org915b9722014-04-24 18:55:13 +0000166#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
167// Test that the alpha type is what we expect.
168static void test_alphaType(skiatest::Reporter* reporter, const SkString& filename,
169 bool requireUnpremul) {
170 SkBitmap bm;
171 SkFILEStream stream(filename.c_str());
172
173 SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
174 if (NULL == decoder.get()) {
175 return;
176 }
177
178 decoder->setRequireUnpremultipliedColors(requireUnpremul);
179
180 // Decode just the bounds. This should always succeed.
181 bool success = decoder->decode(&stream, &bm, SkBitmap::kARGB_8888_Config,
182 SkImageDecoder::kDecodeBounds_Mode);
183 REPORTER_ASSERT(reporter, success);
184 if (!success) {
185 return;
186 }
187
188 // Keep track of the alpha type for testing later. If the full decode
189 // succeeds, the alpha type should be the same, unless the full decode
190 // determined that the alpha type should actually be opaque, which may
191 // not be known when only decoding the bounds.
192 const SkAlphaType boundsAlphaType = bm.alphaType();
193
194 // rewind should always succeed on SkFILEStream.
195 success = stream.rewind();
196 REPORTER_ASSERT(reporter, success);
197 if (!success) {
198 return;
199 }
200
201 success = decoder->decode(&stream, &bm, SkBitmap::kARGB_8888_Config,
202 SkImageDecoder::kDecodePixels_Mode);
203
204 if (!success) {
205 // When the decoder is set to require unpremul, if it does not support
206 // unpremul it will fail. This is the only reason the decode should
207 // fail (since we know the files we are using to test can be decoded).
208 REPORTER_ASSERT(reporter, requireUnpremul);
209 return;
210 }
211
212 // The bounds decode should return with either the requested
213 // premul/unpremul or opaque, if that value could be determined when only
214 // decoding the bounds.
215 if (requireUnpremul) {
216 REPORTER_ASSERT(reporter, kUnpremul_SkAlphaType == boundsAlphaType
217 || kOpaque_SkAlphaType == boundsAlphaType);
218 } else {
219 REPORTER_ASSERT(reporter, kPremul_SkAlphaType == boundsAlphaType
220 || kOpaque_SkAlphaType == boundsAlphaType);
221 }
222
223 // When decoding the full image, the alpha type should match the one
224 // returned by the bounds decode, unless the full decode determined that
225 // the alpha type is actually opaque.
226 REPORTER_ASSERT(reporter, bm.alphaType() == boundsAlphaType
227 || bm.alphaType() == kOpaque_SkAlphaType);
228}
229
230DEF_TEST(ImageDecoding_alphaType, reporter) {
231 SkString resourcePath = skiatest::Test::GetResourcePath();
232 if (resourcePath.isEmpty()) {
233 SkDebugf("Could not run alphaType test because resourcePath not specified.");
234 return;
235 }
236
237 SkOSFile::Iter iter(resourcePath.c_str());
238 SkString basename;
239 if (iter.next(&basename)) {
240 do {
241 SkString filename = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str());
242 for (int truth = 0; truth <= 1; ++truth) {
243 test_alphaType(reporter, filename, SkToBool(truth));
244 }
245 } while (iter.next(&basename));
246 } else {
247 SkDebugf("Failed to find any files :(\n");
248 }
249
250}
251
252// Using known images, test that decoding into unpremul and premul behave as expected.
253DEF_TEST(ImageDecoding_unpremul, reporter) {
254 SkString resourcePath = skiatest::Test::GetResourcePath();
255 if (resourcePath.isEmpty()) {
256 SkDebugf("Could not run unpremul test because resourcePath not specified.");
257 return;
258 }
259 const char* root = "half-transparent-white-pixel";
260 const char* suffixes[] = { ".png", ".webp" };
261
262 for (size_t i = 0; i < SK_ARRAY_COUNT(suffixes); ++i) {
263 SkString basename = SkStringPrintf("%s%s", root, suffixes[i]);
264 SkString fullName = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str());
265
266 SkBitmap bm;
267 SkFILEStream stream(fullName.c_str());
268
269 if (!stream.isValid()) {
270 SkDebugf("file %s missing from resource directoy %s\n",
271 basename.c_str(), resourcePath.c_str());
272 continue;
273 }
274
275 // This should never fail since we know the images we're decoding.
276 SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
277 REPORTER_ASSERT(reporter, NULL != decoder.get());
278 if (NULL == decoder.get()) {
279 continue;
280 }
281
282 // Test unpremultiplied. We know what color this should result in.
283 decoder->setRequireUnpremultipliedColors(true);
284 bool success = decoder->decode(&stream, &bm, SkBitmap::kARGB_8888_Config,
285 SkImageDecoder::kDecodePixels_Mode);
286 REPORTER_ASSERT(reporter, success);
287 if (!success) {
288 continue;
289 }
290
291 REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1);
292 {
293 SkAutoLockPixels alp(bm);
294 REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7fffffff);
295 }
296
297 success = stream.rewind();
298 REPORTER_ASSERT(reporter, success);
299 if (!success) {
300 continue;
301 }
302
303 // Test premultiplied. Once again, we know which color this should
304 // result in.
305 decoder->setRequireUnpremultipliedColors(false);
306 success = decoder->decode(&stream, &bm, SkBitmap::kARGB_8888_Config,
307 SkImageDecoder::kDecodePixels_Mode);
308 REPORTER_ASSERT(reporter, success);
309 if (!success) {
310 continue;
311 }
312
313 REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1);
314 {
315 SkAutoLockPixels alp(bm);
316 REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7f7f7f7f);
317 }
318 }
319}
320#endif // SK_BUILD_FOR_UNIX/ANDROID skbug.com/2388
321
scroggo@google.com826d63a2013-07-18 20:06:28 +0000322#ifdef SK_DEBUG
323// Create a stream containing a bitmap encoded to Type type.
scroggo@google.comb5571b32013-09-25 21:34:24 +0000324static SkMemoryStream* create_image_stream(SkImageEncoder::Type type) {
scroggo@google.com826d63a2013-07-18 20:06:28 +0000325 SkBitmap bm;
326 const int size = 50;
mike@reedtribe.orgdeee4962014-02-13 14:41:43 +0000327 bm.allocN32Pixels(size, size);
scroggo@google.com826d63a2013-07-18 20:06:28 +0000328 SkCanvas canvas(bm);
329 SkPoint points[2] = {
330 { SkIntToScalar(0), SkIntToScalar(0) },
331 { SkIntToScalar(size), SkIntToScalar(size) }
332 };
333 SkColor colors[2] = { SK_ColorWHITE, SK_ColorBLUE };
334 SkShader* shader = SkGradientShader::CreateLinear(points, colors, NULL,
335 SK_ARRAY_COUNT(colors),
336 SkShader::kClamp_TileMode);
337 SkPaint paint;
338 paint.setShader(shader)->unref();
339 canvas.drawPaint(paint);
340 // Now encode it to a stream.
341 SkAutoTUnref<SkData> data(SkImageEncoder::EncodeData(bm, type, 100));
342 if (NULL == data.get()) {
343 return NULL;
344 }
345 return SkNEW_ARGS(SkMemoryStream, (data.get()));
346}
347
348// For every format that supports tile based decoding, ensure that
349// calling decodeSubset will not fail if the caller has unreffed the
350// stream provided in buildTileIndex.
351// Only runs in debug mode since we are testing for a crash.
352static void test_stream_life() {
353 const SkImageEncoder::Type gTypes[] = {
scroggo@google.com826d63a2013-07-18 20:06:28 +0000354#ifdef SK_BUILD_FOR_ANDROID
scroggo@google.comd79277f2013-08-07 19:53:53 +0000355 SkImageEncoder::kJPEG_Type,
scroggo@google.com826d63a2013-07-18 20:06:28 +0000356 SkImageEncoder::kPNG_Type,
357#endif
358 SkImageEncoder::kWEBP_Type,
359 };
360 for (size_t i = 0; i < SK_ARRAY_COUNT(gTypes); ++i) {
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000361 // SkDebugf("encoding to %i\n", i);
scroggo@google.comb5571b32013-09-25 21:34:24 +0000362 SkAutoTUnref<SkMemoryStream> stream(create_image_stream(gTypes[i]));
scroggo@google.com826d63a2013-07-18 20:06:28 +0000363 if (NULL == stream.get()) {
364 SkDebugf("no stream\n");
365 continue;
366 }
367 SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(stream));
368 if (NULL == decoder.get()) {
369 SkDebugf("no decoder\n");
370 continue;
371 }
372 int width, height;
373 if (!decoder->buildTileIndex(stream.get(), &width, &height)) {
374 SkDebugf("could not build a tile index\n");
375 continue;
376 }
377 // Now unref the stream to make sure it survives
378 stream.reset(NULL);
379 SkBitmap bm;
380 decoder->decodeSubset(&bm, SkIRect::MakeWH(width, height),
381 SkBitmap::kARGB_8888_Config);
382 }
383}
scroggo@google.com8d239242013-10-01 17:27:15 +0000384
385// Test inside SkScaledBitmapSampler.cpp
386extern void test_row_proc_choice();
387
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000388#endif // SK_DEBUG
scroggo@google.com826d63a2013-07-18 20:06:28 +0000389
tfarina@chromium.orge4fafb12013-12-12 21:11:12 +0000390DEF_TEST(ImageDecoding, reporter) {
scroggo@google.comf698c822013-07-18 19:34:49 +0000391 test_unpremul(reporter);
scroggo@google.com826d63a2013-07-18 20:06:28 +0000392#ifdef SK_DEBUG
393 test_stream_life();
scroggo@google.com8d239242013-10-01 17:27:15 +0000394 test_row_proc_choice();
scroggo@google.com826d63a2013-07-18 20:06:28 +0000395#endif
scroggo@google.comf698c822013-07-18 19:34:49 +0000396}
halcanary@google.comdedd44a2013-12-20 16:35:22 +0000397
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000398// expected output for 8x8 bitmap
tfarina@chromium.org58674812014-01-21 23:39:22 +0000399static const int kExpectedWidth = 8;
400static const int kExpectedHeight = 8;
401static const SkColor kExpectedPixels[] = {
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000402 0xffbba570, 0xff395f5d, 0xffe25c39, 0xff197666,
403 0xff3cba27, 0xffdefcb0, 0xffc13874, 0xfffa0093,
404 0xffbda60e, 0xffc01db6, 0xff2bd688, 0xff9362d4,
405 0xffc641b2, 0xffa5cede, 0xff606eba, 0xff8f4bf3,
406 0xff3bf742, 0xff8f02a8, 0xff5509df, 0xffc7027e,
407 0xff24aa8a, 0xff886c96, 0xff625481, 0xff403689,
408 0xffc52152, 0xff78ccd6, 0xffdcb4ab, 0xff09d27d,
409 0xffca00f3, 0xff605d47, 0xff446fb2, 0xff576e46,
410 0xff273df9, 0xffb41a83, 0xfff812c3, 0xffccab67,
411 0xff034218, 0xff7db9a7, 0xff821048, 0xfffe4ab4,
412 0xff6fac98, 0xff941d27, 0xff5fe411, 0xfffbb283,
413 0xffd86e99, 0xff169162, 0xff71128c, 0xff39cab4,
414 0xffa7fe63, 0xff4c956b, 0xffbc22e0, 0xffb272e4,
415 0xff129f4a, 0xffe34513, 0xff3d3742, 0xffbd190a,
416 0xffb07222, 0xff2e23f8, 0xfff089d9, 0xffb35738,
417 0xffa86022, 0xff3340fe, 0xff95fe71, 0xff6a71df
418};
419SK_COMPILE_ASSERT((kExpectedWidth * kExpectedHeight)
420 == SK_ARRAY_COUNT(kExpectedPixels), array_size_mismatch);
halcanary@google.comdedd44a2013-12-20 16:35:22 +0000421
422DEF_TEST(WebP, reporter) {
423 const unsigned char encodedWebP[] = {
424 0x52, 0x49, 0x46, 0x46, 0x2c, 0x01, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
425 0x56, 0x50, 0x38, 0x4c, 0x20, 0x01, 0x00, 0x00, 0x2f, 0x07, 0xc0, 0x01,
426 0x00, 0xff, 0x01, 0x45, 0x03, 0x00, 0xe2, 0xd5, 0xae, 0x60, 0x2b, 0xad,
427 0xd9, 0x68, 0x76, 0xb6, 0x8d, 0x6a, 0x1d, 0xc0, 0xe6, 0x19, 0xd6, 0x16,
428 0xb7, 0xb4, 0xef, 0xcf, 0xc3, 0x15, 0x6c, 0xb3, 0xbd, 0x77, 0x0d, 0x85,
429 0x6d, 0x1b, 0xa9, 0xb1, 0x2b, 0xdc, 0x3d, 0x83, 0xdb, 0x00, 0x00, 0xc8,
430 0x26, 0xe5, 0x01, 0x99, 0x8a, 0xd5, 0xdd, 0xfc, 0x82, 0xcd, 0xcd, 0x9a,
431 0x8c, 0x13, 0xcc, 0x1b, 0xba, 0xf5, 0x05, 0xdb, 0xee, 0x6a, 0xdb, 0x38,
432 0x60, 0xfe, 0x43, 0x2c, 0xd4, 0x6a, 0x99, 0x4d, 0xc6, 0xc0, 0xd3, 0x28,
433 0x1b, 0xc1, 0xb1, 0x17, 0x4e, 0x43, 0x0e, 0x3d, 0x27, 0xe9, 0xe4, 0x84,
434 0x4f, 0x24, 0x62, 0x69, 0x85, 0x43, 0x8d, 0xc2, 0x04, 0x00, 0x07, 0x59,
435 0x60, 0xfd, 0x8b, 0x4d, 0x60, 0x32, 0x72, 0xcf, 0x88, 0x0c, 0x2f, 0x2f,
436 0xad, 0x62, 0xbd, 0x27, 0x09, 0x16, 0x70, 0x78, 0x6c, 0xd9, 0x82, 0xef,
437 0x1a, 0xa2, 0xcc, 0xf0, 0xf1, 0x6f, 0xd8, 0x78, 0x2e, 0x39, 0xa1, 0xcf,
438 0x14, 0x4b, 0x89, 0xb4, 0x1b, 0x48, 0x15, 0x7c, 0x48, 0x6f, 0x8c, 0x20,
439 0xb7, 0x00, 0xcf, 0xfc, 0xdb, 0xd0, 0xe9, 0xe7, 0x42, 0x09, 0xa4, 0x03,
440 0x40, 0xac, 0xda, 0x40, 0x01, 0x00, 0x5f, 0xa1, 0x3d, 0x64, 0xe1, 0xf4,
441 0x03, 0x45, 0x29, 0xe0, 0xe2, 0x4a, 0xc3, 0xa2, 0xe8, 0xe0, 0x25, 0x12,
442 0x74, 0xc6, 0xe8, 0xfb, 0x93, 0x4f, 0x9f, 0x5e, 0xc0, 0xa6, 0x91, 0x1b,
443 0xa4, 0x24, 0x82, 0xc3, 0x61, 0x07, 0x4c, 0x49, 0x4f, 0x53, 0xae, 0x5f,
444 0x5d, 0x39, 0x36, 0xc0, 0x5b, 0x57, 0x54, 0x60, 0x10, 0x00, 0x00, 0xd1,
445 0x68, 0xb6, 0x6d, 0xdb, 0x36, 0x22, 0xfa, 0x1f, 0x35, 0x75, 0x22, 0xec,
446 0x31, 0xbc, 0x5d, 0x8f, 0x87, 0x53, 0xa2, 0x05, 0x8c, 0x2f, 0xcd, 0xa8,
447 0xa7, 0xf3, 0xa3, 0xbd, 0x83, 0x8b, 0x2a, 0xc8, 0x58, 0xf5, 0xac, 0x80,
448 0xe3, 0xfe, 0x66, 0xa4, 0x7c, 0x1b, 0x6c, 0xd1, 0xa9, 0xd8, 0x14, 0xd0,
449 0xc5, 0xb5, 0x39, 0x71, 0x97, 0x19, 0x19, 0x1b
450 };
halcanary@google.comdedd44a2013-12-20 16:35:22 +0000451 SkAutoDataUnref encoded(SkData::NewWithCopy(encodedWebP,
452 sizeof(encodedWebP)));
453 SkBitmap bm;
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000454
455 bool success = SkInstallDiscardablePixelRef(
456 SkDecodingImageGenerator::Create(encoded,
457 SkDecodingImageGenerator::Options()), &bm, NULL);
458
halcanary@google.comdedd44a2013-12-20 16:35:22 +0000459 REPORTER_ASSERT(reporter, success);
460 if (!success) {
461 return;
462 }
463 SkAutoLockPixels alp(bm);
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000464
skia.committer@gmail.com86202072014-01-03 07:01:45 +0000465 bool rightSize = ((kExpectedWidth == bm.width())
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000466 && (kExpectedHeight == bm.height()));
halcanary@google.comdedd44a2013-12-20 16:35:22 +0000467 REPORTER_ASSERT(reporter, rightSize);
468 if (rightSize) {
469 bool error = false;
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000470 const SkColor* correctPixel = kExpectedPixels;
halcanary@google.comdedd44a2013-12-20 16:35:22 +0000471 for (int y = 0; y < bm.height(); ++y) {
472 for (int x = 0; x < bm.width(); ++x) {
473 error |= (*correctPixel != bm.getColor(x, y));
474 ++correctPixel;
475 }
476 }
477 REPORTER_ASSERT(reporter, !error);
478 }
479}
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000480
481////////////////////////////////////////////////////////////////////////////////
482
483// example of how Android will do this inside their BitmapFactory
484static SkPixelRef* install_pixel_ref(SkBitmap* bitmap,
485 SkStreamRewindable* stream,
486 int sampleSize, bool ditherImage) {
487 SkASSERT(bitmap != NULL);
488 SkASSERT(stream != NULL);
489 SkASSERT(stream->rewind());
490 SkASSERT(stream->unique());
commit-bot@chromium.org61e96cd2014-02-11 18:21:45 +0000491 SkColorType colorType = bitmap->colorType();
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000492 SkDecodingImageGenerator::Options opts(sampleSize, ditherImage, colorType);
493 SkAutoTDelete<SkImageGenerator> gen(
494 SkDecodingImageGenerator::Create(stream, opts));
495 SkImageInfo info;
496 if ((NULL == gen.get()) || !gen->getInfo(&info)) {
497 return NULL;
498 }
499 SkDiscardableMemory::Factory* factory = NULL;
500 if (info.getSafeSize(info.minRowBytes()) < (32 * 1024)) {
501 // only use ashmem for large images, since mmaps come at a price
502 factory = SkGetGlobalDiscardableMemoryPool();
503 }
504 if (SkInstallDiscardablePixelRef(gen.detach(), bitmap, factory)) {
505 return bitmap->pixelRef();
506 }
507 return NULL;
508}
509/**
510 * A test for the SkDecodingImageGenerator::Create and
511 * SkInstallDiscardablePixelRef functions.
512 */
513DEF_TEST(ImprovedBitmapFactory, reporter) {
514 SkString resourcePath = skiatest::Test::GetResourcePath();
halcanary@google.comb7acbfe2014-05-09 18:08:24 +0000515 SkString path = SkOSPath::SkPathJoin(
516 resourcePath.c_str(), "randPixels.png");
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000517 SkAutoTUnref<SkStreamRewindable> stream(
518 SkStream::NewFromFile(path.c_str()));
519 if (sk_exists(path.c_str())) {
520 SkBitmap bm;
mike@reedtribe.orgdeee4962014-02-13 14:41:43 +0000521 SkAssertResult(bm.setConfig(SkImageInfo::MakeN32Premul(1, 1)));
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000522 REPORTER_ASSERT(reporter,
523 NULL != install_pixel_ref(&bm, stream.detach(), 1, true));
524 SkAutoLockPixels alp(bm);
525 REPORTER_ASSERT(reporter, NULL != bm.getPixels());
526 }
527}
528
529
530////////////////////////////////////////////////////////////////////////////////
531
532#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
533static inline bool check_rounding(int value, int dividend, int divisor) {
halcanary@google.comb7acbfe2014-05-09 18:08:24 +0000534 // returns true if the value is greater than floor(dividend/divisor)
535 // and less than SkNextPow2(ceil(dividend - divisor))
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000536 return (((divisor * value) > (dividend - divisor))
halcanary@google.comb7acbfe2014-05-09 18:08:24 +0000537 && value <= SkNextPow2(((dividend - 1) / divisor) + 1));
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000538}
539#endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX
540
541
542#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
543 #define kBackwards_SkColorType kRGBA_8888_SkColorType
544#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
545 #define kBackwards_SkColorType kBGRA_8888_SkColorType
546#else
547 #error "SK_*32_SHFIT values must correspond to BGRA or RGBA byte order"
548#endif
549
550static inline const char* SkColorType_to_string(SkColorType colorType) {
551 switch(colorType) {
552 case kAlpha_8_SkColorType: return "Alpha_8";
553 case kRGB_565_SkColorType: return "RGB_565";
554 case kARGB_4444_SkColorType: return "ARGB_4444";
commit-bot@chromium.org28fcae22014-04-11 17:15:40 +0000555 case kN32_SkColorType: return "N32";
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000556 case kBackwards_SkColorType: return "Backwards";
557 case kIndex_8_SkColorType: return "Index_8";
558 default: return "ERROR";
559 }
560}
561
halcanary@google.coma9325fa2014-01-10 14:58:10 +0000562static inline const char* options_colorType(
563 const SkDecodingImageGenerator::Options& opts) {
564 if (opts.fUseRequestedColorType) {
565 return SkColorType_to_string(opts.fRequestedColorType);
566 } else {
567 return "(none)";
568 }
569}
570
571static inline const char* yn(bool value) {
572 if (value) {
573 return "yes";
574 } else {
575 return "no";
576 }
577}
578
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000579/**
580 * Given either a SkStream or a SkData, try to decode the encoded
581 * image using the specified options and report errors.
582 */
583static void test_options(skiatest::Reporter* reporter,
584 const SkDecodingImageGenerator::Options& opts,
585 SkStreamRewindable* encodedStream,
586 SkData* encodedData,
587 bool useData,
588 const SkString& path) {
589 SkBitmap bm;
590 bool success = false;
591 if (useData) {
592 if (NULL == encodedData) {
593 return;
594 }
595 success = SkInstallDiscardablePixelRef(
596 SkDecodingImageGenerator::Create(encodedData, opts), &bm, NULL);
597 } else {
598 if (NULL == encodedStream) {
599 return;
600 }
601 success = SkInstallDiscardablePixelRef(
602 SkDecodingImageGenerator::Create(encodedStream->duplicate(), opts),
603 &bm, NULL);
604 }
605 if (!success) {
606 if (opts.fUseRequestedColorType
607 && (kARGB_4444_SkColorType == opts.fRequestedColorType)) {
608 return; // Ignore known conversion inabilities.
609 }
610 // If we get here, it's a failure and we will need more
611 // information about why it failed.
halcanary@google.coma9325fa2014-01-10 14:58:10 +0000612 ERRORF(reporter, "Bounds decode failed [sampleSize=%d dither=%s "
613 "colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage),
614 options_colorType(opts), path.c_str());
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000615 return;
616 }
617 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX)
618 // Android is the only system that use Skia's image decoders in
619 // production. For now, we'll only verify that samplesize works
620 // on systems where it already is known to work.
621 REPORTER_ASSERT(reporter, check_rounding(bm.height(), kExpectedHeight,
622 opts.fSampleSize));
623 REPORTER_ASSERT(reporter, check_rounding(bm.width(), kExpectedWidth,
624 opts.fSampleSize));
halcanary@google.comb7acbfe2014-05-09 18:08:24 +0000625 // The ImageDecoder API doesn't guarantee that SampleSize does
626 // anything at all, but the decoders that this test excercises all
627 // produce an output size in the following range:
628 // (((sample_size * out_size) > (in_size - sample_size))
629 // && out_size <= SkNextPow2(((in_size - 1) / sample_size) + 1));
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000630 #endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX
631 SkAutoLockPixels alp(bm);
632 if (bm.getPixels() == NULL) {
halcanary@google.coma9325fa2014-01-10 14:58:10 +0000633 ERRORF(reporter, "Pixel decode failed [sampleSize=%d dither=%s "
634 "colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage),
635 options_colorType(opts), path.c_str());
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000636 return;
637 }
638
639 SkBitmap::Config requestedConfig
640 = SkColorTypeToBitmapConfig(opts.fRequestedColorType);
641 REPORTER_ASSERT(reporter,
642 (!opts.fUseRequestedColorType)
643 || (bm.config() == requestedConfig));
644
645 // Condition under which we should check the decoding results:
646 if ((SkBitmap::kARGB_8888_Config == bm.config())
647 && (!path.endsWith(".jpg")) // lossy
648 && (opts.fSampleSize == 1)) { // scaled
649 const SkColor* correctPixels = kExpectedPixels;
650 SkASSERT(bm.height() == kExpectedHeight);
651 SkASSERT(bm.width() == kExpectedWidth);
652 int pixelErrors = 0;
653 for (int y = 0; y < bm.height(); ++y) {
654 for (int x = 0; x < bm.width(); ++x) {
655 if (*correctPixels != bm.getColor(x, y)) {
656 ++pixelErrors;
657 }
658 ++correctPixels;
659 }
660 }
661 if (pixelErrors != 0) {
halcanary@google.coma9325fa2014-01-10 14:58:10 +0000662 ERRORF(reporter, "Pixel-level mismatch (%d of %d) "
663 "[sampleSize=%d dither=%s colorType=%s %s]",
664 pixelErrors, kExpectedHeight * kExpectedWidth,
665 opts.fSampleSize, yn(opts.fDitherImage),
666 options_colorType(opts), path.c_str());
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000667 }
668 }
669}
670
671/**
672 * SkDecodingImageGenerator has an Options struct which lets the
673 * client of the generator set sample size, dithering, and bitmap
674 * config. This test loops through many possible options and tries
675 * them on a set of 5 small encoded images (each in a different
676 * format). We test both SkData and SkStreamRewindable decoding.
677 */
678DEF_TEST(ImageDecoderOptions, reporter) {
679 const char* files[] = {
680 "randPixels.bmp",
681 "randPixels.jpg",
682 "randPixels.png",
683 "randPixels.webp",
684 #if !defined(SK_BUILD_FOR_WIN)
685 // TODO(halcanary): Find out why this fails sometimes.
686 "randPixels.gif",
687 #endif
688 };
689
690 SkString resourceDir = skiatest::Test::GetResourcePath();
halcanary@google.comb7acbfe2014-05-09 18:08:24 +0000691 if (!sk_exists(resourceDir.c_str())) {
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000692 return;
693 }
694
695 int scaleList[] = {1, 2, 3, 4};
696 bool ditherList[] = {true, false};
697 SkColorType colorList[] = {
698 kAlpha_8_SkColorType,
699 kRGB_565_SkColorType,
700 kARGB_4444_SkColorType, // Most decoders will fail on 4444.
commit-bot@chromium.org28fcae22014-04-11 17:15:40 +0000701 kN32_SkColorType
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000702 // Note that indexed color is left out of the list. Lazy
703 // decoding doesn't do indexed color.
704 };
705 const bool useDataList[] = {true, false};
706
707 for (size_t fidx = 0; fidx < SK_ARRAY_COUNT(files); ++fidx) {
halcanary@google.comb7acbfe2014-05-09 18:08:24 +0000708 SkString path = SkOSPath::SkPathJoin(resourceDir.c_str(), files[fidx]);
halcanary@google.com3d50ea12014-01-02 13:15:13 +0000709 if (!sk_exists(path.c_str())) {
710 continue;
711 }
712
713 SkAutoDataUnref encodedData(SkData::NewFromFileName(path.c_str()));
714 REPORTER_ASSERT(reporter, encodedData.get() != NULL);
715 SkAutoTUnref<SkStreamRewindable> encodedStream(
716 SkStream::NewFromFile(path.c_str()));
717 REPORTER_ASSERT(reporter, encodedStream.get() != NULL);
718
719 for (size_t i = 0; i < SK_ARRAY_COUNT(scaleList); ++i) {
720 for (size_t j = 0; j < SK_ARRAY_COUNT(ditherList); ++j) {
721 for (size_t m = 0; m < SK_ARRAY_COUNT(useDataList); ++m) {
722 for (size_t k = 0; k < SK_ARRAY_COUNT(colorList); ++k) {
723 SkDecodingImageGenerator::Options opts(scaleList[i],
724 ditherList[j],
725 colorList[k]);
726 test_options(reporter, opts, encodedStream, encodedData,
727 useDataList[m], path);
728
729 }
730 SkDecodingImageGenerator::Options options(scaleList[i],
731 ditherList[j]);
732 test_options(reporter, options, encodedStream, encodedData,
733 useDataList[m], path);
734 }
735 }
736 }
737 }
738}