blob: 3171057e41afda2721152a5176674df2a1c9a85b [file] [log] [blame]
Kevin Lubickf5ea37f2019-02-28 10:06:18 -05001// helper JS that could be used anywhere in the glue code
Kevin Lubick217056c2018-09-20 17:39:31 -04002
Kevin Lubickf5ea37f2019-02-28 10:06:18 -05003function clamp(c) {
4 return Math.round(Math.max(0, Math.min(c || 0, 255)));
5}
Kevin Lubick217056c2018-09-20 17:39:31 -04006
Kevin Lubickf5ea37f2019-02-28 10:06:18 -05007// Colors are just a 32 bit number with 8 bits each of a, r, g, b
8// The API is the same as CSS's representation of color rgba(), that is
9// r,g,b are 0-255, and a is 0.0 to 1.0.
10// if a is omitted, it will be assumed to be 1.0
11CanvasKit.Color = function(r, g, b, a) {
12 if (a === undefined) {
13 a = 1;
Kevin Lubick217056c2018-09-20 17:39:31 -040014 }
Kevin Lubickee91c072019-03-29 10:39:52 -040015 // The >>> 0 converts the signed int to an unsigned int. Skia's
16 // SkColor object is an unsigned int.
17 // https://stackoverflow.com/a/14891172
18 return ((clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0)) >>> 0;
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050019}
Kevin Lubick217056c2018-09-20 17:39:31 -040020
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050021// returns [r, g, b, a] from a color
22// where a is scaled between 0 and 1.0
23CanvasKit.getColorComponents = function(color) {
24 return [
25 (color >> 16) & 0xFF,
26 (color >> 8) & 0xFF,
27 (color >> 0) & 0xFF,
28 ((color >> 24) & 0xFF) / 255,
29 ]
30}
31
32CanvasKit.multiplyByAlpha = function(color, alpha) {
33 if (alpha === 1) {
34 return color;
Kevin Lubick217056c2018-09-20 17:39:31 -040035 }
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050036 // extract as int from 0 to 255
37 var a = (color >> 24) & 0xFF;
38 a *= alpha;
39 // mask off the old alpha
40 color &= 0xFFFFFF;
Kevin Lubickee91c072019-03-29 10:39:52 -040041 // back to unsigned int to match SkColor.
42 return (clamp(a) << 24 | color) >>> 0;
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050043}
Kevin Lubick61ef7b22018-11-27 13:26:59 -050044
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050045function radiansToDegrees(rad) {
46 return (rad / Math.PI) * 180;
47}
Kevin Lubick12c0e502018-11-28 12:51:56 -050048
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050049function degreesToRadians(deg) {
50 return (deg / 180) * Math.PI;
51}
Kevin Lubick12c0e502018-11-28 12:51:56 -050052
53// See https://stackoverflow.com/a/31090240
54// This contraption keeps closure from minifying away the check
55// if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
56// Defined outside any scopes to make it available in all files.
57var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
Kevin Lubick1646e7d2018-12-07 13:03:08 -050058
59function almostEqual(floata, floatb) {
60 return Math.abs(floata - floatb) < 0.00001;
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050061}
62
63
64var nullptr = 0; // emscripten doesn't like to take null as uintptr_t
65
66// arr can be a normal JS array or a TypedArray
67// dest is something like CanvasKit.HEAPF32
Kevin Lubickd6ba7252019-06-03 14:38:05 -040068// ptr can be optionally provided if the memory was already allocated.
69function copy1dArray(arr, dest, ptr) {
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050070 if (!arr || !arr.length) {
71 return nullptr;
72 }
Kevin Lubicke25df6c2019-10-22 09:04:32 -040073 // This was created with CanvasKit.Malloc, so it's already been copied.
74 if (arr['_ck']) {
75 return arr.byteOffset;
76 }
Kevin Lubickd6ba7252019-06-03 14:38:05 -040077 if (!ptr) {
78 ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
79 }
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050080 // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single
81 // byte elements. When we run _malloc, we always get an offset/pointer into
82 // that block of memory.
83 // CanvasKit exposes some different views to make it easier to work with
84 // different types. HEAPF32 for example, exposes it as a float*
85 // However, to make the ptr line up, we have to do some pointer arithmetic.
86 // Concretely, we need to convert ptr to go from an index into a 1-byte-wide
87 // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32)
88 // and thus we divide ptr by 4.
89 dest.set(arr, ptr / dest.BYTES_PER_ELEMENT);
90 return ptr;
91}
92
93// arr should be a non-jagged 2d JS array (TypedArrays can't be nested
Kevin Lubick37ab53e2019-11-11 10:06:08 -050094// inside themselves). A common use case is points.
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050095// dest is something like CanvasKit.HEAPF32
Kevin Lubickd6ba7252019-06-03 14:38:05 -040096// ptr can be optionally provided if the memory was already allocated.
97function copy2dArray(arr, dest, ptr) {
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050098 if (!arr || !arr.length) {
99 return nullptr;
100 }
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400101 if (!ptr) {
102 ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT);
103 }
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500104 var idx = 0;
105 var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
106 for (var r = 0; r < arr.length; r++) {
107 for (var c = 0; c < arr[0].length; c++) {
108 dest[adjustedPtr + idx] = arr[r][c];
109 idx++;
110 }
111 }
112 return ptr;
113}
114
115// arr should be a non-jagged 3d JS array (TypedArrays can't be nested
116// inside themselves.)
117// dest is something like CanvasKit.HEAPF32
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400118// ptr can be optionally provided if the memory was already allocated.
119function copy3dArray(arr, dest, ptr) {
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500120 if (!arr || !arr.length || !arr[0].length) {
121 return nullptr;
122 }
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400123 if (!ptr) {
124 ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT);
125 }
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500126 var idx = 0;
127 var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
128 for (var x = 0; x < arr.length; x++) {
129 for (var y = 0; y < arr[0].length; y++) {
130 for (var z = 0; z < arr[0][0].length; z++) {
131 dest[adjustedPtr + idx] = arr[x][y][z];
132 idx++;
133 }
134 }
135 }
136 return ptr;
137}
138
139// Caching the Float32Arrays can save having to reallocate them
140// over and over again.
141var Float32ArrayCache = {};
142
143// Takes a 2D array of commands and puts them into the WASM heap
144// as a 1D array. This allows them to referenced from the C++ code.
145// Returns a 2 element array, with the first item being essentially a
146// pointer to the array and the second item being the length of
147// the new 1D array.
148//
149// Example usage:
150// let cmds = [
151// [CanvasKit.MOVE_VERB, 0, 10],
152// [CanvasKit.LINE_VERB, 30, 40],
153// [CanvasKit.QUAD_VERB, 20, 50, 45, 60],
154// ];
155function loadCmdsTypedArray(arr) {
156 var len = 0;
157 for (var r = 0; r < arr.length; r++) {
158 len += arr[r].length;
159 }
160
161 var ta;
162 if (Float32ArrayCache[len]) {
163 ta = Float32ArrayCache[len];
164 } else {
165 ta = new Float32Array(len);
166 Float32ArrayCache[len] = ta;
167 }
168 // Flatten into a 1d array
169 var i = 0;
170 for (var r = 0; r < arr.length; r++) {
171 for (var c = 0; c < arr[r].length; c++) {
172 var item = arr[r][c];
173 ta[i] = item;
174 i++;
175 }
176 }
177
178 var ptr = copy1dArray(ta, CanvasKit.HEAPF32);
179 return [ptr, len];
180}
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400181
Kevin Lubickcc13fd32019-04-05 13:00:01 -0400182function saveBytesToFile(bytes, fileName) {
183 if (!isNode) {
184 // https://stackoverflow.com/a/32094834
185 var blob = new Blob([bytes], {type: 'application/octet-stream'});
186 url = window.URL.createObjectURL(blob);
187 var a = document.createElement('a');
188 document.body.appendChild(a);
189 a.href = url;
190 a.download = fileName;
191 a.click();
192 // clean up after because FF might not download it synchronously
193 setTimeout(function() {
194 URL.revokeObjectURL(url);
195 a.remove();
196 }, 50);
197 } else {
198 var fs = require('fs');
199 // https://stackoverflow.com/a/42006750
200 // https://stackoverflow.com/a/47018122
201 fs.writeFile(fileName, new Buffer(bytes), function(err) {
202 if (err) throw err;
203 });
204 }
205}
Kevin Lubickee91c072019-03-29 10:39:52 -0400206/**
207 * Generic helper for dealing with an array of four floats.
208 */
209CanvasKit.FourFloatArrayHelper = function() {
210 this._floats = [];
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400211 this._ptr = null;
Kevin Lubickee91c072019-03-29 10:39:52 -0400212
213 Object.defineProperty(this, 'length', {
214 enumerable: true,
215 get: function() {
216 return this._floats.length / 4;
217 },
218 });
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400219}
220
221/**
Kevin Lubickee91c072019-03-29 10:39:52 -0400222 * push the four floats onto the end of the array - if build() has already
223 * been called, the call will return without modifying anything.
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400224 */
Kevin Lubickee91c072019-03-29 10:39:52 -0400225CanvasKit.FourFloatArrayHelper.prototype.push = function(f1, f2, f3, f4) {
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400226 if (this._ptr) {
227 SkDebug('Cannot push more points - already built');
228 return;
229 }
Kevin Lubickee91c072019-03-29 10:39:52 -0400230 this._floats.push(f1, f2, f3, f4);
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400231}
232
Kevin Lubickee91c072019-03-29 10:39:52 -0400233/**
234 * Set the four floats at a given index - if build() has already
235 * been called, the WASM memory will be written to directly.
236 */
237CanvasKit.FourFloatArrayHelper.prototype.set = function(idx, f1, f2, f3, f4) {
238 if (idx < 0 || idx >= this._floats.length/4) {
239 SkDebug('Cannot set index ' + idx + ', it is out of range', this._floats.length/4);
240 return;
241 }
242 idx *= 4;
243 var BYTES_PER_ELEMENT = 4;
244 if (this._ptr) {
245 // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
246 var floatPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
247 CanvasKit.HEAPF32[floatPtr] = f1;
248 CanvasKit.HEAPF32[floatPtr + 1] = f2;
249 CanvasKit.HEAPF32[floatPtr + 2] = f3;
250 CanvasKit.HEAPF32[floatPtr + 3] = f4;
251 return;
252 }
253 this._floats[idx] = f1;
254 this._floats[idx + 1] = f2;
255 this._floats[idx + 2] = f3;
256 this._floats[idx + 3] = f4;
257}
258
259/**
260 * Copies the float data to the WASM memory and returns a pointer
261 * to that allocated memory. Once build has been called, this
262 * float array cannot be made bigger.
263 */
264CanvasKit.FourFloatArrayHelper.prototype.build = function() {
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400265 if (this._ptr) {
266 return this._ptr;
267 }
Kevin Lubickee91c072019-03-29 10:39:52 -0400268 this._ptr = copy1dArray(this._floats, CanvasKit.HEAPF32);
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400269 return this._ptr;
270}
271
Kevin Lubickee91c072019-03-29 10:39:52 -0400272/**
273 * Frees the wasm memory associated with this array. Of note,
274 * the points are not removed, so push/set/build can all
275 * be called to make a newly allocated (possibly bigger)
276 * float array.
277 */
278CanvasKit.FourFloatArrayHelper.prototype.delete = function() {
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400279 if (this._ptr) {
280 CanvasKit._free(this._ptr);
281 this._ptr = null;
282 }
283}
Kevin Lubickee91c072019-03-29 10:39:52 -0400284
285/**
286 * Generic helper for dealing with an array of unsigned ints.
287 */
288CanvasKit.OneUIntArrayHelper = function() {
289 this._uints = [];
290 this._ptr = null;
291
292 Object.defineProperty(this, 'length', {
293 enumerable: true,
294 get: function() {
295 return this._uints.length;
296 },
297 });
298}
299
300/**
301 * push the unsigned int onto the end of the array - if build() has already
302 * been called, the call will return without modifying anything.
303 */
304CanvasKit.OneUIntArrayHelper.prototype.push = function(u) {
305 if (this._ptr) {
306 SkDebug('Cannot push more points - already built');
307 return;
308 }
309 this._uints.push(u);
310}
311
312/**
313 * Set the uint at a given index - if build() has already
314 * been called, the WASM memory will be written to directly.
315 */
316CanvasKit.OneUIntArrayHelper.prototype.set = function(idx, u) {
317 if (idx < 0 || idx >= this._uints.length) {
318 SkDebug('Cannot set index ' + idx + ', it is out of range', this._uints.length);
319 return;
320 }
321 idx *= 4;
322 var BYTES_PER_ELEMENT = 4;
323 if (this._ptr) {
324 // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
325 var uintPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
326 CanvasKit.HEAPU32[uintPtr] = u;
327 return;
328 }
329 this._uints[idx] = u;
330}
331
332/**
333 * Copies the uint data to the WASM memory and returns a pointer
334 * to that allocated memory. Once build has been called, this
335 * unit array cannot be made bigger.
336 */
337CanvasKit.OneUIntArrayHelper.prototype.build = function() {
338 if (this._ptr) {
339 return this._ptr;
340 }
341 this._ptr = copy1dArray(this._uints, CanvasKit.HEAPU32);
342 return this._ptr;
343}
344
345/**
346 * Frees the wasm memory associated with this array. Of note,
347 * the points are not removed, so push/set/build can all
348 * be called to make a newly allocated (possibly bigger)
349 * uint array.
350 */
351CanvasKit.OneUIntArrayHelper.prototype.delete = function() {
352 if (this._ptr) {
353 CanvasKit._free(this._ptr);
354 this._ptr = null;
355 }
356}
357
358/**
359 * Helper for building an array of SkRects (which are just structs
360 * of 4 floats).
361 *
362 * It can be more performant to use this helper, as
363 * the C++-side array is only allocated once (on the first call)
364 * to build. Subsequent set() operations operate directly on
365 * the C++-side array, avoiding having to re-allocate (and free)
366 * the array every time.
367 *
368 * Input points are taken as left, top, right, bottom
369 */
370CanvasKit.SkRectBuilder = CanvasKit.FourFloatArrayHelper;
371/**
372 * Helper for building an array of RSXForms (which are just structs
373 * of 4 floats).
374 *
375 * It can be more performant to use this helper, as
376 * the C++-side array is only allocated once (on the first call)
377 * to build. Subsequent set() operations operate directly on
378 * the C++-side array, avoiding having to re-allocate (and free)
379 * the array every time.
380 *
381 * An RSXForm is a compressed form of a rotation+scale matrix.
382 *
383 * [ scos -ssin tx ]
384 * [ ssin scos ty ]
385 * [ 0 0 1 ]
386 *
387 * Input points are taken as scos, ssin, tx, ty
388 */
389CanvasKit.RSXFormBuilder = CanvasKit.FourFloatArrayHelper;
390
391/**
392 * Helper for building an array of SkColor
393 *
394 * It can be more performant to use this helper, as
395 * the C++-side array is only allocated once (on the first call)
396 * to build. Subsequent set() operations operate directly on
397 * the C++-side array, avoiding having to re-allocate (and free)
398 * the array every time.
399 */
400CanvasKit.SkColorBuilder = CanvasKit.OneUIntArrayHelper;
Kevin Lubicke25df6c2019-10-22 09:04:32 -0400401
402/**
403 * Malloc returns a TypedArray backed by the C++ memory of the
404 * given length. It should only be used by advanced users who
405 * can manage memory and initialize values properly. When used
406 * correctly, it can save copying of data between JS and C++.
407 * When used incorrectly, it can lead to memory leaks.
408 *
409 * const ta = CanvasKit.Malloc(Float32Array, 20);
410 * // store data into ta
411 * const cf = CanvasKit.SkColorFilter.MakeMatrix(ta);
412 * // MakeMatrix cleans up the ptr automatically.
413 *
414 * @param {TypedArray} typedArray - constructor for the typedArray.
415 * @param {number} len - number of elements to store.
416 */
417CanvasKit.Malloc = function(typedArray, len) {
418 var byteLen = len * typedArray.BYTES_PER_ELEMENT;
419 var ptr = CanvasKit._malloc(byteLen);
Bryce Thomas1fa54042020-01-14 13:46:30 -0800420 var ta = new typedArray(CanvasKit.HEAPU8.buffer, ptr, len);
Kevin Lubicke25df6c2019-10-22 09:04:32 -0400421 // add a marker that this was allocated in C++ land
422 ta['_ck'] = true;
423 return ta;
424}