Wide color gamut support and working example.

Color space arguments accepted at surface creation, paint, gradient, and other call sites.
Works correctly only when chrome happens to be rendering itself in the same color space
the canvaskit user has chosen, there's not yet end to end color management of
canvases supported in browsers.

readPixels not yet working due to possible chrome bug.

Change-Id: I3dea5b16c60a3871cd2a54f86716f4a438a90135
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/289733
Commit-Queue: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
Nathaniel Nifong 2020-05-21 14:53:41 -04:00 committed by Skia Commit-Bot
parent 54814dc026
commit 3d52abc846
10 changed files with 286 additions and 147 deletions

View File

@ -6,12 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Support for wide-gamut color spaces DisplayP3 and AdobeRGB. However, correct representation on a
WCG monitor requires that the browser is rendering everything to the DisplayP3 or AdobeRGB
profile, since there is not yet any way to indicate to the browser that a canvas element has a
non-sRGB color space. See color support example in extra.html. Only supported for WebGL2 backed
surfaces.
- Added `SkSurface.reportBackendType` which returns either 'CPU' or 'GPU'.
- Added `SkSurface.imageInfo` which returns an ImageInfo object describing the size and color
properties of the surface. colorSpace is added to ImageInfo everywhere it is used.
### Changed
- We now compile/ship with Emscripten v1.39.16.
- We now compile/ship with Emscripten v1.39.16.
- `CanvasKit.MakeCanvasSurface` accepts a new enum specifying one of the three color space and
pixel format combinations supported by CanvasKit.
- all `_Make*Shader` functions now accept a color space argument at the end. leaving it off or
passing null makes it behave as it did before, defaulting to sRGB
- `SkPaint.setColor` accepts a new color space argument, defaulting to sRGB.
### Breaking
- `CanvasKitInit(...)` now directly returns a Promise. As such, `CanvasKitInit(...).ready()`
has been removed.
- `CanvasKit.MakeCanvasSurface` no longer accepts width/height arguments to override those on
the canvas element. Use the canvas element's width/height attributes to dictate the size of
the drawing area, and use CSS width/height to set the size it will appear on the page
(it is rescaled after drawing when css sizing applies).
## [0.15.0] - 2020-05-14

View File

