helper.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // helper JS that could be used anywhere in the glue code
  2. function clamp(c) {
  3. return Math.round(Math.max(0, Math.min(c || 0, 255)));
  4. }
  5. // Colors are just a 32 bit number with 8 bits each of a, r, g, b
  6. // The API is the same as CSS's representation of color rgba(), that is
  7. // r,g,b are 0-255, and a is 0.0 to 1.0.
  8. // if a is omitted, it will be assumed to be 1.0
  9. CanvasKit.Color = function(r, g, b, a) {
  10. if (a === undefined) {
  11. a = 1;
  12. }
  13. // The >>> 0 converts the signed int to an unsigned int. Skia's
  14. // SkColor object is an unsigned int.
  15. // https://stackoverflow.com/a/14891172
  16. return ((clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0)) >>> 0;
  17. }
  18. // returns [r, g, b, a] from a color
  19. // where a is scaled between 0 and 1.0
  20. CanvasKit.getColorComponents = function(color) {
  21. return [
  22. (color >> 16) & 0xFF,
  23. (color >> 8) & 0xFF,
  24. (color >> 0) & 0xFF,
  25. ((color >> 24) & 0xFF) / 255,
  26. ]
  27. }
  28. CanvasKit.multiplyByAlpha = function(color, alpha) {
  29. if (alpha === 1) {
  30. return color;
  31. }
  32. // extract as int from 0 to 255
  33. var a = (color >> 24) & 0xFF;
  34. a *= alpha;
  35. // mask off the old alpha
  36. color &= 0xFFFFFF;
  37. // back to unsigned int to match SkColor.
  38. return (clamp(a) << 24 | color) >>> 0;
  39. }
  40. function radiansToDegrees(rad) {
  41. return (rad / Math.PI) * 180;
  42. }
  43. function degreesToRadians(deg) {
  44. return (deg / 180) * Math.PI;
  45. }
  46. // See https://stackoverflow.com/a/31090240
  47. // This contraption keeps closure from minifying away the check
  48. // if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
  49. // Defined outside any scopes to make it available in all files.
  50. var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
  51. function almostEqual(floata, floatb) {
  52. return Math.abs(floata - floatb) < 0.00001;
  53. }
  54. var nullptr = 0; // emscripten doesn't like to take null as uintptr_t
  55. // arr can be a normal JS array or a TypedArray
  56. // dest is something like CanvasKit.HEAPF32
  57. // ptr can be optionally provided if the memory was already allocated.
  58. function copy1dArray(arr, dest, ptr) {
  59. if (!arr || !arr.length) {
  60. return nullptr;
  61. }
  62. if (!ptr) {
  63. ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
  64. }
  65. // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single
  66. // byte elements. When we run _malloc, we always get an offset/pointer into
  67. // that block of memory.
  68. // CanvasKit exposes some different views to make it easier to work with
  69. // different types. HEAPF32 for example, exposes it as a float*
  70. // However, to make the ptr line up, we have to do some pointer arithmetic.
  71. // Concretely, we need to convert ptr to go from an index into a 1-byte-wide
  72. // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32)
  73. // and thus we divide ptr by 4.
  74. dest.set(arr, ptr / dest.BYTES_PER_ELEMENT);
  75. return ptr;
  76. }
  77. // arr should be a non-jagged 2d JS array (TypedArrays can't be nested
  78. // inside themselves.)
  79. // dest is something like CanvasKit.HEAPF32
  80. // ptr can be optionally provided if the memory was already allocated.
  81. function copy2dArray(arr, dest, ptr) {
  82. if (!arr || !arr.length) {
  83. return nullptr;
  84. }
  85. if (!ptr) {
  86. ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT);
  87. }
  88. var idx = 0;
  89. var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
  90. for (var r = 0; r < arr.length; r++) {
  91. for (var c = 0; c < arr[0].length; c++) {
  92. dest[adjustedPtr + idx] = arr[r][c];
  93. idx++;
  94. }
  95. }
  96. return ptr;
  97. }
  98. // arr should be a non-jagged 3d JS array (TypedArrays can't be nested
  99. // inside themselves.)
  100. // dest is something like CanvasKit.HEAPF32
  101. // ptr can be optionally provided if the memory was already allocated.
  102. function copy3dArray(arr, dest, ptr) {
  103. if (!arr || !arr.length || !arr[0].length) {
  104. return nullptr;
  105. }
  106. if (!ptr) {
  107. ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT);
  108. }
  109. var idx = 0;
  110. var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
  111. for (var x = 0; x < arr.length; x++) {
  112. for (var y = 0; y < arr[0].length; y++) {
  113. for (var z = 0; z < arr[0][0].length; z++) {
  114. dest[adjustedPtr + idx] = arr[x][y][z];
  115. idx++;
  116. }
  117. }
  118. }
  119. return ptr;
  120. }
  121. // Caching the Float32Arrays can save having to reallocate them
  122. // over and over again.
  123. var Float32ArrayCache = {};
  124. // Takes a 2D array of commands and puts them into the WASM heap
  125. // as a 1D array. This allows them to referenced from the C++ code.
  126. // Returns a 2 element array, with the first item being essentially a
  127. // pointer to the array and the second item being the length of
  128. // the new 1D array.
  129. //
  130. // Example usage:
  131. // let cmds = [
  132. // [CanvasKit.MOVE_VERB, 0, 10],
  133. // [CanvasKit.LINE_VERB, 30, 40],
  134. // [CanvasKit.QUAD_VERB, 20, 50, 45, 60],
  135. // ];
  136. function loadCmdsTypedArray(arr) {
  137. var len = 0;
  138. for (var r = 0; r < arr.length; r++) {
  139. len += arr[r].length;
  140. }
  141. var ta;
  142. if (Float32ArrayCache[len]) {
  143. ta = Float32ArrayCache[len];
  144. } else {
  145. ta = new Float32Array(len);
  146. Float32ArrayCache[len] = ta;
  147. }
  148. // Flatten into a 1d array
  149. var i = 0;
  150. for (var r = 0; r < arr.length; r++) {
  151. for (var c = 0; c < arr[r].length; c++) {
  152. var item = arr[r][c];
  153. ta[i] = item;
  154. i++;
  155. }
  156. }
  157. var ptr = copy1dArray(ta, CanvasKit.HEAPF32);
  158. return [ptr, len];
  159. }
  160. function saveBytesToFile(bytes, fileName) {
  161. if (!isNode) {
  162. // https://stackoverflow.com/a/32094834
  163. var blob = new Blob([bytes], {type: 'application/octet-stream'});
  164. url = window.URL.createObjectURL(blob);
  165. var a = document.createElement('a');
  166. document.body.appendChild(a);
  167. a.href = url;
  168. a.download = fileName;
  169. a.click();
  170. // clean up after because FF might not download it synchronously
  171. setTimeout(function() {
  172. URL.revokeObjectURL(url);
  173. a.remove();
  174. }, 50);
  175. } else {
  176. var fs = require('fs');
  177. // https://stackoverflow.com/a/42006750
  178. // https://stackoverflow.com/a/47018122
  179. fs.writeFile(fileName, new Buffer(bytes), function(err) {
  180. if (err) throw err;
  181. });
  182. }
  183. }
  184. /**
  185. * Generic helper for dealing with an array of four floats.
  186. */
  187. CanvasKit.FourFloatArrayHelper = function() {
  188. this._floats = [];
  189. this._ptr = null;
  190. Object.defineProperty(this, 'length', {
  191. enumerable: true,
  192. get: function() {
  193. return this._floats.length / 4;
  194. },
  195. });
  196. }
  197. /**
  198. * push the four floats onto the end of the array - if build() has already
  199. * been called, the call will return without modifying anything.
  200. */
  201. CanvasKit.FourFloatArrayHelper.prototype.push = function(f1, f2, f3, f4) {
  202. if (this._ptr) {
  203. SkDebug('Cannot push more points - already built');
  204. return;
  205. }
  206. this._floats.push(f1, f2, f3, f4);
  207. }
  208. /**
  209. * Set the four floats at a given index - if build() has already
  210. * been called, the WASM memory will be written to directly.
  211. */
  212. CanvasKit.FourFloatArrayHelper.prototype.set = function(idx, f1, f2, f3, f4) {
  213. if (idx < 0 || idx >= this._floats.length/4) {
  214. SkDebug('Cannot set index ' + idx + ', it is out of range', this._floats.length/4);
  215. return;
  216. }
  217. idx *= 4;
  218. var BYTES_PER_ELEMENT = 4;
  219. if (this._ptr) {
  220. // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
  221. var floatPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
  222. CanvasKit.HEAPF32[floatPtr] = f1;
  223. CanvasKit.HEAPF32[floatPtr + 1] = f2;
  224. CanvasKit.HEAPF32[floatPtr + 2] = f3;
  225. CanvasKit.HEAPF32[floatPtr + 3] = f4;
  226. return;
  227. }
  228. this._floats[idx] = f1;
  229. this._floats[idx + 1] = f2;
  230. this._floats[idx + 2] = f3;
  231. this._floats[idx + 3] = f4;
  232. }
  233. /**
  234. * Copies the float data to the WASM memory and returns a pointer
  235. * to that allocated memory. Once build has been called, this
  236. * float array cannot be made bigger.
  237. */
  238. CanvasKit.FourFloatArrayHelper.prototype.build = function() {
  239. if (this._ptr) {
  240. return this._ptr;
  241. }
  242. this._ptr = copy1dArray(this._floats, CanvasKit.HEAPF32);
  243. return this._ptr;
  244. }
  245. /**
  246. * Frees the wasm memory associated with this array. Of note,
  247. * the points are not removed, so push/set/build can all
  248. * be called to make a newly allocated (possibly bigger)
  249. * float array.
  250. */
  251. CanvasKit.FourFloatArrayHelper.prototype.delete = function() {
  252. if (this._ptr) {
  253. CanvasKit._free(this._ptr);
  254. this._ptr = null;
  255. }
  256. }
  257. /**
  258. * Generic helper for dealing with an array of unsigned ints.
  259. */
  260. CanvasKit.OneUIntArrayHelper = function() {
  261. this._uints = [];
  262. this._ptr = null;
  263. Object.defineProperty(this, 'length', {
  264. enumerable: true,
  265. get: function() {
  266. return this._uints.length;
  267. },
  268. });
  269. }
  270. /**
  271. * push the unsigned int onto the end of the array - if build() has already
  272. * been called, the call will return without modifying anything.
  273. */
  274. CanvasKit.OneUIntArrayHelper.prototype.push = function(u) {
  275. if (this._ptr) {
  276. SkDebug('Cannot push more points - already built');
  277. return;
  278. }
  279. this._uints.push(u);
  280. }
  281. /**
  282. * Set the uint at a given index - if build() has already
  283. * been called, the WASM memory will be written to directly.
  284. */
  285. CanvasKit.OneUIntArrayHelper.prototype.set = function(idx, u) {
  286. if (idx < 0 || idx >= this._uints.length) {
  287. SkDebug('Cannot set index ' + idx + ', it is out of range', this._uints.length);
  288. return;
  289. }
  290. idx *= 4;
  291. var BYTES_PER_ELEMENT = 4;
  292. if (this._ptr) {
  293. // convert this._ptr from uint8_t* to SkScalar* by dividing by 4
  294. var uintPtr = (this._ptr / BYTES_PER_ELEMENT) + idx;
  295. CanvasKit.HEAPU32[uintPtr] = u;
  296. return;
  297. }
  298. this._uints[idx] = u;
  299. }
  300. /**
  301. * Copies the uint data to the WASM memory and returns a pointer
  302. * to that allocated memory. Once build has been called, this
  303. * unit array cannot be made bigger.
  304. */
  305. CanvasKit.OneUIntArrayHelper.prototype.build = function() {
  306. if (this._ptr) {
  307. return this._ptr;
  308. }
  309. this._ptr = copy1dArray(this._uints, CanvasKit.HEAPU32);
  310. return this._ptr;
  311. }
  312. /**
  313. * Frees the wasm memory associated with this array. Of note,
  314. * the points are not removed, so push/set/build can all
  315. * be called to make a newly allocated (possibly bigger)
  316. * uint array.
  317. */
  318. CanvasKit.OneUIntArrayHelper.prototype.delete = function() {
  319. if (this._ptr) {
  320. CanvasKit._free(this._ptr);
  321. this._ptr = null;
  322. }
  323. }
  324. /**
  325. * Helper for building an array of SkRects (which are just structs
  326. * of 4 floats).
  327. *
  328. * It can be more performant to use this helper, as
  329. * the C++-side array is only allocated once (on the first call)
  330. * to build. Subsequent set() operations operate directly on
  331. * the C++-side array, avoiding having to re-allocate (and free)
  332. * the array every time.
  333. *
  334. * Input points are taken as left, top, right, bottom
  335. */
  336. CanvasKit.SkRectBuilder = CanvasKit.FourFloatArrayHelper;
  337. /**
  338. * Helper for building an array of RSXForms (which are just structs
  339. * of 4 floats).
  340. *
  341. * It can be more performant to use this helper, as
  342. * the C++-side array is only allocated once (on the first call)
  343. * to build. Subsequent set() operations operate directly on
  344. * the C++-side array, avoiding having to re-allocate (and free)
  345. * the array every time.
  346. *
  347. * An RSXForm is a compressed form of a rotation+scale matrix.
  348. *
  349. * [ scos -ssin tx ]
  350. * [ ssin scos ty ]
  351. * [ 0 0 1 ]
  352. *
  353. * Input points are taken as scos, ssin, tx, ty
  354. */
  355. CanvasKit.RSXFormBuilder = CanvasKit.FourFloatArrayHelper;
  356. /**
  357. * Helper for building an array of SkColor
  358. *
  359. * It can be more performant to use this helper, as
  360. * the C++-side array is only allocated once (on the first call)
  361. * to build. Subsequent set() operations operate directly on
  362. * the C++-side array, avoiding having to re-allocate (and free)
  363. * the array every time.
  364. */
  365. CanvasKit.SkColorBuilder = CanvasKit.OneUIntArrayHelper;