Example #1
0
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;
}
Example #2
0
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;
}
Example #3
0
// 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;
}
Example #4
0
// 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;
    }
}
Example #5
0
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);
    }
}
Example #6
0
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);
}
Example #7
0
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;
}
Example #8
0
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;
}
Example #10
0
// 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;
    }
}
Example #11
0
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);
        }
    }
}
Example #12
0
// 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;
}
Example #13
0
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 = &subset;

            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 = &subset;
    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);
    }
}
Example #14
0
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 ...
    }
}
Example #15
0
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);
        }
    }
}
Example #16
0
    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);
        }
Example #17
0
/*
 * 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();
}
Example #18
0
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;
                        }
                    }
                }
            }
        }
    }
}
Example #19
0
// 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);
                }
            }
        }
    }
}
Example #21
0
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());
}
Example #22
0
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 = &subset;
    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);
        }
    }
}