skia2/tests/ImageTest.cpp
Brian Osman b62f50cf76 Replace nearly all kRespect with kIgnore
- Encoders and decoders always assume kIgnore.
- They are less opinionated about F16 and color space,
  we just trust the color space that's passed in, and
  put that directly in the image (no sRGB encoding).
- SkBitmap and SkPixmap read/write pixels functions were
  defaulting to kResepct, those are now always kIgnore.
- Many other bits of plumbing are simplified, and I
  added a default of kIgnore to SkImage::makeColorSpace,
  so we can phase out that argument entirely.
- Still need to add defaults to other public APIs that
  take SkTransferFunctionBehavior.

- This makes gold think that we've dramatically changed
  the contents of all F16 images, but that's because
  it doesn't understand the (now linear) color space
  that's embedded. Once we triage them all once, they
  will work fine (and they'll look perfect in the browser).

Bug: skia:
Change-Id: I62fa090f96cae1b67d181ce14bd91f34ff2ed747
Reviewed-on: https://skia-review.googlesource.com/140570
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
2018-07-12 20:54:14 +00:00

1385 lines
55 KiB
C++

/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <functional>
#include <initializer_list>
#include <vector>
#include "SkAutoPixmapStorage.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkColorSpacePriv.h"
#include "SkData.h"
#include "SkImageEncoder.h"
#include "SkImageGenerator.h"
#include "SkImage_Base.h"
#include "SkImagePriv.h"
#include "SkMakeUnique.h"
#include "SkPicture.h"
#include "SkPictureRecorder.h"
#include "SkRRect.h"
#include "SkSerialProcs.h"
#include "SkStream.h"
#include "SkSurface.h"
#include "SkUtils.h"
#include "Test.h"
#include "Resources.h"
#include "sk_pixel_iter.h"
#include "sk_tool_utils.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "GrResourceCache.h"
#include "GrTest.h"
#include "GrTexture.h"
#include "SkGr.h"
using namespace sk_gpu_test;
SkImageInfo read_pixels_info(SkImage* image) {
if (as_IB(image)->onImageInfo().colorSpace()) {
return SkImageInfo::MakeS32(image->width(), image->height(), image->alphaType());
}
return SkImageInfo::MakeN32(image->width(), image->height(), image->alphaType());
}
static void assert_equal(skiatest::Reporter* reporter, SkImage* a, const SkIRect* subsetA,
SkImage* b) {
const int widthA = subsetA ? subsetA->width() : a->width();
const int heightA = subsetA ? subsetA->height() : a->height();
REPORTER_ASSERT(reporter, widthA == b->width());
REPORTER_ASSERT(reporter, heightA == b->height());
// see https://bug.skia.org/3965
//REPORTER_ASSERT(reporter, a->isOpaque() == b->isOpaque());
SkAutoPixmapStorage pmapA, pmapB;
pmapA.alloc(read_pixels_info(a));
pmapB.alloc(read_pixels_info(b));
const int srcX = subsetA ? subsetA->x() : 0;
const int srcY = subsetA ? subsetA->y() : 0;
REPORTER_ASSERT(reporter, a->readPixels(pmapA, srcX, srcY));
REPORTER_ASSERT(reporter, b->readPixels(pmapB, 0, 0));
const size_t widthBytes = widthA * 4;
for (int y = 0; y < heightA; ++y) {
REPORTER_ASSERT(reporter, !memcmp(pmapA.addr32(0, y), pmapB.addr32(0, y), widthBytes));
}
}
static void draw_image_test_pattern(SkCanvas* canvas) {
canvas->clear(SK_ColorWHITE);
SkPaint paint;
paint.setColor(SK_ColorBLACK);
canvas->drawRect(SkRect::MakeXYWH(5, 5, 10, 10), paint);
}
static sk_sp<SkImage> create_image() {
const SkImageInfo info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
auto surface(SkSurface::MakeRaster(info));
draw_image_test_pattern(surface->getCanvas());
return surface->makeImageSnapshot();
}
static sk_sp<SkData> create_image_data(SkImageInfo* info) {
*info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
const size_t rowBytes = info->minRowBytes();
sk_sp<SkData> data(SkData::MakeUninitialized(rowBytes * info->height()));
{
SkBitmap bm;
bm.installPixels(*info, data->writable_data(), rowBytes);
SkCanvas canvas(bm);
draw_image_test_pattern(&canvas);
}
return data;
}
static sk_sp<SkImage> create_data_image() {
SkImageInfo info;
sk_sp<SkData> data(create_image_data(&info));
return SkImage::MakeRasterData(info, std::move(data), info.minRowBytes());
}
static sk_sp<SkImage> create_image_large(int maxTextureSize) {
const SkImageInfo info = SkImageInfo::MakeN32(maxTextureSize + 1, 32, kOpaque_SkAlphaType);
auto surface(SkSurface::MakeRaster(info));
surface->getCanvas()->clear(SK_ColorWHITE);
SkPaint paint;
paint.setColor(SK_ColorBLACK);
surface->getCanvas()->drawRect(SkRect::MakeXYWH(4000, 2, 28000, 30), paint);
return surface->makeImageSnapshot();
}
static sk_sp<SkImage> create_picture_image() {
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(10, 10);
canvas->clear(SK_ColorCYAN);
return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(), SkISize::Make(10, 10),
nullptr, nullptr, SkImage::BitDepth::kU8,
SkColorSpace::MakeSRGB());
};
// Want to ensure that our Release is called when the owning image is destroyed
struct RasterDataHolder {
RasterDataHolder() : fReleaseCount(0) {}
sk_sp<SkData> fData;
int fReleaseCount;
static void Release(const void* pixels, void* context) {
RasterDataHolder* self = static_cast<RasterDataHolder*>(context);
self->fReleaseCount++;
self->fData.reset();
}
};
static sk_sp<SkImage> create_rasterproc_image(RasterDataHolder* dataHolder) {
SkASSERT(dataHolder);
SkImageInfo info;
dataHolder->fData = create_image_data(&info);
return SkImage::MakeFromRaster(SkPixmap(info, dataHolder->fData->data(), info.minRowBytes()),
RasterDataHolder::Release, dataHolder);
}
static sk_sp<SkImage> create_codec_image() {
SkImageInfo info;
sk_sp<SkData> data(create_image_data(&info));
SkBitmap bitmap;
bitmap.installPixels(info, data->writable_data(), info.minRowBytes());
sk_sp<SkData> src(sk_tool_utils::EncodeImageToData(bitmap, SkEncodedImageFormat::kPNG, 100));
return SkImage::MakeFromEncoded(std::move(src));
}
static sk_sp<SkImage> create_gpu_image(GrContext* context, bool withMips = false) {
const SkImageInfo info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0,
kBottomLeft_GrSurfaceOrigin, nullptr, withMips));
draw_image_test_pattern(surface->getCanvas());
return surface->makeImageSnapshot();
}
static void test_encode(skiatest::Reporter* reporter, SkImage* image) {
const SkIRect ir = SkIRect::MakeXYWH(5, 5, 10, 10);
sk_sp<SkData> origEncoded = image->encodeToData();
REPORTER_ASSERT(reporter, origEncoded);
REPORTER_ASSERT(reporter, origEncoded->size() > 0);
sk_sp<SkImage> decoded(SkImage::MakeFromEncoded(origEncoded));
if (!decoded) {
ERRORF(reporter, "failed to decode image!");
return;
}
REPORTER_ASSERT(reporter, decoded);
assert_equal(reporter, image, nullptr, decoded.get());
// Now see if we can instantiate an image from a subset of the surface/origEncoded
decoded = SkImage::MakeFromEncoded(origEncoded, &ir);
REPORTER_ASSERT(reporter, decoded);
assert_equal(reporter, image, &ir, decoded.get());
}
DEF_TEST(ImageEncode, reporter) {
test_encode(reporter, create_image().get());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageEncode_Gpu, reporter, ctxInfo) {
test_encode(reporter, create_gpu_image(ctxInfo.grContext()).get());
}
DEF_TEST(Image_MakeFromRasterBitmap, reporter) {
const struct {
SkCopyPixelsMode fCPM;
bool fExpectSameAsMutable;
bool fExpectSameAsImmutable;
} recs[] = {
{ kIfMutable_SkCopyPixelsMode, false, true },
{ kAlways_SkCopyPixelsMode, false, false },
{ kNever_SkCopyPixelsMode, true, true },
};
for (auto rec : recs) {
SkPixmap pm;
SkBitmap bm;
bm.allocN32Pixels(100, 100);
auto img = SkMakeImageFromRasterBitmap(bm, rec.fCPM);
REPORTER_ASSERT(reporter, img->peekPixels(&pm));
const bool sameMutable = pm.addr32(0, 0) == bm.getAddr32(0, 0);
REPORTER_ASSERT(reporter, rec.fExpectSameAsMutable == sameMutable);
REPORTER_ASSERT(reporter, (bm.getGenerationID() == img->uniqueID()) == sameMutable);
bm.notifyPixelsChanged(); // force a new generation ID
bm.setImmutable();
img = SkMakeImageFromRasterBitmap(bm, rec.fCPM);
REPORTER_ASSERT(reporter, img->peekPixels(&pm));
const bool sameImmutable = pm.addr32(0, 0) == bm.getAddr32(0, 0);
REPORTER_ASSERT(reporter, rec.fExpectSameAsImmutable == sameImmutable);
REPORTER_ASSERT(reporter, (bm.getGenerationID() == img->uniqueID()) == sameImmutable);
}
}
// Test that image encoding failures do not break picture serialization/deserialization.
DEF_TEST(Image_Serialize_Encoding_Failure, reporter) {
auto surface(SkSurface::MakeRasterN32Premul(100, 100));
surface->getCanvas()->clear(SK_ColorGREEN);
sk_sp<SkImage> image(surface->makeImageSnapshot());
REPORTER_ASSERT(reporter, image);
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(100, 100);
canvas->drawImage(image, 0, 0);
sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
REPORTER_ASSERT(reporter, picture);
REPORTER_ASSERT(reporter, picture->approximateOpCount() > 0);
bool was_called = false;
SkSerialProcs procs;
procs.fImageProc = [](SkImage*, void* called) {
*(bool*)called = true;
return SkData::MakeEmpty();
};
procs.fImageCtx = &was_called;
REPORTER_ASSERT(reporter, !was_called);
auto data = picture->serialize(&procs);
REPORTER_ASSERT(reporter, was_called);
REPORTER_ASSERT(reporter, data && data->size() > 0);
auto deserialized = SkPicture::MakeFromData(data->data(), data->size());
REPORTER_ASSERT(reporter, deserialized);
REPORTER_ASSERT(reporter, deserialized->approximateOpCount() > 0);
}
// Test that a draw that only partially covers the drawing surface isn't
// interpreted as covering the entire drawing surface (i.e., exercise one of the
// conditions of SkCanvas::wouldOverwriteEntireSurface()).
DEF_TEST(Image_RetainSnapshot, reporter) {
const SkPMColor red = SkPackARGB32(0xFF, 0xFF, 0, 0);
const SkPMColor green = SkPackARGB32(0xFF, 0, 0xFF, 0);
SkImageInfo info = SkImageInfo::MakeN32Premul(2, 2);
auto surface(SkSurface::MakeRaster(info));
surface->getCanvas()->clear(0xFF00FF00);
SkPMColor pixels[4];
memset(pixels, 0xFF, sizeof(pixels)); // init with values we don't expect
const SkImageInfo dstInfo = SkImageInfo::MakeN32Premul(2, 2);
const size_t dstRowBytes = 2 * sizeof(SkPMColor);
sk_sp<SkImage> image1(surface->makeImageSnapshot());
REPORTER_ASSERT(reporter, image1->readPixels(dstInfo, pixels, dstRowBytes, 0, 0));
for (size_t i = 0; i < SK_ARRAY_COUNT(pixels); ++i) {
REPORTER_ASSERT(reporter, pixels[i] == green);
}
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
paint.setColor(SK_ColorRED);
surface->getCanvas()->drawRect(SkRect::MakeXYWH(1, 1, 1, 1), paint);
sk_sp<SkImage> image2(surface->makeImageSnapshot());
REPORTER_ASSERT(reporter, image2->readPixels(dstInfo, pixels, dstRowBytes, 0, 0));
REPORTER_ASSERT(reporter, pixels[0] == green);
REPORTER_ASSERT(reporter, pixels[1] == green);
REPORTER_ASSERT(reporter, pixels[2] == green);
REPORTER_ASSERT(reporter, pixels[3] == red);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
static void make_bitmap_mutable(SkBitmap* bm) {
bm->allocN32Pixels(10, 10);
}
static void make_bitmap_immutable(SkBitmap* bm) {
bm->allocN32Pixels(10, 10);
bm->setImmutable();
}
DEF_TEST(image_newfrombitmap, reporter) {
const struct {
void (*fMakeProc)(SkBitmap*);
bool fExpectPeekSuccess;
bool fExpectSharedID;
bool fExpectLazy;
} rec[] = {
{ make_bitmap_mutable, true, false, false },
{ make_bitmap_immutable, true, true, false },
};
for (size_t i = 0; i < SK_ARRAY_COUNT(rec); ++i) {
SkBitmap bm;
rec[i].fMakeProc(&bm);
sk_sp<SkImage> image(SkImage::MakeFromBitmap(bm));
SkPixmap pmap;
const bool sharedID = (image->uniqueID() == bm.getGenerationID());
REPORTER_ASSERT(reporter, sharedID == rec[i].fExpectSharedID);
const bool peekSuccess = image->peekPixels(&pmap);
REPORTER_ASSERT(reporter, peekSuccess == rec[i].fExpectPeekSuccess);
const bool lazy = image->isLazyGenerated();
REPORTER_ASSERT(reporter, lazy == rec[i].fExpectLazy);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "SkBitmapCache.h"
/*
* This tests the caching (and preemptive purge) of the raster equivalent of a gpu-image.
* We cache it for performance when drawing into a raster surface.
*
* A cleaner test would know if each drawImage call triggered a read-back from the gpu,
* but we don't have that facility (at the moment) so we use a little internal knowledge
* of *how* the raster version is cached, and look for that.
*/
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(c, reporter, ctxInfo) {
SkImageInfo info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
sk_sp<SkImage> image(create_gpu_image(ctxInfo.grContext()));
const uint32_t uniqueID = image->uniqueID();
const auto desc = SkBitmapCacheDesc::Make(image.get());
auto surface(SkSurface::MakeRaster(info));
// now we can test drawing a gpu-backed image into a cpu-backed surface
{
SkBitmap cachedBitmap;
REPORTER_ASSERT(reporter, !SkBitmapCache::Find(desc, &cachedBitmap));
}
surface->getCanvas()->drawImage(image, 0, 0);
{
SkBitmap cachedBitmap;
if (SkBitmapCache::Find(desc, &cachedBitmap)) {
REPORTER_ASSERT(reporter, cachedBitmap.getGenerationID() == uniqueID);
REPORTER_ASSERT(reporter, cachedBitmap.isImmutable());
REPORTER_ASSERT(reporter, cachedBitmap.getPixels());
} else {
// unexpected, but not really a bug, since the cache is global and this test may be
// run w/ other threads competing for its budget.
SkDebugf("SkImage_Gpu2Cpu : cachedBitmap was already purged\n");
}
}
image.reset(nullptr);
{
SkBitmap cachedBitmap;
REPORTER_ASSERT(reporter, !SkBitmapCache::Find(desc, &cachedBitmap));
}
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkImage_makeTextureImage, reporter, contextInfo) {
GrContext* context = contextInfo.grContext();
sk_gpu_test::TestContext* testContext = contextInfo.testContext();
GrContextFactory otherFactory;
ContextInfo otherContextInfo = otherFactory.getContextInfo(contextInfo.type());
testContext->makeCurrent();
std::function<sk_sp<SkImage>()> imageFactories[] = {
create_image,
create_codec_image,
create_data_image,
// Create an image from a picture.
create_picture_image,
// Create a texture image.
[context] { return create_gpu_image(context); },
// Create a texture image with mips
//[context] { return create_gpu_image(context, true); },
// Create a texture image in a another GrContext.
[otherContextInfo] {
auto restore = otherContextInfo.testContext()->makeCurrentAndAutoRestore();
sk_sp<SkImage> otherContextImage = create_gpu_image(otherContextInfo.grContext());
otherContextInfo.grContext()->flush();
return otherContextImage;
}
};
sk_sp<SkColorSpace> dstColorSpaces[] ={
nullptr,
SkColorSpace::MakeSRGB(),
};
for (auto& dstColorSpace : dstColorSpaces) {
for (auto mipMapped : {GrMipMapped::kNo, GrMipMapped::kYes}) {
for (auto factory : imageFactories) {
sk_sp<SkImage> image(factory());
if (!image) {
ERRORF(reporter, "Error creating image.");
continue;
}
sk_sp<SkImage> texImage(image->makeTextureImage(context, dstColorSpace.get(),
mipMapped));
if (!texImage) {
GrContext* imageContext = as_IB(image)->context();
// We expect to fail if image comes from a different GrContext.
if (!image->isTextureBacked() || imageContext == context) {
ERRORF(reporter, "makeTextureImage failed.");
}
continue;
}
if (!texImage->isTextureBacked()) {
ERRORF(reporter, "makeTextureImage returned non-texture image.");
continue;
}
if (GrMipMapped::kYes == mipMapped &&
as_IB(texImage)->peekProxy()->mipMapped() != mipMapped &&
context->contextPriv().caps()->mipMapSupport()) {
ERRORF(reporter, "makeTextureImage returned non-mipmapped texture.");
continue;
}
if (image->isTextureBacked()) {
GrSurfaceProxy* origProxy = as_IB(image)->peekProxy();
GrSurfaceProxy* copyProxy = as_IB(texImage)->peekProxy();
if (origProxy->underlyingUniqueID() != copyProxy->underlyingUniqueID()) {
SkASSERT(origProxy->asTextureProxy());
if (GrMipMapped::kNo == mipMapped ||
GrMipMapped::kYes == origProxy->asTextureProxy()->mipMapped()) {
ERRORF(reporter, "makeTextureImage made unnecessary texture copy.");
}
}
}
if (image->width() != texImage->width() || image->height() != texImage->height()) {
ERRORF(reporter, "makeTextureImage changed the image size.");
}
if (image->alphaType() != texImage->alphaType()) {
ERRORF(reporter, "makeTextureImage changed image alpha type.");
}
}
}
context->flush();
}
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkImage_makeNonTextureImage, reporter, contextInfo) {
GrContext* context = contextInfo.grContext();
std::function<sk_sp<SkImage>()> imageFactories[] = {
create_image,
create_codec_image,
create_data_image,
create_picture_image,
[context] { return create_gpu_image(context); },
};
SkColorSpace* legacyColorSpace = nullptr;
for (auto factory : imageFactories) {
sk_sp<SkImage> image = factory();
if (!image->isTextureBacked()) {
REPORTER_ASSERT(reporter, image->makeNonTextureImage().get() == image.get());
if (!(image = image->makeTextureImage(context, legacyColorSpace))) {
continue;
}
}
auto rasterImage = image->makeNonTextureImage();
if (!rasterImage) {
ERRORF(reporter, "makeNonTextureImage failed for texture-backed image.");
}
REPORTER_ASSERT(reporter, !rasterImage->isTextureBacked());
assert_equal(reporter, image.get(), nullptr, rasterImage.get());
}
}
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(SkImage_drawAbandonedGpuImage, reporter, contextInfo) {
auto context = contextInfo.grContext();
auto image = create_gpu_image(context);
auto info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info));
image->getTexture()->abandon();
surface->getCanvas()->drawImage(image, 0, 0);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrContext_colorTypeSupportedAsImage, reporter, ctxInfo) {
for (int ct = 0; ct < kLastEnum_SkColorType; ++ct) {
static constexpr int kSize = 10;
SkColorType colorType = static_cast<SkColorType>(ct);
bool can = ctxInfo.grContext()->colorTypeSupportedAsImage(colorType);
auto* gpu = ctxInfo.grContext()->contextPriv().getGpu();
GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
nullptr, kSize, kSize, colorType, false, GrMipMapped::kNo);
auto img =
SkImage::MakeFromTexture(ctxInfo.grContext(), backendTex, kTopLeft_GrSurfaceOrigin,
colorType, kOpaque_SkAlphaType, nullptr);
REPORTER_ASSERT(reporter, can == SkToBool(img),
"colorTypeSupportedAsImage:%d, actual:%d, ct:%d", can, SkToBool(img),
colorType);
img.reset();
ctxInfo.grContext()->flush();
if (backendTex.isValid()) {
gpu->deleteTestingOnlyBackendTexture(backendTex);
}
}
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(UnpremulTextureImage, reporter, ctxInfo) {
SkBitmap bmp;
bmp.allocPixels(
SkImageInfo::Make(256, 256, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, nullptr));
for (int y = 0; y < 256; ++y) {
for (int x = 0; x < 256; ++x) {
*bmp.getAddr32(x, y) =
SkColorSetARGB((U8CPU)y, 255 - (U8CPU)y, (U8CPU)x, 255 - (U8CPU)x);
}
}
auto texImage = SkImage::MakeFromBitmap(bmp)->makeTextureImage(ctxInfo.grContext(), nullptr);
if (!texImage || texImage->alphaType() != kUnpremul_SkAlphaType) {
ERRORF(reporter, "Failed to make unpremul texture image.");
return;
}
// The GPU backend always unpremuls the values stored in the texture because it assumes they
// are premul values. (skbug.com/7580).
if (false) {
SkBitmap unpremul;
unpremul.allocPixels(SkImageInfo::Make(256, 256, kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType, nullptr));
if (!texImage->readPixels(unpremul.info(), unpremul.getPixels(), unpremul.rowBytes(), 0,
0)) {
ERRORF(reporter, "Unpremul readback failed.");
return;
}
for (int y = 0; y < 256; ++y) {
for (int x = 0; x < 256; ++x) {
if (*bmp.getAddr32(x, y) != *unpremul.getAddr32(x, y)) {
ERRORF(reporter, "unpremul(0x%08x)->unpremul(0x%08x) at %d, %d.",
*bmp.getAddr32(x, y), *unpremul.getAddr32(x, y), x, y);
return;
}
}
}
}
SkBitmap premul;
premul.allocPixels(
SkImageInfo::Make(256, 256, kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr));
if (!texImage->readPixels(premul.info(), premul.getPixels(), premul.rowBytes(), 0, 0)) {
ERRORF(reporter, "Unpremul readback failed.");
return;
}
for (int y = 0; y < 256; ++y) {
for (int x = 0; x < 256; ++x) {
// Treat bmp's color as a pm color even though it may be the r/b swap of a PM color.
// SkPremultiplyColor acts the same on both channels.
uint32_t origColor = SkPreMultiplyColor(*bmp.getAddr32(x, y));
int32_t origA = (origColor >> 24) & 0xff;
int32_t origB = (origColor >> 16) & 0xff;
int32_t origG = (origColor >> 8) & 0xff;
int32_t origR = (origColor >> 0) & 0xff;
uint32_t read = *premul.getAddr32(x, y);
int32_t readA = (read >> 24) & 0xff;
int32_t readB = (read >> 16) & 0xff;
int32_t readG = (read >> 8) & 0xff;
int32_t readR = (read >> 0) & 0xff;
// We expect that alpha=1 and alpha=0 should come out exact. Otherwise allow a little
// bit of tolerance for GPU vs CPU premul math.
int32_t tol = (origA == 0 || origA == 255) ? 0 : 1;
if (origA != readA || SkTAbs(readB - origB) > tol || SkTAbs(readG - origG) > tol ||
SkTAbs(readR - origR) > tol) {
ERRORF(reporter, "unpremul(0x%08x)->premul(0x%08x) at %d, %d.",
*bmp.getAddr32(x, y), *premul.getAddr32(x, y), x, y);
return;
}
}
}
}
DEF_GPUTEST(AbandonedContextImage, reporter, options) {
using Factory = sk_gpu_test::GrContextFactory;
for (int ct = 0; ct < Factory::kContextTypeCnt; ++ct) {
auto type = static_cast<Factory::ContextType>(ct);
std::unique_ptr<Factory> factory(new Factory);
if (!factory->get(type)) {
continue;
}
sk_sp<SkImage> img;
auto gsurf = SkSurface::MakeRenderTarget(
factory->get(type), SkBudgeted::kYes,
SkImageInfo::Make(100, 100, kRGBA_8888_SkColorType, kPremul_SkAlphaType), 1,
nullptr);
if (!gsurf) {
continue;
}
img = gsurf->makeImageSnapshot();
gsurf.reset();
auto rsurf = SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(100, 100));
REPORTER_ASSERT(reporter, img->isValid(factory->get(type)));
REPORTER_ASSERT(reporter, img->isValid(rsurf->getCanvas()->getGrContext()));
factory->get(type)->abandonContext();
REPORTER_ASSERT(reporter, !img->isValid(factory->get(type)));
REPORTER_ASSERT(reporter, !img->isValid(rsurf->getCanvas()->getGrContext()));
// This shouldn't crash.
rsurf->getCanvas()->drawImage(img, 0, 0);
// Give up all other refs on GrContext.
factory.reset(nullptr);
REPORTER_ASSERT(reporter, !img->isValid(rsurf->getCanvas()->getGrContext()));
// This shouldn't crash.
rsurf->getCanvas()->drawImage(img, 0, 0);
}
}
class EmptyGenerator : public SkImageGenerator {
public:
EmptyGenerator() : SkImageGenerator(SkImageInfo::MakeN32Premul(0, 0)) {}
};
DEF_TEST(ImageEmpty, reporter) {
const SkImageInfo info = SkImageInfo::Make(0, 0, kN32_SkColorType, kPremul_SkAlphaType);
SkPixmap pmap(info, nullptr, 0);
REPORTER_ASSERT(reporter, nullptr == SkImage::MakeRasterCopy(pmap));
REPORTER_ASSERT(reporter, nullptr == SkImage::MakeRasterData(info, nullptr, 0));
REPORTER_ASSERT(reporter, nullptr == SkImage::MakeFromRaster(pmap, nullptr, nullptr));
REPORTER_ASSERT(reporter, nullptr == SkImage::MakeFromGenerator(
skstd::make_unique<EmptyGenerator>()));
}
DEF_TEST(ImageDataRef, reporter) {
SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
size_t rowBytes = info.minRowBytes();
size_t size = info.computeByteSize(rowBytes);
sk_sp<SkData> data = SkData::MakeUninitialized(size);
REPORTER_ASSERT(reporter, data->unique());
sk_sp<SkImage> image = SkImage::MakeRasterData(info, data, rowBytes);
REPORTER_ASSERT(reporter, !data->unique());
image.reset();
REPORTER_ASSERT(reporter, data->unique());
}
static bool has_pixels(const SkPMColor pixels[], int count, SkPMColor expected) {
for (int i = 0; i < count; ++i) {
if (pixels[i] != expected) {
return false;
}
}
return true;
}
static void image_test_read_pixels(skiatest::Reporter* reporter, SkImage* image) {
if (!image) {
ERRORF(reporter, "Failed to create image!");
return;
}
const SkPMColor expected = SkPreMultiplyColor(SK_ColorWHITE);
const SkPMColor notExpected = ~expected;
const int w = 2, h = 2;
const size_t rowBytes = w * sizeof(SkPMColor);
SkPMColor pixels[w*h];
SkImageInfo info;
info = SkImageInfo::MakeUnknown(w, h);
REPORTER_ASSERT(reporter, !image->readPixels(info, pixels, rowBytes, 0, 0));
// out-of-bounds should fail
info = SkImageInfo::MakeN32Premul(w, h);
REPORTER_ASSERT(reporter, !image->readPixels(info, pixels, rowBytes, -w, 0));
REPORTER_ASSERT(reporter, !image->readPixels(info, pixels, rowBytes, 0, -h));
REPORTER_ASSERT(reporter, !image->readPixels(info, pixels, rowBytes, image->width(), 0));
REPORTER_ASSERT(reporter, !image->readPixels(info, pixels, rowBytes, 0, image->height()));
// top-left should succeed
sk_memset32(pixels, notExpected, w*h);
REPORTER_ASSERT(reporter, image->readPixels(info, pixels, rowBytes, 0, 0));
REPORTER_ASSERT(reporter, has_pixels(pixels, w*h, expected));
// bottom-right should succeed
sk_memset32(pixels, notExpected, w*h);
REPORTER_ASSERT(reporter, image->readPixels(info, pixels, rowBytes,
image->width() - w, image->height() - h));
REPORTER_ASSERT(reporter, has_pixels(pixels, w*h, expected));
// partial top-left should succeed
sk_memset32(pixels, notExpected, w*h);
REPORTER_ASSERT(reporter, image->readPixels(info, pixels, rowBytes, -1, -1));
REPORTER_ASSERT(reporter, pixels[3] == expected);
REPORTER_ASSERT(reporter, has_pixels(pixels, w*h - 1, notExpected));
// partial bottom-right should succeed
sk_memset32(pixels, notExpected, w*h);
REPORTER_ASSERT(reporter, image->readPixels(info, pixels, rowBytes,
image->width() - 1, image->height() - 1));
REPORTER_ASSERT(reporter, pixels[0] == expected);
REPORTER_ASSERT(reporter, has_pixels(&pixels[1], w*h - 1, notExpected));
}
DEF_TEST(ImageReadPixels, reporter) {
sk_sp<SkImage> image(create_image());
image_test_read_pixels(reporter, image.get());
image = create_data_image();
image_test_read_pixels(reporter, image.get());
RasterDataHolder dataHolder;
image = create_rasterproc_image(&dataHolder);
image_test_read_pixels(reporter, image.get());
image.reset();
REPORTER_ASSERT(reporter, 1 == dataHolder.fReleaseCount);
image = create_codec_image();
image_test_read_pixels(reporter, image.get());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageReadPixels_Gpu, reporter, ctxInfo) {
image_test_read_pixels(reporter, create_gpu_image(ctxInfo.grContext()).get());
}
static void check_legacy_bitmap(skiatest::Reporter* reporter, const SkImage* image,
const SkBitmap& bitmap) {
REPORTER_ASSERT(reporter, image->width() == bitmap.width());
REPORTER_ASSERT(reporter, image->height() == bitmap.height());
REPORTER_ASSERT(reporter, image->alphaType() == bitmap.alphaType());
REPORTER_ASSERT(reporter, bitmap.isImmutable());
REPORTER_ASSERT(reporter, bitmap.getPixels());
const SkImageInfo info = SkImageInfo::MakeN32(1, 1, bitmap.alphaType());
SkPMColor imageColor;
REPORTER_ASSERT(reporter, image->readPixels(info, &imageColor, sizeof(SkPMColor), 0, 0));
REPORTER_ASSERT(reporter, imageColor == *bitmap.getAddr32(0, 0));
}
static void test_legacy_bitmap(skiatest::Reporter* reporter, const SkImage* image) {
if (!image) {
ERRORF(reporter, "Failed to create image.");
return;
}
SkBitmap bitmap;
REPORTER_ASSERT(reporter, image->asLegacyBitmap(&bitmap));
check_legacy_bitmap(reporter, image, bitmap);
// Test subsetting to exercise the rowBytes logic.
SkBitmap tmp;
REPORTER_ASSERT(reporter, bitmap.extractSubset(&tmp, SkIRect::MakeWH(image->width() / 2,
image->height() / 2)));
sk_sp<SkImage> subsetImage(SkImage::MakeFromBitmap(tmp));
REPORTER_ASSERT(reporter, subsetImage.get());
SkBitmap subsetBitmap;
REPORTER_ASSERT(reporter, subsetImage->asLegacyBitmap(&subsetBitmap));
check_legacy_bitmap(reporter, subsetImage.get(), subsetBitmap);
}
DEF_TEST(ImageLegacyBitmap, reporter) {
sk_sp<SkImage> image(create_image());
test_legacy_bitmap(reporter, image.get());
image = create_data_image();
test_legacy_bitmap(reporter, image.get());
RasterDataHolder dataHolder;
image = create_rasterproc_image(&dataHolder);
test_legacy_bitmap(reporter, image.get());
image.reset();
REPORTER_ASSERT(reporter, 1 == dataHolder.fReleaseCount);
image = create_codec_image();
test_legacy_bitmap(reporter, image.get());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageLegacyBitmap_Gpu, reporter, ctxInfo) {
sk_sp<SkImage> image(create_gpu_image(ctxInfo.grContext()));
test_legacy_bitmap(reporter, image.get());
}
static void test_peek(skiatest::Reporter* reporter, SkImage* image, bool expectPeekSuccess) {
if (!image) {
ERRORF(reporter, "Failed to create image!");
return;
}
SkPixmap pm;
bool success = image->peekPixels(&pm);
REPORTER_ASSERT(reporter, expectPeekSuccess == success);
if (success) {
const SkImageInfo& info = pm.info();
REPORTER_ASSERT(reporter, 20 == info.width());
REPORTER_ASSERT(reporter, 20 == info.height());
REPORTER_ASSERT(reporter, kN32_SkColorType == info.colorType());
REPORTER_ASSERT(reporter, kPremul_SkAlphaType == info.alphaType() ||
kOpaque_SkAlphaType == info.alphaType());
REPORTER_ASSERT(reporter, info.minRowBytes() <= pm.rowBytes());
REPORTER_ASSERT(reporter, SkPreMultiplyColor(SK_ColorWHITE) == *pm.addr32(0, 0));
}
}
DEF_TEST(ImagePeek, reporter) {
sk_sp<SkImage> image(create_image());
test_peek(reporter, image.get(), true);
image = create_data_image();
test_peek(reporter, image.get(), true);
RasterDataHolder dataHolder;
image = create_rasterproc_image(&dataHolder);
test_peek(reporter, image.get(), true);
image.reset();
REPORTER_ASSERT(reporter, 1 == dataHolder.fReleaseCount);
image = create_codec_image();
test_peek(reporter, image.get(), false);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImagePeek_Gpu, reporter, ctxInfo) {
sk_sp<SkImage> image(create_gpu_image(ctxInfo.grContext()));
test_peek(reporter, image.get(), false);
}
struct TextureReleaseChecker {
TextureReleaseChecker() : fReleaseCount(0) {}
int fReleaseCount;
static void Release(void* self) {
static_cast<TextureReleaseChecker*>(self)->fReleaseCount++;
}
};
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(SkImage_NewFromTextureRelease, reporter, ctxInfo) {
const int kWidth = 10;
const int kHeight = 10;
std::unique_ptr<uint32_t[]> pixels(new uint32_t[kWidth * kHeight]);
GrContext* ctx = ctxInfo.grContext();
GrGpu* gpu = ctx->contextPriv().getGpu();
GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
pixels.get(), kWidth, kHeight, kRGBA_8888_GrPixelConfig, true, GrMipMapped::kNo);
TextureReleaseChecker releaseChecker;
GrSurfaceOrigin texOrigin = kBottomLeft_GrSurfaceOrigin;
sk_sp<SkImage> refImg(
SkImage::MakeFromTexture(ctx, backendTex, texOrigin, kRGBA_8888_SkColorType,
kPremul_SkAlphaType, nullptr,
TextureReleaseChecker::Release, &releaseChecker));
GrSurfaceOrigin readBackOrigin;
GrBackendTexture readBackBackendTex = refImg->getBackendTexture(false, &readBackOrigin);
readBackBackendTex.setPixelConfig(kRGBA_8888_GrPixelConfig);
if (!GrBackendTexture::TestingOnly_Equals(readBackBackendTex, backendTex)) {
ERRORF(reporter, "backend mismatch\n");
}
REPORTER_ASSERT(reporter, GrBackendTexture::TestingOnly_Equals(readBackBackendTex, backendTex));
if (readBackOrigin != texOrigin) {
ERRORF(reporter, "origin mismatch %d %d\n", readBackOrigin, texOrigin);
}
REPORTER_ASSERT(reporter, readBackOrigin == texOrigin);
// Now exercise the release proc
REPORTER_ASSERT(reporter, 0 == releaseChecker.fReleaseCount);
refImg.reset(nullptr); // force a release of the image
REPORTER_ASSERT(reporter, 1 == releaseChecker.fReleaseCount);
gpu->deleteTestingOnlyBackendTexture(backendTex);
}
static void test_cross_context_image(skiatest::Reporter* reporter, const GrContextOptions& options,
const char* testName,
std::function<sk_sp<SkImage>(GrContext*)> imageMaker) {
for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
GrContextFactory testFactory(options);
GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
ContextInfo ctxInfo = testFactory.getContextInfo(ctxType);
GrContext* ctx = ctxInfo.grContext();
if (!ctx) {
continue;
}
// If we don't have proper support for this feature, the factory will fallback to returning
// codec-backed images. Those will "work", but some of our checks will fail because we
// expect the cross-context images not to work on multiple contexts at once.
if (!ctx->contextPriv().caps()->crossContextTextureSupport()) {
continue;
}
// We test three lifetime patterns for a single context:
// 1) Create image, free image
// 2) Create image, draw, flush, free image
// 3) Create image, draw, free image, flush
// ... and then repeat the last two patterns with drawing on a second* context:
// 4) Create image, draw*, flush*, free image
// 5) Create image, draw*, free iamge, flush*
// Case #1: Create image, free image
{
sk_sp<SkImage> refImg(imageMaker(ctx));
refImg.reset(nullptr); // force a release of the image
}
SkImageInfo info = SkImageInfo::MakeN32Premul(128, 128);
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info);
if (!surface) {
ERRORF(reporter, "SkSurface::MakeRenderTarget failed for %s.", testName);
continue;
}
SkCanvas* canvas = surface->getCanvas();
// Case #2: Create image, draw, flush, free image
{
sk_sp<SkImage> refImg(imageMaker(ctx));
canvas->drawImage(refImg, 0, 0);
canvas->flush();
refImg.reset(nullptr); // force a release of the image
}
// Case #3: Create image, draw, free image, flush
{
sk_sp<SkImage> refImg(imageMaker(ctx));
canvas->drawImage(refImg, 0, 0);
refImg.reset(nullptr); // force a release of the image
canvas->flush();
}
// Configure second context
sk_gpu_test::TestContext* testContext = ctxInfo.testContext();
ContextInfo otherContextInfo = testFactory.getSharedContextInfo(ctx);
GrContext* otherCtx = otherContextInfo.grContext();
sk_gpu_test::TestContext* otherTestContext = otherContextInfo.testContext();
// Creating a context in a share group may fail
if (!otherCtx) {
continue;
}
surface = SkSurface::MakeRenderTarget(otherCtx, SkBudgeted::kNo, info);
canvas = surface->getCanvas();
// Case #4: Create image, draw*, flush*, free image
{
testContext->makeCurrent();
sk_sp<SkImage> refImg(imageMaker(ctx));
otherTestContext->makeCurrent();
canvas->drawImage(refImg, 0, 0);
canvas->flush();
testContext->makeCurrent();
refImg.reset(nullptr); // force a release of the image
}
// Case #5: Create image, draw*, free image, flush*
{
testContext->makeCurrent();
sk_sp<SkImage> refImg(imageMaker(ctx));
otherTestContext->makeCurrent();
canvas->drawImage(refImg, 0, 0);
testContext->makeCurrent();
refImg.reset(nullptr); // force a release of the image
otherTestContext->makeCurrent();
canvas->flush();
// This is specifically here for vulkan to guarantee the command buffer will finish
// which is when we call the ReleaseProc.
otherCtx->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
}
// Case #6: Verify that only one context can be using the image at a time
{
testContext->makeCurrent();
sk_sp<SkImage> refImg(imageMaker(ctx));
// Any context should be able to borrow the texture at this point
sk_sp<SkColorSpace> texColorSpace;
sk_sp<GrTextureProxy> proxy = as_IB(refImg)->asTextureProxyRef(
ctx, GrSamplerState::ClampNearest(), nullptr, &texColorSpace, nullptr);
REPORTER_ASSERT(reporter, proxy);
// But once it's borrowed, no other context should be able to borrow
otherTestContext->makeCurrent();
sk_sp<GrTextureProxy> otherProxy = as_IB(refImg)->asTextureProxyRef(
otherCtx, GrSamplerState::ClampNearest(), nullptr, &texColorSpace, nullptr);
REPORTER_ASSERT(reporter, !otherProxy);
// Original context (that's already borrowing) should be okay
testContext->makeCurrent();
sk_sp<GrTextureProxy> proxySecondRef = as_IB(refImg)->asTextureProxyRef(
ctx, GrSamplerState::ClampNearest(), nullptr, &texColorSpace, nullptr);
REPORTER_ASSERT(reporter, proxySecondRef);
// Release first ref from the original context
proxy.reset(nullptr);
// We released one proxy but not the other from the current borrowing context. Make sure
// a new context is still not able to borrow the texture.
otherTestContext->makeCurrent();
otherProxy = as_IB(refImg)->asTextureProxyRef(otherCtx, GrSamplerState::ClampNearest(),
nullptr, &texColorSpace, nullptr);
REPORTER_ASSERT(reporter, !otherProxy);
// Release second ref from the original context
testContext->makeCurrent();
proxySecondRef.reset(nullptr);
// Now we should be able to borrow the texture from the other context
otherTestContext->makeCurrent();
otherProxy = as_IB(refImg)->asTextureProxyRef(otherCtx, GrSamplerState::ClampNearest(),
nullptr, &texColorSpace, nullptr);
REPORTER_ASSERT(reporter, otherProxy);
// Release everything
otherProxy.reset(nullptr);
refImg.reset(nullptr);
}
}
}
DEF_GPUTEST(SkImage_MakeCrossContextFromEncodedRelease, reporter, options) {
sk_sp<SkData> data = GetResourceAsData("images/mandrill_128.png");
SkASSERT(data.get());
test_cross_context_image(reporter, options, "SkImage_MakeCrossContextFromEncodedRelease",
[&data](GrContext* ctx) {
return SkImage::MakeCrossContextFromEncoded(ctx, data, false, nullptr);
});
}
DEF_GPUTEST(SkImage_MakeCrossContextFromPixmapRelease, reporter, options) {
SkBitmap bitmap;
SkPixmap pixmap;
SkAssertResult(GetResourceAsBitmap("images/mandrill_128.png", &bitmap) && bitmap.peekPixels(&pixmap));
test_cross_context_image(reporter, options, "SkImage_MakeCrossContextFromPixmapRelease",
[&pixmap](GrContext* ctx) {
return SkImage::MakeCrossContextFromPixmap(ctx, pixmap, false, nullptr);
});
}
DEF_GPUTEST(SkImage_CrossContextGrayAlphaConfigs, reporter, options) {
for (SkColorType ct : { kGray_8_SkColorType, kAlpha_8_SkColorType }) {
SkAutoPixmapStorage pixmap;
pixmap.alloc(SkImageInfo::Make(4, 4, ct, kPremul_SkAlphaType));
for (int i = 0; i < GrContextFactory::kContextTypeCnt; ++i) {
GrContextFactory testFactory(options);
GrContextFactory::ContextType ctxType = static_cast<GrContextFactory::ContextType>(i);
ContextInfo ctxInfo = testFactory.getContextInfo(ctxType);
GrContext* ctx = ctxInfo.grContext();
if (!ctx || !ctx->contextPriv().caps()->crossContextTextureSupport()) {
continue;
}
sk_sp<SkImage> image = SkImage::MakeCrossContextFromPixmap(ctx, pixmap, false, nullptr);
REPORTER_ASSERT(reporter, image);
sk_sp<SkColorSpace> texColorSpace;
sk_sp<GrTextureProxy> proxy = as_IB(image)->asTextureProxyRef(
ctx, GrSamplerState::ClampNearest(), nullptr, &texColorSpace, nullptr);
REPORTER_ASSERT(reporter, proxy);
bool expectAlpha = kAlpha_8_SkColorType == ct;
REPORTER_ASSERT(reporter, expectAlpha == GrPixelConfigIsAlphaOnly(proxy->config()));
}
}
}
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(makeBackendTexture, reporter, ctxInfo) {
GrContext* context = ctxInfo.grContext();
sk_gpu_test::TestContext* testContext = ctxInfo.testContext();
sk_sp<GrContextThreadSafeProxy> proxy = context->threadSafeProxy();
GrContextFactory otherFactory;
ContextInfo otherContextInfo = otherFactory.getContextInfo(ctxInfo.type());
testContext->makeCurrent();
REPORTER_ASSERT(reporter, proxy);
auto createLarge = [context] {
return create_image_large(context->contextPriv().caps()->maxTextureSize());
};
struct {
std::function<sk_sp<SkImage> ()> fImageFactory;
bool fExpectation;
bool fCanTakeDirectly;
} testCases[] = {
{ create_image, true, false },
{ create_codec_image, true, false },
{ create_data_image, true, false },
{ create_picture_image, true, false },
{ [context] { return create_gpu_image(context); }, true, true },
// Create a texture image in a another GrContext.
{ [otherContextInfo] {
auto restore = otherContextInfo.testContext()->makeCurrentAndAutoRestore();
sk_sp<SkImage> otherContextImage = create_gpu_image(otherContextInfo.grContext());
otherContextInfo.grContext()->flush();
return otherContextImage;
}, false, false },
// Create an image that is too large to be texture backed.
{ createLarge, false, false }
};
for (auto testCase : testCases) {
sk_sp<SkImage> image(testCase.fImageFactory());
if (!image) {
ERRORF(reporter, "Failed to create image!");
continue;
}
GrBackendTexture origBackend = image->getBackendTexture(true);
if (testCase.fCanTakeDirectly) {
SkASSERT(origBackend.isValid());
}
GrBackendTexture newBackend;
SkImage::BackendTextureReleaseProc proc;
bool result = SkImage::MakeBackendTextureFromSkImage(context, std::move(image),
&newBackend, &proc);
if (result != testCase.fExpectation) {
static const char *const kFS[] = { "fail", "succeed" };
ERRORF(reporter, "This image was expected to %s but did not.",
kFS[testCase.fExpectation]);
}
if (result) {
SkASSERT(newBackend.isValid());
}
bool tookDirectly = result && GrBackendTexture::TestingOnly_Equals(origBackend, newBackend);
if (testCase.fCanTakeDirectly != tookDirectly) {
static const char *const kExpectedState[] = { "not expected", "expected" };
ERRORF(reporter, "This backend texture was %s to be taken directly.",
kExpectedState[testCase.fCanTakeDirectly]);
}
context->flush();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static sk_sp<SkImage> create_picture_image(sk_sp<SkColorSpace> space) {
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(10, 10);
canvas->clear(SK_ColorCYAN);
return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(), SkISize::Make(10, 10),
nullptr, nullptr, SkImage::BitDepth::kU8, std::move(space));
};
DEF_TEST(Image_ColorSpace, r) {
sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
sk_sp<SkImage> image = GetResourceAsImage("images/mandrill_512_q075.jpg");
REPORTER_ASSERT(r, srgb.get() == image->colorSpace());
image = GetResourceAsImage("images/webp-color-profile-lossy.webp");
SkColorSpaceTransferFn fn;
bool success = image->colorSpace()->isNumericalTransferFn(&fn);
REPORTER_ASSERT(r, success);
REPORTER_ASSERT(r, color_space_almost_equal(1.8f, fn.fG));
sk_sp<SkColorSpace> rec2020 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
SkColorSpace::kRec2020_Gamut);
image = create_picture_image(rec2020);
REPORTER_ASSERT(r, SkColorSpace::Equals(rec2020.get(), image->colorSpace()));
SkBitmap bitmap;
SkImageInfo info = SkImageInfo::MakeN32(10, 10, kPremul_SkAlphaType, rec2020);
bitmap.allocPixels(info);
image = SkImage::MakeFromBitmap(bitmap);
REPORTER_ASSERT(r, SkColorSpace::Equals(rec2020.get(), image->colorSpace()));
sk_sp<SkSurface> surface = SkSurface::MakeRaster(
SkImageInfo::MakeN32Premul(SkISize::Make(10, 10)));
image = surface->makeImageSnapshot();
REPORTER_ASSERT(r, nullptr == image->colorSpace());
surface = SkSurface::MakeRaster(info);
image = surface->makeImageSnapshot();
REPORTER_ASSERT(r, SkColorSpace::Equals(rec2020.get(), image->colorSpace()));
}
DEF_TEST(Image_makeColorSpace, r) {
sk_sp<SkColorSpace> p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
SkColorSpace::kDCIP3_D65_Gamut);
SkColorSpaceTransferFn fn;
fn.fA = 1.f; fn.fB = 0.f; fn.fC = 0.f; fn.fD = 0.f; fn.fE = 0.f; fn.fF = 0.f; fn.fG = 1.8f;
sk_sp<SkColorSpace> adobeGamut = SkColorSpace::MakeRGB(fn, SkColorSpace::kAdobeRGB_Gamut);
SkBitmap srgbBitmap;
srgbBitmap.allocPixels(SkImageInfo::MakeS32(1, 1, kOpaque_SkAlphaType));
*srgbBitmap.getAddr32(0, 0) = SkSwizzle_RGBA_to_PMColor(0xFF604020);
srgbBitmap.setImmutable();
sk_sp<SkImage> srgbImage = SkImage::MakeFromBitmap(srgbBitmap);
sk_sp<SkImage> p3Image = srgbImage->makeColorSpace(p3);
SkBitmap p3Bitmap;
bool success = p3Image->asLegacyBitmap(&p3Bitmap);
auto almost_equal = [](int a, int b) { return SkTAbs(a - b) <= 2; };
REPORTER_ASSERT(r, success);
REPORTER_ASSERT(r, almost_equal(0x28, SkGetPackedR32(*p3Bitmap.getAddr32(0, 0))));
REPORTER_ASSERT(r, almost_equal(0x40, SkGetPackedG32(*p3Bitmap.getAddr32(0, 0))));
REPORTER_ASSERT(r, almost_equal(0x5E, SkGetPackedB32(*p3Bitmap.getAddr32(0, 0))));
sk_sp<SkImage> adobeImage = srgbImage->makeColorSpace(adobeGamut);
SkBitmap adobeBitmap;
success = adobeImage->asLegacyBitmap(&adobeBitmap);
REPORTER_ASSERT(r, success);
REPORTER_ASSERT(r, almost_equal(0x21, SkGetPackedR32(*adobeBitmap.getAddr32(0, 0))));
REPORTER_ASSERT(r, almost_equal(0x31, SkGetPackedG32(*adobeBitmap.getAddr32(0, 0))));
REPORTER_ASSERT(r, almost_equal(0x4C, SkGetPackedB32(*adobeBitmap.getAddr32(0, 0))));
srgbImage = GetResourceAsImage("images/1x1.png");
p3Image = srgbImage->makeColorSpace(p3);
success = p3Image->asLegacyBitmap(&p3Bitmap);
REPORTER_ASSERT(r, success);
REPORTER_ASSERT(r, almost_equal(0x8B, SkGetPackedR32(*p3Bitmap.getAddr32(0, 0))));
REPORTER_ASSERT(r, almost_equal(0x82, SkGetPackedG32(*p3Bitmap.getAddr32(0, 0))));
REPORTER_ASSERT(r, almost_equal(0x77, SkGetPackedB32(*p3Bitmap.getAddr32(0, 0))));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static void make_all_premul(SkBitmap* bm) {
bm->allocPixels(SkImageInfo::MakeN32(256, 256, kPremul_SkAlphaType));
for (int a = 0; a < 256; ++a) {
for (int r = 0; r < 256; ++r) {
// make all valid premul combinations
int c = SkTMin(a, r);
*bm->getAddr32(a, r) = SkPackARGB32(a, c, c, c);
}
}
}
static bool equal(const SkBitmap& a, const SkBitmap& b) {
SkASSERT(a.width() == b.width());
SkASSERT(a.height() == b.height());
for (int y = 0; y < a.height(); ++y) {
for (int x = 0; x < a.width(); ++x) {
SkPMColor pa = *a.getAddr32(x, y);
SkPMColor pb = *b.getAddr32(x, y);
if (pa != pb) {
return false;
}
}
}
return true;
}
DEF_TEST(image_roundtrip_encode, reporter) {
SkBitmap bm0;
make_all_premul(&bm0);
auto img0 = SkImage::MakeFromBitmap(bm0);
sk_sp<SkData> data = img0->encodeToData(SkEncodedImageFormat::kPNG, 100);
auto img1 = SkImage::MakeFromEncoded(data);
SkBitmap bm1;
bm1.allocPixels(SkImageInfo::MakeN32(256, 256, kPremul_SkAlphaType));
img1->readPixels(bm1.info(), bm1.getPixels(), bm1.rowBytes(), 0, 0);
REPORTER_ASSERT(reporter, equal(bm0, bm1));
}
DEF_TEST(image_roundtrip_premul, reporter) {
SkBitmap bm0;
make_all_premul(&bm0);
SkBitmap bm1;
bm1.allocPixels(SkImageInfo::MakeN32(256, 256, kUnpremul_SkAlphaType));
bm0.readPixels(bm1.info(), bm1.getPixels(), bm1.rowBytes(), 0, 0);
SkBitmap bm2;
bm2.allocPixels(SkImageInfo::MakeN32(256, 256, kPremul_SkAlphaType));
bm1.readPixels(bm2.info(), bm2.getPixels(), bm2.rowBytes(), 0, 0);
REPORTER_ASSERT(reporter, equal(bm0, bm2));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static void check_scaled_pixels(skiatest::Reporter* reporter, SkPixmap* pmap, uint32_t expected) {
// Verify that all pixels contain the original test color
for (auto y = 0; y < pmap->height(); ++y) {
for (auto x = 0; x < pmap->width(); ++x) {
uint32_t pixel = *pmap->addr32(x, y);
if (pixel != expected) {
ERRORF(reporter, "Expected scaled pixels to be the same. At %d,%d 0x%08x != 0x%08x",
x, y, pixel, expected);
return;
}
}
}
}
static void test_scale_pixels(skiatest::Reporter* reporter, const SkImage* image,
uint32_t expected) {
SkImageInfo info = SkImageInfo::MakeN32Premul(image->width() * 2, image->height() * 2);
// Make sure to test kDisallow first, so we don't just get a cache hit in that case
for (auto chint : { SkImage::kDisallow_CachingHint, SkImage::kAllow_CachingHint }) {
SkAutoPixmapStorage scaled;
scaled.alloc(info);
if (!image->scalePixels(scaled, kLow_SkFilterQuality, chint)) {
ERRORF(reporter, "Failed to scale image");
continue;
}
check_scaled_pixels(reporter, &scaled, expected);
}
}
DEF_TEST(ImageScalePixels, reporter) {
const SkPMColor pmRed = SkPackARGB32(0xFF, 0xFF, 0, 0);
const SkColor red = SK_ColorRED;
// Test raster image
SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
surface->getCanvas()->clear(red);
sk_sp<SkImage> rasterImage = surface->makeImageSnapshot();
test_scale_pixels(reporter, rasterImage.get(), pmRed);
// Test encoded image
sk_sp<SkData> data = rasterImage->encodeToData();
sk_sp<SkImage> codecImage = SkImage::MakeFromEncoded(data);
test_scale_pixels(reporter, codecImage.get(), pmRed);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageScalePixels_Gpu, reporter, ctxInfo) {
const SkPMColor pmRed = SkPackARGB32(0xFF, 0xFF, 0, 0);
const SkColor red = SK_ColorRED;
SkImageInfo info = SkImageInfo::MakeN32Premul(16, 16);
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(ctxInfo.grContext(), SkBudgeted::kNo,
info);
surface->getCanvas()->clear(red);
sk_sp<SkImage> gpuImage = surface->makeImageSnapshot();
test_scale_pixels(reporter, gpuImage.get(), pmRed);
}
static sk_sp<SkImage> any_image_will_do() {
return GetResourceAsImage("images/mandrill_32.png");
}
DEF_TEST(Image_nonfinite_dst, reporter) {
auto surf = SkSurface::MakeRasterN32Premul(10, 10);
auto img = any_image_will_do();
SkPaint paint;
for (SkScalar bad : { SK_ScalarInfinity, SK_ScalarNaN}) {
for (int bits = 1; bits <= 15; ++bits) {
SkRect dst = { 0, 0, 10, 10 };
if (bits & 1) dst.fLeft = bad;
if (bits & 2) dst.fTop = bad;
if (bits & 4) dst.fRight = bad;
if (bits & 8) dst.fBottom = bad;
surf->getCanvas()->drawImageRect(img, dst, &paint);
// we should draw nothing
sk_tool_utils::PixelIter iter(surf.get());
while (void* addr = iter.next()) {
REPORTER_ASSERT(reporter, *(SkPMColor*)addr == 0);
}
}
}
}