@ -1125,6 +1125,7 @@
height: 50,
alphaType: CanvasKit.AlphaType.Premul,
colorType: CanvasKit.ColorType.RGBA_8888,
colorSpace: CanvasKit.SkColorSpace.SRGB,
});
if (!subSurface) {

View File

@ -35,8 +35,9 @@
<h2> 3D perspective transformations </h2>
<canvas id=camera3d width=500 height=500></canvas>
<h2> Use of offscreen surfaces </h2>
<canvas id=surfaces width=500 height=500></canvas>
<h2> Support for extended color spaces </h2>
<a href="chrome://flags/#force-color-profile">Force P3 profile</a>
<canvas id=colorsupport width=300 height=300></canvas>
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
@ -79,7 +80,7 @@
SkpExample(CanvasKit, skpData);
SurfaceAPI1(CanvasKit);
ColorSupport(CanvasKit);
});
fetch(cdn + 'lego_loader.json').then((resp) => {
@ -240,76 +241,6 @@ const curves = {
"Bindings": []
};
function SurfaceAPI1(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('surfaces');
if (!surface) {
console.error('Could not make surface');
return;
}
console.log('SurfaceAPI1 top surface type = '+surface.reportBackendType() );
const context = CanvasKit.currentContext();
const canvas = surface.getCanvas();
//create a subsurface as a temporary workspace.
const subSurface = surface.makeSurface({
width: 50,
height: 50,
alphaType: CanvasKit.AlphaType.Premul,
colorType: CanvasKit.ColorType.RGBA_8888,
});
if (!subSurface) {
console.error('Could not make subsurface');
return;
}
console.log('SurfaceAPI1 subSurface type = '+subSurface.reportBackendType() );
// draw a small "scene"
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setAntiAlias(true);
const subCanvas = subSurface.getCanvas();
subCanvas.clear(CanvasKit.BLACK);
subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint);
paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish
for (let i = 0; i < 10; i++) {
const x = Math.random() * 50;
const y = Math.random() * 50;
subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint);
}
// Snap it off as an SkImage - this image will be in the form the
// parent surface prefers (e.g. Texture for GPU / Raster for CPU).
const img = subSurface.makeImageSnapshot();
// clean up the temporary surface
subSurface.delete();
paint.delete();
// Make it repeat a bunch with a shader
const pattern = img.makeShader(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror);
const patternPaint = new CanvasKit.SkPaint();
patternPaint.setShader(pattern);
let i = 0;
function drawFrame() {
i++;
CanvasKit.setCurrentContext(context);
canvas.clear(CanvasKit.WHITE);
canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint);
surface.flush();
window.requestAnimationFrame(drawFrame);
}
window.requestAnimationFrame(drawFrame);
}
function ParagraphAPI1(CanvasKit, fontData) {
if (!CanvasKit || !fontData) {
return;
@ -890,4 +821,30 @@ const curves = {
surface.requestAnimationFrame(drawFrame);
}
function ColorSupport(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('colorsupport', CanvasKit.SkColorSpace.ADOBE_RGB);
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
// If the surface is correctly initialized with a higher bit depth color type,
// And chrome is compositing it into a buffer with the P3 color space,
// then the inner round rect should be distinct and less saturated than the full red background.
// Even if the monitor it is viewed on cannot accurately represent that color space.
let red = CanvasKit.Color4f(1, 0, 0, 1);
let paint = new CanvasKit.SkPaint();
paint.setColor(red, CanvasKit.SkColorSpace.ADOBE_RGB);
canvas.drawPaint(paint);
paint.setColor(red, CanvasKit.SkColorSpace.DISPLAY_P3);
canvas.drawRoundRect(CanvasKit.LTRBRect(50, 50, 250, 250), 30, 30, paint);
paint.setColor(red, CanvasKit.SkColorSpace.SRGB);
canvas.drawRoundRect(CanvasKit.LTRBRect(100, 100, 200, 200), 30, 30, paint);
surface.flush();
surface.delete();
}
</script>

View File

@ -12,6 +12,7 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkDrawable.h"
#include "include/core/SkEncodedImageFormat.h"
@ -109,13 +110,29 @@ struct SimpleImageInfo {
int height;
SkColorType colorType;
SkAlphaType alphaType;
// TODO color spaces?
sk_sp<SkColorSpace> colorSpace;
};
SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType, sii.colorSpace);
}
// Set the pixel format based on the colortype.
// These degrees of freedom are removed from canvaskit only to keep the interface simpler.
struct ColorSettings {
ColorSettings(sk_sp<SkColorSpace> colorSpace) {
if (colorSpace == nullptr || colorSpace->isSRGB()) {
colorType = kRGBA_8888_SkColorType;
pixFormat = GL_RGBA8;
} else {
colorType = kRGBA_F16_SkColorType;
pixFormat = GL_RGBA16F;
}
};
SkColorType colorType;
GrGLenum pixFormat;
};
#ifdef SK_GL
sk_sp<GrContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
{
@ -131,31 +148,27 @@ sk_sp<GrContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
return grContext;
}
sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrContext> grContext, int width, int height) {
sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrContext> grContext, int width, int height,
sk_sp<SkColorSpace> colorSpace) {
glClearColor(0, 0, 0, 0);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// Wrap the frame buffer object attached to the screen in a Skia render
// target so Skia can render to it
GrGLint buffer;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
GrGLFramebufferInfo info;
info.fFBOID = (GrGLuint) buffer;
SkColorType colorType;
GrGLint stencil;
glGetIntegerv(GL_STENCIL_BITS, &stencil);
info.fFormat = GL_RGBA8;
colorType = kRGBA_8888_SkColorType;
const auto colorSettings = ColorSettings(colorSpace);
info.fFormat = colorSettings.pixFormat;
GrBackendRenderTarget target(width, height, 0, stencil, info);
sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
kBottomLeft_GrSurfaceOrigin,
colorType, nullptr, nullptr));
kBottomLeft_GrSurfaceOrigin, colorSettings.colorType, colorSpace, nullptr));
return surface;
}
@ -758,16 +771,17 @@ EMSCRIPTEN_BINDINGS(Skia) {
return SkImage::MakeRasterData(info, pixelData, rowBytes);
}), allow_raw_pointers());
function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
uintptr_t /* SkColor4f* */ cPtr, uintptr_t /* SkScalar* */ pPtr,
uintptr_t /* SkColor4f* */ cPtr,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode, uint32_t flags,
uintptr_t /* SkScalar* */ mPtr)->sk_sp<SkShader> {
uintptr_t /* SkScalar* */ mPtr,
sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
SkPoint points[] = { start, end };
// See comment above for uintptr_t explanation
const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
OptionalMatrix localMatrix(mPtr);
// TODO(nifong): do not assume color space. Support and test wide gamut color gradients
return SkGradientShader::MakeLinear(points, colors, SkColorSpace::MakeSRGB(), positions, count,
return SkGradientShader::MakeLinear(points, colors, colorSpace, positions, count,
mode, flags, &localMatrix);
}), allow_raw_pointers());
#ifdef SK_SERIALIZE_SKP
@ -781,42 +795,48 @@ EMSCRIPTEN_BINDINGS(Skia) {
}), allow_raw_pointers());
#endif
function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
uintptr_t /* SkColor4f* */ cPtr, uintptr_t /* SkScalar* */ pPtr,
uintptr_t /* SkColor4f* */ cPtr,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode, uint32_t flags,
uintptr_t /* SkScalar* */ mPtr)->sk_sp<SkShader> {
uintptr_t /* SkScalar* */ mPtr,
sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
OptionalMatrix localMatrix(mPtr);
return SkGradientShader::MakeRadial(center, radius, colors, SkColorSpace::MakeSRGB(), positions, count,
return SkGradientShader::MakeRadial(center, radius, colors, colorSpace, positions, count,
mode, flags, &localMatrix);
}), allow_raw_pointers());
function("_MakeSweepGradientShader", optional_override([](SkScalar cx, SkScalar cy,
uintptr_t /* SkColor4f* */ cPtr, uintptr_t /* SkScalar* */ pPtr,
uintptr_t /* SkColor4f* */ cPtr,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode,
SkScalar startAngle, SkScalar endAngle,
uint32_t flags,
uintptr_t /* SkScalar* */ mPtr)->sk_sp<SkShader> {
uintptr_t /* SkScalar* */ mPtr,
sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
OptionalMatrix localMatrix(mPtr);
return SkGradientShader::MakeSweep(cx, cy, colors, SkColorSpace::MakeSRGB(), positions, count,
return SkGradientShader::MakeSweep(cx, cy, colors, colorSpace, positions, count,
mode, startAngle, endAngle, flags,
&localMatrix);
}), allow_raw_pointers());
function("_MakeTwoPointConicalGradientShader", optional_override([](
SkPoint start, SkScalar startRadius,
SkPoint end, SkScalar endRadius,
uintptr_t /* SkColor4f* */ cPtr, uintptr_t /* SkScalar* */ pPtr,
uintptr_t /* SkColor4f* */ cPtr,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode, uint32_t flags,
uintptr_t /* SkScalar* */ mPtr)->sk_sp<SkShader> {
uintptr_t /* SkScalar* */ mPtr,
sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
const SkColor4f* colors = reinterpret_cast<const SkColor4f*> (cPtr);
const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
OptionalMatrix localMatrix(mPtr);
return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
colors, SkColorSpace::MakeSRGB(), positions, count, mode,
colors, colorSpace, positions, count, mode,
flags, &localMatrix);
}), allow_raw_pointers());
@ -1257,8 +1277,9 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("setAntiAlias", &SkPaint::setAntiAlias)
.function("setAlphaf", &SkPaint::setAlphaf)
.function("setBlendMode", &SkPaint::setBlendMode)
.function("_setColor", optional_override([](SkPaint& self, uintptr_t /* float* */ cPtr) {
self.setColor(ptrToSkColor4f(cPtr));
.function("_setColor", optional_override([](SkPaint& self, uintptr_t /* float* */ cPtr,
sk_sp<SkColorSpace> colorSpace) {
self.setColor(ptrToSkColor4f(cPtr), colorSpace.get());
}))
.function("setColorFilter", &SkPaint::setColorFilter)
.function("setFilterQuality", &SkPaint::setFilterQuality)
@ -1272,6 +1293,21 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("setStrokeWidth", &SkPaint::setStrokeWidth)
.function("setStyle", &SkPaint::setStyle);
class_<SkColorSpace>("SkColorSpace")
.smart_ptr<sk_sp<SkColorSpace>>("sk_sp<SkColorSpace>")
.class_function("Equals", optional_override([](sk_sp<SkColorSpace> a, sk_sp<SkColorSpace> b)->bool {
return SkColorSpace::Equals(a.get(), b.get());
}))
// These are private because they are to be called once in interface.js to
// avoid clients having to delete the returned objects.
.class_function("_MakeSRGB", &SkColorSpace::MakeSRGB)
.class_function("_MakeDisplayP3", optional_override([]()->sk_sp<SkColorSpace> {
return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3);
}))
.class_function("_MakeAdobeRGB", optional_override([]()->sk_sp<SkColorSpace> {
return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB);
}));
class_<SkPathEffect>("SkPathEffect")
.smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>")
.class_function("MakeCorner", &SkCornerPathEffect::Make)
@ -1405,8 +1441,8 @@ EMSCRIPTEN_BINDINGS(Skia) {
.smart_ptr<sk_sp<SkShader>>("sk_sp<SkShader>")
.class_function("Blend", select_overload<sk_sp<SkShader>(SkBlendMode, sk_sp<SkShader>, sk_sp<SkShader>)>(&SkShaders::Blend))
.class_function("_Color",
optional_override([](uintptr_t /* float* */ cPtr)->sk_sp<SkShader> {
return SkShaders::Color(ptrToSkColor4f(cPtr), SkColorSpace::MakeSRGB());
optional_override([](uintptr_t /* float* */ cPtr, sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
return SkShaders::Color(ptrToSkColor4f(cPtr), colorSpace);
})
)
.class_function("Lerp", select_overload<sk_sp<SkShader>(float, sk_sp<SkShader>, sk_sp<SkShader>)>(&SkShaders::Lerp));
@ -1457,6 +1493,10 @@ EMSCRIPTEN_BINDINGS(Skia) {
.smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
.function("_flush", select_overload<void()>(&SkSurface::flushAndSubmit))
.function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers())
.function("imageInfo", optional_override([](SkSurface& self)->SimpleImageInfo {
const auto& ii = self.imageInfo();
return {ii.width(), ii.height(), ii.colorType(), ii.alphaType(), ii.refColorSpace()};
}))
.function("height", &SkSurface::height)
.function("makeImageSnapshot", select_overload<sk_sp<SkImage>()>(&SkSurface::makeImageSnapshot))
.function("makeImageSnapshot", select_overload<sk_sp<SkImage>(const SkIRect& bounds)>(&SkSurface::makeImageSnapshot))
@ -1654,7 +1694,6 @@ EMSCRIPTEN_BINDINGS(Skia) {
.value("TrianglesStrip", SkVertices::VertexMode::kTriangleStrip_VertexMode)
.value("TriangleFan", SkVertices::VertexMode::kTriangleFan_VertexMode);
// A value object is much simpler than a class - it is returned as a JS
// object and does not require delete().
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
@ -1690,10 +1729,11 @@ EMSCRIPTEN_BINDINGS(Skia) {
.field("fBottom", &SkIRect::fBottom);
value_object<SimpleImageInfo>("SkImageInfo")
.field("width", &SimpleImageInfo::width)
.field("height", &SimpleImageInfo::height)
.field("colorType", &SimpleImageInfo::colorType)
.field("alphaType", &SimpleImageInfo::alphaType);
.field("width", &SimpleImageInfo::width)
.field("height", &SimpleImageInfo::height)
.field("colorType", &SimpleImageInfo::colorType)
.field("alphaType", &SimpleImageInfo::alphaType)
.field("colorSpace", &SimpleImageInfo::colorSpace);
// SkPoints can be represented by [x, y]
value_array<SkPoint>("SkPoint")

View File

@ -26,6 +26,10 @@
CanvasKit.MakeCanvasSurface = CanvasKit.MakeSWCanvasSurface;
}
// Note that color spaces are currently not supported in CPU surfaces. due to the limitation
// canvas.getContext('2d').putImageData imposes a limitatin of using an RGBA_8888 color type.
// TODO(nifong): support WGC color spaces while still using an RGBA_8888 color type when
// on a cpu backend.
CanvasKit.MakeSurface = function(width, height) {
/* @dict */
var imageInfo = {
@ -35,6 +39,7 @@
// Since we are sending these pixels directly into the HTML canvas,
// (and those pixels are un-premultiplied, i.e. straight r,g,b,a)
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorSpace': CanvasKit.SkColorSpace.SRGB,
}
var pixelLen = width * height * 4; // it's 8888, so 4 bytes per pixel
// Allocate the buffer of pixels to be drawn into.

View File

@ -259,6 +259,17 @@ var CanvasKit = {
scaled: function() {},
},
SkColorSpace: {
Equals: function() {},
SRGB: {},
DISPLAY_P3: {},
ADOBE_RGB: {},
// private API (from C++ bindings)
_MakeSRGB: function() {},
_MakeDisplayP3: function() {},
_MakeAdobeRGB: function() {},
},
SkContourMeasureIter: {
next: function() {},
},
@ -520,6 +531,7 @@ var CanvasKit = {
// public API (from C++ bindings)
/** @return {CanvasKit.SkCanvas} */
getCanvas: function() {},
imageInfo: function() {},
/** @return {CanvasKit.SkImage} */
makeImageSnapshot: function() {},
makeSurface: function() {},

View File

@ -41,19 +41,22 @@
return CanvasKit.currentContext() || 0;
};
// arg can be of types:
// idOrElement can be of types:
// - String - in which case it is interpreted as an id of a
// canvas element.
// - HTMLCanvasElement - in which the provided canvas element will
// be used directly.
// Width and height can be provided to override those on the canvas
// element, or specify a height for when a context is provided.
CanvasKit.MakeWebGLCanvasSurface = function(arg, width, height) {
var canvas = arg;
// colorSpace - sk_sp<SkColorSpace> - one of the supported color spaces:
// CanvasKit.SkColorSpace.SRGB
// CanvasKit.SkColorSpace.DISPLAY_P3
// CanvasKit.SkColorSpace.ADOBE_RGB
CanvasKit.MakeWebGLCanvasSurface = function(idOrElement, colorSpace) {
colorSpace = colorSpace || null;
var canvas = idOrElement;
if (canvas.tagName !== 'CANVAS') {
canvas = document.getElementById(arg);
canvas = document.getElementById(idOrElement);
if (!canvas) {
throw 'Canvas with id ' + arg + ' was not found';
throw 'Canvas with id ' + idOrElement + ' was not found';
}
}
@ -64,10 +67,6 @@
throw 'failed to create webgl context: err ' + ctx;
}
if (!canvas && (!width || !height)) {
throw 'height and width must be provided with context';
}
var grcontext = this.MakeGrContext(ctx);
if (grcontext) {
@ -76,12 +75,10 @@
grcontext.setResourceCacheLimitBytes(RESOURCE_CACHE_BYTES);
}
// Maybe better to use clientWidth/height. See:
// https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
var surface = this.MakeOnScreenGLSurface(grcontext,
width || canvas.width,
height || canvas.height);
// Note that canvas.width/height here is used because it gives the size of the buffer we're
// rendering into. This may not be the same size the element is displayed on the page, which
// constrolled by css, and available in canvas.clientWidth/height.
var surface = this.MakeOnScreenGLSurface(grcontext, canvas.width, canvas.height, colorSpace);
if (!surface) {
SkDebug('falling back from GPU implementation to a SW based one');
// we need to throw away the old canvas (which was locked to

View File

@ -848,7 +848,8 @@ function CanvasRenderingContext2D(skcanvas) {
}
var img = CanvasKit.MakeImage(imageData.data, imageData.width, imageData.height,
CanvasKit.AlphaType.Unpremul,
CanvasKit.ColorType.RGBA_8888);
CanvasKit.ColorType.RGBA_8888,
CanvasKit.SkColorSpace.SRGB);
var src = CanvasKit.XYWHRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
var dst = CanvasKit.XYWHRect(x+dirtyX, y+dirtyY, dirtyWidth, dirtyHeight);
var inverted = CanvasKit.SkMatrix.invert(this._currentTransform);

View File

@ -8,6 +8,12 @@
CanvasKit.onRuntimeInitialized = function() {
// All calls to 'this' need to go in externs.js so closure doesn't minify them away.
// Create single copies of all three supported color spaces
// These are sk_sp<SkColorSpace>
CanvasKit.SkColorSpace.SRGB = CanvasKit.SkColorSpace._MakeSRGB();
CanvasKit.SkColorSpace.DISPLAY_P3 = CanvasKit.SkColorSpace._MakeDisplayP3();
CanvasKit.SkColorSpace.ADOBE_RGB = CanvasKit.SkColorSpace._MakeAdobeRGB();
// Add some helpers for matrices. This is ported from SkMatrix.cpp
// to save complexity and overhead of going back and forth between
// C++ and JS layers.
@ -1007,11 +1013,16 @@ CanvasKit.onRuntimeInitialized = function() {
// returns Uint8Array
CanvasKit.SkCanvas.prototype.readPixels = function(x, y, w, h, alphaType,
colorType, dstRowBytes) {
colorType, colorSpace, dstRowBytes) {
// supply defaults (which are compatible with HTMLCanvas's getImageData)
alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
colorType = colorType || CanvasKit.ColorType.RGBA_8888;
dstRowBytes = dstRowBytes || (4 * w);
colorSpace = colorSpace || CanvasKit.SkColorSpace.SRGB;
var pixBytes = 4;
if (colorType === CanvasKit.ColorType.RGBA_F16) {
pixBytes = 8;
}
dstRowBytes = dstRowBytes || (pixBytes * w);
var len = h * dstRowBytes
var pptr = CanvasKit._malloc(len);
@ -1020,6 +1031,7 @@ CanvasKit.onRuntimeInitialized = function() {
'height': h,
'colorType': colorType,
'alphaType': alphaType,
'colorSpace': colorSpace,
}, pptr, dstRowBytes, x, y);
if (!ok) {
CanvasKit._free(pptr);
@ -1036,7 +1048,7 @@ CanvasKit.onRuntimeInitialized = function() {
// pixels is a TypedArray. No matter the input size, it will be treated as
// a Uint8Array (essentially, a byte array).
CanvasKit.SkCanvas.prototype.writePixels = function(pixels, srcWidth, srcHeight,
destX, destY, alphaType, colorType) {
destX, destY, alphaType, colorType, colorSpace) {
if (pixels.byteLength % (srcWidth * srcHeight)) {
throw 'pixels length must be a multiple of the srcWidth * srcHeight';
}
@ -1044,6 +1056,7 @@ CanvasKit.onRuntimeInitialized = function() {
// supply defaults (which are compatible with HTMLCanvas's putImageData)
alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
colorType = colorType || CanvasKit.ColorType.RGBA_8888;
colorSpace = colorSpace || CanvasKit.SkColorSpace.SRGB;
var srcRowBytes = bytesPerPixel * srcWidth;
var pptr = CanvasKit._malloc(pixels.byteLength);
@ -1054,6 +1067,7 @@ CanvasKit.onRuntimeInitialized = function() {
'height': srcHeight,
'colorType': colorType,
'alphaType': alphaType,
'colorSpace': colorSpace,
}, pptr, srcRowBytes, destX, destY);
CanvasKit._free(pptr);
@ -1093,9 +1107,11 @@ CanvasKit.onRuntimeInitialized = function() {
return copyColorFromWasm(cPtr);
}
CanvasKit.SkPaint.prototype.setColor = function(color4f) {
CanvasKit.SkPaint.prototype.setColor = function(color4f, colorSpace) {
colorSpace = colorSpace || null; // null will be replaced with sRGB in the C++ method.
// emscripten wouldn't bind undefined to the sk_sp<SkColorSpace> expected here.
var cPtr = copy1dArray(color4f, CanvasKit.HEAPF32);
this._setColor(cPtr);
this._setColor(cPtr, colorSpace);
CanvasKit._free(cPtr);
}
@ -1160,21 +1176,23 @@ CanvasKit.onRuntimeInitialized = function() {
return dpe;
}
CanvasKit.SkShader.Color = function(color4f) {
CanvasKit.SkShader.Color = function(color4f, colorSpace) {
colorSpace = colorSpace || null
var cPtr = copy1dArray(color4f, CanvasKit.HEAPF32);
var result = CanvasKit.SkShader._Color(cPtr);
var result = CanvasKit.SkShader._Color(cPtr, colorSpace);
CanvasKit._free(cPtr);
return result;
}
CanvasKit.SkShader.MakeLinearGradient = function(start, end, colors, pos, mode, localMatrix, flags) {
CanvasKit.SkShader.MakeLinearGradient = function(start, end, colors, pos, mode, localMatrix, flags, colorSpace) {
colorSpace = colorSpace || null
var colorPtr = copy2dArray(colors, CanvasKit.HEAPF32);
var posPtr = copy1dArray(pos, CanvasKit.HEAPF32);
flags = flags || 0;
var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
colors.length, mode, flags, localMatrixPtr);
colors.length, mode, flags, localMatrixPtr, colorSpace);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
@ -1182,14 +1200,15 @@ CanvasKit.onRuntimeInitialized = function() {
return lgs;
}
CanvasKit.SkShader.MakeRadialGradient = function(center, radius, colors, pos, mode, localMatrix, flags) {
CanvasKit.SkShader.MakeRadialGradient = function(center, radius, colors, pos, mode, localMatrix, flags, colorSpace) {
colorSpace = colorSpace || null
var colorPtr = copy2dArray(colors, CanvasKit.HEAPF32);
var posPtr = copy1dArray(pos, CanvasKit.HEAPF32);
flags = flags || 0;
var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
colors.length, mode, flags, localMatrixPtr);
colors.length, mode, flags, localMatrixPtr, colorSpace);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
@ -1197,7 +1216,8 @@ CanvasKit.onRuntimeInitialized = function() {
return rgs;
}
CanvasKit.SkShader.MakeSweepGradient = function(cx, cy, colors, pos, mode, localMatrix, flags, startAngle, endAngle) {
CanvasKit.SkShader.MakeSweepGradient = function(cx, cy, colors, pos, mode, localMatrix, flags, startAngle, endAngle, colorSpace) {
colorSpace = colorSpace || null
var colorPtr = copy2dArray(colors, CanvasKit.HEAPF32);
var posPtr = copy1dArray(pos, CanvasKit.HEAPF32);
flags = flags || 0;
@ -1208,7 +1228,7 @@ CanvasKit.onRuntimeInitialized = function() {
var sgs = CanvasKit._MakeSweepGradientShader(cx, cy, colorPtr, posPtr,
colors.length, mode,
startAngle, endAngle, flags,
localMatrixPtr);
localMatrixPtr, colorSpace);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
@ -1217,7 +1237,8 @@ CanvasKit.onRuntimeInitialized = function() {
}
CanvasKit.SkShader.MakeTwoPointConicalGradient = function(start, startRadius, end, endRadius,
colors, pos, mode, localMatrix, flags) {
colors, pos, mode, localMatrix, flags, colorSpace) {
colorSpace = colorSpace || null
var colorPtr = copy2dArray(colors, CanvasKit.HEAPF32);
var posPtr = copy1dArray(pos, CanvasKit.HEAPF32);
flags = flags || 0;
@ -1225,7 +1246,7 @@ CanvasKit.onRuntimeInitialized = function() {
var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
start, startRadius, end, endRadius,
colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr);
colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr, colorSpace);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
@ -1335,13 +1356,14 @@ CanvasKit.MakeImageFromEncoded = function(data) {
// pixels must be a Uint8Array with bytes representing the pixel values
// (e.g. each set of 4 bytes could represent RGBA values for a single pixel).
CanvasKit.MakeImage = function(pixels, width, height, alphaType, colorType) {
CanvasKit.MakeImage = function(pixels, width, height, alphaType, colorType, colorSpace) {
var bytesPerPixel = pixels.length / (width * height);
var info = {
'width': width,
'height': height,
'alphaType': alphaType,
'colorType': colorType,
'colorSpace': colorSpace,
};
var pptr = copy1dArray(pixels, CanvasKit.HEAPU8);
// No need to _free pptr, Image takes it with SkData::MakeFromMalloc

View File

@ -116,6 +116,7 @@ describe('Core canvas behavior', () => {
const imageInfo = {
alphaType: CanvasKit.AlphaType.Unpremul,
colorType: CanvasKit.ColorType.RGBA_8888,
colorSpace: CanvasKit.SkColorSpace.SRGB,
width: img.width(),
height: img.height(),
};
@ -172,7 +173,8 @@ describe('Core canvas behavior', () => {
0, 0, 255, 255, // opaque blue
255, 0, 255, 100, // transparent purple
]);
const img = CanvasKit.MakeImage(pixels, 1, 4, CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888);
const img = CanvasKit.MakeImage(pixels, 1, 4, CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888,
CanvasKit.SkColorSpace.SRGB);
canvas.drawImage(img, 1, 1, paint);
img.delete();
});
@ -305,7 +307,8 @@ describe('Core canvas behavior', () => {
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.skewed(0.5, 0, 100, 100)
CanvasKit.SkMatrix.skewed(0.5, 0, 100, 100),
null, // color space
);
paint.setShader(rgsSkew);
r = CanvasKit.LTRBRect(0, 100, 100, 200);
@ -318,7 +321,8 @@ describe('Core canvas behavior', () => {
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.skewed(0.5, 0, 100, 100),
1 // interpolate colors in premul
1, // interpolate colors in premul
null, // color space
);
paint.setShader(rgsSkewPremul);
r = CanvasKit.LTRBRect(100, 100, 200, 200);
@ -350,7 +354,8 @@ describe('Core canvas behavior', () => {
[10, 110], 60, // end, radius
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror
CanvasKit.TileMode.Mirror,
null, // color space
);
paint.setShader(cgs);
let r = CanvasKit.LTRBRect(0, 0, 100, 100);
@ -365,6 +370,7 @@ describe('Core canvas behavior', () => {
CanvasKit.TileMode.Mirror,
null, // no local matrix
1, // interpolate colors in premul
null, // color space
);
paint.setShader(cgsPremul);
r = CanvasKit.LTRBRect(100, 0, 200, 100);
@ -377,7 +383,8 @@ describe('Core canvas behavior', () => {
[transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 0, 100)
CanvasKit.SkMatrix.rotated(Math.PI/4, 0, 100),
null, // color space
);
paint.setShader(cgs45);
r = CanvasKit.LTRBRect(0, 100, 100, 200);
@ -391,7 +398,8 @@ describe('Core canvas behavior', () => {
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 100, 100),
1 // interpolate colors in premul
1, // interpolate colors in premul
null, // color space
);
paint.setShader(cgs45Premul);
r = CanvasKit.LTRBRect(100, 100, 200, 200);
@ -585,6 +593,83 @@ describe('Core canvas behavior', () => {
expect(paint.getColor()).toEqual(Float32Array.of(3.3, 2.2, 1.1, 0.5));
});
describe('ColorSpace Support', () => {
it('Can create an SRGB 8888 surface', () => {
const colorSpace = CanvasKit.SkColorSpace.SRGB;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.SRGB);
expect(surface).toBeTruthy('Could not make surface');
let info = surface.imageInfo()
expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_8888);
expect(CanvasKit.SkColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888, colorSpace);
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('Can create a Display P3 surface', () => {
const colorSpace = CanvasKit.SkColorSpace.DISPLAY_P3;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.DISPLAY_P3);
expect(surface).toBeTruthy('Could not make surface');
if (surface.reportBackendType() !== 'GPU') {
console.log('Not expecting color space support in cpu backed suface.');
return;
}
let info = surface.imageInfo()
expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
expect(CanvasKit.SkColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_F16, colorSpace);
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('Can create an Adobe RGB surface', () => {
const colorSpace = CanvasKit.SkColorSpace.ADOBE_RGB;
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.ADOBE_RGB);
expect(surface).toBeTruthy('Could not make surface');
if (surface.reportBackendType() !== 'GPU') {
console.log('Not expecting color space support in cpu backed suface.');
return;
}
let info = surface.imageInfo()
expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
expect(CanvasKit.SkColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_F16, colorSpace);
expect(pixels).toBeTruthy('Could not read pixels from surface');
});
it('combine draws from several color spaces', () => {
const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.SkColorSpace.ADOBE_RGB);
expect(surface).toBeTruthy('Could not make surface');
if (surface.reportBackendType() !== 'GPU') {
console.log('Not expecting color space support in cpu backed suface.');
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.RED, CanvasKit.SkColorSpace.ADOBE_RGB);
canvas.drawPaint(paint);
paint.setColor(CanvasKit.RED, CanvasKit.SkColorSpace.DISPLAY_P3); // 93.7 in adobeRGB
canvas.drawRect(CanvasKit.LTRBRect(200, 0, 400, 600), paint);
paint.setColor(CanvasKit.RED, CanvasKit.SkColorSpace.SRGB); // 85.9 in adobeRGB
canvas.drawRect(CanvasKit.LTRBRect(400, 0, 600, 600), paint);
// this test paints three bands of red, each the maximum red that a color space supports.
// They are each represented by skia by some color in the Adobe RGB space of the surface,
// as floats between 0 and 1.
// TODO(nifong) readpixels and verify correctness after f32 readpixels bug is fixed
});
}); // end describe('ColorSpace Support')
describe('DOMMatrix support', () => {
gm('sweep_gradient_dommatrix', (canvas) => {
const paint = new CanvasKit.SkPaint();