Add SkImage::reinterpretColorSpace

This reinterprets the contents of an image as though they were in a
different color space. Helpful for tools and debug visualization.

Bug: chromium:795132 chromium:985500
Change-Id: Ia8739bbd73d72249b8bee9d51dfa11d560a2a1f5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/234328
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2019-08-14 16:14:51 -04:00 committed by Skia Commit-Bot
parent 30ad4680ad
commit d514837136
13 changed files with 153 additions and 85 deletions

View File

@ -31,3 +31,5 @@ Milestone 78
or an image sequence for a container format that has both (e.g. HEIF).
* SkImage::makeTextureImage no longer takes an SkColorSpace parameter. It was unused.
* SkImage::reinterpretColorSpace - to reinterpret image contents in a new color space.

View File

@ -43,59 +43,40 @@ sk_sp<SkImage> make_color_space(sk_sp<SkImage> orig, sk_sp<SkColorSpace> colorSp
if (colorSpace->gammaIsLinear()) {
srgb = SkColorSpace::MakeSRGBLinear();
}
return SkImageMakeRasterCopyAndAssignColorSpace(xform.get(), srgb.get());
return xform->reinterpretColorSpace(std::move(srgb));
}
class MakeCSGM : public skiagm::GM {
public:
MakeCSGM() {}
DEF_SIMPLE_GM_CAN_FAIL(makecolorspace, canvas, errorMsg, 128 * 3, 128 * 4) {
sk_sp<SkColorSpace> wideGamut = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
SkNamedGamut::kAdobeRGB);
sk_sp<SkColorSpace> wideGamutLinear = wideGamut->makeLinearGamma();
protected:
SkString onShortName() override {
return SkString("makecolorspace");
// Lazy images
sk_sp<SkImage> opaqueImage = GetResourceAsImage("images/mandrill_128.png");
sk_sp<SkImage> premulImage = GetResourceAsImage("images/color_wheel.png");
if (!opaqueImage || !premulImage) {
*errorMsg = "Failed to load images. Did you forget to set the resourcePath?";
return skiagm::DrawResult::kFail;
}
canvas->drawImage(opaqueImage, 0.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamut), 128.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamutLinear), 256.0f, 0.0f);
canvas->drawImage(premulImage, 0.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamut), 128.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamutLinear), 256.0f, 128.0f);
canvas->translate(0.0f, 256.0f);
SkISize onISize() override {
return SkISize::Make(128*3, 128*4);
}
DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
sk_sp<SkColorSpace> wideGamut = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
SkNamedGamut::kAdobeRGB);
sk_sp<SkColorSpace> wideGamutLinear = wideGamut->makeLinearGamma();
// Lazy images
sk_sp<SkImage> opaqueImage = GetResourceAsImage("images/mandrill_128.png");
sk_sp<SkImage> premulImage = GetResourceAsImage("images/color_wheel.png");
if (!opaqueImage || !premulImage) {
*errorMsg = "Failed to load images. Did you forget to set the resourcePath?";
return DrawResult::kFail;
}
canvas->drawImage(opaqueImage, 0.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamut), 128.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamutLinear), 256.0f, 0.0f);
canvas->drawImage(premulImage, 0.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamut), 128.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamutLinear), 256.0f, 128.0f);
canvas->translate(0.0f, 256.0f);
// Raster images
opaqueImage = make_raster_image("images/mandrill_128.png");
premulImage = make_raster_image("images/color_wheel.png");
canvas->drawImage(opaqueImage, 0.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamut), 128.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamutLinear), 256.0f, 0.0f);
canvas->drawImage(premulImage, 0.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamut), 128.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamutLinear), 256.0f, 128.0f);
return DrawResult::kOk;
}
private:
typedef skiagm::GM INHERITED;
};
DEF_GM(return new MakeCSGM;)
// Raster images
opaqueImage = make_raster_image("images/mandrill_128.png");
premulImage = make_raster_image("images/color_wheel.png");
canvas->drawImage(opaqueImage, 0.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamut), 128.0f, 0.0f);
canvas->drawImage(make_color_space(opaqueImage, wideGamutLinear), 256.0f, 0.0f);
canvas->drawImage(premulImage, 0.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamut), 128.0f, 128.0f);
canvas->drawImage(make_color_space(premulImage, wideGamutLinear), 256.0f, 128.0f);
return skiagm::DrawResult::kOk;
}
DEF_SIMPLE_GM_BG(makecolortypeandspace, canvas, 128 * 3, 128 * 4, SK_ColorWHITE) {
sk_sp<SkImage> images[] = {
@ -143,3 +124,52 @@ DEF_SIMPLE_GM_BG(makecolortypeandspace, canvas, 128 * 3, 128 * 4, SK_ColorWHITE)
}
}
}
DEF_SIMPLE_GM_CAN_FAIL(reinterpretcolorspace, canvas, errorMsg, 128 * 3, 128 * 3) {
// We draw a 3x3 grid. The three rows are lazy (encoded), raster, and GPU (or another copy of
// raster so all configs look similar). In each row, we draw three variants:
// - The original image (should look normal).
// - The image, reinterpreted as being in the color-spin space. The pixel data isn't changed,
// so in untagged configs, this looks like the first column. In tagged configs, this has the
// the effect of rotating the colors (RGB -> GBR).
// - The image converted to the color-spin space, then reinterpreted as being sRGB. In all
// configs, this appears to be spun backwards (RGB -> BRG), and tests the composition of
// these two APIs.
// In all cases, every column should be identical. In tagged configs, the 'R' in the columns
// should be Red, Green, Blue.
sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
sk_sp<SkColorSpace> spin = srgb->makeColorSpin();
sk_sp<SkImage> image = GetResourceAsImage("images/color_wheel.png");
if (!image) {
*errorMsg = "Failed to load image. Did you forget to set the resourcePath?";
return skiagm::DrawResult::kFail;
}
// Lazy images
canvas->drawImage(image, 0.0f, 0.0f);
canvas->drawImage(image->reinterpretColorSpace(spin), 128.0f, 0.0f);
canvas->drawImage(image->makeColorSpace(spin)->reinterpretColorSpace(srgb), 256.0f, 0.0f);
canvas->translate(0.0f, 128.0f);
// Raster images
image = image->makeRasterImage();
canvas->drawImage(image, 0.0f, 0.0f);
canvas->drawImage(image->reinterpretColorSpace(spin), 128.0f, 0.0f);
canvas->drawImage(image->makeColorSpace(spin)->reinterpretColorSpace(srgb), 256.0f, 0.0f);
canvas->translate(0.0f, 128.0f);
// GPU images
if (canvas->getGrContext()) {
image = image->makeTextureImage(canvas->getGrContext(), nullptr);
}
canvas->drawImage(image, 0.0f, 0.0f);
canvas->drawImage(image->reinterpretColorSpace(spin), 128.0f, 0.0f);
canvas->drawImage(image->makeColorSpace(spin)->reinterpretColorSpace(srgb), 256.0f, 0.0f);
return skiagm::DrawResult::kOk;
}

