217056c048
drawText is having issues in a release build. Skottie sometimes asserts in debug mode. This possibly has something to do with memory alignment - like https://skia-review.googlesource.com/c/skia/+/155980 helped fix. Patchset 9 shows off integrating Skia drawing to an HTML canvas using Ganesh. To see it locally, set up https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html and then set $EMSDK to be that directory. Then run make clean make local-example and navigate to http://localhost:8000/skia-wasm/example.html Patchset 20 shows off Skottie animating directly to a Canvas. Docs-Preview: https://skia.org/?cl=153882 Bug: skia: Change-Id: I2ad2f4ffac00925ee901982ccbaeb7aa63b1ea23 Reviewed-on: https://skia-review.googlesource.com/153882 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Kevin Lubick <kjlubick@google.com>
342 lines
13 KiB
C++
342 lines
13 KiB
C++
/*
|
|
* Copyright 2018 Google LLC
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "GrBackendSurface.h"
|
|
#include "GrContext.h"
|
|
#include "GrGLInterface.h"
|
|
#include "GrGLTypes.h"
|
|
#include "SkCanvas.h"
|
|
#include "SkCanvas.h"
|
|
#include "SkDashPathEffect.h"
|
|
#include "SkCornerPathEffect.h"
|
|
#include "SkDiscretePathEffect.h"
|
|
#include "SkFontMgr.h"
|
|
#include "SkFontMgrPriv.h"
|
|
#include "SkPaint.h"
|
|
#include "SkPath.h"
|
|
#include "SkPathEffect.h"
|
|
#include "SkScalar.h"
|
|
#include "SkSurface.h"
|
|
#include "SkSurfaceProps.h"
|
|
#include "SkTestFontMgr.h"
|
|
#include "Skottie.h"
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <GL/gl.h>
|
|
|
|
#include <emscripten.h>
|
|
#include <emscripten/bind.h>
|
|
#include <emscripten/html5.h>
|
|
|
|
using namespace emscripten;
|
|
|
|
using JSColor = int32_t;
|
|
|
|
|
|
void EMSCRIPTEN_KEEPALIVE initFonts() {
|
|
gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
|
|
}
|
|
|
|
// Wraps the WebGL context in an SkSurface and returns it.
|
|
sk_sp<SkSurface> getWebGLSurface(std::string id, int width, int height) {
|
|
// Context configurations
|
|
EmscriptenWebGLContextAttributes attrs;
|
|
emscripten_webgl_init_context_attributes(&attrs);
|
|
attrs.alpha = true;
|
|
attrs.premultipliedAlpha = true;
|
|
attrs.majorVersion = 1;
|
|
attrs.enableExtensionsByDefault = true;
|
|
|
|
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(id.c_str(), &attrs);
|
|
if (context < 0) {
|
|
printf("failed to create webgl context %d\n", context);
|
|
return nullptr;
|
|
}
|
|
EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
|
|
if (r < 0) {
|
|
printf("failed to make webgl current %d\n", r);
|
|
return nullptr;
|
|
}
|
|
|
|
glClearColor(0, 0, 0, 0);
|
|
glClearStencil(0);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
|
|
// setup GrContext
|
|
auto interface = GrGLMakeNativeInterface();
|
|
|
|
// setup contexts
|
|
sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
|
|
|
|
// 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;
|
|
|
|
info.fFormat = GL_RGBA8;
|
|
colorType = kRGBA_8888_SkColorType;
|
|
|
|
GrBackendRenderTarget target(width, height, 0, 8, info);
|
|
|
|
sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
|
|
kBottomLeft_GrSurfaceOrigin,
|
|
colorType, nullptr, nullptr));
|
|
return surface;
|
|
}
|
|
|
|
sk_sp<skottie::Animation> MakeAnimation(std::string json) {
|
|
return skottie::Animation::Make(json.c_str(), json.length());
|
|
}
|
|
|
|
//========================================================================================
|
|
// Path things
|
|
//========================================================================================
|
|
|
|
// All these Apply* methods are simple wrappers to avoid returning an object.
|
|
// The default WASM bindings produce code that will leak if a return value
|
|
// isn't assigned to a JS variable and has delete() called on it.
|
|
// These Apply methods, combined with the smarter binding code allow for chainable
|
|
// commands that don't leak if the return value is ignored (i.e. when used intuitively).
|
|
|
|
void ApplyAddPath(SkPath& orig, const SkPath& newPath,
|
|
SkScalar scaleX, SkScalar skewX, SkScalar transX,
|
|
SkScalar skewY, SkScalar scaleY, SkScalar transY,
|
|
SkScalar pers0, SkScalar pers1, SkScalar pers2) {
|
|
SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
|
|
skewY , scaleY, transY,
|
|
pers0 , pers1 , pers2);
|
|
orig.addPath(newPath, m);
|
|
}
|
|
|
|
void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
|
|
SkScalar radius) {
|
|
p.arcTo(x1, y1, x2, y2, radius);
|
|
}
|
|
|
|
void ApplyClose(SkPath& p) {
|
|
p.close();
|
|
}
|
|
|
|
void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
|
|
SkScalar w) {
|
|
p.conicTo(x1, y1, x2, y2, w);
|
|
}
|
|
|
|
void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
|
|
SkScalar x3, SkScalar y3) {
|
|
p.cubicTo(x1, y1, x2, y2, x3, y3);
|
|
}
|
|
|
|
void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
|
|
p.lineTo(x, y);
|
|
}
|
|
|
|
void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
|
|
p.moveTo(x, y);
|
|
}
|
|
|
|
void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
|
|
p.quadTo(x1, y1, x2, y2);
|
|
}
|
|
|
|
void ApplyTransform(SkPath& orig,
|
|
SkScalar scaleX, SkScalar skewX, SkScalar transX,
|
|
SkScalar skewY, SkScalar scaleY, SkScalar transY,
|
|
SkScalar pers0, SkScalar pers1, SkScalar pers2) {
|
|
SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
|
|
skewY , scaleY, transY,
|
|
pers0 , pers1 , pers2);
|
|
orig.transform(m);
|
|
}
|
|
|
|
SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
|
|
SkPath copy(a);
|
|
return copy;
|
|
}
|
|
|
|
bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
|
|
return a == b;
|
|
}
|
|
|
|
// to map from raw memory to a uint8array
|
|
val getSkDataBytes(const SkData *data) {
|
|
return val(typed_memory_view(data->size(), data->bytes()));
|
|
}
|
|
|
|
// Hack to avoid embind creating a binding for SkData destructor
|
|
namespace emscripten {
|
|
namespace internal {
|
|
template<typename ClassType>
|
|
void raw_destructor(ClassType *);
|
|
|
|
template<>
|
|
void raw_destructor<SkData>(SkData *ptr) {
|
|
}
|
|
}
|
|
}
|
|
|
|
// Some timesignatures below have uintptr_t instead of a pointer to a primative
|
|
// type (e.g. SkScalar). This is necessary because we can't use "bind" (EMSCRIPTEN_BINDINGS)
|
|
// and pointers to primitive types (Only bound types like SkPoint). We could if we used
|
|
// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
|
|
// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
|
|
// SkPath or SkCanvas.
|
|
//
|
|
// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
|
|
// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
|
|
// types Pi, Pf"). But, we can just pretend they are numbers and cast them to be pointers and
|
|
// the compiler is happy.
|
|
EMSCRIPTEN_BINDINGS(Skia) {
|
|
function("initFonts", &initFonts);
|
|
function("_getWebGLSurface", &getWebGLSurface, 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("_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());
|
|
|
|
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.
|
|
self.clear(SkColor(color));
|
|
}))
|
|
.function("drawPaint", &SkCanvas::drawPaint)
|
|
.function("drawPath", &SkCanvas::drawPath)
|
|
.function("drawRect", &SkCanvas::drawRect)
|
|
.function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x, SkScalar y, const SkPaint& p) {
|
|
return; // Currently broken, some memory things seem off.
|
|
//self.drawText(text.c_str(), text.length(), x, y, p);
|
|
}))
|
|
.function("flush", &SkCanvas::flush)
|
|
.function("save", &SkCanvas::save)
|
|
.function("translate", &SkCanvas::translate);
|
|
|
|
class_<SkData>("SkData")
|
|
.smart_ptr<sk_sp<SkData>>("sk_sp<SkData>>")
|
|
.function("size", &SkData::size);
|
|
|
|
class_<SkImage>("SkImage")
|
|
.smart_ptr<sk_sp<SkImage>>("sk_sp<SkImage>")
|
|
.function("encodeToData", select_overload<sk_sp<SkData>()const>(&SkImage::encodeToData));
|
|
|
|
class_<SkPaint>("SkPaint")
|
|
.constructor<>()
|
|
.function("copy", optional_override([](const SkPaint& self)->SkPaint {
|
|
SkPaint p(self);
|
|
return p;
|
|
}))
|
|
.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.
|
|
self.setColor(SkColor(color));
|
|
}))
|
|
.function("setPathEffect", &SkPaint::setPathEffect)
|
|
.function("setShader", &SkPaint::setShader)
|
|
.function("setStrokeWidth", &SkPaint::setStrokeWidth)
|
|
.function("setStyle", &SkPaint::setStyle)
|
|
.function("setTextSize", &SkPaint::setTextSize);
|
|
|
|
class_<SkPathEffect>("SkPathEffect")
|
|
.smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>");
|
|
|
|
//TODO make these chainable like PathKit
|
|
class_<SkPath>("SkPath")
|
|
.constructor<>()
|
|
.constructor<const SkPath&>()
|
|
// interface.js has 3 overloads of addPath
|
|
.function("_addPath", &ApplyAddPath)
|
|
.function("_arcTo", &ApplyArcTo)
|
|
.function("_close", &ApplyClose)
|
|
.function("_conicTo", &ApplyConicTo)
|
|
.function("_cubicTo", &ApplyCubicTo)
|
|
.function("_lineTo", &ApplyLineTo)
|
|
.function("_moveTo", &ApplyMoveTo)
|
|
.function("_quadTo", &ApplyQuadTo)
|
|
.function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
|
|
|
|
.function("setFillType", &SkPath::setFillType)
|
|
.function("getFillType", &SkPath::getFillType)
|
|
.function("getBounds", &SkPath::getBounds)
|
|
.function("computeTightBounds", &SkPath::computeTightBounds)
|
|
.function("equals", &Equals)
|
|
.function("copy", &CopyPath);
|
|
|
|
class_<SkSurface>("SkSurface")
|
|
.smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
|
|
.function("width", &SkSurface::width)
|
|
.function("height", &SkSurface::height)
|
|
.function("makeImageSnapshot", &SkSurface::makeImageSnapshot)
|
|
.function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
|
|
|
|
|
|
enum_<SkPaint::Style>("PaintStyle")
|
|
.value("FILL", SkPaint::Style::kFill_Style)
|
|
.value("STROKE", SkPaint::Style::kStroke_Style)
|
|
.value("STROKE_AND_FILL", 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);
|
|
|
|
// A value object is much simpler than a class - it is returned as a JS
|
|
// object and does not require delete().
|
|
// https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
|
|
value_object<SkRect>("SkRect")
|
|
.field("fLeft", &SkRect::fLeft)
|
|
.field("fTop", &SkRect::fTop)
|
|
.field("fRight", &SkRect::fRight)
|
|
.field("fBottom", &SkRect::fBottom);
|
|
|
|
// SkPoints can be represented by [x, y]
|
|
value_array<SkPoint>("SkPoint")
|
|
.element(&SkPoint::fX)
|
|
.element(&SkPoint::fY);
|
|
|
|
// {"w": Number, "h", Number}
|
|
value_object<SkSize>("SkSize")
|
|
.field("w", &SkSize::fWidth)
|
|
.field("h", &SkSize::fHeight);
|
|
|
|
value_object<SkISize>("SkISize")
|
|
.field("w", &SkISize::fWidth)
|
|
.field("h", &SkISize::fHeight);
|
|
|
|
// Animation things (may eventually go in own library)
|
|
class_<skottie::Animation>("Animation")
|
|
.smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
|
|
.function("version", optional_override([](skottie::Animation& self)->std::string {
|
|
return std::string(self.version().c_str());
|
|
}))
|
|
.function("size", &skottie::Animation::size)
|
|
.function("duration", &skottie::Animation::duration)
|
|
.function("seek", &skottie::Animation::seek)
|
|
.function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
|
|
self.render(canvas, nullptr);
|
|
}), allow_raw_pointers())
|
|
.function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas, const SkRect r)->void {
|
|
self.render(canvas, &r);
|
|
}), allow_raw_pointers());
|
|
|
|
function("MakeAnimation", &MakeAnimation);
|
|
|
|
function("currentContext", &emscripten_webgl_get_current_context);
|
|
function("setCurrentContext", &emscripten_webgl_make_context_current);
|
|
}
|