• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// helper JS that could be used anywhere in the glue code
2
3function clamp(c) {
4  return Math.round(Math.max(0, Math.min(c || 0, 255)));
5}
6
7// 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;
14  }
15  // 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;
19}
20
21// 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
32// parseColorString takes in a CSS color value and returns a CanvasKit.Color
33// (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}).
35// 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
102CanvasKit.multiplyByAlpha = function(color, alpha) {
103  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;
113}
114
115function radiansToDegrees(rad) {
116  return (rad / Math.PI) * 180;
117}
118
119function degreesToRadians(deg) {
120  return (deg / 180) * Math.PI;
121}
122
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;}")());
128
129function almostEqual(floata, floatb) {
130  return Math.abs(floata - floatb) < 0.00001;
131}
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
138// ptr can be optionally provided if the memory was already allocated.
139function copy1dArray(arr, dest, ptr) {
140  if (!arr || !arr.length) {
141    return nullptr;
142  }
143  // This was created with CanvasKit.Malloc, so it's already been copied.
144  if (arr['_ck']) {
145    return arr.byteOffset;
146  }
147  if (!ptr) {
148    ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
149  }
150  // 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
164//     inside themselves). A common use case is points.
165// dest is something like CanvasKit.HEAPF32
166// ptr can be optionally provided if the memory was already allocated.
167function copy2dArray(arr, dest, ptr) {
168  if (!arr || !arr.length) {
169    return nullptr;
170  }
171  if (!ptr) {
172    ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT);
173  }
174  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
188// ptr can be optionally provided if the memory was already allocated.
189function copy3dArray(arr, dest, ptr) {
190  if (!arr || !arr.length || !arr[0].length) {
191    return nullptr;
192  }
193  if (!ptr) {
194    ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT);
195  }
196  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}
251
252function 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}
276/**
277 * Generic helper for dealing with an array of four floats.
278 */
279CanvasKit.FourFloatArrayHelper = function() {
280  this._floats = [];
281  this._ptr = null;
282
283  Object.defineProperty(this, 'length', {
284    enumerable: true,
285    get: function() {
286      return this._floats.length / 4;
287    },
288  });
289}
290
291/**
292 * push the four floats onto the end of the array - if build() has already
293 * been called, the call will return without modifying anything.
294 */
295CanvasKit.FourFloatArrayHelper.prototype.push = function(f1, f2, f3, f4) {
296  if (this._ptr) {
297    SkDebug('Cannot push more points - already built');
298    return;
299  }
300  this._floats.push(f1, f2, f3, f4);
301}
302
303/**
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() {
335  if (this._ptr) {
336    return this._ptr;
337  }
338  this._ptr = copy1dArray(this._floats, CanvasKit.HEAPF32);
339  return this._ptr;
340}
341
342/**
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() {
349  if (this._ptr) {
350    CanvasKit._free(this._ptr);
351    this._ptr = null;
352  }
353}
354
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;
471
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);
490  var ta = new typedArray(CanvasKit.HEAPU8.buffer, ptr, len);
491  // add a marker that this was allocated in C++ land
492  ta['_ck'] = true;
493  return ta;
494}
495