View File

@ -1145,6 +1145,12 @@ public:
sk_sp<SkImage> makeColorTypeAndColorSpace(SkColorType targetColorType,
sk_sp<SkColorSpace> targetColorSpace) const;
/** Creates a new SkImage identical to this one, but with a different SkColorSpace.
This does not convert the underlying pixel data, so the resulting image will draw
differently.
*/
sk_sp<SkImage> reinterpretColorSpace(sk_sp<SkColorSpace> newColorSpace) const;
private:
SkImage(const SkImageInfo& info, uint32_t uniqueID);
friend class SkImage_Base;

View File

@ -91,11 +91,4 @@ void SkImage_unpinAsTexture(const SkImage*, GrContext*);
*/
SkIRect SkImage_getSubset(const SkImage*);
/**
* Returns a new image containing the same pixel values as the source, but with a different color
* space assigned. This performs no color space conversion. Primarily used in tests, to visualize
* the results of rendering in wide or narrow gamuts.
*/
sk_sp<SkImage> SkImageMakeRasterCopyAndAssignColorSpace(const SkImage*, SkColorSpace*);
#endif

View File

@ -377,6 +377,25 @@ sk_sp<SkImage> SkImage::makeColorTypeAndColorSpace(SkColorType targetColorType,
targetColorType, std::move(targetColorSpace));
}
sk_sp<SkImage> SkImage::reinterpretColorSpace(sk_sp<SkColorSpace> target) const {
if (!target) {
return nullptr;
}
// No need to create a new image if:
// (1) The color spaces are equal.
// (2) The color type is kAlpha8.
SkColorSpace* colorSpace = this->colorSpace();
if (!colorSpace) {
colorSpace = sk_srgb_singleton();
}
if (SkColorSpace::Equals(colorSpace, target.get()) || this->isAlphaOnly()) {
return sk_ref_sp(const_cast<SkImage*>(this));
}
return as_IB(this)->onReinterpretColorSpace(std::move(target));
}
sk_sp<SkImage> SkImage::makeNonTextureImage() const {
if (!this->isTextureBacked()) {
return sk_ref_sp(const_cast<SkImage*>(this));
@ -510,33 +529,3 @@ SkIRect SkImage_getSubset(const SkImage* image) {
SkASSERT(image);
return as_IB(image)->onGetSubset();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkImage> SkImageMakeRasterCopyAndAssignColorSpace(const SkImage* src,
SkColorSpace* colorSpace) {
// Read the pixels out of the source image, with no conversion
const SkImageInfo& info = src->imageInfo();
if (kUnknown_SkColorType == info.colorType()) {
SkDEBUGFAIL("Unexpected color type");
return nullptr;
}
size_t rowBytes = info.minRowBytes();
size_t size = info.computeByteSize(rowBytes);
if (SkImageInfo::ByteSizeOverflowed(size)) {
return nullptr;
}
auto data = SkData::MakeUninitialized(size);
if (!data) {
return nullptr;
}
SkPixmap pm(info, data->writable_data(), rowBytes);
if (!src->readPixels(pm, 0, 0, SkImage::kDisallow_CachingHint)) {
return nullptr;
}
// Wrap them in a new image with a different color space
return SkImage::MakeRasterData(info.makeColorSpace(sk_ref_sp(colorSpace)), data, rowBytes);
}

View File

@ -100,6 +100,9 @@ public:
virtual sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
SkColorType, sk_sp<SkColorSpace>) const = 0;
virtual sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const = 0;
protected:
SkImage_Base(const SkImageInfo& info, uint32_t uniqueID);

View File

@ -112,6 +112,11 @@ sk_sp<SkImage> SkImage_Gpu::onMakeColorTypeAndColorSpace(GrRecordingContext* con
renderTargetContext->asTextureProxyRef(), std::move(targetCS));
}
sk_sp<SkImage> SkImage_Gpu::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, this->alphaType(), fProxy,
std::move(newCS));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static sk_sp<SkImage> new_wrapped_texture_common(GrContext* ctx,

View File

@ -40,6 +40,8 @@ public:
sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
SkColorType, sk_sp<SkColorSpace>) const final;
sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const final;
/**
* This is the implementation of SkDeferredDisplayListRecorder::makePromiseImage.
*/

