blob: 28752615d899aad0c61cfa4e663e88b31ea7725b [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
Robert Phillipscb77eab2020-03-24 14:19:40 +00007// 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
Kevin Lubickf5ea37f2019-02-28 10:06:18 -05009// 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 }
Robert Phillipscb77eab2020-03-24 14:19:40 +000015 // 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
Robert Phillipscb77eab2020-03-24 14:19:40 +000021// returns [r, g, b, a] from a color
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050022// where a is scaled between 0 and 1.0
23CanvasKit.getColorComponents = function(color) {
24 return [
Robert Phillipscb77eab2020-03-24 14:19:40 +000025 (color >> 16) & 0xFF,
26 (color >> 8) & 0xFF,
27 (color >> 0) & 0xFF,
28 ((color >> 24) & 0xFF) / 255,
29 ]
Kevin Lubickf5ea37f2019-02-28 10:06:18 -050030}
31
Kevin Lubick39284662020-02-20 10:29:55 -050032// parseColorString takes in a CSS color value and returns a CanvasKit.Color
Robert Phillipscb77eab2020-03-24 14:19:40 +000033// (which is just a 32 bit integer, 8 bits per channel). An optional colorMap
34// may be provided which maps custom strings to values (e.g. {'springgreen':4278255487}).
Kevin Lubick39284662020-02-20 10:29:55 -050035// In the CanvasKit canvas2d shim layer, we provide this map for processing
36// canvas2d calls, but not here for code size reasons.
37CanvasKit.parseColorString = function(colorStr, colorMap) {
38 colorStr = colorStr.toLowerCase();
39 // See https://drafts.csswg.org/css-color/#typedef-hex-color
40 if (colorStr.startsWith('#')) {
41 var r, g, b, a = 255;
42 switch (colorStr.length) {
43 case 9: // 8 hex chars #RRGGBBAA
44 a = parseInt(colorStr.slice(7, 9), 16);
45 case 7: // 6 hex chars #RRGGBB
46 r = parseInt(colorStr.slice(1, 3), 16);
47 g = parseInt(colorStr.slice(3, 5), 16);
48 b = parseInt(colorStr.slice(5, 7), 16);
49 break;
50 case 5: // 4 hex chars #RGBA
51 // multiplying by 17 is the same effect as
52 // appending another character of the same value
53 // e.g. e => ee == 14 => 238
54 a = parseInt(colorStr.slice(4, 5), 16) * 17;
55 case 4: // 6 hex chars #RGB
56 r = parseInt(colorStr.slice(1, 2), 16) * 17;
57 g = parseInt(colorStr.slice(2, 3), 16) * 17;
58 b = parseInt(colorStr.slice(3, 4), 16) * 17;
59 break;
60 }
61 return CanvasKit.Color(r, g, b, a/255);
62
63 } else if (colorStr.startsWith('rgba')) {
64 // Trim off rgba( and the closing )
65 colorStr = colorStr.slice(5, -1);
66 var nums = colorStr.split(',');
67 return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
68 valueOrPercent(nums[3]));
69 } else if (colorStr.startsWith('rgb')) {
70 // Trim off rgba( and the closing )
71 colorStr = colorStr.slice(4, -1);
72 var nums = colorStr.split(',');
73 // rgb can take 3 or 4 arguments
74 return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
75 valueOrPercent(nums[3]));
76 } else if (colorStr.startsWith('gray(')) {
77 // TODO
78 } else if (colorStr.startsWith('hsl')) {
79 // TODO
80 } else if (colorMap) {
81 // Try for named color
82 var nc = colorMap[colorStr];
83 if (nc !== undefined) {
84 return nc;
85 }
86 }
87 SkDebug('unrecognized color ' + colorStr);
88 return CanvasKit.BLACK;
89}
90
91function valueOrPercent(aStr) {
92 if (aStr === undefined) {
93 return 1; // default to opaque.
94 }
95 var a = parseFloat(aStr);
96 if (aStr && aStr.indexOf('%') !== -1) {
97 return a / 100;
98 }
99 return a;
100}
101
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500102CanvasKit.multiplyByAlpha = function(color, alpha) {
Robert Phillipscb77eab2020-03-24 14:19:40 +0000103 if (alpha === 1) {
104 return color;
105 }
106 // extract as int from 0 to 255
107 var a = (color >> 24) & 0xFF;
108 a *= alpha;
109 // mask off the old alpha
110 color &= 0xFFFFFF;
111 // back to unsigned int to match SkColor.
112 return (clamp(a) << 24 | color) >>> 0;
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500113}
Kevin Lubick61ef7b22018-11-27 13:26:59 -0500114
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500115function radiansToDegrees(rad) {
116 return (rad / Math.PI) * 180;
117}
Kevin Lubick12c0e502018-11-28 12:51:56 -0500118
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500119function degreesToRadians(deg) {
120 return (deg / 180) * Math.PI;
121}
Kevin Lubick12c0e502018-11-28 12:51:56 -0500122
123// See https://stackoverflow.com/a/31090240
124// This contraption keeps closure from minifying away the check
125// if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
126// Defined outside any scopes to make it available in all files.
127var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
Kevin Lubick1646e7d2018-12-07 13:03:08 -0500128
129function almostEqual(floata, floatb) {
130 return Math.abs(floata - floatb) < 0.00001;
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500131}
132
133
134var nullptr = 0; // emscripten doesn't like to take null as uintptr_t
135
136// arr can be a normal JS array or a TypedArray
137// dest is something like CanvasKit.HEAPF32
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400138// ptr can be optionally provided if the memory was already allocated.
139function copy1dArray(arr, dest, ptr) {
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500140 if (!arr || !arr.length) {
141 return nullptr;
142 }
Kevin Lubicke25df6c2019-10-22 09:04:32 -0400143 // This was created with CanvasKit.Malloc, so it's already been copied.
144 if (arr['_ck']) {
145 return arr.byteOffset;
146 }
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400147 if (!ptr) {
148 ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
149 }
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500150 // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single
151 // byte elements. When we run _malloc, we always get an offset/pointer into
152 // that block of memory.
153 // CanvasKit exposes some different views to make it easier to work with
154 // different types. HEAPF32 for example, exposes it as a float*
155 // However, to make the ptr line up, we have to do some pointer arithmetic.
156 // Concretely, we need to convert ptr to go from an index into a 1-byte-wide
157 // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32)
158 // and thus we divide ptr by 4.
159 dest.set(arr, ptr / dest.BYTES_PER_ELEMENT);
160 return ptr;
161}
162
163// arr should be a non-jagged 2d JS array (TypedArrays can't be nested
Kevin Lubick37ab53e2019-11-11 10:06:08 -0500164// inside themselves). A common use case is points.
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500165// dest is something like CanvasKit.HEAPF32
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400166// ptr can be optionally provided if the memory was already allocated.
167function copy2dArray(arr, dest, ptr) {
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500168 if (!arr || !arr.length) {
169 return nullptr;
170 }
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400171 if (!ptr) {
172 ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT);
173 }
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500174 var idx = 0;
175 var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
176 for (var r = 0; r < arr.length; r++) {
177 for (var c = 0; c < arr[0].length; c++) {
178 dest[adjustedPtr + idx] = arr[r][c];
179 idx++;
180 }
181 }
182 return ptr;
183}
184
185// arr should be a non-jagged 3d JS array (TypedArrays can't be nested
186// inside themselves.)
187// dest is something like CanvasKit.HEAPF32
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400188// ptr can be optionally provided if the memory was already allocated.
189function copy3dArray(arr, dest, ptr) {
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500190 if (!arr || !arr.length || !arr[0].length) {
191 return nullptr;
192 }
Kevin Lubickd6ba7252019-06-03 14:38:05 -0400193 if (!ptr) {
194 ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT);
195 }
Kevin Lubickf5ea37f2019-02-28 10:06:18 -0500196 var idx = 0;
197 var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
198 for (var x = 0; x < arr.length; x++) {
199 for (var y = 0; y < arr[0].length; y++) {
200 for (var z = 0; z < arr[0][0].length; z++) {
201 dest[adjustedPtr + idx] = arr[x][y][z];
202 idx++;
203 }
204 }
205 }
206 return ptr;
207}
208
209// Caching the Float32Arrays can save having to reallocate them
210// over and over again.
211var Float32ArrayCache = {};
212
213// Takes a 2D array of commands and puts them into the WASM heap
214// as a 1D array. This allows them to referenced from the C++ code.
215// Returns a 2 element array, with the first item being essentially a
216// pointer to the array and the second item being the length of
217// the new 1D array.
218//
219// Example usage:
220// let cmds = [
221// [CanvasKit.MOVE_VERB, 0, 10],
222// [CanvasKit.LINE_VERB, 30, 40],
223// [CanvasKit.QUAD_VERB, 20, 50, 45, 60],
224// ];
225function loadCmdsTypedArray(arr) {
226 var len = 0;
227 for (var r = 0; r < arr.length; r++) {
228 len += arr[r].length;
229 }
230
231 var ta;
232 if (Float32ArrayCache[len]) {
233 ta = Float32ArrayCache[len];
234 } else {
235 ta = new Float32Array(len);
236 Float32ArrayCache[len] = ta;
237 }
238 // Flatten into a 1d array
239 var i = 0;
240 for (var r = 0; r < arr.length; r++) {
241 for (var c = 0; c < arr[r].length; c++) {
242 var item = arr[r][c];
243 ta[i] = item;
244 i++;
245 }
246 }
247
248 var ptr = copy1dArray(ta, CanvasKit.HEAPF32);
249 return [ptr, len];
250}
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400251
Kevin Lubickcc13fd32019-04-05 13:00:01 -0400252function saveBytesToFile(bytes, fileName) {
253 if (!isNode) {
254 // https://stackoverflow.com/a/32094834
255 var blob = new Blob([bytes], {type: 'application/octet-stream'});
256 url = window.URL.createObjectURL(blob);
257 var a = document.createElement('a');
258 document.body.appendChild(a);
259 a.href = url;
260 a.download = fileName;
261 a.click();
262 // clean up after because FF might not download it synchronously
263 setTimeout(function() {
264 URL.revokeObjectURL(url);
265 a.remove();
266 }, 50);
267 } else {
268 var fs = require('fs');
269 // https://stackoverflow.com/a/42006750
270 // https://stackoverflow.com/a/47018122
271 fs.writeFile(fileName, new Buffer(bytes), function(err) {
272 if (err) throw err;
273 });
274 }
275}
Kevin Lubickee91c072019-03-29 10:39:52 -0400276/**
277 * Generic helper for dealing with an array of four floats.
278 */
279CanvasKit.FourFloatArrayHelper = function() {
280 this._floats = [];
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400281 this._ptr = null;
Kevin Lubickee91c072019-03-29 10:39:52 -0400282
283 Object.defineProperty(this, 'length', {
284 enumerable: true,
285 get: function() {
286 return this._floats.length / 4;
287 },
288 });
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400289}
290
291/**
Kevin Lubickee91c072019-03-29 10:39:52 -0400292 * push the four floats onto the end of the array - if build() has already
293 * been called, the call will return without modifying anything.
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400294 */
Kevin Lubickee91c072019-03-29 10:39:52 -0400295CanvasKit.FourFloatArrayHelper.prototype.push = function(f1, f2, f3, f4) {
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400296 if (this._ptr) {
297 SkDebug('Cannot push more points - already built');
298 return;
299 }
Kevin Lubickee91c072019-03-29 10:39:52 -0400300 this._floats.push(f1, f2, f3, f4);
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400301}
302
Kevin Lubickee91c072019-03-29 10:39:52 -0400303/**
304 * Set the four floats at a given index - if build() has already
305 * been called, the WASM memory will be written to directly.
306 */
307CanvasKit.FourFloatArrayHelper.prototype.set = function(idx, f1, f2, f3, f4) {
308 if (idx < 0 || idx >= this._floats.length/4) {
309 SkDebug('Cannot set index ' + idx + ', it is out of range', this._floats.length/4);
310 return;
311 }
312 idx *= 4;
313 var BYTES_PER_ELEMENT = 4;
314 if (this._ptr) {
315 // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
316 var floatPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
317 CanvasKit.HEAPF32[floatPtr] = f1;
318 CanvasKit.HEAPF32[floatPtr + 1] = f2;
319 CanvasKit.HEAPF32[floatPtr + 2] = f3;
320 CanvasKit.HEAPF32[floatPtr + 3] = f4;
321 return;
322 }
323 this._floats[idx] = f1;
324 this._floats[idx + 1] = f2;
325 this._floats[idx + 2] = f3;
326 this._floats[idx + 3] = f4;
327}
328
329/**
330 * Copies the float data to the WASM memory and returns a pointer
331 * to that allocated memory. Once build has been called, this
332 * float array cannot be made bigger.
333 */
334CanvasKit.FourFloatArrayHelper.prototype.build = function() {
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400335 if (this._ptr) {
336 return this._ptr;
337 }
Kevin Lubickee91c072019-03-29 10:39:52 -0400338 this._ptr = copy1dArray(this._floats, CanvasKit.HEAPF32);
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400339 return this._ptr;
340}
341
Kevin Lubickee91c072019-03-29 10:39:52 -0400342/**
343 * Frees the wasm memory associated with this array. Of note,
344 * the points are not removed, so push/set/build can all
345 * be called to make a newly allocated (possibly bigger)
346 * float array.
347 */
348CanvasKit.FourFloatArrayHelper.prototype.delete = function() {
Kevin Lubickd3cfbca2019-03-15 15:36:29 -0400349 if (this._ptr) {
350 CanvasKit._free(this._ptr);
351 this._ptr = null;
352 }
353}
Kevin Lubickee91c072019-03-29 10:39:52 -0400354
355/**
356 * Generic helper for dealing with an array of unsigned ints.
357 */
358CanvasKit.OneUIntArrayHelper = function() {
359 this._uints = [];
360 this._ptr = null;
361
362 Object.defineProperty(this, 'length', {
363 enumerable: true,
364 get: function() {
365 return this._uints.length;
366 },
367 });
368}
369
370/**
371 * push the unsigned int onto the end of the array - if build() has already
372 * been called, the call will return without modifying anything.
373 */
374CanvasKit.OneUIntArrayHelper.prototype.push = function(u) {
375 if (this._ptr) {
376 SkDebug('Cannot push more points - already built');
377 return;
378 }
379 this._uints.push(u);
380}
381
382/**
383 * Set the uint at a given index - if build() has already
384 * been called, the WASM memory will be written to directly.
385 */
386CanvasKit.OneUIntArrayHelper.prototype.set = function(idx, u) {
387 if (idx < 0 || idx >= this._uints.length) {
388 SkDebug('Cannot set index ' + idx + ', it is out of range', this._uints.length);
389 return;
390 }
391 idx *= 4;
392 var BYTES_PER_ELEMENT = 4;
393 if (this._ptr) {
394 // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
395 var uintPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
396 CanvasKit.HEAPU32[uintPtr] = u;
397 return;
398 }
399 this._uints[idx] = u;
400}
401
402/**
403 * Copies the uint data to the WASM memory and returns a pointer
404 * to that allocated memory. Once build has been called, this
405 * unit array cannot be made bigger.
406 */
407CanvasKit.OneUIntArrayHelper.prototype.build = function() {
408 if (this._ptr) {
409 return this._ptr;
410 }
411 this._ptr = copy1dArray(this._uints, CanvasKit.HEAPU32);
412 return this._ptr;
413}
414
415/**
416 * Frees the wasm memory associated with this array. Of note,
417 * the points are not removed, so push/set/build can all
418 * be called to make a newly allocated (possibly bigger)
419 * uint array.
420 */
421CanvasKit.OneUIntArrayHelper.prototype.delete = function() {
422 if (this._ptr) {
423 CanvasKit._free(this._ptr);
424 this._ptr = null;
425 }
426}
427
428/**
429 * Helper for building an array of SkRects (which are just structs
430 * of 4 floats).
431 *
432 * It can be more performant to use this helper, as
433 * the C++-side array is only allocated once (on the first call)
434 * to build. Subsequent set() operations operate directly on
435 * the C++-side array, avoiding having to re-allocate (and free)
436 * the array every time.
437 *
438 * Input points are taken as left, top, right, bottom
439 */
440CanvasKit.SkRectBuilder = CanvasKit.FourFloatArrayHelper;
441/**
442 * Helper for building an array of RSXForms (which are just structs
443 * of 4 floats).
444 *
445 * It can be more performant to use this helper, as
446 * the C++-side array is only allocated once (on the first call)
447 * to build. Subsequent set() operations operate directly on
448 * the C++-side array, avoiding having to re-allocate (and free)
449 * the array every time.
450 *
451 * An RSXForm is a compressed form of a rotation+scale matrix.
452 *
453 * [ scos -ssin tx ]
454 * [ ssin scos ty ]
455 * [ 0 0 1 ]
456 *
457 * Input points are taken as scos, ssin, tx, ty
458 */
459CanvasKit.RSXFormBuilder = CanvasKit.FourFloatArrayHelper;
460
461/**
462 * Helper for building an array of SkColor
463 *
464 * It can be more performant to use this helper, as
465 * the C++-side array is only allocated once (on the first call)
466 * to build. Subsequent set() operations operate directly on
467 * the C++-side array, avoiding having to re-allocate (and free)
468 * the array every time.
469 */
470CanvasKit.SkColorBuilder = CanvasKit.OneUIntArrayHelper;
Kevin Lubicke25df6c2019-10-22 09:04:32 -0400471
472/**
473 * Malloc returns a TypedArray backed by the C++ memory of the
474 * given length. It should only be used by advanced users who
475 * can manage memory and initialize values properly. When used
476 * correctly, it can save copying of data between JS and C++.
477 * When used incorrectly, it can lead to memory leaks.
478 *
479 * const ta = CanvasKit.Malloc(Float32Array, 20);
480 * // store data into ta
481 * const cf = CanvasKit.SkColorFilter.MakeMatrix(ta);
482 * // MakeMatrix cleans up the ptr automatically.
483 *
484 * @param {TypedArray} typedArray - constructor for the typedArray.
485 * @param {number} len - number of elements to store.
486 */
487CanvasKit.Malloc = function(typedArray, len) {
488 var byteLen = len * typedArray.BYTES_PER_ELEMENT;
489 var ptr = CanvasKit._malloc(byteLen);
Bryce Thomas1fa54042020-01-14 13:46:30 -0800490 var ta = new typedArray(CanvasKit.HEAPU8.buffer, ptr, len);
Kevin Lubicke25df6c2019-10-22 09:04:32 -0400491 // add a marker that this was allocated in C++ land
492 ta['_ck'] = true;
493 return ta;
494}