• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 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 "SkBitmap.h"
10 #include "SkCodec.h"
11 #include "SkMD5.h"
12 #include "Test.h"
13 
resource(const char path[])14 static SkStreamAsset* resource(const char path[]) {
15     SkString fullPath = GetResourcePath(path);
16     return SkStream::NewFromFile(fullPath.c_str());
17 }
18 
md5(const SkBitmap & bm,SkMD5::Digest * digest)19 static void md5(const SkBitmap& bm, SkMD5::Digest* digest) {
20     SkAutoLockPixels autoLockPixels(bm);
21     SkASSERT(bm.getPixels());
22     SkMD5 md5;
23     size_t rowLen = bm.info().bytesPerPixel() * bm.width();
24     for (int y = 0; y < bm.height(); ++y) {
25         md5.update(static_cast<uint8_t*>(bm.getAddr(0, y)), rowLen);
26     }
27     md5.finish(*digest);
28 }
29 
check(skiatest::Reporter * r,const char path[],SkISize size,bool supportsScanlineDecoding)30 static void check(skiatest::Reporter* r,
31                   const char path[],
32                   SkISize size,
33                   bool supportsScanlineDecoding) {
34     SkAutoTDelete<SkStream> stream(resource(path));
35     if (!stream) {
36         SkDebugf("Missing resource '%s'\n", path);
37         return;
38     }
39     SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach()));
40     if (!codec) {
41         ERRORF(r, "Unable to decode '%s'", path);
42         return;
43     }
44 
45     // This test is used primarily to verify rewinding works properly.  Using kN32 allows
46     // us to test this without the added overhead of creating different bitmaps depending
47     // on the color type (ex: building a color table for kIndex8).  DM is where we test
48     // decodes to all possible destination color types.
49     SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
50     REPORTER_ASSERT(r, info.dimensions() == size);
51     SkBitmap bm;
52     bm.allocPixels(info);
53     SkAutoLockPixels autoLockPixels(bm);
54     SkImageGenerator::Result result =
55         codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL);
56     REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess);
57 
58     SkMD5::Digest digest1, digest2;
59     md5(bm, &digest1);
60 
61     bm.eraseColor(SK_ColorYELLOW);
62 
63     result =
64         codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL);
65 
66     REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess);
67     // verify that re-decoding gives the same result.
68     md5(bm, &digest2);
69     REPORTER_ASSERT(r, digest1 == digest2);
70 
71     SkScanlineDecoder* scanlineDecoder = codec->getScanlineDecoder(info);
72     if (supportsScanlineDecoding) {
73         bm.eraseColor(SK_ColorYELLOW);
74         REPORTER_ASSERT(r, scanlineDecoder);
75         for (int y = 0; y < info.height(); y++) {
76             result = scanlineDecoder->getScanlines(bm.getAddr(0, y), 1, 0);
77             REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess);
78         }
79         // verify that scanline decoding gives the same result.
80         SkMD5::Digest digest3;
81         md5(bm, &digest3);
82         REPORTER_ASSERT(r, digest3 == digest1);
83     } else {
84         REPORTER_ASSERT(r, !scanlineDecoder);
85     }
86 }
87 
DEF_TEST(Codec,r)88 DEF_TEST(Codec, r) {
89     // WBMP
90     check(r, "mandrill.wbmp", SkISize::Make(512, 512), false);
91 
92     // BMP
93     check(r, "randPixels.bmp", SkISize::Make(8, 8), false);
94 
95     // ICO
96     // These two tests examine interestingly different behavior:
97     // Decodes an embedded BMP image
98     check(r, "color_wheel.ico", SkISize::Make(128, 128), false);
99     // Decodes an embedded PNG image
100     check(r, "google_chrome.ico", SkISize::Make(256, 256), false);
101 
102     // GIF
103     check(r, "box.gif", SkISize::Make(200, 55), false);
104     check(r, "color_wheel.gif", SkISize::Make(128, 128), false);
105     check(r, "randPixels.gif", SkISize::Make(8, 8), false);
106 
107     // JPG
108     check(r, "CMYK.jpg", SkISize::Make(642, 516), true);
109     check(r, "color_wheel.jpg", SkISize::Make(128, 128), true);
110     check(r, "grayscale.jpg", SkISize::Make(128, 128), true);
111     check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true);
112     check(r, "randPixels.jpg", SkISize::Make(8, 8), true);
113 
114     // PNG
115     check(r, "arrow.png", SkISize::Make(187, 312), true);
116     check(r, "baby_tux.png", SkISize::Make(240, 246), true);
117     check(r, "color_wheel.png", SkISize::Make(128, 128), true);
118     check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true);
119     check(r, "mandrill_128.png", SkISize::Make(128, 128), true);
120     check(r, "mandrill_16.png", SkISize::Make(16, 16), true);
121     check(r, "mandrill_256.png", SkISize::Make(256, 256), true);
122     check(r, "mandrill_32.png", SkISize::Make(32, 32), true);
123     check(r, "mandrill_512.png", SkISize::Make(512, 512), true);
124     check(r, "mandrill_64.png", SkISize::Make(64, 64), true);
125     check(r, "plane.png", SkISize::Make(250, 126), true);
126     check(r, "randPixels.png", SkISize::Make(8, 8), true);
127     check(r, "yellow_rose.png", SkISize::Make(400, 301), true);
128 }
129 
test_invalid_stream(skiatest::Reporter * r,const void * stream,size_t len)130 static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_t len) {
131     SkCodec* codec = SkCodec::NewFromStream(new SkMemoryStream(stream, len, false));
132     // We should not have gotten a codec. Bots should catch us if we leaked anything.
133     REPORTER_ASSERT(r, !codec);
134 }
135 
136 // Ensure that SkCodec::NewFromStream handles freeing the passed in SkStream,
137 // even on failure. Test some bad streams.
DEF_TEST(Codec_leaks,r)138 DEF_TEST(Codec_leaks, r) {
139     // No codec should claim this as their format, so this tests SkCodec::NewFromStream.
140     const char nonSupportedStream[] = "hello world";
141     // The other strings should look like the beginning of a file type, so we'll call some
142     // internal version of NewFromStream, which must also delete the stream on failure.
143     const unsigned char emptyPng[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
144     const unsigned char emptyJpeg[] = { 0xFF, 0xD8, 0xFF };
145     const char emptyWebp[] = "RIFF1234WEBPVP";
146     const char emptyBmp[] = { 'B', 'M' };
147     const char emptyIco[] = { '\x00', '\x00', '\x01', '\x00' };
148     const char emptyGif[] = "GIFVER";
149 
150     test_invalid_stream(r, nonSupportedStream, sizeof(nonSupportedStream));
151     test_invalid_stream(r, emptyPng, sizeof(emptyPng));
152     test_invalid_stream(r, emptyJpeg, sizeof(emptyJpeg));
153     test_invalid_stream(r, emptyWebp, sizeof(emptyWebp));
154     test_invalid_stream(r, emptyBmp, sizeof(emptyBmp));
155     test_invalid_stream(r, emptyIco, sizeof(emptyIco));
156     test_invalid_stream(r, emptyGif, sizeof(emptyGif));
157 }
158 
test_dimensions(skiatest::Reporter * r,const char path[])159 static void test_dimensions(skiatest::Reporter* r, const char path[]) {
160     // Create the codec from the resource file
161     SkAutoTDelete<SkStream> stream(resource(path));
162     if (!stream) {
163         SkDebugf("Missing resource '%s'\n", path);
164         return;
165     }
166     SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach()));
167     if (!codec) {
168         ERRORF(r, "Unable to create codec '%s'", path);
169         return;
170     }
171 
172     // Check that the decode is successful for a variety of scales
173     for (float scale = -0.05f; scale < 2.0f; scale += 0.05f) {
174         // Scale the output dimensions
175         SkISize scaledDims = codec->getScaledDimensions(scale);
176         SkImageInfo scaledInfo = codec->getInfo().makeWH(scaledDims.width(), scaledDims.height());
177 
178         // Set up for the decode
179         size_t rowBytes = scaledDims.width() * sizeof(SkPMColor);
180         size_t totalBytes = scaledInfo.getSafeSize(rowBytes);
181         SkAutoTMalloc<SkPMColor> pixels(totalBytes);
182 
183         SkImageGenerator::Result result =
184                 codec->getPixels(scaledInfo, pixels.get(), rowBytes, NULL, NULL, NULL);
185         REPORTER_ASSERT(r, SkImageGenerator::kSuccess == result);
186     }
187 }
188 
189 // Ensure that onGetScaledDimensions returns valid image dimensions to use for decodes
DEF_TEST(Codec_Dimensions,r)190 DEF_TEST(Codec_Dimensions, r) {
191     // JPG
192     test_dimensions(r, "CMYK.jpg");
193     test_dimensions(r, "color_wheel.jpg");
194     test_dimensions(r, "grayscale.jpg");
195     test_dimensions(r, "mandrill_512_q075.jpg");
196     test_dimensions(r, "randPixels.jpg");
197 }
198 
test_empty(skiatest::Reporter * r,const char path[])199 static void test_empty(skiatest::Reporter* r, const char path[]) {
200     SkAutoTDelete<SkStream> stream(resource(path));
201     if (!stream) {
202         SkDebugf("Missing resource '%s'\n", path);
203         return;
204     }
205     SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach()));
206     REPORTER_ASSERT(r, NULL == codec);
207 }
208 
DEF_TEST(Codec_Empty,r)209 DEF_TEST(Codec_Empty, r) {
210     // Test images that should not be able to create a codec
211     test_empty(r, "empty_images/zero-dims.gif");
212     test_empty(r, "empty_images/zero-embedded.ico");
213     test_empty(r, "empty_images/zero-width.bmp");
214     test_empty(r, "empty_images/zero-height.bmp");
215     test_empty(r, "empty_images/zero-width.jpg");
216     test_empty(r, "empty_images/zero-height.jpg");
217     test_empty(r, "empty_images/zero-width.png");
218     test_empty(r, "empty_images/zero-height.png");
219     test_empty(r, "empty_images/zero-width.wbmp");
220     test_empty(r, "empty_images/zero-height.wbmp");
221 }
222