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:
parent
30ad4680ad
commit
d514837136
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user