static bool equal(const SkBitmap& bm1, const SkBitmap& bm2) { if (bm1.width() != bm2.width() || bm1.height() != bm2.height() || bm1.config() != bm2.config()) { return false; } size_t pixelBytes = bm1.width() * bm1.bytesPerPixel(); for (int y = 0; y < bm1.height(); y++) { if (memcmp(bm1.getAddr(0, y), bm2.getAddr(0, y), pixelBytes)) { return false; } } return true; }
bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int srcStride, int x, int y, int width, int height, const SkBitmap& dstBitmap) { SkAutoLockPixels alp(dstBitmap); void* dst = dstBitmap.getPixels(); FromColorProc proc = ChooseFromColorProc(dstBitmap.config()); if (NULL == dst || NULL == proc) { return false; } const jint* array = env->GetIntArrayElements(srcColors, NULL); const SkColor* src = (const SkColor*)array + srcOffset; // reset to to actual choice from caller dst = dstBitmap.getAddr(x, y); // now copy/convert each scanline for (int y = 0; y < height; y++) { proc(dst, src, width, x, y); src += srcStride; dst = (char*)dst + dstBitmap.rowBytes(); } dstBitmap.notifyPixelsChanged(); env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), JNI_ABORT); return true; }
// Utility function to read the value of a given pixel in bm. All // values converted to uint32_t for simplification of comparisons. static uint32_t getPixel(int x, int y, const SkBitmap& bm) { uint32_t val = 0; uint16_t val16; uint8_t val8; SkAutoLockPixels lock(bm); const void* rawAddr = bm.getAddr(x,y); switch (bm.config()) { case SkBitmap::kARGB_8888_Config: memcpy(&val, rawAddr, sizeof(uint32_t)); break; case SkBitmap::kARGB_4444_Config: case SkBitmap::kRGB_565_Config: memcpy(&val16, rawAddr, sizeof(uint16_t)); val = val16; break; case SkBitmap::kA8_Config: case SkBitmap::kIndex8_Config: memcpy(&val8, rawAddr, sizeof(uint8_t)); val = val8; break; default: break; } return val; }
// Utility function to set value of any pixel in bm. // bm.getConfig() specifies what format 'val' must be // converted to, but at present uint32_t can handle all formats. static void setPixel(int x, int y, uint32_t val, SkBitmap& bm) { uint16_t val16; uint8_t val8; SkAutoLockPixels lock(bm); void* rawAddr = bm.getAddr(x,y); switch (bm.config()) { case SkBitmap::kARGB_8888_Config: memcpy(rawAddr, &val, sizeof(uint32_t)); break; case SkBitmap::kARGB_4444_Config: case SkBitmap::kRGB_565_Config: val16 = val & 0xFFFF; memcpy(rawAddr, &val16, sizeof(uint16_t)); break; case SkBitmap::kA8_Config: case SkBitmap::kIndex8_Config: val8 = val & 0xFF; memcpy(rawAddr, &val8, sizeof(uint8_t)); break; default: // Ignore. break; } }
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 bool check_for_all_zeros(const SkBitmap& bm) { size_t count = bm.width() * bm.bytesPerPixel(); for (int y = 0; y < bm.height(); y++) { const uint8_t* ptr = reinterpret_cast<const uint8_t*>(bm.getAddr(0, y)); for (size_t i = 0; i < count; i++) { if (ptr[i]) { return false; } } } return true; }
static bool bitmap_diff(SkCanvas* canvas, const SkBitmap& orig, SkBitmap* diff) { const SkBitmap& src = canvas->getDevice()->accessBitmap(false); SkAutoLockPixels alp0(src); SkAutoLockPixels alp1(orig); for (int y = 0; y < src.height(); y++) { const void* srcP = src.getAddr(0, y); const void* origP = orig.getAddr(0, y); size_t bytes = src.width() * src.bytesPerPixel(); if (memcmp(srcP, origP, bytes)) { SkDebugf("---------- difference on line %d\n", y); return true; } } return false; }
static bool check_color(const SkBitmap& bm, SkPMColor expect32, uint16_t expect16, uint8_t expect8, skiatest::Reporter* reporter) { uint32_t expect; Proc proc = find_proc(bm, expect32, expect16, expect8, &expect); for (int y = 0; y < bm.height(); y++) { uint32_t bad; int x = proc(bm.getAddr(0, y), bm.width(), expect, &bad); if (x >= 0) { SkString str; str.printf("BlitRow config=%s [%d %d] expected %x got %x", gConfigName[bm.config()], x, y, expect, bad); reporter->reportFailed(str); return false; } } return true; }
// Utility function to set value of any pixel in bm. // bm.getConfig() specifies what format 'val' must be // converted to, but at present uint32_t can handle all formats. static void setPixel(int x, int y, uint32_t val, SkBitmap& bm) { uint16_t val16; uint8_t val8; SkAutoLockPixels lock(bm); void* rawAddr = bm.getAddr(x,y); switch (bm.bytesPerPixel()) { case 4: memcpy(rawAddr, &val, sizeof(uint32_t)); break; case 2: val16 = val & 0xFFFF; memcpy(rawAddr, &val16, sizeof(uint16_t)); break; case 1: val8 = val & 0xFF; memcpy(rawAddr, &val8, sizeof(uint8_t)); break; default: // Ignore. break; } }
void SubsetSingleBench::onDraw(const int n, SkCanvas* canvas) { // When the color type is kIndex8, we will need to store the color table. If it is // used, it will be initialized by the codec. int colorCount; SkPMColor colors[256]; if (fUseCodec) { for (int count = 0; count < n; count++) { SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(fStream->duplicate())); const SkImageInfo info = codec->getInfo().makeColorType(fColorType); SkAutoTDeleteArray<uint8_t> row(SkNEW_ARRAY(uint8_t, info.minRowBytes())); SkScanlineDecoder* scanlineDecoder = codec->getScanlineDecoder( info, NULL, colors, &colorCount); SkBitmap bitmap; bitmap.allocPixels(info.makeWH(fSubsetWidth, fSubsetHeight)); scanlineDecoder->skipScanlines(fOffsetTop); uint32_t bpp = info.bytesPerPixel(); for (uint32_t y = 0; y < fSubsetHeight; y++) { scanlineDecoder->getScanlines(row.get(), 1, 0); memcpy(bitmap.getAddr(0, y), row.get() + fOffsetLeft * bpp, fSubsetWidth * bpp); } } } else { for (int count = 0; count < n; count++) { SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(fStream)); int width, height; decoder->buildTileIndex(fStream->duplicate(), &width, &height); SkBitmap bitmap; SkIRect rect = SkIRect::MakeXYWH(fOffsetLeft, fOffsetTop, fSubsetWidth, fSubsetHeight); decoder->decodeSubset(&bitmap, rect, fColorType); } } }
// Utility function to read the value of a given pixel in bm. All // values converted to uint32_t for simplification of comparisons. static uint32_t getPixel(int x, int y, const SkBitmap& bm) { uint32_t val = 0; uint16_t val16; uint8_t val8; SkAutoLockPixels lock(bm); const void* rawAddr = bm.getAddr(x,y); switch (bm.bytesPerPixel()) { case 4: memcpy(&val, rawAddr, sizeof(uint32_t)); break; case 2: memcpy(&val16, rawAddr, sizeof(uint16_t)); val = val16; break; case 1: memcpy(&val8, rawAddr, sizeof(uint8_t)); val = val8; break; default: break; } return val; }
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); } }
DEF_TEST(BitmapCopy, reporter) { static const bool isExtracted[] = { false, true }; for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) { SkBitmap srcOpaque, srcPremul; setup_src_bitmaps(&srcOpaque, &srcPremul, gPairs[i].fColorType); for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) { SkBitmap dst; bool success = srcPremul.copyTo(&dst, gPairs[j].fColorType); bool expected = gPairs[i].fValid[j] != '0'; if (success != expected) { ERRORF(reporter, "SkBitmap::copyTo from %s to %s. expected %s " "returned %s", gColorTypeName[i], gColorTypeName[j], boolStr(expected), boolStr(success)); } bool canSucceed = srcPremul.canCopyTo(gPairs[j].fColorType); if (success != canSucceed) { ERRORF(reporter, "SkBitmap::copyTo from %s to %s. returned %s " "canCopyTo %s", gColorTypeName[i], gColorTypeName[j], boolStr(success), boolStr(canSucceed)); } if (success) { REPORTER_ASSERT(reporter, srcPremul.width() == dst.width()); REPORTER_ASSERT(reporter, srcPremul.height() == dst.height()); REPORTER_ASSERT(reporter, dst.colorType() == gPairs[j].fColorType); test_isOpaque(reporter, srcOpaque, srcPremul, dst.colorType()); if (srcPremul.colorType() == dst.colorType()) { SkAutoLockPixels srcLock(srcPremul); SkAutoLockPixels dstLock(dst); REPORTER_ASSERT(reporter, srcPremul.readyToDraw()); REPORTER_ASSERT(reporter, dst.readyToDraw()); const char* srcP = (const char*)srcPremul.getAddr(0, 0); const char* dstP = (const char*)dst.getAddr(0, 0); REPORTER_ASSERT(reporter, srcP != dstP); REPORTER_ASSERT(reporter, !memcmp(srcP, dstP, srcPremul.getSize())); REPORTER_ASSERT(reporter, srcPremul.getGenerationID() == dst.getGenerationID()); } else { REPORTER_ASSERT(reporter, srcPremul.getGenerationID() != dst.getGenerationID()); } } else { // dst should be unchanged from its initial state REPORTER_ASSERT(reporter, dst.colorType() == kUnknown_SkColorType); REPORTER_ASSERT(reporter, dst.width() == 0); REPORTER_ASSERT(reporter, dst.height() == 0); } } // for (size_t j = ... // Tests for getSafeSize(), getSafeSize64(), copyPixelsTo(), // copyPixelsFrom(). // for (size_t copyCase = 0; copyCase < SK_ARRAY_COUNT(isExtracted); ++copyCase) { // Test copying to/from external buffer. // Note: the tests below have hard-coded values --- // Please take care if modifying. // Tests for getSafeSize64(). // Test with a very large configuration without pixel buffer // attached. SkBitmap tstSafeSize; tstSafeSize.setInfo(SkImageInfo::Make(100000000U, 100000000U, gPairs[i].fColorType, kPremul_SkAlphaType)); int64_t safeSize = tstSafeSize.computeSafeSize64(); if (safeSize < 0) { ERRORF(reporter, "getSafeSize64() negative: %s", gColorTypeName[tstSafeSize.colorType()]); } bool sizeFail = false; // Compare against hand-computed values. switch (gPairs[i].fColorType) { case kUnknown_SkColorType: break; case kAlpha_8_SkColorType: case kIndex_8_SkColorType: if (safeSize != 0x2386F26FC10000LL) { sizeFail = true; } break; case kRGB_565_SkColorType: case kARGB_4444_SkColorType: if (safeSize != 0x470DE4DF820000LL) { sizeFail = true; } break; case kN32_SkColorType: if (safeSize != 0x8E1BC9BF040000LL) { sizeFail = true; } break; default: break; } if (sizeFail) { ERRORF(reporter, "computeSafeSize64() wrong size: %s", gColorTypeName[tstSafeSize.colorType()]); } int subW = 2; int subH = 2; // Create bitmap to act as source for copies and subsets. SkBitmap src, subset; SkColorTable* ct = nullptr; if (kIndex_8_SkColorType == src.colorType()) { ct = init_ctable(); } int localSubW; if (isExtracted[copyCase]) { // A larger image to extract from. localSubW = 2 * subW + 1; } else { // Tests expect a 2x2 bitmap, so make smaller. localSubW = subW; } // could fail if we pass kIndex_8 for the colortype if (src.tryAllocPixels(SkImageInfo::Make(localSubW, subH, gPairs[i].fColorType, kPremul_SkAlphaType))) { // failure is fine, as we will notice later on } SkSafeUnref(ct); // Either copy src or extract into 'subset', which is used // for subsequent calls to copyPixelsTo/From. bool srcReady = false; // Test relies on older behavior that extractSubset will fail on // kUnknown_SkColorType if (kUnknown_SkColorType != src.colorType() && isExtracted[copyCase]) { // The extractedSubset() test case allows us to test copy- // ing when src and dst mave possibly different strides. SkIRect r; r.set(1, 0, 1 + subW, subH); // 2x2 extracted bitmap srcReady = src.extractSubset(&subset, r); } else { srcReady = src.copyTo(&subset); } // Not all configurations will generate a valid 'subset'. if (srcReady) { // Allocate our target buffer 'buf' for all copies. // To simplify verifying correctness of copies attach // buf to a SkBitmap, but copies are done using the // raw buffer pointer. const size_t bufSize = subH * SkColorTypeMinRowBytes(src.colorType(), subW) * 2; SkAutoTMalloc<uint8_t> autoBuf (bufSize); uint8_t* buf = autoBuf.get(); SkBitmap bufBm; // Attach buf to this bitmap. bool successExpected; // Set up values for each pixel being copied. Coordinates coords(subW * subH); for (int x = 0; x < subW; ++x) for (int y = 0; y < subH; ++y) { int index = y * subW + x; SkASSERT(index < coords.length); coords[index]->fX = x; coords[index]->fY = y; } writeCoordPixels(subset, coords); // Test #1 //////////////////////////////////////////// const SkImageInfo info = SkImageInfo::Make(subW, subH, gPairs[i].fColorType, kPremul_SkAlphaType); // Before/after comparisons easier if we attach buf // to an appropriately configured SkBitmap. memset(buf, 0xFF, bufSize); // Config with stride greater than src but that fits in buf. bufBm.installPixels(info, buf, info.minRowBytes() * 2); successExpected = false; // Then attempt to copy with a stride that is too large // to fit in the buffer. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes() * 3) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, 1.5*maxRowBytes)", reporter); // Test #2 //////////////////////////////////////////// // This test should always succeed, but in the case // of extracted bitmaps only because we handle the // issue of getSafeSize(). Without getSafeSize() // buffer overrun/read would occur. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, subset.rowBytes()); successExpected = subset.getSafeSize() <= bufSize; REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize)", reporter); // Test #3 //////////////////////////////////////////// // Copy with different stride between src and dst. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, subset.rowBytes()+1); successExpected = true; // Should always work. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, subset.rowBytes()+1) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, rowBytes+1)", reporter); // Test #4 //////////////////////////////////////////// // Test copy with stride too small. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, info.minRowBytes()); successExpected = false; // Request copy with stride too small. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes()-1) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, rowBytes()-1)", reporter); #if 0 // copyPixelsFrom is gone // Test #5 //////////////////////////////////////////// // Tests the case where the source stride is too small // for the source configuration. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, info.minRowBytes()); writeCoordPixels(bufBm, coords); REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, bufSize, 1) == false); // Test #6 /////////////////////////////////////////// // Tests basic copy from an external buffer to the bitmap. // If the bitmap is "extracted", this also tests the case // where the source stride is different from the dest. // stride. // We've made the buffer large enough to always succeed. bufBm.installPixels(info, buf, info.minRowBytes()); writeCoordPixels(bufBm, coords); REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, bufSize, bufBm.rowBytes()) == true); reportCopyVerification(bufBm, subset, coords, "copyPixelsFrom(buf, bufSize)", reporter); // Test #7 //////////////////////////////////////////// // Tests the case where the source buffer is too small // for the transfer. REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, 1, subset.rowBytes()) == false); #endif } } // for (size_t copyCase ... } }
void SubsetZoomBench::onDraw(const int n, SkCanvas* canvas) { // When the color type is kIndex8, we will need to store the color table. If it is // used, it will be initialized by the codec. int colorCount; SkPMColor colors[256]; if (fUseCodec) { for (int count = 0; count < n; count++) { SkAutoTDelete<SkScanlineDecoder> scanlineDecoder( SkScanlineDecoder::NewFromStream(fStream->duplicate())); const SkImageInfo info = scanlineDecoder->getInfo().makeColorType(fColorType); SkAutoTDeleteArray<uint8_t> row(new uint8_t[info.minRowBytes()]); scanlineDecoder->start(info, nullptr, colors, &colorCount); const int centerX = info.width() / 2; const int centerY = info.height() / 2; int w = fSubsetWidth; int h = fSubsetHeight; do { const int subsetStartX = SkTMax(0, centerX - w / 2); const int subsetStartY = SkTMax(0, centerY - h / 2); const int subsetWidth = SkTMin(w, info.width() - subsetStartX); const int subsetHeight = SkTMin(h, info.height() - subsetStartY); // Note that if we subsetted and scaled in a single step, we could use the // same bitmap - as is often done in actual use cases. SkBitmap bitmap; SkImageInfo subsetInfo = info.makeWH(subsetWidth, subsetHeight); alloc_pixels(&bitmap, subsetInfo, colors, colorCount); uint32_t bpp = info.bytesPerPixel(); scanlineDecoder->skipScanlines(subsetStartY); for (int y = 0; y < subsetHeight; y++) { scanlineDecoder->getScanlines(row.get(), 1, 0); memcpy(bitmap.getAddr(0, y), row.get() + subsetStartX * bpp, subsetWidth * bpp); } w <<= 1; h <<= 1; } while (w < 2 * info.width() || h < 2 * info.height()); } } else { for (int count = 0; count < n; count++) { int width, height; SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(fStream)); decoder->buildTileIndex(fStream->duplicate(), &width, &height); const int centerX = width / 2; const int centerY = height / 2; int w = fSubsetWidth; int h = fSubsetHeight; do { const int subsetStartX = SkTMax(0, centerX - w / 2); const int subsetStartY = SkTMax(0, centerY - h / 2); const int subsetWidth = SkTMin(w, width - subsetStartX); const int subsetHeight = SkTMin(h, height - subsetStartY); SkBitmap bitmap; SkIRect rect = SkIRect::MakeXYWH(subsetStartX, subsetStartY, subsetWidth, subsetHeight); decoder->decodeSubset(&bitmap, rect, fColorType); w <<= 1; h <<= 1; } while (w < 2 * width || h < 2 * height); } } }
const SkColor color = 0xbf400000; auto grade = [&](int x, int y){ SkBitmap bm; bm.allocPixels(SkImageInfo::Make(1,1, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB())); if (!canvas->readPixels(bm, x,y)) { // Picture-backed canvases, that sort of thing. Just assume they're good. MarkGMGood(canvas, 140,40); return; } SkColor pixel; memcpy(&pixel, bm.getAddr(0,0), sizeof(pixel)); auto close = [](int x, int y) { return x-y < 2 && y-x < 2; }; if (close(SkColorGetR(pixel), SkColorGetR(color)) && close(SkColorGetG(pixel), SkColorGetG(color)) && close(SkColorGetB(pixel), SkColorGetB(color)) && close(SkColorGetA(pixel), SkColorGetA(color))) { MarkGMGood(canvas, 140,40); } else { MarkGMBad(canvas, 140,40); }
/* * Three differences from the Android version: * Returns a Skia bitmap instead of an Android bitmap. * Android version attempts to reuse a recycled bitmap. * Removed the options object and used parameters for color type and * sample size. */ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY, int inputWidth, int inputHeight, int sampleSize, SkColorType dstColorType) { // Reject color types not supported by this method if (kIndex_8_SkColorType == dstColorType || kGray_8_SkColorType == dstColorType) { SkDebugf("Error: Color type not supported.\n"); return nullptr; } // The client may not necessarily request a region that is fully within // the image. We may need to do some calculation to determine what part // of the image to decode. // The left offset of the portion of the image we want, where zero // indicates the left edge of the image. int imageSubsetX; // The size of the output bitmap is determined by the size of the // requested region, not by the size of the intersection of the region // and the image dimensions. If inputX is negative, we will need to // place decoded pixels into the output bitmap starting at a left offset. // If this is non-zero, imageSubsetX must be zero. int outX; // The width of the portion of the image that we will write to the output // bitmap. If the region is not fully contained within the image, this // will not be the same as inputWidth. int imageSubsetWidth; set_subset_region(inputX, inputWidth, this->width(), &imageSubsetX, &outX, &imageSubsetWidth); // The top offset of the portion of the image we want, where zero // indicates the top edge of the image. int imageSubsetY; // The size of the output bitmap is determined by the size of the // requested region, not by the size of the intersection of the region // and the image dimensions. If inputY is negative, we will need to // place decoded pixels into the output bitmap starting at a top offset. // If this is non-zero, imageSubsetY must be zero. int outY; // The height of the portion of the image that we will write to the output // bitmap. If the region is not fully contained within the image, this // will not be the same as inputHeight. int imageSubsetHeight; set_subset_region(inputY, inputHeight, this->height(), &imageSubsetY, &outY, &imageSubsetHeight); if (imageSubsetWidth <= 0 || imageSubsetHeight <= 0) { SkDebugf("Error: Region must intersect part of the image.\n"); return nullptr; } // Create the image info for the decode SkAlphaType dstAlphaType = fDecoder->getInfo().alphaType(); if (kUnpremul_SkAlphaType == dstAlphaType) { dstAlphaType = kPremul_SkAlphaType; } SkImageInfo decodeInfo = SkImageInfo::Make(this->width(), this->height(), dstColorType, dstAlphaType); // Start the scanline decoder SkCodec::Result r = fDecoder->startScanlineDecode(decodeInfo); if (SkCodec::kSuccess != r) { SkDebugf("Error: Could not start scanline decoder.\n"); return nullptr; } // Allocate a bitmap for the unscaled decode SkBitmap tmp; SkImageInfo tmpInfo = decodeInfo.makeWH(this->width(), imageSubsetHeight); if (!tmp.tryAllocPixels(tmpInfo)) { SkDebugf("Error: Could not allocate pixels.\n"); return nullptr; } // Skip the unneeded rows if (!fDecoder->skipScanlines(imageSubsetY)) { SkDebugf("Error: Failed to skip scanlines.\n"); return nullptr; } // Decode the necessary rows fDecoder->getScanlines(tmp.getAddr(0, 0), imageSubsetHeight, tmp.rowBytes()); // Calculate the size of the output const int outWidth = get_scaled_dimension(inputWidth, sampleSize); const int outHeight = get_scaled_dimension(inputHeight, sampleSize); // Initialize the destination bitmap SkAutoTDelete<SkBitmap> bitmap(new SkBitmap()); SkImageInfo dstInfo = decodeInfo.makeWH(outWidth, outHeight); if (!bitmap->tryAllocPixels(dstInfo)) { SkDebugf("Error: Could not allocate pixels.\n"); return nullptr; } // Zero the bitmap if the region is not completely within the image. // TODO (msarett): Can we make this faster by implementing it to only // zero parts of the image that we won't overwrite with // pixels? // TODO (msarett): This could be skipped if memory is zero initialized. // This would matter if this code is moved to Android and // uses Android bitmaps. if (0 != outX || 0 != outY || inputX + inputWidth > this->width() || inputY + inputHeight > this->height()) { bitmap->eraseColor(0); } // Use a canvas to crop and scale to the destination bitmap SkCanvas canvas(*bitmap); // TODO (msarett): Maybe we can take advantage of the fact that SkRect uses floats? SkRect src = SkRect::MakeXYWH((SkScalar) imageSubsetX, (SkScalar) 0, (SkScalar) imageSubsetWidth, (SkScalar) imageSubsetHeight); SkRect dst = SkRect::MakeXYWH((SkScalar) (outX / sampleSize), (SkScalar) (outY / sampleSize), (SkScalar) get_scaled_dimension(imageSubsetWidth, sampleSize), (SkScalar) get_scaled_dimension(imageSubsetHeight, sampleSize)); SkPaint paint; // Overwrite the dst with the src pixels paint.setXfermodeMode(SkXfermode::kSrc_Mode); // TODO (msarett): Test multiple filter qualities. kNone is the default. canvas.drawBitmapRect(tmp, src, dst, &paint); return bitmap.detach(); }
DEF_TEST(Codec_frames, r) { #define kOpaque kOpaque_SkAlphaType #define kUnpremul kUnpremul_SkAlphaType #define kKeep SkCodecAnimation::DisposalMethod::kKeep #define kRestoreBG SkCodecAnimation::DisposalMethod::kRestoreBGColor #define kRestorePrev SkCodecAnimation::DisposalMethod::kRestorePrevious static const struct { const char* fName; int fFrameCount; // One less than fFramecount, since the first frame is always // independent. std::vector<int> fRequiredFrames; // Same, since the first frame should match getInfo std::vector<SkAlphaType> fAlphas; // The size of this one should match fFrameCount for animated, empty // otherwise. std::vector<int> fDurations; int fRepetitionCount; std::vector<SkCodecAnimation::DisposalMethod> fDisposalMethods; } gRecs[] = { { "images/required.gif", 7, { 0, 1, 2, 3, 4, 5 }, { kOpaque, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul }, { 100, 100, 100, 100, 100, 100, 100 }, 0, { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } }, { "images/alphabetAnim.gif", 13, { SkCodec::kNone, 0, 0, 0, 0, 5, 6, SkCodec::kNone, SkCodec::kNone, 9, 10, 11 }, { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul }, { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 }, 0, { kKeep, kRestorePrev, kRestorePrev, kRestorePrev, kRestorePrev, kRestoreBG, kKeep, kRestoreBG, kRestoreBG, kKeep, kKeep, kRestoreBG, kKeep } }, { "images/randPixelsAnim2.gif", 4, // required frames { 0, 0, 1 }, // alphas { kOpaque, kOpaque, kOpaque }, // durations { 0, 1000, 170, 40 }, // repetition count 0, { kKeep, kKeep, kRestorePrev, kKeep } }, { "images/randPixelsAnim.gif", 13, // required frames { 0, 1, 2, 3, 4, 3, 6, 7, 7, 7, 9, 9 }, { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul }, // durations { 0, 1000, 170, 40, 220, 7770, 90, 90, 90, 90, 90, 90, 90 }, // repetition count 0, { kKeep, kKeep, kKeep, kKeep, kRestoreBG, kRestoreBG, kRestoreBG, kRestoreBG, kRestorePrev, kRestoreBG, kRestorePrev, kRestorePrev, kRestorePrev, } }, { "images/box.gif", 1, {}, {}, {}, 0, { kKeep } }, { "images/color_wheel.gif", 1, {}, {}, {}, 0, { kKeep } }, { "images/test640x479.gif", 4, { 0, 1, 2 }, { kOpaque, kOpaque, kOpaque }, { 200, 200, 200, 200 }, SkCodec::kRepetitionCountInfinite, { kKeep, kKeep, kKeep, kKeep } }, { "images/colorTables.gif", 2, { 0 }, { kOpaque }, { 1000, 1000 }, 5, { kKeep, kKeep } }, { "images/arrow.png", 1, {}, {}, {}, 0, {} }, { "images/google_chrome.ico", 1, {}, {}, {}, 0, {} }, { "images/brickwork-texture.jpg", 1, {}, {}, {}, 0, {} }, #if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32)) { "images/dng_with_preview.dng", 1, {}, {}, {}, 0, {} }, #endif { "images/mandrill.wbmp", 1, {}, {}, {}, 0, {} }, { "images/randPixels.bmp", 1, {}, {}, {}, 0, {} }, { "images/yellow_rose.webp", 1, {}, {}, {}, 0, {} }, { "images/webp-animated.webp", 3, { 0, 1 }, { kOpaque, kOpaque }, { 1000, 500, 1000 }, SkCodec::kRepetitionCountInfinite, { kKeep, kKeep, kKeep } }, { "images/blendBG.webp", 7, { 0, SkCodec::kNone, SkCodec::kNone, SkCodec::kNone, 4, 4 }, { kOpaque, kOpaque, kUnpremul, kOpaque, kUnpremul, kUnpremul }, { 525, 500, 525, 437, 609, 729, 444 }, 7, { kKeep, kKeep, kKeep, kKeep, kKeep, kKeep, kKeep } }, { "images/required.webp", 7, { 0, 1, 1, SkCodec::kNone, 4, 4 }, { kOpaque, kUnpremul, kUnpremul, kOpaque, kOpaque, kOpaque }, { 100, 100, 100, 100, 100, 100, 100 }, 1, { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } }, }; #undef kOpaque #undef kUnpremul #undef kKeep #undef kRestorePrev #undef kRestoreBG for (const auto& rec : gRecs) { sk_sp<SkData> data(GetResourceAsData(rec.fName)); if (!data) { // Useful error statement, but sometimes people run tests without // resources, and they do not want to see these messages. //ERRORF(r, "Missing resources? Could not find '%s'", rec.fName); continue; } std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data)); if (!codec) { ERRORF(r, "Failed to create an SkCodec from '%s'", rec.fName); continue; } { SkCodec::FrameInfo frameInfo; REPORTER_ASSERT(r, !codec->getFrameInfo(0, &frameInfo)); } const int repetitionCount = codec->getRepetitionCount(); if (repetitionCount != rec.fRepetitionCount) { ERRORF(r, "%s repetition count does not match! expected: %i\tactual: %i", rec.fName, rec.fRepetitionCount, repetitionCount); } const int expected = rec.fFrameCount; if (rec.fRequiredFrames.size() + 1 != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fRequiredFrames; expected: %i\tactual: %i", rec.fName, expected - 1, rec.fRequiredFrames.size()); continue; } if (expected > 1) { if (rec.fDurations.size() != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fDurations; expected: %i\tactual: %i", rec.fName, expected, rec.fDurations.size()); continue; } if (rec.fAlphas.size() + 1 != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fAlphas; expected: %i\tactual: %i", rec.fName, expected - 1, rec.fAlphas.size()); continue; } if (rec.fDisposalMethods.size() != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fDisposalMethods; " "expected %i\tactual: %i", rec.fName, expected, rec.fDisposalMethods.size()); continue; } } enum class TestMode { kVector, kIndividual, }; for (auto mode : { TestMode::kVector, TestMode::kIndividual }) { // Re-create the codec to reset state and test parsing. codec = SkCodec::MakeFromData(data); int frameCount; std::vector<SkCodec::FrameInfo> frameInfos; switch (mode) { case TestMode::kVector: frameInfos = codec->getFrameInfo(); // getFrameInfo returns empty set for non-animated. frameCount = frameInfos.empty() ? 1 : frameInfos.size(); break; case TestMode::kIndividual: frameCount = codec->getFrameCount(); break; } if (frameCount != expected) { ERRORF(r, "'%s' expected frame count: %i\tactual: %i", rec.fName, expected, frameCount); continue; } // From here on, we are only concerned with animated images. if (1 == frameCount) { continue; } for (int i = 0; i < frameCount; i++) { SkCodec::FrameInfo frameInfo; switch (mode) { case TestMode::kVector: frameInfo = frameInfos[i]; break; case TestMode::kIndividual: REPORTER_ASSERT(r, codec->getFrameInfo(i, nullptr)); REPORTER_ASSERT(r, codec->getFrameInfo(i, &frameInfo)); break; } if (rec.fDurations[i] != frameInfo.fDuration) { ERRORF(r, "%s frame %i's durations do not match! expected: %i\tactual: %i", rec.fName, i, rec.fDurations[i], frameInfo.fDuration); } auto to_string = [](SkAlphaType alpha) { switch (alpha) { case kUnpremul_SkAlphaType: return "unpremul"; case kOpaque_SkAlphaType: return "opaque"; default: SkASSERT(false); return "unknown"; } }; auto expectedAlpha = 0 == i ? codec->getInfo().alphaType() : rec.fAlphas[i-1]; auto alpha = frameInfo.fAlphaType; if (expectedAlpha != alpha) { ERRORF(r, "%s's frame %i has wrong alpha type! expected: %s\tactual: %s", rec.fName, i, to_string(expectedAlpha), to_string(alpha)); } if (0 == i) { REPORTER_ASSERT(r, frameInfo.fRequiredFrame == SkCodec::kNone); } else if (rec.fRequiredFrames[i-1] != frameInfo.fRequiredFrame) { ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i", rec.fName, i, rec.fRequiredFrames[i-1], frameInfo.fRequiredFrame); } REPORTER_ASSERT(r, frameInfo.fDisposalMethod == rec.fDisposalMethods[i]); } if (TestMode::kIndividual == mode) { // No need to test decoding twice. continue; } // Compare decoding in multiple ways: // - Start from scratch for each frame. |codec| will have to decode the required frame // (and any it depends on) to decode. This is stored in |cachedFrames|. // - Provide the frame that a frame depends on, so |codec| just has to blend. // - Provide a frame after the required frame, which will be covered up by the newest // frame. // All should look the same. std::vector<SkBitmap> cachedFrames(frameCount); const auto info = codec->getInfo().makeColorType(kN32_SkColorType); auto decode = [&](SkBitmap* bm, int index, int cachedIndex) { auto decodeInfo = info; if (index > 0) { decodeInfo = info.makeAlphaType(frameInfos[index].fAlphaType); } bm->allocPixels(decodeInfo); if (cachedIndex != SkCodec::kNone) { // First copy the pixels from the cached frame const bool success = sk_tool_utils::copy_to(bm, kN32_SkColorType, cachedFrames[cachedIndex]); REPORTER_ASSERT(r, success); } SkCodec::Options opts; opts.fFrameIndex = index; opts.fPriorFrame = cachedIndex; const auto result = codec->getPixels(decodeInfo, bm->getPixels(), bm->rowBytes(), &opts); if (cachedIndex != SkCodec::kNone && restore_previous(frameInfos[cachedIndex])) { if (result == SkCodec::kInvalidParameters) { return true; } ERRORF(r, "Using a kRestorePrevious frame as fPriorFrame should fail"); return false; } if (result != SkCodec::kSuccess) { ERRORF(r, "Failed to decode frame %i from %s when providing prior frame %i, " "error %i", index, rec.fName, cachedIndex, result); } return result == SkCodec::kSuccess; }; for (int i = 0; i < frameCount; i++) { SkBitmap& cachedFrame = cachedFrames[i]; if (!decode(&cachedFrame, i, SkCodec::kNone)) { continue; } const auto reqFrame = frameInfos[i].fRequiredFrame; if (reqFrame == SkCodec::kNone) { // Nothing to compare against. continue; } for (int j = reqFrame; j < i; j++) { SkBitmap frame; if (restore_previous(frameInfos[j])) { (void) decode(&frame, i, j); continue; } if (!decode(&frame, i, j)) { continue; } // Now verify they're equal. const size_t rowLen = info.bytesPerPixel() * info.width(); for (int y = 0; y < info.height(); y++) { const void* cachedAddr = cachedFrame.getAddr(0, y); SkASSERT(cachedAddr != nullptr); const void* addr = frame.getAddr(0, y); SkASSERT(addr != nullptr); const bool lineMatches = memcmp(cachedAddr, addr, rowLen) == 0; if (!lineMatches) { SkString name = SkStringPrintf("cached_%i", i); write_bm(name.c_str(), cachedFrame); name = SkStringPrintf("frame_%i", i); write_bm(name.c_str(), frame); ERRORF(r, "%s's frame %i is different (starting from line %i) when " "providing prior frame %i!", rec.fName, i, y, j); break; } } } } } } }
// 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); }
void SubsetTranslateBench::onDraw(const int n, SkCanvas* canvas) { // When the color type is kIndex8, we will need to store the color table. If it is // used, it will be initialized by the codec. int colorCount; SkPMColor colors[256]; if (fUseCodec) { for (int count = 0; count < n; count++) { SkAutoTDelete<SkScanlineDecoder> scanlineDecoder( SkScanlineDecoder::NewFromStream(fStream->duplicate())); const SkImageInfo info = scanlineDecoder->getInfo().makeColorType(fColorType); SkAutoTDeleteArray<uint8_t> row(new uint8_t[info.minRowBytes()]); scanlineDecoder->start(info, nullptr, colors, &colorCount); SkBitmap bitmap; // Note that we use the same bitmap for all of the subsets. // It might be larger than necessary for the end subsets. SkImageInfo subsetInfo = info.makeWH(fSubsetWidth, fSubsetHeight); alloc_pixels(&bitmap, subsetInfo, colors, colorCount); for (int x = 0; x < info.width(); x += fSubsetWidth) { for (int y = 0; y < info.height(); y += fSubsetHeight) { scanlineDecoder->skipScanlines(y); const uint32_t currSubsetWidth = x + (int) fSubsetWidth > info.width() ? info.width() - x : fSubsetWidth; const uint32_t currSubsetHeight = y + (int) fSubsetHeight > info.height() ? info.height() - y : fSubsetHeight; const uint32_t bpp = info.bytesPerPixel(); for (uint32_t y = 0; y < currSubsetHeight; y++) { scanlineDecoder->getScanlines(row.get(), 1, 0); memcpy(bitmap.getAddr(0, y), row.get() + x * bpp, currSubsetWidth * bpp); } } } } } else { // We create a color table here to satisfy allocPixels() when the output // type is kIndex8. It's okay that this is uninitialized since we never // use it. SkColorTable* colorTable = new SkColorTable(colors, 0); for (int count = 0; count < n; count++) { int width, height; SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(fStream)); decoder->buildTileIndex(fStream->duplicate(), &width, &height); SkBitmap bitmap; // Note that we use the same bitmap for all of the subsets. // It might be larger than necessary for the end subsets. // If we do not include this step, decodeSubset() would allocate space // for the pixels automatically, but this would not allow us to reuse the // same bitmap as the other subsets. We want to reuse the same bitmap // because it gives a more fair comparison with SkCodec and is a common // use case of BitmapRegionDecoder. bitmap.allocPixels(SkImageInfo::Make(fSubsetWidth, fSubsetHeight, fColorType, kOpaque_SkAlphaType), nullptr, colorTable); for (int x = 0; x < width; x += fSubsetWidth) { for (int y = 0; y < height; y += fSubsetHeight) { const uint32_t currSubsetWidth = x + (int) fSubsetWidth > width ? width - x : fSubsetWidth; const uint32_t currSubsetHeight = y + (int) fSubsetHeight > height ? height - y : fSubsetHeight; SkIRect rect = SkIRect::MakeXYWH(x, y, currSubsetWidth, currSubsetHeight); decoder->decodeSubset(&bitmap, rect, fColorType); } } } } }
DEF_TEST(Codec_pngChunkReader, r) { // Create a dummy bitmap. Use unpremul RGBA for libpng. SkBitmap bm; const int w = 1; const int h = 1; const SkImageInfo bmInfo = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType); bm.setInfo(bmInfo); bm.allocPixels(); bm.eraseColor(SK_ColorBLUE); SkMD5::Digest goodDigest; md5(bm, &goodDigest); // Write to a png file. png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); REPORTER_ASSERT(r, png); if (!png) { return; } png_infop info = png_create_info_struct(png); REPORTER_ASSERT(r, info); if (!info) { png_destroy_write_struct(&png, nullptr); return; } if (setjmp(png_jmpbuf(png))) { ERRORF(r, "failed writing png"); png_destroy_write_struct(&png, &info); return; } SkDynamicMemoryWStream wStream; png_set_write_fn(png, (void*) (&wStream), codex_test_write_fn, nullptr); png_set_IHDR(png, info, (png_uint_32)w, (png_uint_32)h, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // Create some chunks that match the Android framework's use. static png_unknown_chunk gUnknowns[] = { { "npOl", (png_byte*)"outline", sizeof("outline"), PNG_HAVE_IHDR }, { "npLb", (png_byte*)"layoutBounds", sizeof("layoutBounds"), PNG_HAVE_IHDR }, { "npTc", (png_byte*)"ninePatchData", sizeof("ninePatchData"), PNG_HAVE_IHDR }, }; png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"npOl\0npLb\0npTc\0", 3); png_set_unknown_chunks(png, info, gUnknowns, SK_ARRAY_COUNT(gUnknowns)); #if PNG_LIBPNG_VER < 10600 /* Deal with unknown chunk location bug in 1.5.x and earlier */ png_set_unknown_chunk_location(png, info, 0, PNG_HAVE_IHDR); png_set_unknown_chunk_location(png, info, 1, PNG_HAVE_IHDR); #endif png_write_info(png, info); for (int j = 0; j < h; j++) { png_bytep row = (png_bytep)(bm.getAddr(0, j)); png_write_rows(png, &row, 1); } png_write_end(png, info); png_destroy_write_struct(&png, &info); class ChunkReader : public SkPngChunkReader { public: ChunkReader(skiatest::Reporter* r) : fReporter(r) { this->reset(); } bool readChunk(const char tag[], const void* data, size_t length) override { for (size_t i = 0; i < SK_ARRAY_COUNT(gUnknowns); ++i) { if (!strcmp(tag, (const char*) gUnknowns[i].name)) { // Tag matches. This should have been the first time we see it. REPORTER_ASSERT(fReporter, !fSeen[i]); fSeen[i] = true; // Data and length should match REPORTER_ASSERT(fReporter, length == gUnknowns[i].size); REPORTER_ASSERT(fReporter, !strcmp((const char*) data, (const char*) gUnknowns[i].data)); return true; } } ERRORF(fReporter, "Saw an unexpected unknown chunk."); return true; } bool allHaveBeenSeen() { bool ret = true; for (auto seen : fSeen) { ret &= seen; } return ret; } void reset() { sk_bzero(fSeen, sizeof(fSeen)); } private: skiatest::Reporter* fReporter; // Unowned bool fSeen[3]; }; ChunkReader chunkReader(r); // Now read the file with SkCodec. SkAutoTUnref<SkData> data(wStream.copyToData()); SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data, &chunkReader)); REPORTER_ASSERT(r, codec); if (!codec) { return; } // Now compare to the original. SkBitmap decodedBm; decodedBm.setInfo(codec->getInfo()); decodedBm.allocPixels(); SkCodec::Result result = codec->getPixels(codec->getInfo(), decodedBm.getPixels(), decodedBm.rowBytes()); REPORTER_ASSERT(r, SkCodec::kSuccess == result); if (decodedBm.colorType() != bm.colorType()) { SkBitmap tmp; bool success = decodedBm.copyTo(&tmp, bm.colorType()); REPORTER_ASSERT(r, success); if (!success) { return; } tmp.swap(decodedBm); } compare_to_good_digest(r, goodDigest, decodedBm); REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen()); // Decoding again will read the chunks again. chunkReader.reset(); REPORTER_ASSERT(r, !chunkReader.allHaveBeenSeen()); result = codec->getPixels(codec->getInfo(), decodedBm.getPixels(), decodedBm.rowBytes()); REPORTER_ASSERT(r, SkCodec::kSuccess == result); REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen()); }
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); } } }