From 17423878a1a2e17445d2d1572c76629915e93157 Mon Sep 17 00:00:00 2001 From: Alok Pepakayala Date: Sun, 24 Sep 2023 00:21:42 +0530 Subject: [PATCH 1/2] Word cloud with masks Use an image mask to prepopulate the board before the placement. Example added to the examples folder. --- build/d3.layout.cloud.js | 49 +++++++++++++++++++++++++-- examples/mask.png | Bin 0 -> 6283 bytes examples/mask_example.html | 67 +++++++++++++++++++++++++++++++++++++ index.js | 49 +++++++++++++++++++++++++-- 4 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 examples/mask.png create mode 100644 examples/mask_example.html diff --git a/build/d3.layout.cloud.js b/build/d3.layout.cloud.js index 02f2916..d5bbea6 100644 --- a/build/d3.layout.cloud.js +++ b/build/d3.layout.cloud.js @@ -13,6 +13,7 @@ const SPIRALS = { const cw = 1 << 11 >> 5; const ch = 1 << 11; +const random = Math.random; module.exports = function() { var size = [256, 256], @@ -30,7 +31,9 @@ module.exports = function() { timer = null, random = Math.random, cloud = {}, - canvas = cloudCanvas; + canvas = cloudCanvas, + mask = false + maskBoard = null; cloud.canvas = function(_) { return arguments.length ? (canvas = functor(_), cloud) : canvas; @@ -38,7 +41,7 @@ module.exports = function() { cloud.start = function() { var contextAndRatio = getContext(canvas()), - board = zeroArray((size[0] >> 5) * size[1]), + board = mask ? maskBoard : zeroArray((size[0] >> 5) * size[1]), bounds = null, n = words.length, i = -1, @@ -161,6 +164,16 @@ module.exports = function() { return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval; }; + cloud.mask = function(_){ + if(arguments.length){ + mask = _ + maskBoard = createMaskBoard(_,size[0],size[1]) + return cloud + }else{ + return mask + } + }; + cloud.words = function(_) { return arguments.length ? (words = _, cloud) : words; }; @@ -404,6 +417,38 @@ function functor(d) { return typeof d === "function" ? d : function() { return d; }; } +function get_mask_pixels(mask,w,h){ + var canvas = document.createElement('canvas') + canvas.width = w + canvas.height = h; + var ctx = canvas.getContext("2d"); + ctx.drawImage(mask, 0, 0, w, h) + return ctx.getImageData(0, 0, w, h).data +} + +function create_mask_board_from_pixels(mask_pixels, w,h){ + var w32 = w >> 5 //divedes by 32 + var sprite = [] + // Zero the buffer + for (var i = 0; i < h * w32; i++){ + sprite[i] = 0 + } + for (var j = 0; j < h; j++) { + for (var i = 0; i < w; i++) { + var k = w32 * j + (i >> 5); + var m = mask_pixels[(j * w + (i)) << 2] ? 1 << (31 - (i % 32)) : 0; + sprite[k] |= m; + } + } + return sprite.slice(0, w * w32) +} + +function createMaskBoard(mask,w,h){ + var pixels = get_mask_pixels(mask,w,h) + var board = create_mask_board_from_pixels(pixels,w,h) + return board +} + },{"d3-dispatch":2}],2:[function(require,module,exports){ // https://d3js.org/d3-dispatch/ v1.0.6 Copyright 2019 Mike Bostock (function (global, factory) { diff --git a/examples/mask.png b/examples/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..7d768963f8d80930f7ca999c8437fcd2c34d1686 GIT binary patch literal 6283 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Νl5JXOL}DnsYHK8BjWUHBpeC0RA)`*bIB0did!J95%=LJRkqBEG)HwI#=> zSuu+vxAISWV5ms$LWgE?T=S*Q(3gV91-^J{bqeZ+&sRl)pl&=l!m1ROZ6_Pg_^VNmc<{(6(g)rr+WJ3@3i%?dhxCOi zV6Cp6S1jiccaEGw-s1tE6~mohgf}8)3iqm*(vD3G87{5uQC6g@yw}27F?|+3-E*`Z za7lm`O1xZa@>ReE49j>8?qupf1WuGVBq(~HuQVBo+QM~#iNWv{(r|L%(ipMZ1}USk zt_V4&muZ}$&_(RU@XV;vUYbC^EdrG<43oA9*Tm7TU|`K=TUH*tWF~j9kE~V7>b(h zBfjH9JrTuqXy6sqxRm7>4c5cIvq%p5=t^8^1}Q6^oKU4>Gw`-M>)Ui0Sml3uf6`>& zttoBB0f8U(4ZDx70Pq(^L;L#_Uxgw@aEtjYmHkwp*F$mTFt758%TaKQs ze=3OY)uGC%<)iclQ<}HYY2l>6CHHeY$V#nv*v>MhV7QI!hp|oNmVyo4szL;_+79mT zx~*p;g4K0HlzKc|T)RR7DeL7!NmdHjtKT6Fq0q^*AeCmq5yIGLFgn=}dnC_Dt8lG< z-WnUK|0#R2#&lo?G-9AEHT-vhEp>XhHa@``xob$|g`#S}GmWW84?BQP#@%K~R~Q{d zCUfzjYV3wPF|7I)!Rf>Cuu9pbDB%d*A~Iz$g{6vtWgcaOxt2;}V4!Kb?G<4Cu8|4m z)@-O!*K3!a$9^d!$_;DAxr}A^(@g=l^#=MAVe^(o)lA+(#7r<&<2X85vphXEuB(M_ zurc`y6qUV*?*zHysELY1IA;Vh&Q-wIb|>XyuU3&L&p}0v;#sT>%3LsziYz((dRg+}Yxb>2|qsf4`y8^bVU%M9!My1ail*`RN zYV?E0CNT$oE_z%( zH9R77l6qM%24O5>&A5AZG<3A>=gB`Y13}wU2)#IIZ$qoWYj&$)VD= zRKgCjz%>~IMK#P$JXvWXj7?_z2}#?m6FNS!tBEg4wv>GIZ + + + + + + + + + + + + \ No newline at end of file diff --git a/index.js b/index.js index 7be341e..11514f4 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const SPIRALS = { const cw = 1 << 11 >> 5; const ch = 1 << 11; +const random = Math.random; module.exports = function() { var size = [256, 256], @@ -29,7 +30,9 @@ module.exports = function() { timer = null, random = Math.random, cloud = {}, - canvas = cloudCanvas; + canvas = cloudCanvas, + mask = false + maskBoard = null; cloud.canvas = function(_) { return arguments.length ? (canvas = functor(_), cloud) : canvas; @@ -37,7 +40,7 @@ module.exports = function() { cloud.start = function() { var contextAndRatio = getContext(canvas()), - board = zeroArray((size[0] >> 5) * size[1]), + board = mask ? maskBoard : zeroArray((size[0] >> 5) * size[1]), bounds = null, n = words.length, i = -1, @@ -160,6 +163,16 @@ module.exports = function() { return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval; }; + cloud.mask = function(_){ + if(arguments.length){ + mask = _ + maskBoard = createMaskBoard(_,size[0],size[1]) + return cloud + }else{ + return mask + } + }; + cloud.words = function(_) { return arguments.length ? (words = _, cloud) : words; }; @@ -402,3 +415,35 @@ function cloudCanvas() { function functor(d) { return typeof d === "function" ? d : function() { return d; }; } + +function get_mask_pixels(mask,w,h){ + var canvas = document.createElement('canvas') + canvas.width = w + canvas.height = h; + var ctx = canvas.getContext("2d"); + ctx.drawImage(mask, 0, 0, w, h) + return ctx.getImageData(0, 0, w, h).data +} + +function create_mask_board_from_pixels(mask_pixels, w,h){ + var w32 = w >> 5 //divedes by 32 + var sprite = [] + // Zero the buffer + for (var i = 0; i < h * w32; i++){ + sprite[i] = 0 + } + for (var j = 0; j < h; j++) { + for (var i = 0; i < w; i++) { + var k = w32 * j + (i >> 5); + var m = mask_pixels[(j * w + (i)) << 2] ? 1 << (31 - (i % 32)) : 0; + sprite[k] |= m; + } + } + return sprite.slice(0, w * w32) +} + +function createMaskBoard(mask,w,h){ + var pixels = get_mask_pixels(mask,w,h) + var board = create_mask_board_from_pixels(pixels,w,h) + return board +} From 7d4d60f985f9b8a711c8f80a25922ec591df0caf Mon Sep 17 00:00:00 2001 From: Alok Pepakayala Date: Wed, 27 Sep 2023 16:04:33 +0530 Subject: [PATCH 2/2] Update mask in Readme --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 8d38095..31b4fb9 100644 --- a/README.md +++ b/README.md @@ -168,3 +168,23 @@ function() { return document.createElement("canvas"); } When using Node.js, you will almost definitely override this default, e.g. using the [canvas module](https://www.npmjs.com/package/canvas). + +# mask([mask]) + +If specified, sets the **mask** image used internally +to draw text fitting within the transaparent areas of the provided image. If not specified, returns the current mask image, defaults to null. + +```js +// Mask Example +var img = document.querySelector("img#mask") +img.onload = ()=>{ + d3.layout.cloud() + .size([500, 500]) + .mask(img) +} +``` + +Ensure the image is loaded before using it, typically using the onload event on the image. + +Note: The image must be hosted with the appropriate CORS headers if being hosted on a different origin. +