1 /* 2 * Copyright 2016 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" 9 #include "SkCodec.h" 10 #include "SkData.h" 11 #include "SkImageInfo.h" 12 #include "SkMakeUnique.h" 13 #include "SkRWBuffer.h" 14 #include "SkString.h" 15 16 #include "FakeStreams.h" 17 #include "Resources.h" 18 #include "Test.h" 19 20 static SkImageInfo standardize_info(SkCodec* codec) { 21 SkImageInfo defaultInfo = codec->getInfo(); 22 // Note: This drops the SkColorSpace, allowing the equality check between two 23 // different codecs created from the same file to have the same SkImageInfo. 24 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height()); 25 } 26 27 static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) { 28 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(data))); 29 if (!codec) { 30 return false; 31 } 32 33 const SkImageInfo info = standardize_info(codec.get()); 34 dst->allocPixels(info); 35 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes()); 36 } 37 38 static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) { 39 const SkImageInfo& info = bm1.info(); 40 if (info != bm2.info()) { 41 ERRORF(r, "Bitmaps have different image infos!"); 42 return; 43 } 44 const size_t rowBytes = info.minRowBytes(); 45 for (int i = 0; i < info.height(); i++) { 46 if (memcmp(bm1.getAddr(0, i), bm2.getAddr(0, i), rowBytes)) { 47 ERRORF(r, "Bitmaps have different pixels, starting on line %i!", i); 48 return; 49 } 50 } 51 } 52 53 static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) { 54 sk_sp<SkData> file = GetResourceAsData(name); 55 if (!file) { 56 SkDebugf("missing resource %s\n", name); 57 return; 58 } 59 60 SkBitmap truth; 61 if (!create_truth(file, &truth)) { 62 ERRORF(r, "Failed to decode %s\n", name); 63 return; 64 } 65 66 // Now decode part of the file 67 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes)); 68 69 // Note that we cheat and hold on to a pointer to stream, though it is owned by 70 // partialCodec. 71 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream))); 72 if (!partialCodec) { 73 // Technically, this could be a small file where half the file is not 74 // enough. 75 ERRORF(r, "Failed to create codec for %s", name); 76 return; 77 } 78 79 const SkImageInfo info = standardize_info(partialCodec.get()); 80 SkASSERT(info == truth.info()); 81 SkBitmap incremental; 82 incremental.allocPixels(info); 83 84 while (true) { 85 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info, 86 incremental.getPixels(), incremental.rowBytes()); 87 if (startResult == SkCodec::kSuccess) { 88 break; 89 } 90 91 if (stream->isAllDataReceived()) { 92 ERRORF(r, "Failed to start incremental decode\n"); 93 return; 94 } 95 96 // Append some data. The size is arbitrary, but deliberately different from 97 // the buffer size used by SkPngCodec. 98 stream->addNewData(1000); 99 } 100 101 while (true) { 102 const SkCodec::Result result = partialCodec->incrementalDecode(); 103 104 if (result == SkCodec::kSuccess) { 105 break; 106 } 107 108 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); 109 110 if (stream->isAllDataReceived()) { 111 ERRORF(r, "Failed to completely decode %s", name); 112 return; 113 } 114 115 // Append some data. The size is arbitrary, but deliberately different from 116 // the buffer size used by SkPngCodec. 117 stream->addNewData(1000); 118 } 119 120 // compare to original 121 compare_bitmaps(r, truth, incremental); 122 } 123 124 DEF_TEST(Codec_partial, r) { 125 #if 0 126 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to 127 // support incremental decoding. 128 test_partial(r, "images/plane.png"); 129 test_partial(r, "images/plane_interlaced.png"); 130 test_partial(r, "images/yellow_rose.png"); 131 test_partial(r, "images/index8.png"); 132 test_partial(r, "images/color_wheel.png"); 133 test_partial(r, "images/mandrill_256.png"); 134 test_partial(r, "images/mandrill_32.png"); 135 test_partial(r, "images/arrow.png"); 136 test_partial(r, "images/randPixels.png"); 137 test_partial(r, "images/baby_tux.png"); 138 #endif 139 test_partial(r, "images/box.gif"); 140 test_partial(r, "images/randPixels.gif", 215); 141 test_partial(r, "images/color_wheel.gif"); 142 } 143 144 // Verify that when decoding an animated gif byte by byte we report the correct 145 // fRequiredFrame as soon as getFrameInfo reports the frame. 146 DEF_TEST(Codec_requiredFrame, r) { 147 auto path = "images/colorTables.gif"; 148 sk_sp<SkData> file = GetResourceAsData(path); 149 if (!file) { 150 return; 151 } 152 153 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file)); 154 if (!codec) { 155 ERRORF(r, "Failed to create codec from %s", path); 156 return; 157 } 158 159 auto frameInfo = codec->getFrameInfo(); 160 if (frameInfo.size() <= 1) { 161 ERRORF(r, "Test is uninteresting with 0 or 1 frames"); 162 return; 163 } 164 165 HaltingStream* stream(nullptr); 166 std::unique_ptr<SkCodec> partialCodec(nullptr); 167 for (size_t i = 0; !partialCodec; i++) { 168 if (file->size() == i) { 169 ERRORF(r, "Should have created a partial codec for %s", path); 170 return; 171 } 172 stream = new HaltingStream(file, i); 173 partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream)); 174 } 175 176 std::vector<SkCodec::FrameInfo> partialInfo; 177 size_t frameToCompare = 0; 178 for (; stream->getLength() <= file->size(); stream->addNewData(1)) { 179 partialInfo = partialCodec->getFrameInfo(); 180 for (; frameToCompare < partialInfo.size(); frameToCompare++) { 181 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame 182 == frameInfo[frameToCompare].fRequiredFrame); 183 } 184 185 if (frameToCompare == frameInfo.size()) { 186 break; 187 } 188 } 189 } 190 191 DEF_TEST(Codec_partialAnim, r) { 192 auto path = "images/test640x479.gif"; 193 sk_sp<SkData> file = GetResourceAsData(path); 194 if (!file) { 195 return; 196 } 197 198 // This stream will be owned by fullCodec, but we hang on to the pointer 199 // to determine frame offsets. 200 std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(skstd::make_unique<SkMemoryStream>(file))); 201 const auto info = standardize_info(fullCodec.get()); 202 203 // frameByteCounts stores the number of bytes to decode a particular frame. 204 // - [0] is the number of bytes for the header 205 // - frames[i] requires frameByteCounts[i+1] bytes to decode 206 const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 }; 207 std::vector<SkBitmap> frames; 208 for (size_t i = 0; true; i++) { 209 SkBitmap frame; 210 frame.allocPixels(info); 211 212 SkCodec::Options opts; 213 opts.fFrameIndex = i; 214 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(), 215 frame.rowBytes(), &opts); 216 217 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) { 218 // We need to distinguish between a partial frame and no more frames. 219 // getFrameInfo lets us do this, since it tells the number of frames 220 // not considering whether they are complete. 221 // FIXME: Should we use a different Result? 222 if (fullCodec->getFrameInfo().size() > i) { 223 // This is a partial frame. 224 frames.push_back(frame); 225 } 226 break; 227 } 228 229 if (result != SkCodec::kSuccess) { 230 ERRORF(r, "Failed to decode frame %i from %s", i, path); 231 return; 232 } 233 234 frames.push_back(frame); 235 } 236 237 // Now decode frames partially, then completely, and compare to the original. 238 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]); 239 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream( 240 std::unique_ptr<SkStream>(haltingStream))); 241 if (!partialCodec) { 242 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i", 243 path, frameByteCounts[0], file->size()); 244 return; 245 } 246 247 SkASSERT(frameByteCounts.size() > frames.size()); 248 for (size_t i = 0; i < frames.size(); i++) { 249 const size_t fullFrameBytes = frameByteCounts[i + 1]; 250 const size_t firstHalf = fullFrameBytes / 2; 251 const size_t secondHalf = fullFrameBytes - firstHalf; 252 253 haltingStream->addNewData(firstHalf); 254 auto frameInfo = partialCodec->getFrameInfo(); 255 REPORTER_ASSERT(r, frameInfo.size() == i + 1); 256 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived); 257 258 SkBitmap frame; 259 frame.allocPixels(info); 260 261 SkCodec::Options opts; 262 opts.fFrameIndex = i; 263 SkCodec::Result result = partialCodec->startIncrementalDecode(info, 264 frame.getPixels(), frame.rowBytes(), &opts); 265 if (result != SkCodec::kSuccess) { 266 ERRORF(r, "Failed to start incremental decode for %s on frame %i", 267 path, i); 268 return; 269 } 270 271 result = partialCodec->incrementalDecode(); 272 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result); 273 274 haltingStream->addNewData(secondHalf); 275 result = partialCodec->incrementalDecode(); 276 REPORTER_ASSERT(r, SkCodec::kSuccess == result); 277 278 frameInfo = partialCodec->getFrameInfo(); 279 REPORTER_ASSERT(r, frameInfo.size() == i + 1); 280 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived); 281 compare_bitmaps(r, frames[i], frame); 282 } 283 } 284 285 // Test that calling getPixels when an incremental decode has been 286 // started (but not finished) makes the next call to incrementalDecode 287 // require a call to startIncrementalDecode. 288 static void test_interleaved(skiatest::Reporter* r, const char* name) { 289 sk_sp<SkData> file = GetResourceAsData(name); 290 if (!file) { 291 return; 292 } 293 const size_t halfSize = file->size() / 2; 294 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream( 295 skstd::make_unique<HaltingStream>(std::move(file), halfSize))); 296 if (!partialCodec) { 297 ERRORF(r, "Failed to create codec for %s", name); 298 return; 299 } 300 301 const SkImageInfo info = standardize_info(partialCodec.get()); 302 SkBitmap incremental; 303 incremental.allocPixels(info); 304 305 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info, 306 incremental.getPixels(), incremental.rowBytes()); 307 if (startResult != SkCodec::kSuccess) { 308 ERRORF(r, "Failed to start incremental decode\n"); 309 return; 310 } 311 312 SkCodec::Result result = partialCodec->incrementalDecode(); 313 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); 314 315 SkBitmap full; 316 full.allocPixels(info); 317 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes()); 318 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); 319 320 // Now incremental decode will fail 321 result = partialCodec->incrementalDecode(); 322 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters); 323 } 324 325 DEF_TEST(Codec_rewind, r) { 326 test_interleaved(r, "images/plane.png"); 327 test_interleaved(r, "images/plane_interlaced.png"); 328 test_interleaved(r, "images/box.gif"); 329 } 330 331 // Modified version of the giflib logo, from 332 // http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html 333 // The global color map has been replaced with a local color map. 334 static unsigned char gNoGlobalColorMap[] = { 335 // Header 336 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 337 338 // Logical screen descriptor 339 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00, 340 341 // Image descriptor 342 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81, 343 344 // Local color table 345 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 346 347 // Image data 348 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75, 349 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00, 350 351 // Trailer 352 0x3B, 353 }; 354 355 // Test that a gif file truncated before its local color map behaves as expected. 356 DEF_TEST(Codec_GifPreMap, r) { 357 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap)); 358 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data)); 359 if (!codec) { 360 ERRORF(r, "failed to create codec"); 361 return; 362 } 363 364 SkBitmap truth; 365 auto info = standardize_info(codec.get()); 366 truth.allocPixels(info); 367 368 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes()); 369 REPORTER_ASSERT(r, result == SkCodec::kSuccess); 370 371 // Truncate to 23 bytes, just before the color map. This should fail to decode. 372 codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23)); 373 REPORTER_ASSERT(r, codec); 374 if (codec) { 375 SkBitmap bm; 376 bm.allocPixels(info); 377 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); 378 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput); 379 } 380 381 // Again, truncate to 23 bytes, this time for an incremental decode. We 382 // cannot start an incremental decode until we have more data. If we did, 383 // we would be using the wrong color table. 384 HaltingStream* stream = new HaltingStream(data, 23); 385 codec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream)); 386 REPORTER_ASSERT(r, codec); 387 if (codec) { 388 SkBitmap bm; 389 bm.allocPixels(info); 390 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes()); 391 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); 392 393 stream->addNewData(data->size()); 394 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes()); 395 REPORTER_ASSERT(r, result == SkCodec::kSuccess); 396 397 result = codec->incrementalDecode(); 398 REPORTER_ASSERT(r, result == SkCodec::kSuccess); 399 compare_bitmaps(r, truth, bm); 400 } 401 } 402 403 DEF_TEST(Codec_emptyIDAT, r) { 404 const char* name = "images/baby_tux.png"; 405 sk_sp<SkData> file = GetResourceAsData(name); 406 if (!file) { 407 return; 408 } 409 410 // Truncate to the beginning of the IDAT, immediately after the IDAT tag. 411 file = SkData::MakeSubset(file.get(), 0, 80); 412 413 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file))); 414 if (!codec) { 415 ERRORF(r, "Failed to create a codec for %s", name); 416 return; 417 } 418 419 SkBitmap bm; 420 const auto info = standardize_info(codec.get()); 421 bm.allocPixels(info); 422 423 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); 424 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result); 425 } 426 427 DEF_TEST(Codec_incomplete, r) { 428 for (const char* name : { "images/baby_tux.png", 429 "images/baby_tux.webp", 430 "images/CMYK.jpg", 431 "images/color_wheel.gif", 432 "images/google_chrome.ico", 433 "images/rle.bmp", 434 "images/mandrill.wbmp", 435 }) { 436 sk_sp<SkData> file = GetResourceAsData(name); 437 if (!name) { 438 continue; 439 } 440 441 for (size_t len = 14; len <= file->size(); len += 5) { 442 SkCodec::Result result; 443 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream( 444 skstd::make_unique<SkMemoryStream>(file->data(), len), &result)); 445 if (codec) { 446 if (result != SkCodec::kSuccess) { 447 ERRORF(r, "Created an SkCodec for %s with %lu bytes, but " 448 "reported an error %i", name, len, result); 449 } 450 break; 451 } 452 453 if (SkCodec::kIncompleteInput != result) { 454 ERRORF(r, "Reported error %i for %s with %lu bytes", 455 result, name, len); 456 break; 457 } 458 } 459 } 460 } 461