diff options
author | Kevin Lubick <kjlubick@google.com> | 2018-11-03 07:51:19 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-11-03 12:18:04 +0000 |
commit | b5ae3b5afc29d78c9add8e16cb77d2de6cce2358 (patch) | |
tree | a222781a944b5b55f70e44306fb2ad7d1576e7d0 /experimental | |
parent | 0e6fc6fdae02bbc52aff49f5c3fb5ce0dcfe459a (diff) | |
download | skqp-b5ae3b5afc29d78c9add8e16cb77d2de6cce2358.tar.gz |
[canvaskit] Add drawVertices API
This also does some clean up to how we name enums - the caps felt a bit
obnoxious. CAPS are reserved now for constants (like colors).
Small bug fix with leaking memory on discrete path effects
This also adds a few more things from PathKit
Bug: skia:
Change-Id: Iad7e21ac36d35a36a8b255dc82b1dcc886344db1
Reviewed-on: https://skia-review.googlesource.com/c/166804
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Diffstat (limited to 'experimental')
-rw-r--r-- | experimental/canvaskit/canvaskit/example.html | 212 | ||||
-rw-r--r-- | experimental/canvaskit/canvaskit_bindings.cpp | 347 | ||||
-rwxr-xr-x | experimental/canvaskit/compile.sh | 5 | ||||
-rw-r--r-- | experimental/canvaskit/externs.js | 43 | ||||
-rw-r--r-- | experimental/canvaskit/htmlcanvas/canvas2d.js | 4 | ||||
-rw-r--r-- | experimental/canvaskit/interface.js | 225 |
6 files changed, 768 insertions, 68 deletions
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html index 8df01c7be5..6173ff9a73 100644 --- a/experimental/canvaskit/canvaskit/example.html +++ b/experimental/canvaskit/canvaskit/example.html @@ -17,6 +17,9 @@ </style> <h2> CanvasKit draws Paths to the browser</h2> +<canvas id=vertex1 width=300 height=300></canvas> +<canvas id=vertex2 width=300 height=300></canvas> +<canvas id=gradient1 width=300 height=300></canvas> <canvas id=patheffect width=300 height=300></canvas> <canvas id=paths width=200 height=200></canvas> <canvas id=ink width=300 height=300></canvas> @@ -50,24 +53,40 @@ var nimaFile = null; var nimaTexture = null; + + var bonesImage = null; CanvasKitInit({ locateFile: (file) => '/node_modules/canvaskit/bin/'+file, }).then((CK) => { - CK.initFonts(); - CanvasKit = CK; - DrawingExample(CanvasKit); - PathExample(CanvasKit); - InkExample(CanvasKit); - // Set bounds to fix the 4:3 resolution of the legos - addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON, - {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300})); - // Re-size to fit - SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); - SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); - SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); - NimaExample(CanvasKit, nimaFile, nimaTexture); + // when debugging, it can be handy to not run directly in the then, because if there + // is a failure (for example, miscalling an API), the WASM loader tries to re-load + // the web assembly in the (much slower) ArrayBuffer version. This will also fail + // and thus there is a lot of extra log spew. + // Thus, the setTimeout to run on the next microtask avoids this second loading + // and the log spew. + setTimeout(() => { + CK.initFonts(); + CanvasKit = CK; + DrawingExample(CanvasKit); + PathExample(CanvasKit); + InkExample(CanvasKit); + // Set bounds to fix the 4:3 resolution of the legos + addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON, + {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300})); + // Re-size to fit + SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); + SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); + SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); + + NimaExample(CanvasKit, nimaFile, nimaTexture); + + CanvasAPI1(CanvasKit); + + VertexAPI1(CanvasKit); + VertexAPI2(CanvasKit, bonesImage); - CanvasAPI1(CanvasKit); + GradiantAPI1(CanvasKit); + }, 0); }); fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => { @@ -121,6 +140,17 @@ }); }); + fetch('https://storage.googleapis.com/skia-cdn/misc/bones.jpg').then((resp) => { + resp.blob().then((blob) => { + let reader = new FileReader(); + reader.addEventListener("loadend", function() { + bonesImage = reader.result; + VertexAPI2(CanvasKit, bonesImage); + }); + reader.readAsArrayBuffer(blob); + }); + }); + function addScreenshotListener(surface) { if (!surface) { return; @@ -156,7 +186,7 @@ const paint = new CanvasKit.SkPaint(); const textPaint = new CanvasKit.SkPaint(); - textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); + textPaint.setColor(CanvasKit.RED); textPaint.setTextSize(30); textPaint.setAntiAlias(true); @@ -172,12 +202,12 @@ i++; paint.setPathEffect(dpe); - paint.setStyle(CanvasKit.PaintStyle.STROKE); + paint.setStyle(CanvasKit.PaintStyle.Stroke); paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30)); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); - canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); + canvas.clear(CanvasKit.TRANSPARENT); canvas.drawPath(path, paint); canvas.drawText('Try Clicking!', 10, 280, textPaint); @@ -221,7 +251,7 @@ paint.setStrokeWidth(1.0); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); - paint.setStyle(CanvasKit.PaintStyle.STROKE); + paint.setStyle(CanvasKit.PaintStyle.Stroke); const path = new CanvasKit.SkPath(); path.moveTo(20, 5); @@ -277,7 +307,7 @@ let paint = new CanvasKit.SkPaint(); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); - paint.setStyle(CanvasKit.PaintStyle.STROKE); + paint.setStyle(CanvasKit.PaintStyle.Stroke); paint.setStrokeWidth(4.0); paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50)); @@ -381,7 +411,7 @@ let seek = ((Date.now() - firstFrame) / duration) % 1.0; CanvasKit.setCurrentContext(context); animation.seek(seek); - canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); + canvas.clear(CanvasKit.TRANSPARENT); animation.render(canvas, bounds); surface.flush(); window.requestAnimationFrame(drawFrame); @@ -443,7 +473,7 @@ function drawFrame() { let seek = ((Date.now() - firstFrame) / 1000.0); CanvasKit.setCurrentContext(context); - canvas.clear(CanvasKit.Color(255, 255, 255, 0.0)); + canvas.clear(CanvasKit.TRANSPARENT); animation.seek(seek); animation.render(canvas); surface.flush(); @@ -452,4 +482,144 @@ window.requestAnimationFrame(drawFrame); } + function VertexAPI1(CanvasKit) { + const surface = CanvasKit.MakeCanvasSurface('vertex1'); + if (!surface) { + console.error('Could not make surface'); + return; + } + const context = CanvasKit.currentContext(); + const canvas = surface.getCanvas(); + let paint = new CanvasKit.SkPaint(); + + // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6 + // for original c++ version. + let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]]; + let colors = [CanvasKit.RED, CanvasKit.BLUE, + CanvasKit.YELLOW, CanvasKit.CYAN]; + let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan, + points, null, colors); + + canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint); + surface.flush(); + + vertices.delete(); + + // See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d + // for original c++ version. + points = [[ 300, 300 ], [ 50, 300 ], [ 200, 200 ], [ 300, 50 ]]; + let texs = [[ 0, 0 ], [ 0, 250 ], [ 250, 250 ], [ 250, 0 ]]; + vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan, + points, texs, colors); + + let shader = CanvasKit.MakeLinearGradientShader([0, 0], [250, 0], + colors, null, CanvasKit.TileMode.Clamp); + paint.setShader(shader); + + canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint); + surface.flush(); + + shader.delete(); + paint.delete(); + surface.delete(); + + } + + function VertexAPI2(CanvasKit, bonesImage) { + if (!CanvasKit || !bonesImage) { + return; + } + const surface = CanvasKit.MakeCanvasSurface('vertex2'); + if (!surface) { + console.error('Could not make surface'); + return; + } + const context = CanvasKit.currentContext(); + const canvas = surface.getCanvas(); + let paint = new CanvasKit.SkPaint(); + + let shader = CanvasKit.MakeImageShader(bonesImage, + CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp); + + // comment this out to see just the triangles move. + paint.setShader(shader); + + // points is the destination location on the canvas We want the output + // to be a 280x280 box (to start). + let points = [[ 0, 0 ], [ 280, 0 ], [ 280, 280 ], [ 0, 280 ]]; + // texs is the coordinates of the source in the texture + // (provided by the image shader). The image is 334x226 px big. + let texs = [[ 0, 0 ], [ 334, 0 ], [ 334, 226 ], [ 0, 226 ]]; + let boneidxs = [[1,0,0,0], [2,0,0,0], [3,0,0,0], [2,3,0,0]]; + let bonewts = [[1,0,0,0], [1,0,0,0], [1,0,0,0], [.5,.5,0,0]]; + let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan, + points, texs, null, boneidxs, bonewts); + + function drawFrame() { + let now = Date.now(); + let bones = [ + [[1,0, // world bone (move 10px down and to the right to center) + 0,1, + 10,10]], + [[1,0, // identity bone (bone for vertices that are static) + 0,1, + 0,0]], + [[1,0, // ossilate in x bone + 0,1, + 10*Math.sin(now/500),0]], + [[1,0, // ossilate in y bone + 0,1, + 0,30*Math.cos(now/500)]], + ]; + let tVerts = vertices.applyBones(bones); + CanvasKit.setCurrentContext(context); + //canvas.clear(CanvasKit.TRANSPARENT); + canvas.drawVertices(tVerts, CanvasKit.BlendMode.Src, paint); + surface.flush(); + + tVerts.delete(); + window.requestAnimationFrame(drawFrame); + } + window.requestAnimationFrame(drawFrame); + //tVerts.delete(); + //vertices.delete(); + + //shader && shader.delete(); + //paint.delete(); + //surface.delete(); + + } + /** + SkColor colors[2] = {SK_ColorBLUE, SK_ColorYELLOW}; + SkPaint paint; + paint.setShader(SkGradientShader::MakeRadial( + SkPoint::Make(128.0f, 128.0f), 180.0f, + colors, nullptr, 2, SkShader::kClamp_TileMode, 0, nullptr)); + canvas->drawPaint(paint);*/ + function GradiantAPI1(CanvasKit) { + const surface = CanvasKit.MakeCanvasSurface('gradient1'); + if (!surface) { + console.error('Could not make surface'); + return; + } + const context = CanvasKit.currentContext(); + const canvas = surface.getCanvas(); + let paint = new CanvasKit.SkPaint(); + + // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6 + // for original c++ version. + let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]]; + let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED]; + let pos = [0, .7, 1.0]; + let transform = [2, 0, 0, + 0, 2, 0, + 0, 0, 1] + let shader = CanvasKit.MakeRadialGradientShader([150,150], 130, colors, + pos, CanvasKit.TileMode.Mirror, transform); + + paint.setShader(shader); + paint.setTextSize(100); + canvas.drawText("Radial", 10, 200, paint); + surface.flush(); + } </script> diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp index 129ee50c8d..9f3db2ba33 100644 --- a/experimental/canvaskit/canvaskit_bindings.cpp +++ b/experimental/canvaskit/canvaskit_bindings.cpp @@ -12,19 +12,33 @@ #include "GrGLTypes.h" #endif +#include "SkBlendMode.h" #include "SkCanvas.h" -#include "SkDashPathEffect.h" +#include "SkColor.h" #include "SkCornerPathEffect.h" +#include "SkDashPathEffect.h" +#include "SkDashPathEffect.h" +#include "SkData.h" #include "SkDiscretePathEffect.h" #include "SkFontMgr.h" #include "SkFontMgrPriv.h" +#include "SkGradientShader.h" +#include "SkImageShader.h" #include "SkPaint.h" +#include "SkParsePath.h" #include "SkPath.h" #include "SkPathEffect.h" +#include "SkPathOps.h" #include "SkScalar.h" +#include "SkShader.h" +#include "SkString.h" +#include "SkStrokeRec.h" #include "SkSurface.h" #include "SkSurfaceProps.h" #include "SkTestFontMgr.h" +#include "SkTrimPathEffect.h" +#include "SkVertices.h" + #if SK_INCLUDE_SKOTTIE #include "Skottie.h" #endif @@ -47,6 +61,14 @@ using namespace emscripten; // Self-documenting types using JSArray = emscripten::val; using JSColor = int32_t; +using JSString = emscripten::val; +using SkPathOrNull = emscripten::val; +using Uint8Array = emscripten::val; + +// Aliases for less typing +using BoneIndices = SkVertices::BoneIndices; +using BoneWeights = SkVertices::BoneWeights; +using Bone = SkVertices::Bone; void EMSCRIPTEN_KEEPALIVE initFonts() { gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr; @@ -106,11 +128,17 @@ sk_sp<SkSurface> getWebGLSurface(std::string id, int width, int height) { } #endif -#if SK_INCLUDE_SKOTTIE -sk_sp<skottie::Animation> MakeAnimation(std::string json) { - return skottie::Animation::Make(json.c_str(), json.length()); +struct SimpleMatrix { + SkScalar scaleX, skewX, transX; + SkScalar skewY, scaleY, transY; + SkScalar pers0, pers1, pers2; +}; + +SkMatrix toSkMatrix(const SimpleMatrix& sm) { + return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX, + sm.skewY , sm.scaleY, sm.transY, + sm.pers0 , sm.pers1 , sm.pers2); } -#endif //======================================================================================== // Path things @@ -173,6 +201,28 @@ void ApplyTransform(SkPath& orig, orig.transform(m); } +bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) { + return Simplify(path, &path); +} + +bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) { + return Op(pathOne, pathTwo, op, &pathOne); +} + +JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) { + SkString s; + SkParsePath::ToSVGString(path, &s); + return emscripten::val(s.c_str()); +} + +SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) { + SkPath out; + if (Op(pathOne, pathTwo, op, &out)) { + return emscripten::val(out); + } + return emscripten::val::null(); +} + SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) { SkPath copy(a); return copy; @@ -182,12 +232,68 @@ bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) { return a == b; } +//======================================================================================== +// Path Effects +//======================================================================================== + +bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) { + SkScalar intervals[] = { on, off }; + auto pe = SkDashPathEffect::Make(intervals, 2, phase); + if (!pe) { + SkDebugf("Invalid args to dash()\n"); + return false; + } + SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle); + if (pe->filterPath(&path, path, &rec, nullptr)) { + return true; + } + SkDebugf("Could not make dashed path\n"); + return false; +} + +bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) { + auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal; + auto pe = SkTrimPathEffect::Make(startT, stopT, mode); + if (!pe) { + SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n"); + return false; + } + SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle); + if (pe->filterPath(&path, path, &rec, nullptr)) { + return true; + } + SkDebugf("Could not trim path\n"); + return false; +} + +struct StrokeOpts { + // Default values are set in chaining.js which allows clients + // to set any number of them. Otherwise, the binding code complains if + // any are omitted. + SkScalar width; + SkScalar miter_limit; + SkPaint::Join join; + SkPaint::Cap cap; +}; + +bool ApplyStroke(SkPath& path, StrokeOpts opts) { + SkPaint p; + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeCap(opts.cap); + p.setStrokeJoin(opts.join); + p.setStrokeWidth(opts.width); + p.setStrokeMiter(opts.miter_limit); + + return p.getFillPath(path, &path); +} + // to map from raw memory to a uint8array -val getSkDataBytes(const SkData *data) { - return val(typed_memory_view(data->size(), data->bytes())); +Uint8Array getSkDataBytes(const SkData *data) { + return Uint8Array(typed_memory_view(data->size(), data->bytes())); } -// Hack to avoid embind creating a binding for SkData destructor +// These objects have private destructors / delete mthods - I don't think +// we need to do anything other than tell emscripten to do nothing. namespace emscripten { namespace internal { template<typename ClassType> @@ -196,6 +302,10 @@ namespace emscripten { template<> void raw_destructor<SkData>(SkData *ptr) { } + + template<> + void raw_destructor<SkVertices>(SkVertices *ptr) { + } } } @@ -222,21 +332,100 @@ EMSCRIPTEN_BINDINGS(Skia) { return SkSurface::MakeRasterN32Premul(width, height, nullptr); }), allow_raw_pointers()); #endif + function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers()); function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers()); function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers()); - // Won't be called directly, there's a JS helper to deal with typed arrays. + function("MakePathFromOp", &MakePathFromOp); + + // These won't be called directly, there's a JS helper to deal with typed arrays. function("_MakeSkDashPathEffect", optional_override([](uintptr_t /* float* */ cptr, int count, SkScalar phase)->sk_sp<SkPathEffect> { // See comment above for uintptr_t explanation const float* intervals = reinterpret_cast<const float*>(cptr); return SkDashPathEffect::Make(intervals, count, phase); }), allow_raw_pointers()); - function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers()); + function("_MakeImageShader", optional_override([](uintptr_t /* uint8_t* */ iPtr, int ilen, + SkShader::TileMode tx, SkShader::TileMode ty)->sk_sp<SkShader> { + // See comment above for uintptr_t explanation + const uint8_t* imgBytes = reinterpret_cast<const uint8_t*>(iPtr); + + auto imgData = SkData::MakeFromMalloc(imgBytes, ilen); + auto img = SkImage::MakeFromEncoded(imgData); + if (!img) { + SkDebugf("Could not decode image\n"); + return nullptr; + } + + return SkImageShader::Make(img, tx, ty, nullptr); + }), allow_raw_pointers()); + function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end, + uintptr_t /* SkColor* */ cPtr, uintptr_t /* SkScalar* */ pPtr, + int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> { + SkPoint points[] = { start, end }; + // See comment above for uintptr_t explanation + const SkColor* colors = reinterpret_cast<const SkColor*> (cPtr); + const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr); + + return SkGradientShader::MakeLinear(points, colors, positions, count, + mode, flags, nullptr); + }), allow_raw_pointers()); + function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end, + uintptr_t /* SkColor* */ cPtr, uintptr_t /* SkScalar* */ pPtr, + int count, SkShader::TileMode mode, uint32_t flags, + const SimpleMatrix& lm)->sk_sp<SkShader> { + SkPoint points[] = { start, end }; + // See comment above for uintptr_t explanation + const SkColor* colors = reinterpret_cast<const SkColor*> (cPtr); + const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr); + + SkMatrix localMatrix = toSkMatrix(lm); + + return SkGradientShader::MakeLinear(points, colors, positions, count, + mode, flags, &localMatrix); + }), allow_raw_pointers()); + function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius, + uintptr_t /* SkColor* */ cPtr, uintptr_t /* SkScalar* */ pPtr, + int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> { + // See comment above for uintptr_t explanation + const SkColor* colors = reinterpret_cast<const SkColor*> (cPtr); + const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr); + + return SkGradientShader::MakeRadial(center, radius, colors, positions, count, + mode, flags, nullptr); + }), allow_raw_pointers()); + function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius, + uintptr_t /* SkColor* */ cPtr, uintptr_t /* SkScalar* */ pPtr, + int count, SkShader::TileMode mode, uint32_t flags, + const SimpleMatrix& lm)->sk_sp<SkShader> { + // See comment above for uintptr_t explanation + const SkColor* colors = reinterpret_cast<const SkColor*> (cPtr); + const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr); + + SkMatrix localMatrix = toSkMatrix(lm); + return SkGradientShader::MakeRadial(center, radius, colors, positions, count, + mode, flags, &localMatrix); + }), allow_raw_pointers()); + function("_MakeSkVertices", optional_override([](SkVertices::VertexMode mode, int vertexCount, + uintptr_t /* SkPoint* */ pPtr, uintptr_t /* SkPoint* */ tPtr, + uintptr_t /* SkColor* */ cPtr, + uintptr_t /* BoneIndices* */ biPtr, uintptr_t /* BoneWeights* */ bwPtr, + int indexCount, uintptr_t /* uint16_t * */ iPtr)->sk_sp<SkVertices> { + // See comment above for uintptr_t explanation + const SkPoint* positions = reinterpret_cast<const SkPoint*>(pPtr); + const SkPoint* texs = reinterpret_cast<const SkPoint*>(tPtr); + const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr); + const BoneIndices* boneIndices = reinterpret_cast<const BoneIndices*>(biPtr); + const BoneWeights* boneWeights = reinterpret_cast<const BoneWeights*>(bwPtr); + const uint16_t* indices = reinterpret_cast<const uint16_t*>(iPtr); + + return SkVertices::MakeCopy(mode, vertexCount, positions, texs, colors, + boneIndices, boneWeights, indexCount, indices); + }), allow_raw_pointers()); class_<SkCanvas>("SkCanvas") .constructor<>() .function("clear", optional_override([](SkCanvas& self, JSColor color)->void { // JS side gives us a signed int instead of an unsigned int for color - // Add a lambda to change it out. + // Add a optional_override to change it out. self.clear(SkColor(color)); })) .function("drawPaint", &SkCanvas::drawPaint) @@ -248,8 +437,9 @@ EMSCRIPTEN_BINDINGS(Skia) { // Otherwise, go with std::wstring and set UTF-32 encoding. self.drawText(text.c_str(), text.length(), x, y, p); })) + .function("drawVertices", select_overload<void (const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(&SkCanvas::drawVertices)) .function("flush", &SkCanvas::flush) - .function("rotate", select_overload<void (SkScalar degrees, SkScalar px, SkScalar py)>(&SkCanvas::rotate)) + .function("rotate", select_overload<void (SkScalar, SkScalar, SkScalar)>(&SkCanvas::rotate)) .function("save", &SkCanvas::save) .function("scale", &SkCanvas::scale) .function("setMatrix", &SkCanvas::setMatrix) @@ -279,7 +469,7 @@ EMSCRIPTEN_BINDINGS(Skia) { .function("setAntiAlias", &SkPaint::setAntiAlias) .function("setColor", optional_override([](SkPaint& self, JSColor color)->void { // JS side gives us a signed int instead of an unsigned int for color - // Add a lambda to change it out. + // Add a optional_override to change it out. self.setColor(SkColor(color)); })) .function("setPathEffect", &SkPaint::setPathEffect) @@ -305,6 +495,18 @@ EMSCRIPTEN_BINDINGS(Skia) { .function("_quadTo", &ApplyQuadTo) .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform)) + // PathEffects + .function("_dash", &ApplyDash) + .function("_trim", &ApplyTrim) + .function("_stroke", &ApplyStroke) + + // PathOps + .function("_simplify", &ApplySimplify) + .function("_op", &ApplyPathOp) + + // Exporting + .function("toSVGString", &ToSVGString) + .function("setFillType", &SkPath::setFillType) .function("getFillType", &SkPath::getFillType) .function("getBounds", &SkPath::getBounds) @@ -312,6 +514,9 @@ EMSCRIPTEN_BINDINGS(Skia) { .function("equals", &Equals) .function("copy", &CopyPath); + class_<SkShader>("SkShader") + .smart_ptr<sk_sp<SkShader>>("sk_sp<SkShader>"); + class_<SkSurface>("SkSurface") .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>") .function("width", &SkSurface::width) @@ -319,23 +524,90 @@ EMSCRIPTEN_BINDINGS(Skia) { .function("_flush", &SkSurface::flush) .function("makeImageSnapshot", &SkSurface::makeImageSnapshot) .function("_readPixels", optional_override([](SkSurface& self, int width, int height, uintptr_t /* uint8_t* */ cptr)->bool { - auto* dst = reinterpret_cast<uint8_t*>(cptr); + uint8_t* dst = reinterpret_cast<uint8_t*>(cptr); auto dstInfo = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType); return self.readPixels(dstInfo, dst, width*4, 0, 0); })) .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers()); + class_<SkVertices>("SkVertices") + .smart_ptr<sk_sp<SkVertices>>("sk_sp<SkVertices>") + .function("_applyBones", optional_override([](SkVertices& self, uintptr_t /* Bone* */ bptr, int boneCount)->sk_sp<SkVertices> { + // See comment above for uintptr_t explanation + const Bone* bones = reinterpret_cast<const Bone*>(bptr); + return self.applyBones(bones, boneCount); + })) + .function("bounds", &SkVertices::bounds) + .function("mode", &SkVertices::mode) + .function("uniqueID", &SkVertices::uniqueID) + .function("dumpPositions", optional_override([](SkVertices& self)->void { + auto pos = self.positions(); + for(int i = 0; i< self.vertexCount(); i++) { + SkDebugf("position[%d] = (%f, %f)\n", i, pos[i].x(), pos[i].y()); + } + })) + .function("vertexCount", &SkVertices::vertexCount); + + + enum_<SkBlendMode>("BlendMode") + .value("Clear", SkBlendMode::kClear) + .value("Src", SkBlendMode::kSrc) + .value("Dst", SkBlendMode::kDst) + .value("SrcOver", SkBlendMode::kSrcOver) + .value("DstOver", SkBlendMode::kDstOver) + .value("SrcIn", SkBlendMode::kSrcIn) + .value("DstIn", SkBlendMode::kDstIn) + .value("SrcOut", SkBlendMode::kSrcOut) + .value("DstOut", SkBlendMode::kDstOut) + .value("SrcATop", SkBlendMode::kSrcATop) + .value("DstATop", SkBlendMode::kDstATop) + .value("Xor", SkBlendMode::kXor) + .value("Plus", SkBlendMode::kPlus) + .value("Modulate", SkBlendMode::kModulate) + .value("Screen", SkBlendMode::kScreen) + .value("Overlay", SkBlendMode::kOverlay) + .value("Darken", SkBlendMode::kDarken) + .value("Lighten", SkBlendMode::kLighten) + .value("ColorDodge", SkBlendMode::kColorDodge) + .value("ColorBurn", SkBlendMode::kColorBurn) + .value("HardLight", SkBlendMode::kHardLight) + .value("SoftLight", SkBlendMode::kSoftLight) + .value("Difference", SkBlendMode::kDifference) + .value("Exclusion", SkBlendMode::kExclusion) + .value("Multiply", SkBlendMode::kMultiply) + .value("Hue", SkBlendMode::kHue) + .value("Saturation", SkBlendMode::kSaturation) + .value("Color", SkBlendMode::kColor) + .value("Luminosity", SkBlendMode::kLuminosity); enum_<SkPaint::Style>("PaintStyle") - .value("FILL", SkPaint::Style::kFill_Style) - .value("STROKE", SkPaint::Style::kStroke_Style) - .value("STROKE_AND_FILL", SkPaint::Style::kStrokeAndFill_Style); + .value("Fill", SkPaint::Style::kFill_Style) + .value("Stroke", SkPaint::Style::kStroke_Style) + .value("StrokeAndFill", SkPaint::Style::kStrokeAndFill_Style); enum_<SkPath::FillType>("FillType") - .value("WINDING", SkPath::FillType::kWinding_FillType) - .value("EVENODD", SkPath::FillType::kEvenOdd_FillType) - .value("INVERSE_WINDING", SkPath::FillType::kInverseWinding_FillType) - .value("INVERSE_EVENODD", SkPath::FillType::kInverseEvenOdd_FillType); + .value("Winding", SkPath::FillType::kWinding_FillType) + .value("EvenOdd", SkPath::FillType::kEvenOdd_FillType) + .value("InverseWinding", SkPath::FillType::kInverseWinding_FillType) + .value("InverseEvenOdd", SkPath::FillType::kInverseEvenOdd_FillType); + + enum_<SkPathOp>("PathOp") + .value("Difference", SkPathOp::kDifference_SkPathOp) + .value("Intersect", SkPathOp::kIntersect_SkPathOp) + .value("Union", SkPathOp::kUnion_SkPathOp) + .value("XOR", SkPathOp::kXOR_SkPathOp) + .value("ReverseDifference", SkPathOp::kReverseDifference_SkPathOp); + + enum_<SkShader::TileMode>("TileMode") + .value("Clamp", SkShader::TileMode::kClamp_TileMode) + .value("Repeat", SkShader::TileMode::kRepeat_TileMode) + .value("Mirror", SkShader::TileMode::kMirror_TileMode); + + enum_<SkVertices::VertexMode>("VertexMode") + .value("Triangles", SkVertices::VertexMode::kTriangles_VertexMode) + .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(). @@ -360,6 +632,31 @@ EMSCRIPTEN_BINDINGS(Skia) { .field("w", &SkISize::fWidth) .field("h", &SkISize::fHeight); + // Allows clients to supply a 1D array of 9 elements and the bindings + // will automatically turn it into a 3x3 2D matrix. + // e.g. path.transform([0,1,2,3,4,5,6,7,8]) + // This is likely simpler for the client than exposing SkMatrix + // directly and requiring them to do a lot of .delete(). + value_array<SimpleMatrix>("SkMatrix") + .element(&SimpleMatrix::scaleX) + .element(&SimpleMatrix::skewX) + .element(&SimpleMatrix::transX) + + .element(&SimpleMatrix::skewY) + .element(&SimpleMatrix::scaleY) + .element(&SimpleMatrix::transY) + + .element(&SimpleMatrix::pers0) + .element(&SimpleMatrix::pers1) + .element(&SimpleMatrix::pers2); + + constant("TRANSPARENT", (JSColor) SK_ColorTRANSPARENT); + constant("RED", (JSColor) SK_ColorRED); + constant("BLUE", (JSColor) SK_ColorBLUE); + constant("YELLOW", (JSColor) SK_ColorYELLOW); + constant("CYAN", (JSColor) SK_ColorCYAN); + // TODO(?) + #if SK_INCLUDE_SKOTTIE // Animation things (may eventually go in own library) class_<skottie::Animation>("Animation") @@ -377,7 +674,9 @@ EMSCRIPTEN_BINDINGS(Skia) { self.render(canvas, &r); }), allow_raw_pointers()); - function("MakeAnimation", &MakeAnimation); + function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> { + return skottie::Animation::Make(json.c_str(), json.length()); + })); constant("skottie", true); #endif @@ -405,8 +704,8 @@ EMSCRIPTEN_BINDINGS(Skia) { const uint8_t* nimaBytes = reinterpret_cast<const uint8_t*>(nptr); const uint8_t* textureBytes = reinterpret_cast<const uint8_t*>(tptr); - auto nima = SkData::MakeWithoutCopy(nimaBytes, nlen); - auto texture = SkData::MakeWithoutCopy(textureBytes, tlen); + auto nima = SkData::MakeFromMalloc(nimaBytes, nlen); + auto texture = SkData::MakeFromMalloc(textureBytes, tlen); return new NimaActor(nima, texture); }), allow_raw_pointers()); constant("nima", true); diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh index 026ca326fa..b7a8653de1 100755 --- a/experimental/canvaskit/compile.sh +++ b/experimental/canvaskit/compile.sh @@ -117,7 +117,8 @@ echo "Compiling bitcode" skia_use_freetype=true \ skia_use_icu=false \ skia_use_libheif=false \ - skia_use_libjpeg_turbo=false \ + skia_use_system_libjpeg_turbo = false \ + skia_use_libjpeg_turbo=true \ skia_use_libpng=true \ skia_use_libwebp=false \ skia_use_lua=false \ @@ -161,7 +162,9 @@ ${EMCXX} \ -Imodules/skottie/include \ -Imodules/sksg/include \ -Isrc/core/ \ + -Isrc/gpu/ \ -Isrc/sfnt/ \ + -Isrc/shaders/ \ -Isrc/utils/ \ -Itools \ -Itools/fonts \ diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js index c11b4caac9..4ced26d656 100644 --- a/experimental/canvaskit/externs.js +++ b/experimental/canvaskit/externs.js @@ -29,8 +29,12 @@ var CanvasKit = { LTRBRect: function() {}, MakeCanvas: function() {}, MakeCanvasSurface: function() {}, + MakeImageShader: function() {}, + MakeLinearGradientShader: function() {}, + MakeRadialGradientShader: function() {}, MakeNimaActor: function() {}, MakeSkDashPathEffect: function() {}, + MakeSkVertices: function() {}, MakeSurface: function() {}, currentContext: function() {}, getSkDataBytes: function() {}, @@ -39,8 +43,12 @@ var CanvasKit = { // private API (i.e. things declared in the bindings that we use // in the pre-js file) + _MakeImageShader: function() {}, + _MakeLinearGradientShader: function() {}, _MakeNimaActor: function() {}, + _MakeRadialGradientShader: function() {}, _MakeSkDashPathEffect: function() {}, + _MakeSkVertices: function() {}, _getRasterN32PremulSurface: function() {}, _getWebGLSurface: function() {}, @@ -89,13 +97,16 @@ var CanvasKit = { _close: function() {}, _conicTo: function() {}, _cubicTo: function() {}, + _dash: function() {}, _lineTo: function() {}, _moveTo: function() {}, _op: function() {}, _quadTo: function() {}, _rect: function() {}, _simplify: function() {}, + _stroke: function() {}, _transform: function() {}, + _trim: function() {}, delete: function() {}, }, @@ -137,6 +148,16 @@ var CanvasKit = { delete: function() {}, }, + SkVertices: { + // public API (from C++ bindings) + /** @return {CanvasKit.SkVertices} */ + applyBones: function() {}, + + // private API + /** @return {CanvasKit.SkVertices} */ + _applyBones: function() {}, + }, + // Constants and Enums gpu: {}, skottie: {}, @@ -162,11 +183,19 @@ var CanvasKit = { /** * @type {Float32Array} */ - HEAPF32: {}, // only needed for TypedArray mallocs + HEAPF32: {}, /** * @type {Uint8Array} */ HEAPU8: {}, + /** + * @type {Uint16Array} + */ + HEAPU16: {}, + /** + * @type {Int32Array} + */ + HEAP32: {}, _malloc: function() {}, _free: function() {}, @@ -181,16 +210,28 @@ CanvasKit.SkPath.prototype.arcTo = function() {}; CanvasKit.SkPath.prototype.close = function() {}; CanvasKit.SkPath.prototype.conicTo = function() {}; CanvasKit.SkPath.prototype.cubicTo = function() {}; +CanvasKit.SkPath.prototype.dash = function() {}; CanvasKit.SkPath.prototype.lineTo = function() {}; CanvasKit.SkPath.prototype.moveTo = function() {}; CanvasKit.SkPath.prototype.op = function() {}; CanvasKit.SkPath.prototype.quadTo = function() {}; CanvasKit.SkPath.prototype.rect = function() {}; CanvasKit.SkPath.prototype.simplify = function() {}; +CanvasKit.SkPath.prototype.stroke = function() {}; CanvasKit.SkPath.prototype.transform = function() {}; +CanvasKit.SkPath.prototype.trim = function() {}; CanvasKit.SkSurface.prototype.flush = function() {}; CanvasKit.SkSurface.prototype.dispose = function() {}; +CanvasKit.SkVertices.prototype.applyBones = function() {}; + +// Define StrokeOpts object +var StrokeOpts = {}; +StrokeOpts.prototype.width; +StrokeOpts.prototype.miter_limit; +StrokeOpts.prototype.cap; +StrokeOpts.prototype.join; + // Not sure why this is needed - might be a bug in emsdk that this isn't properly declared. function loadWebAssemblyModule() {} diff --git a/experimental/canvaskit/htmlcanvas/canvas2d.js b/experimental/canvaskit/htmlcanvas/canvas2d.js index 4289d87796..0d13fb0dbd 100644 --- a/experimental/canvaskit/htmlcanvas/canvas2d.js +++ b/experimental/canvaskit/htmlcanvas/canvas2d.js @@ -141,14 +141,14 @@ this.stroke = function() { if (this._currentPath) { - this._paint.setStyle(CanvasKit.PaintStyle.STROKE); + this._paint.setStyle(CanvasKit.PaintStyle.Stroke); this._canvas.drawPath(this._currentPath, this._paint); } } this.strokeText = function(text, x, y, maxWidth) { // TODO do something with maxWidth, probably involving measure - this._paint.setStyle(CanvasKit.PaintStyle.STROKE); + this._paint.setStyle(CanvasKit.PaintStyle.Stroke); this._canvas.drawText(text, x, y, this._paint); } diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js index d74f380e4d..be9cab1a83 100644 --- a/experimental/canvaskit/interface.js +++ b/experimental/canvaskit/interface.js @@ -1,7 +1,7 @@ // Adds JS functions to augment the CanvasKit interface. // For example, if there is a wrapper around the C++ call or logic to allow // chaining, it should go here. -(function(CanvasKit){ +(function(CanvasKit) { // CanvasKit.onRuntimeInitialized is called after the WASM library has loaded. // Anything that modifies an exposed class (e.g. SkPath) should be set // after onRuntimeInitialized, otherwise, it can happen outside of that scope. @@ -11,7 +11,9 @@ // Takes 1, 2, or 10 args, where the first arg is always the path. // The options for the remaining args are: // - an array of 9 parameters - // - the 9 parameters of a full Matrix + // - the 9 parameters of a full matrix + // an array of 6 parameters (omitting perspective) + // the 6 non-perspective params of a matrix. if (arguments.length === 1) { // Add path, unchanged. Use identify matrix this._addPath(arguments[0], 1, 0, 0, @@ -22,16 +24,17 @@ var sm = arguments[1]; this._addPath(arguments[0], a[1], a[2], a[3], a[4], a[5], a[6], - a[7], a[8], a[9]); - } else if (arguments.length === 10) { + a[7] || 0, a[8] || 0, a[9] || 1); + } else if (arguments.length === 7 || arguments.length === 10) { // User provided the 9 params of a (full) matrix directly. + // (or just the 6 non perspective ones) // These are in the same order as what Skia expects. var a = arguments; this._addPath(arguments[0], a[1], a[2], a[3], a[4], a[5], a[6], - a[7], a[8], a[9]); + a[7] || 0, a[8] || 0, a[9] || 1); } else { - console.err('addPath expected to take 1, 2, or 10 args. Got ' + arguments.length); + console.err('addPath expected to take 1, 2, 7, or 10 args. Got ' + arguments.length); return null; } return this; @@ -57,6 +60,13 @@ return this; }; + CanvasKit.SkPath.prototype.dash = function(on, off, phase) { + if (this._dash(on, off, phase)) { + return this; + } + return null; + }; + CanvasKit.SkPath.prototype.lineTo = function(x, y) { this._lineTo(x, y); return this; @@ -86,26 +96,62 @@ return null; }; + CanvasKit.SkPath.prototype.stroke = function(opts) { + // Fill out any missing values with the default values. + /** + * See externs.js for this definition + * @type {StrokeOpts} + */ + opts = opts || {}; + opts.width = opts.width || 1; + opts.miter_limit = opts.miter_limit || 4; + opts.cap = opts.cap || CanvasKit.StrokeCap.BUTT; + opts.join = opts.join || CanvasKit.StrokeJoin.MITER; + if (this._stroke(opts)) { + return this; + } + return null; + }; + CanvasKit.SkPath.prototype.transform = function() { // Takes 1 or 9 args if (arguments.length === 1) { - // argument 1 should be a 9 element array. + // argument 1 should be a 6 or 9 element array. var a = arguments[0]; this._transform(a[0], a[1], a[2], a[3], a[4], a[5], - a[6], a[7], a[8]); - } else if (arguments.length === 9) { - // these arguments are the 9 members of the matrix + a[6] || 0, a[7] || 0, a[8] || 1); + } else if (arguments.length === 6 || arguments.length === 9) { + // these arguments are the 6 or 9 members of the matrix var a = arguments; this._transform(a[0], a[1], a[2], a[3], a[4], a[5], - a[6], a[7], a[8]); + a[6] || 0, a[7] || 0, a[8] || 1); } else { - console.err('transform expected to take 1 or 9 arguments. Got ' + arguments.length); - return null; + throw 'transform expected to take 1 or 9 arguments. Got ' + arguments.length; } return this; }; + // isComplement is optional, defaults to false + CanvasKit.SkPath.prototype.trim = function(startT, stopT, isComplement) { + if (this._trim(startT, stopT, !!isComplement)) { + return this; + } + return null; + }; + + // bones should be a 3d array. + // Each bone is a 3x2 transformation matrix in column major order: + // | scaleX skewX transX | + // | skewY scaleY transY | + // and bones is an array of those matrices. + // Returns a copy of this (SkVertices) with the bones applied. + CanvasKit.SkVertices.prototype.applyBones = function(bones) { + var bPtr = copy3dArray(bones, CanvasKit.HEAPF32); + var vert = this._applyBones(bPtr, bones.length); + CanvasKit._free(bPtr); + return vert; + } // Run through the JS files that are added at compile time. if (CanvasKit._extraInitializations) { @@ -125,6 +171,68 @@ }; } + var nullptr = 0; // emscripten doesn't like to take null as uintptr_t + + // arr can be a normal JS array or a TypedArray + // dest is something like CanvasKit.HEAPF32 + function copy1dArray(arr, dest) { + if (!arr || !arr.length) { + return nullptr; + } + var ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT); + // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single + // byte elements. When we run _malloc, we always get an offset/pointer into + // that block of memory. + // CanvasKit exposes some different views to make it easier to work with + // different types. HEAPF32 for example, exposes it as a float* + // However, to make the ptr line up, we have to do some pointer arithmetic. + // Concretely, we need to convert ptr to go from an index into a 1-byte-wide + // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32) + // and thus we divide ptr by 4. + dest.set(arr, ptr / dest.BYTES_PER_ELEMENT); + return ptr; + } + + // arr should be a non-jagged 2d JS array (TypeyArrays can't be nested + // inside themselves.) + // dest is something like CanvasKit.HEAPF32 + function copy2dArray(arr, dest) { + if (!arr || !arr.length) { + return nullptr; + } + var ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT); + var idx = 0; + var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT; + for (var r = 0; r < arr.length; r++) { + for (var c = 0; c < arr[0].length; c++) { + dest[adjustedPtr + idx] = arr[r][c]; + idx++; + } + } + return ptr; + } + + // arr should be a non-jagged 3d JS array (TypeyArrays can't be nested + // inside themselves.) + // dest is something like CanvasKit.HEAPF32 + function copy3dArray(arr, dest) { + if (!arr || !arr.length || !arr[0].length) { + return nullptr; + } + var ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT); + var idx = 0; + var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT; + for (var x = 0; x < arr.length; x++) { + for (var y = 0; y < arr[0].length; y++) { + for (var z = 0; z < arr[0][0].length; z++) { + dest[adjustedPtr + idx] = arr[x][y][z]; + idx++; + } + } + } + return ptr; + } + CanvasKit.MakeSkDashPathEffect = function(intervals, phase) { if (!phase) { phase = 0; @@ -132,13 +240,91 @@ if (!intervals.length || intervals.length % 2 === 1) { throw 'Intervals array must have even length'; } - if (!(intervals instanceof Float32Array)) { - intervals = Float32Array.from(intervals); + var ptr = copy1dArray(intervals, CanvasKit.HEAPF32); + var dpe = CanvasKit._MakeSkDashPathEffect(ptr, intervals.length, phase); + CanvasKit._free(ptr); + return dpe; + } + + CanvasKit.MakeImageShader = function(imgData, xTileMode, yTileMode) { + var iptr = CanvasKit._malloc(imgData.byteLength); + CanvasKit.HEAPU8.set(new Uint8Array(imgData), iptr); + // No need to _free iptr, ImageShader takes it with SkData::MakeFromMalloc + + return CanvasKit._MakeImageShader(iptr, imgData.byteLength, xTileMode, yTileMode); + } + + CanvasKit.MakeLinearGradientShader = function(start, end, colors, pos, mode, localMatrix, flags) { + var colorPtr = copy1dArray(colors, CanvasKit.HEAP32); + var posPtr = copy1dArray(pos, CanvasKit.HEAPF32); + flags = flags || 0; + + if (localMatrix) { + // Add perspective args if not provided. + if (localMatrix.length === 6) { + localMatrix.push(0, 0, 1); + } + var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr, + colors.length, mode, flags, localMatrix); + } else { + var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr, + colors.length, mode, flags); + } + + CanvasKit._free(colorPtr); + CanvasKit._free(posPtr); + return lgs; + } + + CanvasKit.MakeRadialGradientShader = function(center, radius, colors, pos, mode, localMatrix, flags) { + // TODO: matrix and flags + var colorPtr = copy1dArray(colors, CanvasKit.HEAP32); + var posPtr = copy1dArray(pos, CanvasKit.HEAPF32); + flags = flags || 0; + + if (localMatrix) { + // Add perspective args if not provided. + if (localMatrix.length === 6) { + localMatrix.push(0, 0, 1); + } + var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr, + colors.length, mode, flags, localMatrix); + } else { + var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr, + colors.length, mode, flags); } - var BYTES_PER_ELEMENT = 4; // Float32Array always has 4 bytes per element - var ptr = CanvasKit._malloc(intervals.length * BYTES_PER_ELEMENT); - CanvasKit.HEAPF32.set(intervals, ptr / BYTES_PER_ELEMENT); - return CanvasKit._MakeSkDashPathEffect(ptr, intervals.length, phase); + + CanvasKit._free(colorPtr); + CanvasKit._free(posPtr); + return rgs; + } + + CanvasKit.MakeSkVertices = function(mode, positions, textureCoordinates, colors, + boneIndices, boneWeights, indices) { + var positionPtr = copy2dArray(positions, CanvasKit.HEAPF32); + var texPtr = copy2dArray(textureCoordinates, CanvasKit.HEAPF32); + // Since we write the colors to memory as signed integers (JSColor), we can + // read them out on the other side as unsigned ints (SkColor) just fine + // - it's effectively casting. + var colorPtr = copy1dArray(colors, CanvasKit.HEAP32); + + var boneIdxPtr = copy2dArray(boneIndices, CanvasKit.HEAP32); + var boneWtPtr = copy2dArray(boneWeights, CanvasKit.HEAPF32); + var idxPtr = copy1dArray(indices, CanvasKit.HEAPU16); + + var idxCount = (indices && indices.length) || 0; + // _MakeVertices will copy all the values in, so we are free to release + // the memory after. + var vertices = CanvasKit._MakeSkVertices(mode, positions.length, positionPtr, + texPtr, colorPtr, boneIdxPtr, boneWtPtr, + idxCount, idxPtr); + positionPtr && CanvasKit._free(positionPtr); + texPtr && CanvasKit._free(texPtr); + colorPtr && CanvasKit._free(colorPtr); + idxPtr && CanvasKit._free(idxPtr); + boneIdxPtr && CanvasKit._free(boneIdxPtr); + boneWtPtr && CanvasKit._free(boneWtPtr); + return vertices; } CanvasKit.MakeNimaActor = function(nimaFile, nimaTexture) { @@ -146,6 +332,7 @@ CanvasKit.HEAPU8.set(new Uint8Array(nimaFile), nptr); var tptr = CanvasKit._malloc(nimaTexture.byteLength); CanvasKit.HEAPU8.set(new Uint8Array(nimaTexture), tptr); + // No need to _free these ptrs, NimaActor takes them with SkData::MakeFromMalloc return CanvasKit._MakeNimaActor(nptr, nimaFile.byteLength, tptr, nimaTexture.byteLength); } |