Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(261)

Unified Diff: third_party/WebKit/Source/core/frame/ImageBitmap.cpp

Issue 2522693002: Color correct ImageBitmap(HTMLImageElement*) constructor (Closed)
Patch Set: Rebaseline Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/frame/ImageBitmap.cpp
diff --git a/third_party/WebKit/Source/core/frame/ImageBitmap.cpp b/third_party/WebKit/Source/core/frame/ImageBitmap.cpp
index 776800c8b50edcf2a1e08e937bc39e32d2611268..6d5d1e07bd965a58c4eb79a4d762211a97967d0c 100644
--- a/third_party/WebKit/Source/core/frame/ImageBitmap.cpp
+++ b/third_party/WebKit/Source/core/frame/ImageBitmap.cpp
@@ -10,6 +10,7 @@
#include "platform/graphics/skia/SkiaUtils.h"
#include "platform/image-decoders/ImageDecoder.h"
#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "wtf/CheckedNumeric.h"
#include "wtf/PtrUtil.h"
@@ -31,12 +32,16 @@ struct ParsedOptions {
unsigned resizeHeight = 0;
IntRect cropRect;
SkFilterQuality resizeQuality = kLow_SkFilterQuality;
- // This value should be changed in the future when we support
- // createImageBitmap with higher bit depth, in the parseOptions() function.
- // For now, it is always 4.
- int bytesPerPixel = 4;
+ sk_sp<SkColorSpace> dstColorSpace = nullptr;
+ sk_sp<SkColorSpace> latestColorSpace = nullptr;
+ SkColorType dstColorType = kN32_SkColorType;
+ SkColorType latestColorType = kN32_SkColorType;
};
+ParsedOptions defaultOptions() {
+ return ParsedOptions();
+}
+
// The following two functions are helpers used in cropImage
static inline IntRect normalizeRect(const IntRect& rect) {
return IntRect(std::min(rect.x(), rect.maxX()),
@@ -63,6 +68,36 @@ ParsedOptions parseOptions(const ImageBitmapOptions& options,
options.premultiplyAlpha() == "premultiply");
}
+ if (options.colorSpaceConversion() != "none") {
+ if (!RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled() ||
+ !RuntimeEnabledFeatures::colorCorrectRenderingEnabled()) {
+ DCHECK_EQ(options.colorSpaceConversion(), "default");
+ // TODO(zakerinasab): Replace sRGB with a call to
+ // ImageDecoder::globalTargetColorSpace() when the crash problem on Mac
+ // is fixed. crbug.com/668546.
+ if (RuntimeEnabledFeatures::colorCorrectRenderingDefaultModeEnabled()) {
+ parsedOptions.dstColorSpace =
+ SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
+ parsedOptions.dstColorType = SkColorType::kN32_SkColorType;
+ }
+ } else {
+ if (options.colorSpaceConversion() == "default" ||
+ options.colorSpaceConversion() == "srgb") {
+ parsedOptions.dstColorSpace =
+ SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
+ parsedOptions.dstColorType = SkColorType::kN32_SkColorType;
+ } else if (options.colorSpaceConversion() == "linear-rgb") {
+ parsedOptions.dstColorSpace =
+ SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named);
+ parsedOptions.dstColorType = SkColorType::kRGBA_F16_SkColorType;
+ } else {
+ NOTREACHED()
+ << "Invalid ImageBitmap creation attribute colorSpaceConversion: "
+ << options.colorSpaceConversion();
+ }
+ }
+ }
+
int sourceWidth = sourceSize.width();
int sourceHeight = sourceSize.height();
if (!cropRect) {
@@ -110,7 +145,7 @@ ParsedOptions parseOptions(const ImageBitmapOptions& options,
bool dstBufferSizeHasOverflow(ParsedOptions options) {
dcheng 2016/12/11 20:11:58 Nit: not introduced by this CL, but this should be
zakerinasab1 2016/12/12 18:35:21 Done.
CheckedNumeric<unsigned> totalBytes = options.cropRect.width();
totalBytes *= options.cropRect.height();
- totalBytes *= options.bytesPerPixel;
+ totalBytes *= SkColorTypeBytesPerPixel(options.latestColorType);
if (!totalBytes.IsValid())
return true;
@@ -118,7 +153,7 @@ bool dstBufferSizeHasOverflow(ParsedOptions options) {
return false;
totalBytes = options.resizeWidth;
totalBytes *= options.resizeHeight;
- totalBytes *= options.bytesPerPixel;
+ totalBytes *= SkColorTypeBytesPerPixel(options.latestColorType);
if (!totalBytes.IsValid())
return true;
@@ -187,14 +222,19 @@ static void swizzleImageData(unsigned char* srcAddr,
}
}
-static sk_sp<SkImage> flipSkImageVertically(SkImage* input,
- AlphaDisposition alphaOp) {
+static sk_sp<SkImage> flipSkImageVertically(
+ SkImage* input,
+ bool enforceAlphaPremultiply = false,
dcheng 2016/12/11 20:11:58 Ditto about preferring enums over bools here as we
zakerinasab1 2016/12/12 18:35:21 Done.
+ const ParsedOptions& options = defaultOptions()) {
unsigned width = static_cast<unsigned>(input->width());
unsigned height = static_cast<unsigned>(input->height());
- SkImageInfo info = SkImageInfo::MakeN32(input->width(), input->height(),
- (alphaOp == PremultiplyAlpha)
- ? kPremul_SkAlphaType
- : kUnpremul_SkAlphaType);
+ SkAlphaType alphaType = (enforceAlphaPremultiply || options.premultiplyAlpha)
+ ? kPremul_SkAlphaType
+ : kUnpremul_SkAlphaType;
+ SkImageInfo info = SkImageInfo::Make(input->width(), input->height(),
+ options.latestColorType, alphaType,
+ options.latestColorSpace);
+
unsigned imageRowBytes = width * info.bytesPerPixel();
RefPtr<Uint8Array> imagePixels = copySkImageData(input, info);
if (!imagePixels)
@@ -210,9 +250,13 @@ static sk_sp<SkImage> flipSkImageVertically(SkImage* input,
return newSkImageFromRaster(info, std::move(imagePixels), imageRowBytes);
}
-static sk_sp<SkImage> premulSkImageToUnPremul(SkImage* input) {
- SkImageInfo info = SkImageInfo::Make(input->width(), input->height(),
- kN32_SkColorType, kUnpremul_SkAlphaType);
+static sk_sp<SkImage> premulSkImageToUnPremul(
+ SkImage* input,
+ const ParsedOptions& options = defaultOptions()) {
+ SkImageInfo info = SkImageInfo::Make(
+ input->width(), input->height(), options.latestColorType,
+ kUnpremul_SkAlphaType, options.latestColorSpace);
+
RefPtr<Uint8Array> dstPixels = copySkImageData(input, info);
if (!dstPixels)
return nullptr;
@@ -221,9 +265,13 @@ static sk_sp<SkImage> premulSkImageToUnPremul(SkImage* input) {
static_cast<unsigned>(input->width()) * info.bytesPerPixel());
}
-static sk_sp<SkImage> unPremulSkImageToPremul(SkImage* input) {
- SkImageInfo info = SkImageInfo::Make(input->width(), input->height(),
- kN32_SkColorType, kPremul_SkAlphaType);
+static sk_sp<SkImage> unPremulSkImageToPremul(
+ SkImage* input,
+ const ParsedOptions& options = defaultOptions()) {
+ SkImageInfo info = SkImageInfo::Make(
+ input->width(), input->height(), options.latestColorType,
+ kPremul_SkAlphaType, options.latestColorSpace);
+
RefPtr<Uint8Array> dstPixels = copySkImageData(input, info);
if (!dstPixels)
return nullptr;
@@ -232,15 +280,124 @@ static sk_sp<SkImage> unPremulSkImageToPremul(SkImage* input) {
static_cast<unsigned>(input->width()) * info.bytesPerPixel());
}
+// This code is borrowed from third_party/skia/src/codec/SkCodecPriv.h
+// If you need to change this, please check SkColorSpaceXform::ColorFormat
+// and SkColorType for proper coverage.
+static inline SkColorSpaceXform::ColorFormat getXformFormat(
+ SkColorType colorType) {
+ switch (colorType) {
+ case kRGBA_8888_SkColorType:
+ return SkColorSpaceXform::kRGBA_8888_ColorFormat;
+ case kBGRA_8888_SkColorType:
+ return SkColorSpaceXform::kBGRA_8888_ColorFormat;
+ case kRGBA_F16_SkColorType:
+ return SkColorSpaceXform::kRGBA_F16_ColorFormat;
+ default:
+ SkASSERT(false);
dcheng 2016/12/11 20:11:58 I think we should be writing NOTREACHED in Blink c
zakerinasab1 2016/12/12 18:35:21 Done.
+ return SkColorSpaceXform::kRGBA_8888_ColorFormat;
+ }
+}
+
+static inline void updateLatestColorInformation(ParsedOptions& options) {
+ options.latestColorType = options.dstColorType;
+ options.latestColorSpace = options.dstColorSpace;
+}
+
+// TODO (zakrinasab). Rewrite this when SkImage::readPixels() respectes the
+// color space of the passed SkImageInfo (crbug.com/skia/6021) and SkImage
+// exposes SkColorSpace and SkColorType (crbug.com/skia/6022).
+
+static sk_sp<SkImage> applyColorSpaceConversion(const sk_sp<SkImage> image,
dcheng 2016/12/11 20:11:58 This refs the SkImage unnecessarily, as this metho
zakerinasab1 2016/12/12 18:35:21 I changed the function signature. PTAL. Passing Sk
dcheng 2016/12/12 21:13:57 Ah... another option is to pass by value (instead
+ ParsedOptions& options) {
+ if (!options.dstColorSpace)
+ return image;
+ if (SkColorSpace::Equals(options.latestColorSpace.get(),
+ options.dstColorSpace.get()))
+ return image;
+
+ // If we have the color space information of the source image, we can use
+ // SkColorSpaceXform. Otherwise, we need to draw the image on a canvas and
+ // take a snapshot.
+ if (options.latestColorSpace) {
+ SkImageInfo info = SkImageInfo::Make(
+ image->width(), image->height(), options.latestColorType,
+ image->alphaType(), options.latestColorSpace);
+ size_t size = image->width() * image->height() * info.bytesPerPixel();
+ auto srcPixels = WTF::makeUnique<uint8_t>(size);
+ if (!image->readPixels(info, &srcPixels,
+ image->width() * info.bytesPerPixel(), 0, 0)) {
+ return image;
+ }
+
+ // For in-place color correction, bytes per pixel must be equal for source
+ // and destination color spaces.
+ std::unique_ptr<uint8_t> dstPixels = nullptr;
+ if (SkColorTypeBytesPerPixel(options.dstColorType) !=
+ SkColorTypeBytesPerPixel(options.latestColorType)) {
+ size = image->width() * image->height() *
+ SkColorTypeBytesPerPixel(options.latestColorType);
+ dstPixels = WTF::makeUnique<uint8_t>(size);
+ }
+
+ std::unique_ptr<SkColorSpaceXform> xform = SkColorSpaceXform::New(
+ options.latestColorSpace.get(), options.dstColorSpace.get());
+ xform->apply(getXformFormat(options.dstColorType),
+ dstPixels ? &dstPixels : &srcPixels,
+ getXformFormat(options.latestColorType), &srcPixels,
+ image->width() * image->height(), kUnpremul_SkAlphaType);
+
+ SkImageInfo dstInfo =
+ SkImageInfo::Make(image->width(), image->height(), options.dstColorType,
+ image->alphaType(), options.dstColorSpace);
+ sk_sp<SkData> data(SkData::MakeWithoutCopy(&dstPixels, size));
+ sk_sp<SkImage> coloredImage = SkImage::MakeRasterData(
+ dstInfo, data, image->width() * dstInfo.bytesPerPixel());
+ if (coloredImage) {
+ updateLatestColorInformation(options);
+ return coloredImage;
+ }
+ return image;
+ }
+
+ // Skia does not support drawing to unpremul surfaces/canvases.
+ sk_sp<SkImage> unPremulImage = nullptr;
+ if (image->alphaType() == kUnpremul_SkAlphaType)
+ unPremulImage = unPremulSkImageToPremul(image.get(), options);
+
+ // If the color space of the source SkImage is null, the following code
+ // does not do any color conversion even thought the new SkImage will be
+ // tagged by the new color space. If this is the case, the following code
+ // will tag a wrong color space to the SkImage. However, this cannot be
+ // addressed here and the code that creates the SkImage must tag the
+ // SkImage with proper color space.
+ SkImageInfo imageInfo = SkImageInfo::Make(
+ image->width(), image->height(), options.dstColorType,
+ unPremulImage ? unPremulImage->alphaType() : image->alphaType(),
+ options.dstColorSpace);
+ sk_sp<SkSurface> surface = SkSurface::MakeRaster(imageInfo);
+ surface->getCanvas()->drawImage(unPremulImage ? unPremulImage : image, 0, 0);
+ sk_sp<SkImage> coloredImage = surface->makeImageSnapshot();
+ updateLatestColorInformation(options);
+ return coloredImage;
+}
+
sk_sp<SkImage> ImageBitmap::getSkImageFromDecoder(
- std::unique_ptr<ImageDecoder> decoder) {
+ std::unique_ptr<ImageDecoder> decoder,
+ SkColorType* decodedColorType,
+ sk_sp<SkColorSpace> decodedColorSpace,
+ bool returnColorSpaceInformation) {
if (!decoder->frameCount())
return nullptr;
ImageFrame* frame = decoder->frameBufferAtIndex(0);
if (!frame || frame->getStatus() != ImageFrame::FrameComplete)
return nullptr;
DCHECK(!frame->bitmap().isNull() && !frame->bitmap().empty());
- return frame->finalizePixelsAndGetImage();
+ sk_sp<SkImage> image = frame->finalizePixelsAndGetImage();
+ if (returnColorSpaceInformation) {
+ *decodedColorType = frame->bitmap().colorType();
+ decodedColorSpace = sk_sp<SkColorSpace>(frame->bitmap().colorSpace());
dcheng 2016/12/11 20:11:58 This parameter is passed by value, so the caller w
zakerinasab1 2016/12/12 18:35:21 The parameter is fixed. The flag is needed as the
+ }
+ return image;
}
bool ImageBitmap::isResizeOptionValid(const ImageBitmapOptions& options,
@@ -272,9 +429,9 @@ bool ImageBitmap::isSourceSizeValid(int sourceWidth,
// premuliplied format For example, if the image is already in unpremultiplied
// format and we want the created ImageBitmap in the same format, then we don't
// need to use the ImageDecoder to decode the image.
-static PassRefPtr<StaticBitmapImage> cropImage(
+static PassRefPtr<StaticBitmapImage> cropImageAndApplyColorSpaceConversion(
Image* image,
- const ParsedOptions& parsedOptions,
+ ParsedOptions& parsedOptions,
AlphaDisposition imageFormat,
ColorBehavior colorBehavior) {
ASSERT(image);
@@ -314,18 +471,22 @@ static PassRefPtr<StaticBitmapImage> cropImage(
colorBehavior));
if (!decoder)
return nullptr;
- skiaImage = ImageBitmap::getSkImageFromDecoder(std::move(decoder));
+ skiaImage = ImageBitmap::getSkImageFromDecoder(
+ std::move(decoder), &parsedOptions.latestColorType,
+ parsedOptions.latestColorSpace, true);
if (!skiaImage)
return nullptr;
}
if (parsedOptions.cropRect == srcRect && !parsedOptions.shouldScaleInput) {
sk_sp<SkImage> croppedSkImage = skiaImage->makeSubset(srcRect);
- if (parsedOptions.flipY)
- return StaticBitmapImage::create(flipSkImageVertically(
- croppedSkImage.get(), parsedOptions.premultiplyAlpha
- ? PremultiplyAlpha
- : DontPremultiplyAlpha));
+ if (parsedOptions.dstColorSpace) {
+ croppedSkImage = applyColorSpaceConversion(croppedSkImage, parsedOptions);
+ }
+ if (parsedOptions.flipY) {
+ return StaticBitmapImage::create(
+ flipSkImageVertically(croppedSkImage.get(), false, parsedOptions));
+ }
// Special case: The first parameter image is unpremul but we need to turn
// it into premul.
if (parsedOptions.premultiplyAlpha && imageFormat == DontPremultiplyAlpha)
@@ -367,6 +528,8 @@ static PassRefPtr<StaticBitmapImage> cropImage(
surface->getCanvas()->drawImage(skiaImage, dstLeft, dstTop);
}
skiaImage = surface->makeImageSnapshot();
+ if (parsedOptions.dstColorSpace)
+ skiaImage = applyColorSpaceConversion(skiaImage, parsedOptions);
if (parsedOptions.premultiplyAlpha) {
if (imageFormat == DontPremultiplyAlpha)
@@ -388,12 +551,14 @@ ImageBitmap::ImageBitmap(HTMLImageElement* image,
return;
if (options.colorSpaceConversion() == "none") {
- m_image = cropImage(input.get(), parsedOptions, PremultiplyAlpha,
- ColorBehavior::ignore());
+ m_image = cropImageAndApplyColorSpaceConversion(
+ input.get(), parsedOptions, PremultiplyAlpha, ColorBehavior::ignore());
} else {
- m_image = cropImage(input.get(), parsedOptions, PremultiplyAlpha,
- ColorBehavior::transformToGlobalTarget());
+ m_image = cropImageAndApplyColorSpaceConversion(
+ input.get(), parsedOptions, PremultiplyAlpha,
+ ColorBehavior::transformToGlobalTarget());
}
+
if (!m_image)
return;
// In the case where the source image is lazy-decoded, m_image may not be in
@@ -401,8 +566,10 @@ ImageBitmap::ImageBitmap(HTMLImageElement* image,
sk_sp<SkImage> skImage = m_image->imageForCurrentFrame();
SkPixmap pixmap;
if (!skImage->isTextureBacked() && !skImage->peekPixels(&pixmap)) {
- sk_sp<SkSurface> surface =
- SkSurface::MakeRasterN32Premul(skImage->width(), skImage->height());
+ SkImageInfo imageInfo = SkImageInfo::Make(
+ skImage->width(), skImage->height(), parsedOptions.dstColorType,
+ kPremul_SkAlphaType, parsedOptions.dstColorSpace);
+ sk_sp<SkSurface> surface = SkSurface::MakeRaster(imageInfo);
surface->getCanvas()->drawImage(skImage, 0, 0);
m_image = StaticBitmapImage::create(surface->makeImageSnapshot());
}
@@ -483,8 +650,9 @@ ImageBitmap::ImageBitmap(HTMLCanvasElement* canvas,
parsedOptions.premultiplyAlpha = true;
isPremultiplyAlphaReverted = true;
}
- m_image = cropImage(input.get(), parsedOptions, PremultiplyAlpha,
- ColorBehavior::transformToGlobalTarget());
+ m_image = cropImageAndApplyColorSpaceConversion(
+ input.get(), parsedOptions, PremultiplyAlpha,
+ ColorBehavior::transformToGlobalTarget());
if (!m_image)
return;
if (isPremultiplyAlphaReverted) {
@@ -667,7 +835,7 @@ ImageBitmap::ImageBitmap(ImageData* data,
sk_sp<SkImage> skImage =
buffer->newSkImageSnapshot(PreferNoAcceleration, SnapshotReasonUnknown);
if (parsedOptions.flipY)
- skImage = flipSkImageVertically(skImage.get(), PremultiplyAlpha);
+ skImage = flipSkImageVertically(skImage.get(), true);
if (!skImage)
return;
if (parsedOptions.shouldScaleInput) {
@@ -695,7 +863,7 @@ ImageBitmap::ImageBitmap(ImageBitmap* bitmap,
if (dstBufferSizeHasOverflow(parsedOptions))
return;
- m_image = cropImage(
+ m_image = cropImageAndApplyColorSpaceConversion(
input.get(), parsedOptions,
bitmap->isPremultiplied() ? PremultiplyAlpha : DontPremultiplyAlpha,
ColorBehavior::transformToGlobalTarget());
@@ -714,8 +882,9 @@ ImageBitmap::ImageBitmap(PassRefPtr<StaticBitmapImage> image,
if (dstBufferSizeHasOverflow(parsedOptions))
return;
- m_image = cropImage(input.get(), parsedOptions, PremultiplyAlpha,
- ColorBehavior::transformToGlobalTarget());
+ m_image = cropImageAndApplyColorSpaceConversion(
+ input.get(), parsedOptions, PremultiplyAlpha,
+ ColorBehavior::transformToGlobalTarget());
if (!m_image)
return;

Powered by Google App Engine
This is Rietveld 408576698