bool SkCachingPixelRef::onNewLockPixels(LockRec* rec) { if (fErrorInDecoding) { return false; // don't try again. } const SkImageInfo& info = this->info(); SkBitmap bitmap; SkASSERT(NULL == fScaledCacheId); fScaledCacheId = SkScaledImageCache::FindAndLock(this->getGenerationID(), info.fWidth, info.fHeight, &bitmap); if (NULL == fScaledCacheId) { // Cache has been purged, must re-decode. if ((!bitmap.setInfo(info, fRowBytes)) || !bitmap.allocPixels()) { fErrorInDecoding = true; return false; } SkAutoLockPixels autoLockPixels(bitmap); if (!fImageGenerator->getPixels(info, bitmap.getPixels(), fRowBytes)) { fErrorInDecoding = true; return false; } fScaledCacheId = SkScaledImageCache::AddAndLock(this->getGenerationID(), info.fWidth, info.fHeight, bitmap); SkASSERT(fScaledCacheId != NULL); } // Now bitmap should contain a concrete PixelRef of the decoded // image. SkAutoLockPixels autoLockPixels(bitmap); void* pixels = bitmap.getPixels(); SkASSERT(pixels != NULL); // At this point, the autoLockPixels will unlockPixels() // to remove bitmap's lock on the pixels. We will then // destroy bitmap. The *only* guarantee that this pointer // remains valid is the guarantee made by // SkScaledImageCache that it will not destroy the *other* // bitmap (SkScaledImageCache::Rec.fBitmap) that holds a // reference to the concrete PixelRef while this record is // locked. rec->fPixels = pixels; rec->fColorTable = NULL; rec->fRowBytes = bitmap.rowBytes(); return true; }
/** This function tests three differently encoded images against the original bitmap */ static void test_three_encodings(skiatest::Reporter* reporter, InstallEncoded install) { SkBitmap original; make_test_image(&original); REPORTER_ASSERT(reporter, !original.empty()); REPORTER_ASSERT(reporter, !original.isNull()); if (original.empty() || original.isNull()) { return; } static const SkImageEncoder::Type types[] = { SkImageEncoder::kPNG_Type, SkImageEncoder::kJPEG_Type, SkImageEncoder::kWEBP_Type }; for (size_t i = 0; i < SK_ARRAY_COUNT(types); i++) { SkImageEncoder::Type type = types[i]; SkAutoDataUnref encoded(create_data_from_bitmap(original, type)); REPORTER_ASSERT(reporter, encoded.get() != NULL); if (NULL == encoded.get()) { continue; } SkBitmap lazy; bool installSuccess = install(encoded.get(), &lazy); REPORTER_ASSERT(reporter, installSuccess); if (!installSuccess) { continue; } REPORTER_ASSERT(reporter, NULL == lazy.getPixels()); { SkAutoLockPixels autoLockPixels(lazy); // now pixels are good. REPORTER_ASSERT(reporter, lazy.getPixels()); if (NULL == lazy.getPixels()) { continue; } } // pixels should be gone! REPORTER_ASSERT(reporter, NULL == lazy.getPixels()); { SkAutoLockPixels autoLockPixels(lazy); // now pixels are good. REPORTER_ASSERT(reporter, lazy.getPixels()); if (NULL == lazy.getPixels()) { continue; } } bool comparePixels = (SkImageEncoder::kPNG_Type == type); compare_bitmaps(reporter, original, lazy, comparePixels); } }
// Make either A8 or gray8 bitmap. static SkBitmap make_bitmap(SkColorType ct) { SkBitmap bm; switch (ct) { case kAlpha_8_SkColorType: bm.allocPixels(SkImageInfo::MakeA8(SCALE, SCALE)); break; case kGray_8_SkColorType: bm.allocPixels( SkImageInfo::Make(SCALE, SCALE, ct, kOpaque_SkAlphaType)); break; default: SkASSERT(false); return bm; } SkAutoLockPixels autoLockPixels(bm); uint8_t spectrum[256]; for (int y = 0; y < 256; ++y) { spectrum[y] = y; } for (int y = 0; y < 128; ++y) { // Shift over one byte each scanline. memcpy(bm.getAddr8(0, y), &spectrum[y], 128); } bm.setImmutable(); return bm; }
static void check(skiatest::Reporter* r, const char path[], SkISize size, bool supportsScanlineDecoding) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach())); if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; } // This test is used primarily to verify rewinding works properly. Using kN32 allows // us to test this without the added overhead of creating different bitmaps depending // on the color type (ex: building a color table for kIndex8). DM is where we test // decodes to all possible destination color types. SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); REPORTER_ASSERT(r, info.dimensions() == size); SkBitmap bm; bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkImageGenerator::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL); REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess); SkMD5::Digest digest1, digest2; md5(bm, &digest1); bm.eraseColor(SK_ColorYELLOW); result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL); REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess); // verify that re-decoding gives the same result. md5(bm, &digest2); REPORTER_ASSERT(r, digest1 == digest2); SkScanlineDecoder* scanlineDecoder = codec->getScanlineDecoder(info); if (supportsScanlineDecoding) { bm.eraseColor(SK_ColorYELLOW); REPORTER_ASSERT(r, scanlineDecoder); for (int y = 0; y < info.height(); y++) { result = scanlineDecoder->getScanlines(bm.getAddr(0, y), 1, 0); REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess); } // verify that scanline decoding gives the same result. SkMD5::Digest digest3; md5(bm, &digest3); REPORTER_ASSERT(r, digest3 == digest1); } else { REPORTER_ASSERT(r, !scanlineDecoder); } }
static void md5(const SkBitmap& bm, SkMD5::Digest* digest) { SkAutoLockPixels autoLockPixels(bm); SkASSERT(bm.getPixels()); SkMD5 md5; size_t rowLen = bm.info().bytesPerPixel() * bm.width(); for (int y = 0; y < bm.height(); ++y) { md5.update(static_cast<uint8_t*>(bm.getAddr(0, y)), rowLen); } md5.finish(*digest); }
static void bitmap_alpha_to_a8(const SkBitmap& bitmap, SkWStream* out) { if (!bitmap.getPixels()) { fill_stream(out, '\xFF', pixel_count(bitmap)); return; } SkBitmap copy; const SkBitmap& bm = not4444(bitmap, ©); SkAutoLockPixels autoLockPixels(bm); SkColorType colorType = bm.colorType(); switch (colorType) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: { SkAutoTMalloc<uint8_t> scanline(bm.width()); for (int y = 0; y < bm.height(); ++y) { uint8_t* dst = scanline.get(); const SkPMColor* src = bm.getAddr32(0, y); for (int x = 0; x < bm.width(); ++x) { *dst++ = SkGetA32Component(*src++, colorType); } out->write(scanline.get(), bm.width()); } return; } case kAlpha_8_SkColorType: for (int y = 0; y < bm.height(); ++y) { out->write(bm.getAddr8(0, y), bm.width()); } return; case kIndex_8_SkColorType: { SkColorTable* ct = bm.getColorTable(); SkASSERT(ct); SkAutoTMalloc<uint8_t> scanline(bm.width()); for (int y = 0; y < bm.height(); ++y) { uint8_t* dst = scanline.get(); const uint8_t* src = bm.getAddr8(0, y); for (int x = 0; x < bm.width(); ++x) { *dst++ = SkGetPackedA32((*ct)[*src++]); } out->write(scanline.get(), bm.width()); } return; } case kRGB_565_SkColorType: case kGray_8_SkColorType: SkDEBUGFAIL("color type has no alpha"); return; case kARGB_4444_SkColorType: SkDEBUGFAIL("4444 color type should have been converted to N32"); return; case kUnknown_SkColorType: default: SkDEBUGFAIL("unexpected color type"); } }
static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const SkImageInfo& info, const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) { REPORTER_ASSERT(r, info.dimensions() == size); bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); REPORTER_ASSERT(r, result == expectedResult); md5(bm, digest); if (goodDigest) { REPORTER_ASSERT(r, *digest == *goodDigest); } { // Test decoding to 565 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); SkCodec::Result expected565 = info.alphaType() == kOpaque_SkAlphaType ? expectedResult : SkCodec::kInvalidConversion; test_info(r, codec, info565, expected565, nullptr); } // Verify that re-decoding gives the same result. It is interesting to check this after // a decode to 565, since choosing to decode to 565 may result in some of the decode // options being modified. These options should return to their defaults on another // decode to kN32, so the new digest should match the old digest. test_info(r, codec, info, expectedResult, digest); { // Check alpha type conversions if (info.alphaType() == kOpaque_SkAlphaType) { test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), expectedResult, digest); test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), expectedResult, digest); } else { // Decoding to opaque should fail test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), SkCodec::kInvalidConversion, nullptr); SkAlphaType otherAt = info.alphaType(); if (kPremul_SkAlphaType == otherAt) { otherAt = kUnpremul_SkAlphaType; } else { otherAt = kPremul_SkAlphaType; } // The other non-opaque alpha type should always succeed, but not match. test_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr); } } }
static void test_info(skiatest::Reporter* r, Codec* codec, const SkImageInfo& info, SkCodec::Result expectedResult, const SkMD5::Digest* goodDigest) { SkBitmap bm; bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); REPORTER_ASSERT(r, result == expectedResult); if (goodDigest) { compare_to_good_digest(r, *goodDigest, bm); } }
// https://bug.skia.org/4390 DEF_TEST(ImageFromIndex8Bitmap, r) { SkPMColor pmColors[1] = {SkPreMultiplyColor(SK_ColorWHITE)}; SkBitmap bm; SkAutoTUnref<SkColorTable> ctable( new SkColorTable(pmColors, SK_ARRAY_COUNT(pmColors))); SkImageInfo info = SkImageInfo::Make(1, 1, kIndex_8_SkColorType, kPremul_SkAlphaType); bm.allocPixels(info, nullptr, ctable); SkAutoLockPixels autoLockPixels(bm); *bm.getAddr8(0, 0) = 0; SkAutoTUnref<SkImage> img(SkImage::NewFromBitmap(bm)); REPORTER_ASSERT(r, img.get() != nullptr); }
static void emit_image_xobject(SkWStream* stream, const SkImage* image, bool alpha, const sk_sp<SkPDFObject>& smask, const SkPDFObjNumMap& objNumMap, const SkPDFSubstituteMap& substitutes) { SkBitmap bitmap; image_get_ro_pixels(image, &bitmap); // TODO(halcanary): test SkAutoLockPixels autoLockPixels(bitmap); // with malformed images. // Write to a temporary buffer to get the compressed length. SkDynamicMemoryWStream buffer; SkDeflateWStream deflateWStream(&buffer); if (alpha) { bitmap_alpha_to_a8(bitmap, &deflateWStream); } else { bitmap_to_pdf_pixels(bitmap, &deflateWStream); } deflateWStream.finalize(); // call before detachAsStream(). std::unique_ptr<SkStreamAsset> asset(buffer.detachAsStream()); SkPDFDict pdfDict("XObject"); pdfDict.insertName("Subtype", "Image"); pdfDict.insertInt("Width", bitmap.width()); pdfDict.insertInt("Height", bitmap.height()); if (alpha) { pdfDict.insertName("ColorSpace", "DeviceGray"); } else if (bitmap.colorType() == kIndex_8_SkColorType) { SkASSERT(1 == pdf_color_component_count(bitmap.colorType())); pdfDict.insertObject("ColorSpace", make_indexed_color_space(bitmap.getColorTable(), bitmap.alphaType())); } else if (1 == pdf_color_component_count(bitmap.colorType())) { pdfDict.insertName("ColorSpace", "DeviceGray"); } else { pdfDict.insertName("ColorSpace", "DeviceRGB"); } if (smask) { pdfDict.insertObjRef("SMask", smask); } pdfDict.insertInt("BitsPerComponent", 8); pdfDict.insertName("Filter", "FlateDecode"); pdfDict.insertInt("Length", asset->getLength()); pdfDict.emitObject(stream, objNumMap, substitutes); pdf_stream_begin(stream); stream->writeStream(asset.get(), asset->getLength()); pdf_stream_end(stream); }
static void check_pixelref(TestImageGenerator::TestType type, skiatest::Reporter* reporter, SkDiscardableMemory::Factory* factory) { SkAutoTDelete<SkImageGenerator> gen(new TestImageGenerator(type, reporter)); REPORTER_ASSERT(reporter, gen.get() != nullptr); SkBitmap lazy; bool success = SkDEPRECATED_InstallDiscardablePixelRef(gen.detach(), nullptr, &lazy, factory); REPORTER_ASSERT(reporter, success); if (TestImageGenerator::kSucceedGetPixels_TestType == type) { check_test_image_generator_bitmap(reporter, lazy); } else if (TestImageGenerator::kFailGetPixels_TestType == type) { SkAutoLockPixels autoLockPixels(lazy); REPORTER_ASSERT(reporter, nullptr == lazy.getPixels()); } }
void image_get_ro_pixels(const SkImage* image, SkBitmap* dst) { if(as_IB(image)->getROPixels(dst) && dst->dimensions() == image->dimensions()) { if (dst->colorType() != kIndex_8_SkColorType) { return; } // We must check to see if the bitmap has a color table. SkAutoLockPixels autoLockPixels(*dst); if (!dst->getColorTable()) { // We can't use an indexed bitmap with no colortable. dst->reset(); } else { return; } } // no pixels or wrong size: fill with zeros. SkAlphaType at = image->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType; dst->setInfo(SkImageInfo::MakeN32(image->width(), image->height(), at)); }
static void check_test_image_generator_bitmap(skiatest::Reporter* reporter, const SkBitmap& bm) { REPORTER_ASSERT(reporter, TestImageGenerator::Width() == bm.width()); REPORTER_ASSERT(reporter, TestImageGenerator::Height() == bm.height()); SkAutoLockPixels autoLockPixels(bm); REPORTER_ASSERT(reporter, bm.getPixels()); if (NULL == bm.getPixels()) { return; } int errors = 0; for (int y = 0; y < bm.height(); ++y) { for (int x = 0; x < bm.width(); ++x) { if (TestImageGenerator::Color() != *bm.getAddr32(x, y)) { ++errors; } } } REPORTER_ASSERT(reporter, 0 == errors); }
void PDFDefaultBitmap::emitObject(SkWStream* stream, const SkPDFObjNumMap& objNumMap, const SkPDFSubstituteMap& substitutes) const { SkAutoLockPixels autoLockPixels(fBitmap); SkASSERT(fBitmap.colorType() != kIndex_8_SkColorType || fBitmap.getColorTable()); // Write to a temporary buffer to get the compressed length. SkDynamicMemoryWStream buffer; SkDeflateWStream deflateWStream(&buffer); bitmap_to_pdf_pixels(fBitmap, &deflateWStream); deflateWStream.finalize(); // call before detachAsStream(). SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream()); SkPDFDict pdfDict("XObject"); pdfDict.insertName("Subtype", "Image"); pdfDict.insertInt("Width", fBitmap.width()); pdfDict.insertInt("Height", fBitmap.height()); if (fBitmap.colorType() == kIndex_8_SkColorType) { SkASSERT(1 == pdf_color_component_count(fBitmap.colorType())); pdfDict.insertObject("ColorSpace", make_indexed_color_space(fBitmap.getColorTable())); } else if (1 == pdf_color_component_count(fBitmap.colorType())) { pdfDict.insertName("ColorSpace", "DeviceGray"); } else { pdfDict.insertName("ColorSpace", "DeviceRGB"); } pdfDict.insertInt("BitsPerComponent", 8); if (fSMask) { pdfDict.insertObjRef("SMask", SkRef(fSMask.get())); } pdfDict.insertName("Filter", "FlateDecode"); pdfDict.insertInt("Length", asset->getLength()); pdfDict.emitObject(stream, objNumMap, substitutes); pdf_stream_begin(stream); stream->writeStream(asset.get(), asset->getLength()); pdf_stream_end(stream); }
static void check_pixelref(TestImageGenerator::TestType type, skiatest::Reporter* reporter, PixelRefType pixelRefType, SkDiscardableMemory::Factory* factory) { SkASSERT((pixelRefType >= 0) && (pixelRefType <= kLast_PixelRefType)); SkAutoTDelete<SkImageGenerator> gen(SkNEW_ARGS(TestImageGenerator, (type, reporter))); REPORTER_ASSERT(reporter, gen.get() != NULL); SkBitmap lazy; bool success; if (kSkCaching_PixelRefType == pixelRefType) { // Ignore factory; use global cache. success = SkCachingPixelRef::Install(gen.detach(), &lazy); } else { success = SkInstallDiscardablePixelRef(gen.detach(), NULL, &lazy, factory); } REPORTER_ASSERT(reporter, success); if (TestImageGenerator::kSucceedGetPixels_TestType == type) { check_test_image_generator_bitmap(reporter, lazy); } else if (TestImageGenerator::kFailGetPixels_TestType == type) { SkAutoLockPixels autoLockPixels(lazy); REPORTER_ASSERT(reporter, NULL == lazy.getPixels()); } }
// Test interlaced PNG in stripes, similar to DM's kStripe_Mode DEF_TEST(Codec_stripes, r) { const char * path = "plane_interlaced.png"; SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); } SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach())); REPORTER_ASSERT(r, codec); if (!codec) { return; } switch (codec->getScanlineOrder()) { case SkCodec::kBottomUp_SkScanlineOrder: case SkCodec::kOutOfOrder_SkScanlineOrder: ERRORF(r, "This scanline order will not match the original."); return; default: break; } // Baseline for what the image should look like, using N32. const SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); SkBitmap bm; bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); REPORTER_ASSERT(r, result == SkCodec::kSuccess); SkMD5::Digest digest; md5(bm, &digest); // Now decode in stripes const int height = info.height(); const int numStripes = 4; int stripeHeight; int remainingLines; SkTDivMod(height, numStripes, &stripeHeight, &remainingLines); bm.eraseColor(SK_ColorYELLOW); result = codec->startScanlineDecode(info); REPORTER_ASSERT(r, result == SkCodec::kSuccess); // Odd stripes for (int i = 1; i < numStripes; i += 2) { // Skip the even stripes bool skipResult = codec->skipScanlines(stripeHeight); REPORTER_ASSERT(r, skipResult); int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight, bm.rowBytes()); REPORTER_ASSERT(r, linesDecoded == stripeHeight); } // Even stripes result = codec->startScanlineDecode(info); REPORTER_ASSERT(r, result == SkCodec::kSuccess); for (int i = 0; i < numStripes; i += 2) { int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight, bm.rowBytes()); REPORTER_ASSERT(r, linesDecoded == stripeHeight); // Skip the odd stripes if (i + 1 < numStripes) { bool skipResult = codec->skipScanlines(stripeHeight); REPORTER_ASSERT(r, skipResult); } } // Remainder at the end if (remainingLines > 0) { result = codec->startScanlineDecode(info); REPORTER_ASSERT(r, result == SkCodec::kSuccess); bool skipResult = codec->skipScanlines(height - remainingLines); REPORTER_ASSERT(r, skipResult); int linesDecoded = codec->getScanlines(bm.getAddr(0, height - remainingLines), remainingLines, bm.rowBytes()); REPORTER_ASSERT(r, linesDecoded == remainingLines); } compare_to_good_digest(r, digest, bm); }
static void check(skiatest::Reporter* r, const char path[], SkISize size, bool supportsScanlineDecoding, bool supportsSubsetDecoding, bool supportsIncomplete = true) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkCodec> codec(nullptr); bool isIncomplete = supportsIncomplete; if (isIncomplete) { size_t size = stream->getLength(); SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size / 3))); codec.reset(SkCodec::NewFromData(data)); } else { codec.reset(SkCodec::NewFromStream(stream.detach())); } if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; } // Test full image decodes with SkCodec SkMD5::Digest codecDigest; SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); SkBitmap bm; SkCodec::Result expectedResult = isIncomplete ? SkCodec::kIncompleteInput : SkCodec::kSuccess; test_codec(r, codec.get(), bm, info, size, expectedResult, &codecDigest, nullptr); // Scanline decoding follows. // Need to call startScanlineDecode() first. REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0); REPORTER_ASSERT(r, codec->skipScanlines(1) == 0); const SkCodec::Result startResult = codec->startScanlineDecode(info); if (supportsScanlineDecoding) { bm.eraseColor(SK_ColorYELLOW); REPORTER_ASSERT(r, startResult == SkCodec::kSuccess); for (int y = 0; y < info.height(); y++) { const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0); if (!isIncomplete) { REPORTER_ASSERT(r, 1 == lines); } } // verify that scanline decoding gives the same result. if (SkCodec::kTopDown_SkScanlineOrder == codec->getScanlineOrder()) { compare_to_good_digest(r, codecDigest, bm); } // Cannot continue to decode scanlines beyond the end REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0); // Interrupting a scanline decode with a full decode starts from // scratch REPORTER_ASSERT(r, codec->startScanlineDecode(info) == SkCodec::kSuccess); const int lines = codec->getScanlines(bm.getAddr(0, 0), 1, 0); if (!isIncomplete) { REPORTER_ASSERT(r, lines == 1); } REPORTER_ASSERT(r, codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes()) == expectedResult); REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0); REPORTER_ASSERT(r, codec->skipScanlines(1) == 0); // Test partial scanline decodes if (supports_scaled_codec(path) && info.width() >= 3) { SkCodec::Options options; int width = info.width(); int height = info.height(); SkIRect subset = SkIRect::MakeXYWH(2 * (width / 3), 0, width / 3, height); options.fSubset = ⊂ const SkCodec::Result partialStartResult = codec->startScanlineDecode(info, &options, nullptr, nullptr); REPORTER_ASSERT(r, partialStartResult == SkCodec::kSuccess); for (int y = 0; y < height; y++) { const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0); if (!isIncomplete) { REPORTER_ASSERT(r, 1 == lines); } } } } else { REPORTER_ASSERT(r, startResult == SkCodec::kUnimplemented); } // The rest of this function tests decoding subsets, and will decode an arbitrary number of // random subsets. // Do not attempt to decode subsets of an image of only once pixel, since there is no // meaningful subset. if (size.width() * size.height() == 1) { return; } SkRandom rand; SkIRect subset; SkCodec::Options opts; opts.fSubset = ⊂ for (int i = 0; i < 5; i++) { subset = generate_random_subset(&rand, size.width(), size.height()); SkASSERT(!subset.isEmpty()); const bool supported = codec->getValidSubset(&subset); REPORTER_ASSERT(r, supported == supportsSubsetDecoding); SkImageInfo subsetInfo = info.makeWH(subset.width(), subset.height()); SkBitmap bm; bm.allocPixels(subsetInfo); const SkCodec::Result result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes(), &opts, nullptr, nullptr); if (supportsSubsetDecoding) { REPORTER_ASSERT(r, result == expectedResult); // Webp is the only codec that supports subsets, and it will have modified the subset // to have even left/top. REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); } else { // No subsets will work. REPORTER_ASSERT(r, result == SkCodec::kUnimplemented); } } // SkScaledCodec tests if ((supportsScanlineDecoding || supportsSubsetDecoding) && supports_scaled_codec(path)) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkAndroidCodec> androidCodec(nullptr); if (isIncomplete) { size_t size = stream->getLength(); SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size / 3))); androidCodec.reset(SkAndroidCodec::NewFromData(data)); } else { androidCodec.reset(SkAndroidCodec::NewFromStream(stream.detach())); } if (!androidCodec) { ERRORF(r, "Unable to decode '%s'", path); return; } SkBitmap bm; SkMD5::Digest scaledCodecDigest; test_codec(r, androidCodec.get(), bm, info, size, expectedResult, &scaledCodecDigest, &codecDigest); } // Test SkCodecImageGenerator if (!isIncomplete) { SkAutoTDelete<SkStream> stream(resource(path)); SkAutoTUnref<SkData> fullData(SkData::NewFromStream(stream, stream->getLength())); SkAutoTDelete<SkImageGenerator> gen(SkCodecImageGenerator::NewFromEncodedCodec(fullData)); SkBitmap bm; bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); REPORTER_ASSERT(r, gen->getPixels(info, bm.getPixels(), bm.rowBytes())); compare_to_good_digest(r, codecDigest, bm); } // If we've just tested incomplete decodes, let's run the same test again on full decodes. if (isIncomplete) { check(r, path, size, supportsScanlineDecoding, supportsSubsetDecoding, false); } }
static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const SkImageInfo& info, const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) { REPORTER_ASSERT(r, info.dimensions() == size); bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); REPORTER_ASSERT(r, result == expectedResult); md5(bm, digest); if (goodDigest) { REPORTER_ASSERT(r, *digest == *goodDigest); } { // Test decoding to 565 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); if (info.alphaType() == kOpaque_SkAlphaType) { // Decoding to 565 should succeed. SkBitmap bm565; bm565.allocPixels(info565); SkAutoLockPixels alp(bm565); // This will allow comparison even if the image is incomplete. bm565.eraseColor(SK_ColorBLACK); REPORTER_ASSERT(r, expectedResult == codec->getPixels(info565, bm565.getPixels(), bm565.rowBytes())); SkMD5::Digest digest565; md5(bm565, &digest565); // A dumb client's request for non-opaque should also succeed. for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) { info565 = info565.makeAlphaType(alpha); test_info(r, codec, info565, expectedResult, &digest565); } } else { test_info(r, codec, info565, SkCodec::kInvalidConversion, nullptr); } } if (codec->getInfo().colorType() == kGray_8_SkColorType) { SkImageInfo grayInfo = codec->getInfo(); SkBitmap grayBm; grayBm.allocPixels(grayInfo); SkAutoLockPixels alp(grayBm); grayBm.eraseColor(SK_ColorBLACK); REPORTER_ASSERT(r, expectedResult == codec->getPixels(grayInfo, grayBm.getPixels(), grayBm.rowBytes())); SkMD5::Digest grayDigest; md5(grayBm, &grayDigest); for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) { grayInfo = grayInfo.makeAlphaType(alpha); test_info(r, codec, grayInfo, expectedResult, &grayDigest); } } // Verify that re-decoding gives the same result. It is interesting to check this after // a decode to 565, since choosing to decode to 565 may result in some of the decode // options being modified. These options should return to their defaults on another // decode to kN32, so the new digest should match the old digest. test_info(r, codec, info, expectedResult, digest); { // Check alpha type conversions if (info.alphaType() == kOpaque_SkAlphaType) { test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), expectedResult, digest); test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), expectedResult, digest); } else { // Decoding to opaque should fail test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), SkCodec::kInvalidConversion, nullptr); SkAlphaType otherAt = info.alphaType(); if (kPremul_SkAlphaType == otherAt) { otherAt = kUnpremul_SkAlphaType; } else { otherAt = kPremul_SkAlphaType; } // The other non-opaque alpha type should always succeed, but not match. test_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr); } } }
static void bitmap_to_pdf_pixels(const SkBitmap& bitmap, SkWStream* out) { if (!bitmap.getPixels()) { size_t size = pixel_count(bitmap) * pdf_color_component_count(bitmap.colorType()); fill_stream(out, '\x00', size); return; } SkBitmap copy; const SkBitmap& bm = not4444(bitmap, ©); SkAutoLockPixels autoLockPixels(bm); SkColorType colorType = bm.colorType(); switch (colorType) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: { SkASSERT(3 == pdf_color_component_count(colorType)); SkAutoTMalloc<uint8_t> scanline(3 * bm.width()); for (int y = 0; y < bm.height(); ++y) { const uint32_t* src = bm.getAddr32(0, y); uint8_t* dst = scanline.get(); for (int x = 0; x < bm.width(); ++x) { uint32_t color = *src++; U8CPU alpha = SkGetA32Component(color, colorType); if (alpha != SK_AlphaTRANSPARENT) { pmcolor_to_rgb24(color, dst, colorType); } else { get_neighbor_avg_color(bm, x, y, dst, colorType); } dst += 3; } out->write(scanline.get(), 3 * bm.width()); } return; } case kRGB_565_SkColorType: { SkASSERT(3 == pdf_color_component_count(colorType)); SkAutoTMalloc<uint8_t> scanline(3 * bm.width()); for (int y = 0; y < bm.height(); ++y) { const uint16_t* src = bm.getAddr16(0, y); uint8_t* dst = scanline.get(); for (int x = 0; x < bm.width(); ++x) { U16CPU color565 = *src++; *dst++ = SkPacked16ToR32(color565); *dst++ = SkPacked16ToG32(color565); *dst++ = SkPacked16ToB32(color565); } out->write(scanline.get(), 3 * bm.width()); } return; } case kAlpha_8_SkColorType: SkASSERT(1 == pdf_color_component_count(colorType)); fill_stream(out, '\x00', pixel_count(bm)); return; case kGray_8_SkColorType: case kIndex_8_SkColorType: SkASSERT(1 == pdf_color_component_count(colorType)); // these two formats need no transformation to serialize. for (int y = 0; y < bm.height(); ++y) { out->write(bm.getAddr8(0, y), bm.width()); } return; case kUnknown_SkColorType: case kARGB_4444_SkColorType: default: SkDEBUGFAIL("unexpected color type"); } }
static void check(skiatest::Reporter* r, const char path[], SkISize size, bool supportsScanlineDecoding, bool supportsSubsetDecoding, bool supports565 = true) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach())); if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; } // This test is used primarily to verify rewinding works properly. Using kN32 allows // us to test this without the added overhead of creating different bitmaps depending // on the color type (ex: building a color table for kIndex8). DM is where we test // decodes to all possible destination color types. SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); REPORTER_ASSERT(r, info.dimensions() == size); { // Test decoding to 565 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); SkCodec::Result expected = (supports565 && info.alphaType() == kOpaque_SkAlphaType) ? SkCodec::kSuccess : SkCodec::kInvalidConversion; test_info(r, codec, info565, expected, NULL); } SkBitmap bm; bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL); REPORTER_ASSERT(r, result == SkCodec::kSuccess); SkMD5::Digest digest; md5(bm, &digest); // verify that re-decoding gives the same result. test_info(r, codec, info, SkCodec::kSuccess, &digest); { // Check alpha type conversions if (info.alphaType() == kOpaque_SkAlphaType) { test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), SkCodec::kInvalidConversion, NULL); test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), SkCodec::kInvalidConversion, NULL); } else { // Decoding to opaque should fail test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), SkCodec::kInvalidConversion, NULL); SkAlphaType otherAt = info.alphaType(); if (kPremul_SkAlphaType == otherAt) { otherAt = kUnpremul_SkAlphaType; } else { otherAt = kPremul_SkAlphaType; } // The other non-opaque alpha type should always succeed, but not match. test_info(r, codec, info.makeAlphaType(otherAt), SkCodec::kSuccess, NULL); } } // Scanline decoding follows. stream.reset(resource(path)); SkAutoTDelete<SkScanlineDecoder> scanlineDecoder( SkScanlineDecoder::NewFromStream(stream.detach())); if (supportsScanlineDecoding) { bm.eraseColor(SK_ColorYELLOW); REPORTER_ASSERT(r, scanlineDecoder); REPORTER_ASSERT(r, scanlineDecoder->start(info) == SkCodec::kSuccess); for (int y = 0; y < info.height(); y++) { result = scanlineDecoder->getScanlines(bm.getAddr(0, y), 1, 0); REPORTER_ASSERT(r, result == SkCodec::kSuccess); } // verify that scanline decoding gives the same result. compare_to_good_digest(r, digest, bm); } else { REPORTER_ASSERT(r, !scanlineDecoder); } // The rest of this function tests decoding subsets, and will decode an arbitrary number of // random subsets. // Do not attempt to decode subsets of an image of only once pixel, since there is no // meaningful subset. if (size.width() * size.height() == 1) { return; } SkRandom rand; SkIRect subset; SkCodec::Options opts; opts.fSubset = ⊂ for (int i = 0; i < 5; i++) { subset = generate_random_subset(&rand, size.width(), size.height()); SkASSERT(!subset.isEmpty()); const bool supported = codec->getValidSubset(&subset); REPORTER_ASSERT(r, supported == supportsSubsetDecoding); SkImageInfo subsetInfo = info.makeWH(subset.width(), subset.height()); SkBitmap bm; bm.allocPixels(subsetInfo); const SkCodec::Result result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes(), &opts, NULL, NULL); if (supportsSubsetDecoding) { REPORTER_ASSERT(r, result == SkCodec::kSuccess); // Webp is the only codec that supports subsets, and it will have modified the subset // to have even left/top. REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); } else { // No subsets will work. REPORTER_ASSERT(r, result == SkCodec::kUnimplemented); } } }