Accept Flat Uint32Array, Float32Array, or 2d Float32Array as the color argument to MakeLinearGradient
TODO:
1. Accept a Color builder or a TypeArray from CanvasKit.Malloc
2. Apply the same treatment to all other gradient functions, MakeSkVertices, and drawAtlas
Change-Id: I94fa67a3c00d7b1ecdc004af4ffd3193404c1a30
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294707
Commit-Queue: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index a14d54b..5047e3f 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -6,6 +6,15 @@
## [Unreleased]
+### Changed
+ - In all places where color arrays are accepted (gradient makers, drawAtlas, and MakeSkVertices),
+ You can now provide either flat Float32Arrays of float colors, Uint32Arrays of int colors, or
+ 2d Arrays of Float32Array(4) colors. The one thing you should not pass is an Array of numbers,
+ since canvaskit wouldn't be able to tell whether they're ints or floats without checking them all.
+ The fastest choice for gradients is the flat Float32Array, the fastest choice for drawAtlas and
+ MakeSkVertices is the flat Uint32Array.
+ - Color arrays may also be objects created with CanvasKit.Malloc
+
## [0.16.2] - 2020-06-05
### Fixed
diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp
index f91090b..9365409 100644
--- a/modules/canvaskit/canvaskit_bindings.cpp
+++ b/modules/canvaskit/canvaskit_bindings.cpp
@@ -772,19 +772,33 @@
return SkImage::MakeRasterData(info, pixelData, rowBytes);
}), allow_raw_pointers());
+
+ // Here and in other gradient functions, cPtr is a pointer to an array of data
+ // representing colors. whether this is an array of SkColor or SkColor4f is indicated
+ // by the colorType argument. Only RGBA_8888 and RGBA_F32 are accepted.
function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
- uintptr_t /* SkColor4f* */ cPtr,
+ uintptr_t cPtr, SkColorType colorType,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode, uint32_t flags,
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);
- return SkGradientShader::MakeLinear(points, colors, colorSpace, positions, count,
+
+ if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+ const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
+ return SkGradientShader::MakeLinear(points, colors, colorSpace, positions, count,
mode, flags, &localMatrix);
+ } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+ const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
+ return SkGradientShader::MakeLinear(points, colors, positions, count,
+ mode, flags, &localMatrix);
+ } else {
+ SkDebugf("%d is not an accepted colorType\n", colorType);
+ return nullptr;
+ }
}), allow_raw_pointers());
#ifdef SK_SERIALIZE_SKP
function("_MakeSkPicture", optional_override([](uintptr_t /* unint8_t* */ dPtr,
@@ -797,20 +811,29 @@
}), allow_raw_pointers());
#endif
function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
- uintptr_t /* SkColor4f* */ cPtr,
+ uintptr_t cPtr, SkColorType colorType,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode, uint32_t flags,
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, colorSpace, positions, count,
- mode, flags, &localMatrix);
+ if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+ const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
+ return SkGradientShader::MakeRadial(center, radius, colors, colorSpace, positions, count,
+ mode, flags, &localMatrix);
+ } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+ const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
+ return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
+ mode, flags, &localMatrix);
+ } else {
+ SkDebugf("%d is not an accepted colorType\n", colorType);
+ return nullptr;
+ }
}), allow_raw_pointers());
function("_MakeSweepGradientShader", optional_override([](SkScalar cx, SkScalar cy,
- uintptr_t /* SkColor4f* */ cPtr,
+ uintptr_t cPtr, SkColorType colorType,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode,
SkScalar startAngle, SkScalar endAngle,
@@ -818,28 +841,49 @@
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, colorSpace, positions, count,
- mode, startAngle, endAngle, flags,
- &localMatrix);
+ if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+ const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
+ return SkGradientShader::MakeSweep(cx, cy, colors, colorSpace, positions, count,
+ mode, startAngle, endAngle, flags,
+ &localMatrix);
+ } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+ const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
+ return SkGradientShader::MakeSweep(cx, cy, colors, positions, count,
+ mode, startAngle, endAngle, flags,
+ &localMatrix);
+ } else {
+ SkDebugf("%d is not an accepted colorType\n", colorType);
+ return nullptr;
+ }
}), allow_raw_pointers());
function("_MakeTwoPointConicalGradientShader", optional_override([](
SkPoint start, SkScalar startRadius,
SkPoint end, SkScalar endRadius,
- uintptr_t /* SkColor4f* */ cPtr,
+ uintptr_t cPtr, SkColorType colorType,
uintptr_t /* SkScalar* */ pPtr,
int count, SkTileMode mode, uint32_t flags,
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, colorSpace, positions, count, mode,
- flags, &localMatrix);
+
+ if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+ const SkColor4f* colors = reinterpret_cast<const SkColor4f*>(cPtr);
+ return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
+ colors, colorSpace, positions, count, mode,
+ flags, &localMatrix);
+ } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+ const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
+ return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
+ colors, positions, count, mode,
+ flags, &localMatrix);
+ } else {
+ SkDebugf("%d is not an accepted colorType\n", colorType);
+ return nullptr;
+ }
}), allow_raw_pointers());
#ifdef SK_GL
@@ -898,9 +942,7 @@
self.concat(m);
}))
.function("drawArc", &SkCanvas::drawArc)
- // _drawAtlas takes an SkColor, unlike most private functions handling color.
- // This is because it takes an array of colors. Converting it on the Javascript side allows
- // an allocation to be avoided here.
+ // _drawAtlas takes an array of SkColor. There is no SkColor4f override.
.function("_drawAtlas", optional_override([](SkCanvas& self,
const sk_sp<SkImage>& atlas, uintptr_t /* SkRSXform* */ xptr,
uintptr_t /* SkRect* */ rptr, uintptr_t /* SkColor* */ cptr, int count,
diff --git a/modules/canvaskit/helper.js b/modules/canvaskit/helper.js
index cceb909..1fe2c40 100644
--- a/modules/canvaskit/helper.js
+++ b/modules/canvaskit/helper.js
@@ -153,6 +153,23 @@
function toUint32Color(c) {
return ((clamp(c[3]*255) << 24) | (clamp(c[0]*255) << 16) | (clamp(c[1]*255) << 8) | (clamp(c[2]*255) << 0)) >>> 0;
}
+// Accepts various colors representations and converts them to an array of int colors.
+// Does not handle builders.
+function assureIntColors(arr) {
+ if (arr instanceof Float32Array) {
+ var count = Math.floor(arr.length / 4);
+ var result = new Uint32Array(count);
+ for (var i = 0; i < count; i ++) {
+ result[i] = toUint32Color(arr.slice(i*4, (i+1)*4));
+ }
+ return result;
+ } else if (arr instanceof Uint32Array) {
+ return arr;
+ } else if (arr instanceof Array && arr[0] instanceof Float32Array) {
+ return arr.map(toUint32Color);
+ }
+
+}
function uIntColorToCanvasKitColor(c) {
return CanvasKit.Color(
(c >> 16) & 0xFF,
@@ -259,6 +276,36 @@
return ptr;
}
+// Copies an array of colors to wasm, returning an object with the pointer
+// and info necessary to use the copied colors.
+// Accepts either a flat Float32Array, flat Uint32Array or Array of Float32Arrays.
+// If color is an object that was allocated with CanvasKit.Malloc, it's pointer is
+// returned and no extra copy is performed.
+// Array of Float32Arrays is deprecated and planned to be removed, prefer flat
+// Float32Array
+// TODO(nifong): have this accept color builders.
+function copyFlexibleColorArray(colors) {
+ var result = {
+ colorPtr: nullptr,
+ count: colors.length,
+ colorType: CanvasKit.ColorType.RGBA_F32,
+ }
+ if (colors instanceof Float32Array) {
+ result.colorPtr = copy1dArray(colors, "HEAPF32");
+ result.count = colors.length / 4;
+
+ } else if (colors instanceof Uint32Array) {
+ result.colorPtr = copy1dArray(colors, "HEAPU32");
+ result.colorType = CanvasKit.ColorType.RGBA_8888;
+
+ } else if (colors instanceof Array && colors[0] instanceof Float32Array) {
+ result.colorPtr = copy2dArray(colors, "HEAPF32");
+ } else {
+ throw('Invalid argument to copyFlexibleColorArray, Not a color array '+typeof(colors));
+ }
+ return result;
+}
+
var defaultPerspective = Float32Array.of(0, 0, 1);
var _scratch3x3MatrixPtr = nullptr;
diff --git a/modules/canvaskit/interface.js b/modules/canvaskit/interface.js
index 23a4c4a..183f988 100644
--- a/modules/canvaskit/interface.js
+++ b/modules/canvaskit/interface.js
@@ -881,8 +881,8 @@
// and CanvasKit.SkColorBuilder (fastest)
// Or they can be an array of floats of length 4*number of destinations.
// colors are optional and used to tint the drawn images using the optional blend mode
- // drawAtlas ONLY accepts uint colors such as those created with CanvasKit.ColorAsInt(r, g, b, a)
- // whether they are provided as an array or a builder.
+ // Colors may be an SkColorBuilder, a Uint32Array of int colors,
+ // a Flat Float32Array of float colors or a 2d Array of Float32Array(4) (deprecated)
CanvasKit.SkCanvas.prototype.drawAtlas = function(atlas, srcRects, dstXforms, paint,
/*optional*/ blendMode, colors) {
if (!atlas || !paint || !srcRects || !dstXforms) {
@@ -924,7 +924,7 @@
if (colors.build) {
colorPtr = colors.build();
} else {
- colorPtr = copy1dArray(colors, "HEAPU32");
+ colorPtr = copy1dArray(assureIntColors(colors), "HEAPU32");
}
}
@@ -1192,49 +1192,49 @@
CanvasKit.SkShader.MakeLinearGradient = function(start, end, colors, pos, mode, localMatrix, flags, colorSpace) {
colorSpace = colorSpace || null
- var colorPtr = copy2dArray(colors, "HEAPF32");
- var posPtr = copy1dArray(pos, "HEAPF32");
+ var cPtrInfo = copyFlexibleColorArray(colors);
+ var posPtr = copy1dArray(pos, "HEAPF32");
flags = flags || 0;
var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
- var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
- colors.length, mode, flags, localMatrixPtr, colorSpace);
+ var lgs = CanvasKit._MakeLinearGradientShader(start, end, cPtrInfo.colorPtr, cPtrInfo.colorType, posPtr,
+ cPtrInfo.count, mode, flags, localMatrixPtr, colorSpace);
- CanvasKit._free(colorPtr);
+ freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
return lgs;
}
CanvasKit.SkShader.MakeRadialGradient = function(center, radius, colors, pos, mode, localMatrix, flags, colorSpace) {
colorSpace = colorSpace || null
- var colorPtr = copy2dArray(colors, "HEAPF32");
+ var cPtrInfo = copyFlexibleColorArray(colors);
var posPtr = copy1dArray(pos, "HEAPF32");
flags = flags || 0;
var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
- var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
- colors.length, mode, flags, localMatrixPtr, colorSpace);
+ var rgs = CanvasKit._MakeRadialGradientShader(center, radius, cPtrInfo.colorPtr, cPtrInfo.colorType, posPtr,
+ cPtrInfo.count, mode, flags, localMatrixPtr, colorSpace);
- CanvasKit._free(colorPtr);
+ freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
return rgs;
}
CanvasKit.SkShader.MakeSweepGradient = function(cx, cy, colors, pos, mode, localMatrix, flags, startAngle, endAngle, colorSpace) {
colorSpace = colorSpace || null
- var colorPtr = copy2dArray(colors, "HEAPF32");
+ var cPtrInfo = copyFlexibleColorArray(colors);
var posPtr = copy1dArray(pos, "HEAPF32");
flags = flags || 0;
startAngle = startAngle || 0;
endAngle = endAngle || 360;
var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
- var sgs = CanvasKit._MakeSweepGradientShader(cx, cy, colorPtr, posPtr,
- colors.length, mode,
+ var sgs = CanvasKit._MakeSweepGradientShader(cx, cy, cPtrInfo.colorPtr, cPtrInfo.colorType, posPtr,
+ cPtrInfo.count, mode,
startAngle, endAngle, flags,
localMatrixPtr, colorSpace);
- CanvasKit._free(colorPtr);
+ freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
return sgs;
}
@@ -1242,16 +1242,16 @@
CanvasKit.SkShader.MakeTwoPointConicalGradient = function(start, startRadius, end, endRadius,
colors, pos, mode, localMatrix, flags, colorSpace) {
colorSpace = colorSpace || null
- var colorPtr = copy2dArray(colors, "HEAPF32");
+ var cPtrInfo = copyFlexibleColorArray(colors);
var posPtr = copy1dArray(pos, "HEAPF32");
flags = flags || 0;
var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
- start, startRadius, end, endRadius,
- colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr, colorSpace);
+ start, startRadius, end, endRadius, cPtrInfo.colorPtr, cPtrInfo.colorType,
+ posPtr, cPtrInfo.count, mode, flags, localMatrixPtr, colorSpace);
- CanvasKit._free(colorPtr);
+ freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
return rgs;
}
@@ -1381,7 +1381,11 @@
return CanvasKit._MakeImage(info, pptr, pixels.length, width * bytesPerPixel);
}
-// colors is an array of float color arrays
+// Colors may be a Uint32Array of int colors, a Flat Float32Array of float colors
+// or a 2d Array of Float32Array(4) (deprecated)
+// the underlying skia function accepts only int colors so it is recommended
+// to pass an array of int colors to avoid an extra conversion.
+// SkColorBuilder is not accepted.
CanvasKit.MakeSkVertices = function(mode, positions, textureCoordinates, colors,
indices, isVolatile) {
// Default isVolitile to true if not set
@@ -1400,15 +1404,18 @@
flags |= (1 << 2);
}
- var builder = new CanvasKit._SkVerticesBuilder(mode, positions.length, idxCount, flags);
+ var builder = new CanvasKit._SkVerticesBuilder(mode, positions.length, idxCount, flags);
copy2dArray(positions, "HEAPF32", builder.positions());
if (builder.texCoords()) {
copy2dArray(textureCoordinates, "HEAPF32", builder.texCoords());
}
if (builder.colors()) {
- // Convert from canvaskit 4f colors to 32 bit uint colors which builder supports.
- copy1dArray(colors.map(toUint32Color), "HEAPU32", builder.colors());
+ if (colors.build) {
+ throw('Color builder not accepted by MakeSkVertices, use array of ints');
+ } else {
+ copy1dArray(assureIntColors(colors), "HEAPU32", builder.colors());
+ }
}
if (builder.indices()) {
copy1dArray(indices, "HEAPU16", builder.indices());
diff --git a/modules/canvaskit/tests/canvas.spec.js b/modules/canvaskit/tests/canvas.spec.js
index a5851e4..bb283ed 100644
--- a/modules/canvaskit/tests/canvas.spec.js
+++ b/modules/canvaskit/tests/canvas.spec.js
@@ -583,8 +583,31 @@
paint.setAntiAlias(true);
const points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+ // 2d float color array
const colors = [CanvasKit.RED, CanvasKit.BLUE,
- CanvasKit.YELLOW, CanvasKit.CYAN];
+ CanvasKit.YELLOW, CanvasKit.CYAN];
+ const vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+ points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
+
+ const bounds = vertices.bounds();
+ expect(bounds.fLeft).toEqual(0);
+ expect(bounds.fTop).toEqual(0);
+ expect(bounds.fRight).toEqual(250);
+ expect(bounds.fBottom).toEqual(250);
+
+ canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
+ vertices.delete();
+ paint.delete();
+ });
+
+ gm('drawvertices_canvas_flat_floats', (canvas) => {
+ const paint = new CanvasKit.SkPaint();
+ paint.setAntiAlias(true);
+
+ const points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+ // 1d float color array
+ const colors = Float32Array.of(...CanvasKit.RED, ...CanvasKit.BLUE,
+ ...CanvasKit.YELLOW, ...CanvasKit.CYAN);
const vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
diff --git a/modules/canvaskit/tests/core.spec.js b/modules/canvaskit/tests/core.spec.js
index c6346ac..f314776 100644
--- a/modules/canvaskit/tests/core.spec.js
+++ b/modules/canvaskit/tests/core.spec.js
@@ -265,12 +265,12 @@
0.5, 0, 300, 300,
];
- const colors = [
+ const colors = Uint32Array.of(
CanvasKit.ColorAsInt( 85, 170, 10, 128), // light green
CanvasKit.ColorAsInt( 51, 51, 191, 128), // light blue
CanvasKit.ColorAsInt( 0, 0, 0, 128),
CanvasKit.ColorAsInt(256, 256, 256, 128),
- ];
+ );
canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.Modulate, colors);
@@ -322,7 +322,10 @@
const lgsPremul = CanvasKit.SkShader.MakeLinearGradient(
[100, 0], [150, 100], // start and stop points
- [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
+ Uint32Array.of(
+ CanvasKit.ColorAsInt(0, 255, 255, 0),
+ CanvasKit.ColorAsInt(0, 0, 255, 255),
+ CanvasKit.ColorAsInt(255, 0, 0, 255)),
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
null, // no local matrix
@@ -335,7 +338,7 @@
const lgs45 = CanvasKit.SkShader.MakeLinearGradient(
[0, 100], [50, 200], // start and stop points
- [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
+ Float32Array.of(...transparentGreen, ...CanvasKit.BLUE, ...CanvasKit.RED),
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 0, 100),
@@ -345,14 +348,21 @@
canvas.drawRect(r, paint);
canvas.drawRect(r, strokePaint);
+ // malloc'd color array
+ const colors = CanvasKit.Malloc(Float32Array, 12);
+ const typedColorsArray = colors.toTypedArray();
+ typedColorsArray.set(transparentGreen, 0);
+ typedColorsArray.set(CanvasKit.BLUE, 4);
+ typedColorsArray.set(CanvasKit.RED, 8);
const lgs45Premul = CanvasKit.SkShader.MakeLinearGradient(
[100, 100], [150, 200], // start and stop points
- [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
+ typedColorsArray,
[0, 0.65, 1.0],
CanvasKit.TileMode.Mirror,
CanvasKit.SkMatrix.rotated(Math.PI/4, 100, 100),
1 // interpolate colors in premul
);
+ CanvasKit.Free(colors);
paint.setShader(lgs45Premul);
r = CanvasKit.LTRBRect(100, 100, 200, 200);
canvas.drawRect(r, paint);