View File

@ -208,6 +208,12 @@ sk_sp<SkImage> SkImage_GpuYUVA::onMakeColorTypeAndColorSpace(GrRecordingContext*
return result;
}
sk_sp<SkImage> SkImage_GpuYUVA::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
return sk_make_sp<SkImage_GpuYUVA>(fContext, this->width(), this->height(),
kNeedNewImageUniqueID, fYUVColorSpace, fProxies, fNumProxies,
fYUVAIndices, fOrigin, std::move(newCS));
}
//////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkImage> SkImage::MakeFromYUVATextures(GrContext* ctx,

View File

@ -41,6 +41,8 @@ public:
sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
SkColorType, sk_sp<SkColorSpace>) const final;
sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const final;
virtual bool isYUVA() const override { return true; }
bool setupMipmapsForPlanes(GrRecordingContext*) const;

View File

@ -279,6 +279,24 @@ sk_sp<SkImage> SkImage_Lazy::onMakeColorTypeAndColorSpace(GrRecordingContext*,
return result;
}
sk_sp<SkImage> SkImage_Lazy::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
// TODO: The correct thing is to clone the generator, and modify its color space. That's hard,
// because we don't have a clone method, and generator is public (and derived-from by clients).
// So do the simple/inefficient thing here, and fallback to raster when this is called.
// We allocate the bitmap with the new color space, then generate the image using the original.
SkBitmap bitmap;
if (bitmap.tryAllocPixels(this->imageInfo().makeColorSpace(std::move(newCS)))) {
SkPixmap pixmap = bitmap.pixmap();
pixmap.setColorSpace(this->refColorSpace());
if (generate_pixels(ScopedGenerator(fSharedGenerator), pixmap, fOrigin.x(), fOrigin.y())) {
bitmap.setImmutable();
return SkImage::MakeFromBitmap(bitmap);
}
}
return nullptr;
}
sk_sp<SkImage> SkImage::MakeFromGenerator(std::unique_ptr<SkImageGenerator> generator,
const SkIRect* subset) {
SkImage_Lazy::Validator

View File

@ -54,6 +54,7 @@ public:
bool onIsLazyGenerated() const override { return true; }
sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
SkColorType, sk_sp<SkColorSpace>) const override;
sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const final;
bool onIsValid(GrContext*) const override;

View File

@ -98,6 +98,8 @@ public:
sk_sp<SkImage> onMakeColorTypeAndColorSpace(GrRecordingContext*,
SkColorType, sk_sp<SkColorSpace>) const override;
sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const override;
bool onIsValid(GrContext* context) const override { return true; }
void notifyAddedToRasterCache() const override {
// We explicitly DON'T want to call INHERITED::notifyAddedToRasterCache. That ties the
@ -347,3 +349,12 @@ sk_sp<SkImage> SkImage_Raster::onMakeColorTypeAndColorSpace(GrRecordingContext*,
dst.setImmutable();
return SkImage::MakeFromBitmap(dst);
}
sk_sp<SkImage> SkImage_Raster::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
// TODO: If our bitmap is immutable, then we could theoretically create another image sharing
// our pixelRef. That doesn't work (without more invasive logic), because the image gets its
// gen ID from the bitmap, which gets it from the pixelRef.
SkPixmap pixmap = fBitmap.pixmap();
pixmap.setColorSpace(std::move(newCS));
return SkImage::MakeRasterCopy(pixmap);
}