Productionize PathKit
- Remove some old API with VerbArgs that we didn't really like. - move from experimental/wasm -> experimental/pathkit and rename wasm_main.cpp to pathkit_wasm_bindings (more descriptive). - Make compile.sh nicer to use (with some form of command line args). - Use MODULARIZE=1 to make this play nicer with other WASM libraries and easier to import. - Add seperate ToCanvas() API - Move Region stuff behind the PATHKIT_TESTING flag (saves 100k on binary size). - Add npm package for wasm version. asm.js version should also be supported for older browsers. - Remove shell.html, which was largely too complicated. Replace it with example.html, which is more succinct and demos the more relevant APIs. See https://www.npmjs.com/package/experimental-pathkit-wasm Bug: skia:8216 Change-Id: I15f14dd8acd77331729998ae3e30d73e4b006761 Reviewed-on: https://skia-review.googlesource.com/144790 Reviewed-by: Joe Gregorio <jcgregorio@google.com> Reviewed-by: Florin Malita <fmalita@chromium.org> Commit-Queue: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
parent
8949c8a2f8
commit
e1b36fe3e5
3
experimental/pathkit/.gitignore
vendored
Normal file
3
experimental/pathkit/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
**/bin
|
||||
node_modules/
|
||||
package-lock.json
|
41
experimental/pathkit/Makefile
Normal file
41
experimental/pathkit/Makefile
Normal file
@ -0,0 +1,41 @@
|
||||
build:
|
||||
./compile.sh
|
||||
|
||||
npm:
|
||||
mkdir -p ./npm-wasm/bin
|
||||
mkdir -p ./npm-asmjs/bin
|
||||
./compile.sh
|
||||
cp ../../out/pathkit/pathkit.js ./npm-wasm/bin
|
||||
cp ../../out/pathkit/pathkit.wasm ./npm-wasm/bin
|
||||
#./compile.sh asm.js TODO
|
||||
|
||||
publish:
|
||||
cd npm-wasm; npm publish
|
||||
|
||||
update-major:
|
||||
cd npm-wasm; npm version major
|
||||
echo "Don't forget to publish."
|
||||
|
||||
update-minor:
|
||||
cd npm-wasm; npm version minor
|
||||
echo "Don't forget to publish."
|
||||
|
||||
update-patch:
|
||||
cd npm-wasm; npm version patch
|
||||
echo "Don't forget to publish."
|
||||
|
||||
dev:
|
||||
./compile.sh dev
|
||||
|
||||
dev-npm:
|
||||
mkdir -p ./npm-wasm/bin
|
||||
mkdir -p ./npm-asmjs/bin
|
||||
./compile.sh dev
|
||||
cp ../../out/pathkit/pathkit.js ./npm-wasm/bin
|
||||
cp ../../out/pathkit/pathkit.wasm ./npm-wasm/bin
|
||||
#./compile.sh asm.js TODO
|
||||
|
||||
example:
|
||||
npm install experimental-pathkit-wasm
|
||||
echo "Go check out localhost:8000/npm-wasm/example.html"
|
||||
python serve.py
|
15
experimental/pathkit/README.md
Normal file
15
experimental/pathkit/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
PathKit WASM API
|
||||
================
|
||||
|
||||
This library lets you use Skia's feature-rich PathOps API in the browser.
|
||||
|
||||
|
||||
Compiling the source
|
||||
--------------------
|
||||
|
||||
Download the [Enscriptem SDK](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html).
|
||||
|
||||
Set the EMSDK environment variable to the directory you installed it to.
|
||||
|
||||
Run `./experimental/pathkit/compile.sh` to compile a production, WASM build to `$SKIA_HOME/out/pathkit`.
|
||||
Add "--help" for more options.
|
@ -5,26 +5,62 @@
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
||||
# Run this from $SKIA_HOME, not from the directory this file is in.
|
||||
# This expects the environment variable EMSDK to be set
|
||||
HTML_SHELL="./experimental/wasm/shell.html"
|
||||
BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
|
||||
HTML_SHELL=$BASE_DIR/shell.html
|
||||
BUILD_DIR="out/pathkit"
|
||||
|
||||
# This expects the environment variable EMSDK to be set
|
||||
if [[ ! -d $EMSDK ]]; then
|
||||
echo "Be sure to set the EMSDK environment variable."
|
||||
exit 1
|
||||
echo "Be sure to set the EMSDK environment variable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Navigate to SKIA_HOME from where this file is located.
|
||||
pushd $BASE_DIR/../..
|
||||
|
||||
# Run this from $SKIA_HOME, not from the directory this file is in.
|
||||
if [[ ! -d ./src ]]; then
|
||||
echo "Cannot locate Skia source. Is the source checkout okay? Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $@ == *help* ]]; then
|
||||
echo "By default, this script builds a production WASM build of PathKit."
|
||||
echo ""
|
||||
echo "This script takes several optional parameters:"
|
||||
echo " dev = Make a build suitable for running tests or debugging"
|
||||
echo " asm.js = Build for asm.js instead of WASM"
|
||||
echo " serve = starts a webserver allowing a user to navigate to"
|
||||
echo " localhost:8000/pathkit.html to view the demo page."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
# Use -O0 for larger builds (but generally quicker)
|
||||
# Use -Oz for (much slower, but smaller/faster) production builds
|
||||
RELEASE_CONF="-Oz"
|
||||
if [[ $@ == *dev* ]]; then
|
||||
echo "Building a Debug/Testing build"
|
||||
RELEASE_CONF="-O0 -s ASSERTIONS=1 -s DEMANGLE_SUPPORT=1 -g2 -DPATHKIT_TESTING"
|
||||
fi
|
||||
|
||||
WASM_CONF="-s WASM=1"
|
||||
if [[ $@ == *asm.js* ]]; then
|
||||
echo "Building with asm.js instead of WASM"
|
||||
WASM_CONF="-s WASM=0 -s ALLOW_MEMORY_GROWTH=1 --separate-asm"
|
||||
fi
|
||||
|
||||
OUTPUT="-o $BUILD_DIR/pathkit.js"
|
||||
|
||||
source $EMSDK/emsdk_env.sh
|
||||
|
||||
echo "Compiling"
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p out/wasm
|
||||
mkdir -p $BUILD_DIR
|
||||
|
||||
# Use -O0 for larger builds (but generally quicker)
|
||||
# Use -Oz for (much slower, but smaller/faster) production builds
|
||||
em++ -Oz -std=c++14 \
|
||||
em++ $RELEASE_CONF -std=c++14 \
|
||||
-Iinclude/config \
|
||||
-Iinclude/core \
|
||||
-Iinclude/gpu \
|
||||
@ -36,13 +72,14 @@ em++ -Oz -std=c++14 \
|
||||
-Isrc/shaders \
|
||||
-Isrc/opts \
|
||||
--bind \
|
||||
-s WASM=1 \
|
||||
$WASM_CONF \
|
||||
-s MODULARIZE=1 \
|
||||
-s EXPORT_NAME="PathKitInit" \
|
||||
-s NO_EXIT_RUNTIME=1 \
|
||||
-s ERROR_ON_UNDEFINED_SYMBOLS=1 \
|
||||
-s ERROR_ON_MISSING_LIBRARIES=1 \
|
||||
--shell-file $HTML_SHELL \
|
||||
-o out/wasm/pathkit.html \
|
||||
experimental/wasm/wasm_main.cpp \
|
||||
$OUTPUT \
|
||||
$BASE_DIR/pathkit_wasm_bindings.cpp \
|
||||
src/core/SkAnalyticEdge.cpp \
|
||||
src/core/SkArenaAlloc.cpp \
|
||||
src/core/SkBlitter.cpp \
|
||||
@ -74,15 +111,8 @@ src/ports/SkMemory_malloc.cpp \
|
||||
src/utils/SkParse.cpp \
|
||||
src/utils/SkParsePath.cpp
|
||||
|
||||
# Add the following for debugging (bloats production code size otherwise)
|
||||
# list of all (most?) settings: https://github.com/kripken/emscripten/blob/incoming/src/settings.js
|
||||
#-s ASSERTIONS=1 \
|
||||
#-s DEMANGLE_SUPPORT=1 \
|
||||
#-g2
|
||||
if [[ $@ == *serve* ]]; then
|
||||
pushd $BUILD_DIR
|
||||
python serve.py
|
||||
fi
|
||||
|
||||
# To build with ASM.js (instead of WASM)
|
||||
# This doesn't give the same results as native c++ or wasm....
|
||||
#-s WASM=0 \
|
||||
#-s ALLOW_MEMORY_GROWTH=1 \
|
||||
|
||||
python -m SimpleHTTPServer 8000
|
190
experimental/pathkit/npm-wasm/LICENSE
Normal file
190
experimental/pathkit/npm-wasm/LICENSE
Normal file
@ -0,0 +1,190 @@
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
54
experimental/pathkit/npm-wasm/README.md
Normal file
54
experimental/pathkit/npm-wasm/README.md
Normal file
@ -0,0 +1,54 @@
|
||||
A WASM version of Skia's PathOps toolkit.
|
||||
|
||||
To use the library, run `npm install experimental-pathkit-wasm` and then simply include it:
|
||||
|
||||
<script src="/node_modules/experimental-pathkit-wasm/bin/pathkit.js"></script>
|
||||
PathKitInit({
|
||||
locateFile: (file) => '/node_modules/experimental-pathkit-wasm/bin/'+file,
|
||||
}).then((PathKit) => {
|
||||
// Code goes here using PathKit
|
||||
});
|
||||
|
||||
PathKit comes in two parts, a JS loader and the actual WASM code. The JS loader creates
|
||||
a global `PathKitInit` that can be called to load the WASM code. The `locateFile` function
|
||||
is used to tell the JS loader where to find the .wasm file. By default, it will
|
||||
look for /pathkit.wasm, so if this is not the case, use `locateFile` to configure
|
||||
this properly.
|
||||
The `PathKit` object returned through the .then() callback is fully loaded and ready to use.
|
||||
|
||||
See example.html for a fuller example of how to use the library.
|
||||
|
||||
Using PathKit and WebPack
|
||||
-------------------------
|
||||
|
||||
WebPack's support for WASM is still somewhat experimental, but PathKit can be
|
||||
used with a few configuration changes.
|
||||
|
||||
In the JS code, use require():
|
||||
|
||||
const PathKitInit = require('experimental-pathkit-wasm/bin/pathkit.js')
|
||||
PathKitInit().then((PathKit) => {
|
||||
// Code goes here using PathKit
|
||||
})
|
||||
|
||||
Since WebPack does not expose the entire `/node_modules/` directory, but instead
|
||||
packages only the needed pieces, we have to copy pathkit.wasm into the build directory.
|
||||
One such solution is to use [CopyWebpackPlugin](https://github.com/webpack-contrib/copy-webpack-plugin)
|
||||
For example, add the following plugin:
|
||||
|
||||
config.plugins.push(
|
||||
new CopyWebpackPlugin([
|
||||
{ from: 'node_modules/experimental-pathkit-wasm/bin/pathkit.wasm' }
|
||||
])
|
||||
);
|
||||
|
||||
If webpack gives an error similar to:
|
||||
|
||||
ERROR in ./node_modules/experimental-pathkit-wasm/bin/pathkit.js
|
||||
Module not found: Error: Can't resolve 'fs' in '...'
|
||||
|
||||
Then, add the following configuration change to the node section of the config:
|
||||
|
||||
config.node = {
|
||||
fs: 'empty'
|
||||
};
|
77
experimental/pathkit/npm-wasm/example.html
Normal file
77
experimental/pathkit/npm-wasm/example.html
Normal file
@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<title>PathKit (Skia + WASM)</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<style>
|
||||
svg, canvas {
|
||||
border: 1px dashed #AAA;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<svg id=svg xmlns='http://www.w3.org/2000/svg' width=200 height=200></svg>
|
||||
|
||||
<canvas id="canvas1"></canvas>
|
||||
|
||||
<canvas id="canvas2"></canvas>
|
||||
|
||||
<script type="text/javascript" src="/node_modules/experimental-pathkit-wasm/bin/pathkit.js"></script>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
PathKitInit({
|
||||
locateFile: (file) => '/node_modules/experimental-pathkit-wasm/bin/'+file,
|
||||
}).then((PathKit) => {
|
||||
|
||||
let firstPath = PathKit.FromSVGString('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
|
||||
|
||||
let secondPath = PathKit.NewPath();
|
||||
// Acts somewhat like the Canvas API
|
||||
secondPath.moveTo(1, 1);
|
||||
secondPath.lineTo(20, 1);
|
||||
secondPath.lineTo(10, 30);
|
||||
secondPath.close();
|
||||
|
||||
let combinedPath = PathKit.ApplyPathOp(firstPath, secondPath, PathKit.PathOp.INTERSECT);
|
||||
let simpleStr = PathKit.ToSVGString(combinedPath);
|
||||
|
||||
let newSVG = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
newSVG.setAttribute('stroke', 'rgb(0,0,200)');
|
||||
newSVG.setAttribute('fill', 'white');
|
||||
newSVG.setAttribute('transform', 'scale(5,5)');
|
||||
newSVG.setAttribute('d', simpleStr);
|
||||
document.getElementById('svg').appendChild(newSVG);
|
||||
|
||||
// Draw directly to Canvas
|
||||
let ctx = document.getElementById('canvas1').getContext('2d');
|
||||
ctx.strokeStyle = 'green';
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.scale(8, 4);
|
||||
ctx.beginPath();
|
||||
PathKit.ToCanvas(combinedPath, ctx);
|
||||
ctx.stroke();
|
||||
|
||||
// create Path2D object and use it in a Canvas.
|
||||
let path2D = PathKit.ToPath2D(combinedPath);
|
||||
ctx = document.getElementById('canvas2').getContext('2d');
|
||||
ctx.scale(8, 4);
|
||||
ctx.fillStyle = 'purple';
|
||||
ctx.strokeStyle = 'orange';
|
||||
ctx.fill(path2D);
|
||||
ctx.stroke(path2D);
|
||||
|
||||
// clean up WASM memory
|
||||
// See http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html?highlight=memory#memory-management
|
||||
firstPath.delete();
|
||||
secondPath.delete();
|
||||
combinedPath.delete();
|
||||
});
|
||||
|
||||
</script>
|
11
experimental/pathkit/npm-wasm/package.json
Normal file
11
experimental/pathkit/npm-wasm/package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "experimental-pathkit-wasm",
|
||||
"version": "0.0.4",
|
||||
"description": "A WASM version of Skia's PathOps toolkit",
|
||||
"main": "bin/pathkit.js",
|
||||
"homepage": "https://github.com/google/skia/tree/master/experimental/pathkit",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"license": "Apache-2.0"
|
||||
}
|
@ -38,47 +38,9 @@ void VisitPath(const SkPath& p, VisitFunc&& f) {
|
||||
}
|
||||
}
|
||||
|
||||
void EMSCRIPTEN_KEEPALIVE SkPathToVerbsArgsArray(const SkPath& path,
|
||||
emscripten::val /*Array*/ verbs,
|
||||
emscripten::val /*Array*/ args) {
|
||||
VisitPath(path, [&verbs, &args](SkPath::Verb verb, const SkPoint pts[4]) {
|
||||
switch (verb) {
|
||||
case SkPath::kMove_Verb:
|
||||
verbs.call<void>("push", MOVE);
|
||||
args.call<void>("push", pts[0].x(), pts[0].y());
|
||||
break;
|
||||
case SkPath::kLine_Verb:
|
||||
verbs.call<void>("push", LINE);
|
||||
args.call<void>("push", pts[1].x(), pts[1].y());
|
||||
break;
|
||||
case SkPath::kQuad_Verb:
|
||||
verbs.call<void>("push", QUAD);
|
||||
args.call<void>("push", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
|
||||
break;
|
||||
case SkPath::kConic_Verb:
|
||||
printf("unsupported conic verb\n");
|
||||
// TODO(kjlubick): Port in the logic from SkParsePath::ToSVGString?
|
||||
break;
|
||||
case SkPath::kCubic_Verb:
|
||||
verbs.call<void>("push", CUBIC);
|
||||
args.call<void>("push",
|
||||
pts[1].x(), pts[1].y(),
|
||||
pts[2].x(), pts[2].y(),
|
||||
pts[3].x(), pts[3].y());
|
||||
break;
|
||||
case SkPath::kClose_Verb:
|
||||
verbs.call<void>("push", CLOSE);
|
||||
break;
|
||||
case SkPath::kDone_Verb:
|
||||
SkASSERT(false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
emscripten::val JSArray = emscripten::val::global("Array");
|
||||
|
||||
emscripten::val EMSCRIPTEN_KEEPALIVE SkPathToCmdArray(SkPath path) {
|
||||
emscripten::val EMSCRIPTEN_KEEPALIVE ToCmds(SkPath path) {
|
||||
val cmds = JSArray.new_();
|
||||
|
||||
VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4]) {
|
||||
@ -125,62 +87,7 @@ emscripten::val EMSCRIPTEN_KEEPALIVE SkPathToCmdArray(SkPath path) {
|
||||
// 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.
|
||||
SkPath EMSCRIPTEN_KEEPALIVE SkPathFromVerbsArgsTyped(uintptr_t /* uint8_t* */ vptr, int numVerbs,
|
||||
uintptr_t /* float* */ aptr, int numArgs) {
|
||||
const auto* verbs = reinterpret_cast<const uint8_t*>(vptr);
|
||||
const auto* args = reinterpret_cast<const float*>(aptr);
|
||||
SkPath path;
|
||||
int argsIndex = 0;
|
||||
float x1, y1, x2, y2, x3, y3;
|
||||
|
||||
// if there are not enough arguments, bail with the path we've constructed so far.
|
||||
#define CHECK_NUM_ARGS(n) \
|
||||
if ((argsIndex + n) > numArgs) { \
|
||||
SkDebugf("Not enough args to match the verbs. Saw %d args\n", numArgs); \
|
||||
return path; \
|
||||
}
|
||||
|
||||
for(int i = 0; i < numVerbs; i++){
|
||||
switch (verbs[i]) {
|
||||
case MOVE:
|
||||
CHECK_NUM_ARGS(2);
|
||||
x1 = args[argsIndex++], y1 = args[argsIndex++];
|
||||
path.moveTo(x1, y1);
|
||||
break;
|
||||
case LINE:
|
||||
CHECK_NUM_ARGS(2);
|
||||
x1 = args[argsIndex++], y1 = args[argsIndex++];
|
||||
path.lineTo(x1, y1);
|
||||
break;
|
||||
case QUAD:
|
||||
CHECK_NUM_ARGS(4);
|
||||
x1 = args[argsIndex++], y1 = args[argsIndex++];
|
||||
x2 = args[argsIndex++], y2 = args[argsIndex++];
|
||||
path.quadTo(x1, y1, x2, y2);
|
||||
break;
|
||||
case CUBIC:
|
||||
CHECK_NUM_ARGS(6);
|
||||
x1 = args[argsIndex++], y1 = args[argsIndex++];
|
||||
x2 = args[argsIndex++], y2 = args[argsIndex++];
|
||||
x3 = args[argsIndex++], y3 = args[argsIndex++];
|
||||
path.cubicTo(x1, y1, x2, y2, x3, y3);
|
||||
break;
|
||||
case CLOSE:
|
||||
path.close();
|
||||
break;
|
||||
default:
|
||||
SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verbs[i]);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
#undef CHECK_NUM_ARGS
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// See above comment for rational of pointer mess
|
||||
SkPath EMSCRIPTEN_KEEPALIVE SkPathFromCmdTyped(uintptr_t /* float* */ cptr, int numCmds) {
|
||||
SkPath EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
|
||||
const auto* cmds = reinterpret_cast<const float*>(cptr);
|
||||
SkPath path;
|
||||
float x1, y1, x2, y2, x3, y3;
|
||||
@ -231,6 +138,10 @@ SkPath EMSCRIPTEN_KEEPALIVE SkPathFromCmdTyped(uintptr_t /* float* */ cptr, int
|
||||
return path;
|
||||
}
|
||||
|
||||
SkPath EMSCRIPTEN_KEEPALIVE NewPath() {
|
||||
return SkPath();
|
||||
}
|
||||
|
||||
//========================================================================================
|
||||
// SVG THINGS
|
||||
//========================================================================================
|
||||
@ -278,36 +189,43 @@ SkPath EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder builder) {
|
||||
// Canvas THINGS
|
||||
//========================================================================================
|
||||
|
||||
emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(SkPath path, val/* Path2D&*/ retVal) {
|
||||
void EMSCRIPTEN_KEEPALIVE ToCanvas(SkPath path, val/* Path2D or Canvas*/ ctx) {
|
||||
SkPath::Iter iter(path, false);
|
||||
SkPoint pts[4];
|
||||
SkPath::Verb verb;
|
||||
while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
|
||||
switch (verb) {
|
||||
case SkPath::kMove_Verb:
|
||||
retVal.call<void>("moveTo", pts[0].x(), pts[0].y());
|
||||
ctx.call<void>("moveTo", pts[0].x(), pts[0].y());
|
||||
break;
|
||||
case SkPath::kLine_Verb:
|
||||
retVal.call<void>("lineTo", pts[1].x(), pts[1].y());
|
||||
ctx.call<void>("lineTo", pts[1].x(), pts[1].y());
|
||||
break;
|
||||
case SkPath::kQuad_Verb:
|
||||
retVal.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
|
||||
ctx.call<void>("quadraticCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
|
||||
break;
|
||||
case SkPath::kConic_Verb:
|
||||
printf("unsupported conic verb\n");
|
||||
// TODO(kjlubick): Port in the logic from SkParsePath::ToSVGString?
|
||||
break;
|
||||
case SkPath::kCubic_Verb:
|
||||
retVal.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
|
||||
ctx.call<void>("bezierCurveTo", pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y(),
|
||||
pts[3].x(), pts[3].y());
|
||||
break;
|
||||
case SkPath::kClose_Verb:
|
||||
retVal.call<void>("closePath");
|
||||
ctx.call<void>("closePath");
|
||||
break;
|
||||
case SkPath::kDone_Verb:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emscripten::val JSPath2D = emscripten::val::global("Path2D");
|
||||
|
||||
emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(SkPath path) {
|
||||
val retVal = JSPath2D.new_();
|
||||
ToCanvas(path, retVal);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@ -315,11 +233,13 @@ emscripten::val EMSCRIPTEN_KEEPALIVE ToPath2D(SkPath path, val/* Path2D&*/ retVa
|
||||
// Region things
|
||||
//========================================================================================
|
||||
|
||||
#ifdef PATHKIT_TESTING
|
||||
SkPath GetBoundaryPathFromRegion(SkRegion region) {
|
||||
SkPath p;
|
||||
region.getBoundaryPath(&p);
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Binds the classes to the JS
|
||||
EMSCRIPTEN_BINDINGS(skia) {
|
||||
@ -335,14 +255,69 @@ EMSCRIPTEN_BINDINGS(skia) {
|
||||
.function("cubicTo",
|
||||
select_overload<void(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::cubicTo))
|
||||
.function("close", &SkPath::close)
|
||||
// Uncomment below for debugging.
|
||||
.function("dump", select_overload<void() const>(&SkPath::dump));
|
||||
#ifdef PATHKIT_TESTING
|
||||
.function("dump", select_overload<void() const>(&SkPath::dump))
|
||||
#endif
|
||||
;
|
||||
|
||||
class_<SkOpBuilder>("SkOpBuilder")
|
||||
.constructor<>()
|
||||
|
||||
.function("add", &SkOpBuilder::add);
|
||||
|
||||
|
||||
// Without these function() bindings, the function would be exposed but oblivious to
|
||||
// our types (e.g. SkPath)
|
||||
|
||||
// Import
|
||||
function("FromSVGString", &FromSVGString);
|
||||
function("FromCmds", &FromCmds);
|
||||
function("NewPath", &NewPath);
|
||||
// Path2D is opaque, so we can't read in from it.
|
||||
|
||||
// Export
|
||||
function("ToPath2D", &ToPath2D);
|
||||
function("ToCanvas", &ToCanvas);
|
||||
function("ToSVGString", &ToSVGString);
|
||||
function("ToCmds", &ToCmds);
|
||||
|
||||
// PathOps
|
||||
function("SimplifyPath", &SimplifyPath);
|
||||
function("ApplyPathOp", &ApplyPathOp);
|
||||
function("ResolveBuilder", &ResolveBuilder);
|
||||
|
||||
enum_<SkPathOp>("PathOp")
|
||||
.value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
|
||||
.value("INTERSECT", SkPathOp::kIntersect_SkPathOp)
|
||||
.value("UNION", SkPathOp::kUnion_SkPathOp)
|
||||
.value("XOR", SkPathOp::kXOR_SkPathOp)
|
||||
.value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
|
||||
|
||||
constant("MOVE_VERB", MOVE);
|
||||
constant("LINE_VERB", LINE);
|
||||
constant("QUAD_VERB", QUAD);
|
||||
constant("CUBIC_VERB", CUBIC);
|
||||
constant("CLOSE_VERB", CLOSE);
|
||||
|
||||
// coming soon - Stroke
|
||||
|
||||
// coming soon - Matrix
|
||||
|
||||
// coming soon - Bounds/Trim
|
||||
|
||||
#ifdef PATHKIT_TESTING
|
||||
function("SkBits2Float", &SkBits2Float);
|
||||
|
||||
function("GetBoundaryPathFromRegion", &GetBoundaryPathFromRegion);
|
||||
|
||||
enum_<SkRegion::Op>("RegionOp")
|
||||
.value("DIFFERENCE", SkRegion::Op::kDifference_Op)
|
||||
.value("INTERSECT", SkRegion::Op::kIntersect_Op)
|
||||
.value("UNION", SkRegion::Op::kUnion_Op)
|
||||
.value("XOR", SkRegion::Op::kXOR_Op)
|
||||
.value("REVERSE_DIFFERENCE", SkRegion::Op::kReverseDifference_Op)
|
||||
.value("REPLACE", SkRegion::Op::kReplace_Op);
|
||||
|
||||
class_<SkRegion>("SkRegion")
|
||||
.constructor<>()
|
||||
|
||||
@ -354,48 +329,6 @@ EMSCRIPTEN_BINDINGS(skia) {
|
||||
.function("opRegion",
|
||||
select_overload<bool(const SkRegion&, SkRegion::Op)>(&SkRegion::op))
|
||||
.function("opRegionAB",
|
||||
select_overload<bool(const SkRegion&, const SkRegion&, SkRegion::Op)>(&SkRegion::op))
|
||||
;
|
||||
|
||||
|
||||
// Without this, module._ToPath2D (yes with an underscore)
|
||||
// would be exposed, but be unable to correctly handle the SkPath type.
|
||||
function("ToPath2D", &ToPath2D);
|
||||
function("ToSVGString", &ToSVGString);
|
||||
function("FromSVGString", &FromSVGString);
|
||||
|
||||
function("SkPathToVerbsArgsArray", &SkPathToVerbsArgsArray);
|
||||
function("SkPathFromVerbsArgsTyped", &SkPathFromVerbsArgsTyped);
|
||||
|
||||
function("SkPathFromCmdTyped", &SkPathFromCmdTyped);
|
||||
function("SkPathToCmdArray", &SkPathToCmdArray);
|
||||
|
||||
function("SimplifyPath", &SimplifyPath);
|
||||
function("ApplyPathOp", &ApplyPathOp);
|
||||
function("ResolveBuilder", &ResolveBuilder);
|
||||
|
||||
function("SkBits2Float", &SkBits2Float);
|
||||
|
||||
function("GetBoundaryPathFromRegion", &GetBoundaryPathFromRegion);
|
||||
|
||||
enum_<SkPathOp>("PathOp")
|
||||
.value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
|
||||
.value("INTERSECT", SkPathOp::kIntersect_SkPathOp)
|
||||
.value("UNION", SkPathOp::kUnion_SkPathOp)
|
||||
.value("XOR", SkPathOp::kXOR_SkPathOp)
|
||||
.value("REVERSE_DIFFERENCE", SkPathOp::kReverseDifference_SkPathOp);
|
||||
|
||||
enum_<SkRegion::Op>("RegionOp")
|
||||
.value("DIFFERENCE", SkRegion::Op::kDifference_Op)
|
||||
.value("INTERSECT", SkRegion::Op::kIntersect_Op)
|
||||
.value("UNION", SkRegion::Op::kUnion_Op)
|
||||
.value("XOR", SkRegion::Op::kXOR_Op)
|
||||
.value("REVERSE_DIFFERENCE", SkRegion::Op::kReverseDifference_Op)
|
||||
.value("REPLACE", SkRegion::Op::kReplace_Op);
|
||||
|
||||
constant("MOVE_VERB", MOVE);
|
||||
constant("LINE_VERB", LINE);
|
||||
constant("QUAD_VERB", QUAD);
|
||||
constant("CUBIC_VERB", CUBIC);
|
||||
constant("CLOSE_VERB", CLOSE);
|
||||
select_overload<bool(const SkRegion&, const SkRegion&, SkRegion::Op)>(&SkRegion::op));
|
||||
#endif
|
||||
}
|
20
experimental/pathkit/serve.py
Normal file
20
experimental/pathkit/serve.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright 2018 Google LLC
|
||||
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
|
||||
PORT = 8000
|
||||
|
||||
class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
pass
|
||||
|
||||
Handler.extensions_map['.js'] = 'application/javascript'
|
||||
# Without the correct MIME type, async compilation doesn't work
|
||||
Handler.extensions_map['.wasm'] = 'application/wasm'
|
||||
|
||||
httpd = SocketServer.TCPServer(("", PORT), Handler)
|
||||
|
||||
httpd.serve_forever()
|
@ -1,545 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Skia and WASM</title>
|
||||
<style>
|
||||
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
|
||||
textarea.emscripten { font-family: monospace; width: 80%; }
|
||||
div.emscripten { text-align: center; }
|
||||
div.emscripten_border { border: 1px solid black; }
|
||||
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
|
||||
canvas.emscripten { border: 0px none; background-color: black; }
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin: 0px auto;
|
||||
-webkit-animation: rotation .8s linear infinite;
|
||||
-moz-animation: rotation .8s linear infinite;
|
||||
-o-animation: rotation .8s linear infinite;
|
||||
animation: rotation 0.8s linear infinite;
|
||||
border-left: 10px solid rgb(0,150,240);
|
||||
border-right: 10px solid rgb(0,150,240);
|
||||
border-bottom: 10px solid rgb(0,150,240);
|
||||
border-top: 10px solid rgb(100,0,200);
|
||||
border-radius: 100%;
|
||||
background-color: rgb(200,100,250);
|
||||
}
|
||||
@-webkit-keyframes rotation {
|
||||
from {-webkit-transform: rotate(0deg);}
|
||||
to {-webkit-transform: rotate(360deg);}
|
||||
}
|
||||
@-moz-keyframes rotation {
|
||||
from {-moz-transform: rotate(0deg);}
|
||||
to {-moz-transform: rotate(360deg);}
|
||||
}
|
||||
@-o-keyframes rotation {
|
||||
from {-o-transform: rotate(0deg);}
|
||||
to {-o-transform: rotate(360deg);}
|
||||
}
|
||||
@keyframes rotation {
|
||||
from {transform: rotate(0deg);}
|
||||
to {transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<hr/>
|
||||
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
|
||||
<div class="emscripten" id="status">Downloading...</div>
|
||||
<div class="emscripten">
|
||||
<progress value="0" max="100" id="progress" hidden=1></progress>
|
||||
</div>
|
||||
<div class="emscripten_border">
|
||||
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
|
||||
</div>
|
||||
<svg id=svg xmlns='http://www.w3.org/2000/svg' width=50 height=50 viewPort='0 0 50 50'>
|
||||
</svg>
|
||||
<hr/>
|
||||
<div class="emscripten">
|
||||
<input type="checkbox" id="resize">Resize canvas
|
||||
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
|
||||
|
||||
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
|
||||
document.getElementById('resize').checked)">
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<textarea class="emscripten" id="output" rows="8"></textarea>
|
||||
<hr>
|
||||
<script type='text/javascript'>
|
||||
function entryPoint() {
|
||||
// See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#call-compiled-c-c-code-directly-from-javascript
|
||||
const FAST_TIMES = 100 + 11 /*warmups*/;
|
||||
const SLOW_TIMES = 10 + 11 /*warmups*/;
|
||||
|
||||
var path;
|
||||
for (let i = 0; i< FAST_TIMES; i++){
|
||||
if (i === 10) {
|
||||
// let it "warm up"
|
||||
console.time('Path_with_calls'); // about 3.9s for 1M iterations
|
||||
}
|
||||
path = new Module.SkPath();
|
||||
path.moveTo(0,0);
|
||||
path.lineTo(40,0);
|
||||
path.lineTo(20,20);
|
||||
path.close();
|
||||
path.moveTo(20,0);
|
||||
path.lineTo(60,0);
|
||||
path.lineTo(40,20);
|
||||
path.close();
|
||||
if (i < FAST_TIMES - 1) {
|
||||
path.delete();
|
||||
}
|
||||
}
|
||||
console.timeEnd('Path_with_calls');
|
||||
|
||||
var result;
|
||||
for(let i = 0; i < SLOW_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('simplify'); // about 5.4s for 100k iterations
|
||||
}
|
||||
result = Module.SimplifyPath(path);
|
||||
if (i < SLOW_TIMES - 1) {
|
||||
result.delete();
|
||||
}
|
||||
}
|
||||
console.timeEnd('simplify');
|
||||
|
||||
var p2d;
|
||||
for(let i = 0; i < FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('ToPath2D'); // about 6.3s for 1M iterations
|
||||
}
|
||||
p2d = Module.ToPath2D(result, new Path2D());
|
||||
}
|
||||
console.timeEnd('ToPath2D');
|
||||
|
||||
let ctx = Module.canvas.getContext('2d')
|
||||
ctx.strokeStyle = 'red';
|
||||
ctx.stroke(p2d);
|
||||
|
||||
var svgPtr;
|
||||
for(let i=0; i < FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('ToSVGString (simple)'); // about 7.1s for 1M iterations
|
||||
}
|
||||
svgPtr = Module.ToSVGString(result);
|
||||
}
|
||||
console.timeEnd('ToSVGString (simple)');
|
||||
Module.print('Got SVG Data: ' + svgPtr);
|
||||
|
||||
var mountains;
|
||||
for(let i=0; i < FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('FromSVGString (simple)');
|
||||
}
|
||||
mountains = Module.FromSVGString(svgPtr);
|
||||
if (i < FAST_TIMES - 1) {
|
||||
mountains.delete();
|
||||
}
|
||||
}
|
||||
console.timeEnd('FromSVGString (simple)');
|
||||
|
||||
path.delete();
|
||||
result.delete();
|
||||
mountains.delete();
|
||||
|
||||
//==============================================================================
|
||||
|
||||
function uint8TypedArray(arr) {
|
||||
const ta = new Uint8ClampedArray(arr.length);
|
||||
for (let i = 0; i < arr.length; i++){
|
||||
ta[i] = arr[i];
|
||||
}
|
||||
|
||||
retVal = Module._malloc(ta.length * ta.BYTES_PER_ELEMENT);
|
||||
Module.HEAPU8.set(ta, retVal / ta.BYTES_PER_ELEMENT);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
function floatTypedArray(arr) {
|
||||
const ta = new Float32Array(arr.length);
|
||||
for (let i = 0; i < arr.length; i++){
|
||||
ta[i] = arr[i];
|
||||
}
|
||||
|
||||
retVal = Module._malloc(ta.length * ta.BYTES_PER_ELEMENT);
|
||||
Module.HEAPF32.set(ta, retVal / ta.BYTES_PER_ELEMENT);
|
||||
return retVal;
|
||||
}
|
||||
var path4;
|
||||
for (let i = 0; i< FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('path_with_typed_array'); // about 3.6s for 1M iterations
|
||||
}
|
||||
let verbs = [];
|
||||
let args = [];
|
||||
|
||||
verbs.push(Module.MOVE_VERB);
|
||||
args.push(100); args.push(0);
|
||||
verbs.push(Module.LINE_VERB);
|
||||
args.push(140); args.push(0);
|
||||
verbs.push(Module.LINE_VERB);
|
||||
args.push(120); args.push(20);
|
||||
verbs.push(Module.CLOSE_VERB);
|
||||
|
||||
verbs.push(Module.MOVE_VERB);
|
||||
args.push(120); args.push(0);
|
||||
verbs.push(Module.LINE_VERB);
|
||||
args.push(160); args.push(0);
|
||||
verbs.push(Module.LINE_VERB);
|
||||
args.push(140); args.push(20);
|
||||
verbs.push(Module.CLOSE_VERB);
|
||||
|
||||
let tVerbs = uint8TypedArray(verbs);
|
||||
let tArgs = floatTypedArray(args);
|
||||
|
||||
path4 = Module.SkPathFromVerbsArgsTyped(tVerbs, verbs.length,
|
||||
tArgs, args.length);
|
||||
if (i < FAST_TIMES - 1) {
|
||||
path4.delete();
|
||||
}
|
||||
Module._free(tVerbs);
|
||||
Module._free(tArgs);
|
||||
}
|
||||
console.timeEnd('path_with_typed_array');
|
||||
//path4.dump();
|
||||
|
||||
var v4, a4;
|
||||
for (let i = 0; i< FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('to_verbs_args_array'); // about 5.4s for 1M iterations
|
||||
}
|
||||
v4 = [];
|
||||
a4 = [];
|
||||
Module.SkPathToVerbsArgsArray(path4, v4, a4);
|
||||
}
|
||||
console.timeEnd('to_verbs_args_array');
|
||||
|
||||
Module.print(`Got path with ${v4.length} verbs and ${a4.length} args`);
|
||||
Module.print(v4[0] + ' (0 for value is MoveTo, 1 for LineTo, etc)');
|
||||
Module.print(a4[0], a4[1] + ' (coordinates)');
|
||||
|
||||
path4.delete();
|
||||
//==============================================================================
|
||||
|
||||
let p1 = new Module.SkPath();
|
||||
p1.moveTo(0,60);
|
||||
p1.lineTo(40,60);
|
||||
p1.lineTo(20,80);
|
||||
p1.close();
|
||||
let p2 = new Module.SkPath();
|
||||
p2.moveTo(20,60);
|
||||
p2.lineTo(60,60);
|
||||
p2.lineTo(40,80);
|
||||
p2.close();
|
||||
|
||||
var p3;
|
||||
for (let i = 0; i< SLOW_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('ApplyPathOp'); // about 5.2s for 100k iterations
|
||||
}
|
||||
p3 = Module.ApplyPathOp(p1, p2, Module.PathOp.INTERSECT);
|
||||
if (i < SLOW_TIMES - 1) {
|
||||
p3.delete();
|
||||
}
|
||||
}
|
||||
console.timeEnd('ApplyPathOp');
|
||||
|
||||
ctx = Module.canvas.getContext('2d');
|
||||
ctx.strokeStyle = 'green';
|
||||
// Alternative way to use Path2D (potentially for browsers that
|
||||
// don't support the Path2D?)
|
||||
ctx.beginPath();
|
||||
Module.ToPath2D(p3, ctx);
|
||||
ctx.stroke();
|
||||
|
||||
var builder, p4;
|
||||
for (let i = 0; i< SLOW_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('PathOpBuilder'); // about 12.3s for 100k iterations
|
||||
}
|
||||
builder = new Module.SkOpBuilder();
|
||||
builder.add(p1, Module.PathOp.UNION);
|
||||
builder.add(p2, Module.PathOp.UNION);
|
||||
builder.add(p3, Module.PathOp.DIFFERENCE);
|
||||
p4 = Module.ResolveBuilder(builder);
|
||||
if (i < SLOW_TIMES - 1) {
|
||||
p4.delete();
|
||||
}
|
||||
builder.delete();
|
||||
}
|
||||
console.timeEnd('PathOpBuilder');
|
||||
|
||||
|
||||
p2d = Module.ToPath2D(p4, new Path2D());
|
||||
|
||||
ctx = Module.canvas.getContext('2d');
|
||||
ctx.fillStyle = 'purple';
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.fill(p2d);
|
||||
ctx.stroke(p2d);
|
||||
|
||||
p1.delete();
|
||||
p2.delete();
|
||||
p3.delete();
|
||||
p4.delete();
|
||||
|
||||
//=====================================================================================
|
||||
|
||||
function floatTypedArrayFrom2D(arr) {
|
||||
// expects 2d array where index 0 is verb and index 1-n are args
|
||||
let len = 0;
|
||||
for (cmd of arr) {
|
||||
len += cmd.length;
|
||||
}
|
||||
|
||||
const ta = new Float32Array(len);
|
||||
let i = 0;
|
||||
for (cmd of arr) {
|
||||
for (c of cmd) {
|
||||
ta[i] = c;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
retVal = Module._malloc(ta.length * ta.BYTES_PER_ELEMENT);
|
||||
Module.HEAPF32.set(ta, retVal / ta.BYTES_PER_ELEMENT);
|
||||
return [retVal, len];
|
||||
}
|
||||
|
||||
function SkPathFromCmdTyped(cmdArr) {
|
||||
let [cmd, len] = floatTypedArrayFrom2D(cmdArr);
|
||||
let path = Module.SkPathFromCmdTyped(cmd, len);
|
||||
Module._free(cmd);
|
||||
return path;
|
||||
}
|
||||
|
||||
var path5;
|
||||
for (let i = 0; i< FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('path_cmd_typed (simple)'); // about 4.6s for 1M iterations
|
||||
}
|
||||
let commands = [];
|
||||
|
||||
commands.push([Module.MOVE_VERB, 100, 60]);
|
||||
commands.push([Module.LINE_VERB, 140, 60]);
|
||||
commands.push([Module.LINE_VERB, 120, 80]);
|
||||
commands.push([Module.CLOSE_VERB]);
|
||||
|
||||
commands.push([Module.MOVE_VERB, 120, 60]);
|
||||
commands.push([Module.LINE_VERB, 160, 60]);
|
||||
commands.push([Module.LINE_VERB, 140, 80]);
|
||||
commands.push([Module.CLOSE_VERB]);
|
||||
|
||||
path5 = SkPathFromCmdTyped(commands);
|
||||
|
||||
if (i < FAST_TIMES - 1) {
|
||||
path5.delete();
|
||||
}
|
||||
}
|
||||
console.timeEnd('path_cmd_typed (simple)');
|
||||
|
||||
p2d = Module.ToPath2D(path5, new Path2D());
|
||||
|
||||
ctx = Module.canvas.getContext('2d');
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fill(p2d);
|
||||
|
||||
var cmd5;
|
||||
for (let i = 0; i< FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('to_cmd_array'); // about 9.1s for 1M iterations
|
||||
}
|
||||
cmd5 = Module.SkPathToCmdArray(path5);
|
||||
}
|
||||
console.timeEnd('to_cmd_array');
|
||||
|
||||
|
||||
Module.print(`Got path with ${cmd5.length} commands`);
|
||||
Module.print(cmd5[0][0] + ' (0 for value is MoveTo, 1 for LineTo, etc)');
|
||||
Module.print(cmd5[0][1], cmd5[0][2] + ' (coordinates)');
|
||||
//console.log(cmd5);
|
||||
|
||||
path5.delete();
|
||||
//==========================================================
|
||||
const svgPath = 'M6 18c0 .55.45 1 1 1h1v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h2v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h1c.55 0 1-.45 1-1V8H6v10zM3.5 8C2.67 8 2 8.67 2 9.5v7c0 .83.67 1.5 1.5 1.5S5 17.33 5 16.5v-7C5 8.67 4.33 8 3.5 8zm17 0c-.83 0-1.5.67-1.5 1.5v7c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-7c0-.83-.67-1.5-1.5-1.5zm-4.97-5.84l1.3-1.3c.2-.2.2-.51 0-.71-.2-.2-.51-.2-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.15c-.2-.2-.51-.2-.71 0-.2.2-.2.51 0 .71l1.31 1.31C6.97 3.26 6 5.01 6 7h12c0-1.99-.97-3.75-2.47-4.84zM10 5H9V4h1v1zm5 0h-1V4h1v1z';
|
||||
|
||||
var android;
|
||||
for(let i=0; i < SLOW_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('FromSVGString (moderate)'); // about 6.8s for 100k iterations
|
||||
}
|
||||
android = Module.FromSVGString(svgPath);
|
||||
if (i < SLOW_TIMES - 1) {
|
||||
android.delete();
|
||||
}
|
||||
}
|
||||
console.timeEnd('FromSVGString (moderate)');
|
||||
|
||||
result = Module.SimplifyPath(android);
|
||||
//result.dump();
|
||||
let androidSimple = Module.ToSVGString(result);
|
||||
|
||||
let newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
newPath.setAttribute('fill', 'rgb(0,200,0)');
|
||||
newPath.setAttribute('d', androidSimple);
|
||||
document.getElementById('svg').appendChild(newPath);
|
||||
|
||||
androidCMDs = Module.SkPathToCmdArray(android);
|
||||
|
||||
android.delete();
|
||||
result.delete();
|
||||
|
||||
var android2;
|
||||
for (let i = 0; i< FAST_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('path_cmd_typed (moderate)'); // about 2.2s for 100k
|
||||
}
|
||||
|
||||
android2 = SkPathFromCmdTyped(androidCMDs);
|
||||
|
||||
if (i < FAST_TIMES - 1) {
|
||||
android2.delete();
|
||||
}
|
||||
}
|
||||
console.timeEnd('path_cmd_typed (moderate)');
|
||||
var android2Str;
|
||||
for(let i=0; i < SLOW_TIMES; i++) {
|
||||
if (i === 10) {
|
||||
console.time('ToSVGString (moderate)');
|
||||
}
|
||||
android2Str = Module.ToSVGString(android2);
|
||||
}
|
||||
console.timeEnd('ToSVGString (moderate)');
|
||||
|
||||
newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
newPath.setAttribute('fill', 'rgb(0,0,200)');
|
||||
newPath.setAttribute('d', android2Str);
|
||||
newPath.setAttribute('transform', 'translate(25 0)');
|
||||
document.getElementById('svg').appendChild(newPath);
|
||||
|
||||
android2.delete();
|
||||
|
||||
// ===========================================================
|
||||
|
||||
let float = Module.SkBits2Float(parseInt("0x3f2aaaab"));
|
||||
Module.print('SkBits2Float(0x3f2aaaab) = 0.666667', float);
|
||||
|
||||
p1 = new Module.SkPath();
|
||||
p1.moveTo(0,60);
|
||||
p1.lineTo(40,60);
|
||||
p1.lineTo(20,80);
|
||||
p1.close();
|
||||
p2 = new Module.SkPath();
|
||||
p2.moveTo(20,60);
|
||||
p2.lineTo(60,60);
|
||||
p2.lineTo(40,80);
|
||||
p2.close();
|
||||
|
||||
let rgnA = new Module.SkRegion();
|
||||
let rgnB = new Module.SkRegion();
|
||||
let openClip = new Module.SkRegion();
|
||||
let rgnOut = new Module.SkRegion();
|
||||
openClip.setRect(-16000, -16000, 16000, 16000);
|
||||
rgnA.setPath(p1, openClip);
|
||||
rgnB.setPath(p2, openClip);
|
||||
rgnOut.opRegionAB(rgnA, rgnB, Module.RegionOp.INTERSECT);
|
||||
let pathOut = Module.GetBoundaryPathFromRegion(rgnOut);
|
||||
|
||||
p1.delete();
|
||||
p2.delete();
|
||||
rgnA.delete();
|
||||
rgnB.delete();
|
||||
openClip.delete();
|
||||
rgnOut.delete();
|
||||
pathOut.delete();
|
||||
}
|
||||
</script>
|
||||
<script type='text/javascript'>
|
||||
var statusElement = document.getElementById('status');
|
||||
var progressElement = document.getElementById('progress');
|
||||
var spinnerElement = document.getElementById('spinner');
|
||||
|
||||
var Module = {
|
||||
preRun: [],
|
||||
postRun: [entryPoint],
|
||||
print: (function() {
|
||||
var element = document.getElementById('output');
|
||||
if (element) element.value = ''; // clear browser cache
|
||||
return function(text) {
|
||||
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||
// These replacements are necessary if you render to raw HTML
|
||||
//text = text.replace(/&/g, "&");
|
||||
//text = text.replace(/</g, "<");
|
||||
//text = text.replace(/>/g, ">");
|
||||
//text = text.replace('\n', '<br>', 'g');
|
||||
console.log(text);
|
||||
if (element) {
|
||||
element.value += text + "\n";
|
||||
element.scrollTop = element.scrollHeight; // focus on bottom
|
||||
}
|
||||
};
|
||||
})(),
|
||||
printErr: function(text) {
|
||||
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||
if (0) { // XXX disabled for safety typeof dump == 'function') {
|
||||
dump(text + '\n'); // fast, straight to the real console
|
||||
} else {
|
||||
console.error(text);
|
||||
}
|
||||
},
|
||||
canvas: (function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
|
||||
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
|
||||
// application robust, you may want to override this behavior before shipping!
|
||||
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
|
||||
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
|
||||
|
||||
return canvas;
|
||||
})(),
|
||||
setStatus: function(text) {
|
||||
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
|
||||
if (text === Module.setStatus.last.text) return;
|
||||
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
|
||||
var now = Date.now();
|
||||
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
|
||||
Module.setStatus.last.time = now;
|
||||
Module.setStatus.last.text = text;
|
||||
if (m) {
|
||||
text = m[1];
|
||||
progressElement.value = parseInt(m[2])*100;
|
||||
progressElement.max = parseInt(m[4])*100;
|
||||
progressElement.hidden = false;
|
||||
spinnerElement.hidden = false;
|
||||
} else {
|
||||
progressElement.value = null;
|
||||
progressElement.max = null;
|
||||
progressElement.hidden = true;
|
||||
if (!text) spinnerElement.hidden = true;
|
||||
}
|
||||
statusElement.innerHTML = text;
|
||||
},
|
||||
totalDependencies: 0,
|
||||
monitorRunDependencies: function(left) {
|
||||
this.totalDependencies = Math.max(this.totalDependencies, left);
|
||||
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
|
||||
}
|
||||
};
|
||||
Module.setStatus('Downloading...');
|
||||
window.onerror = function() {
|
||||
Module.setStatus('Exception thrown, see JavaScript console');
|
||||
spinnerElement.style.display = 'none';
|
||||
Module.setStatus = function(text) {
|
||||
if (text) Module.printErr('[post-exception status] ' + text);
|
||||
};
|
||||
};
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user