zoomimg.js (5256B)
1 const m = require("lib/mithril"); 2 const stream = require("lib/mithril-stream"); 3 const sthelp = require("./modules/stream-helpers.js"); 4 5 // size: number 6 // size in pixels (screen not css) 7 // image: (x, y) -> pixel-value 8 // 0 <= x, y, < 1 9 // transform a position in the image to a local state value like a color. 10 // do not fluctuate locally (image and noise arguments are somewhat similar 11 // to vertex and fragment shader) 12 // alternatively image can be an Image object. then pixel-value for the noise 13 // function will be [r, g, b] 14 // noise: (x, y, pixel-value) -> [r, g, b] 15 // 0 <= r, g, b <= 255 16 // take pixel coordinates and the value generated by image() and transform 17 // them to rgb values 18 // update: stream 19 // set zoom level by stream value 20 module.exports = () => { 21 let sliderupdate; 22 let zoom; 23 return { 24 oninit: (vnode) => { 25 zoom = stream(1); 26 sliderupdate = stream(); 27 }, 28 oncreate: (vnode) => { 29 const canvas = vnode.dom.querySelector("canvas"); 30 const input_rough = vnode.dom.querySelector("input#rough") 31 const input_fine = vnode.dom.querySelector("input#fine") 32 33 input_rough.value = 1; 34 input_fine.value = 0; 35 36 sliderupdate.map(_ => 37 parseFloat(input_rough.value) 38 + 0.01 * parseFloat(input_fine.value) 39 ).map(val => zoom(Math.min(Math.max(val, 0), 2))); 40 41 if (vnode.attrs.update) vnode.attrs.update.map(zoom); 42 43 // only update sliders on external zoom set (sliders do not need to 44 // update themselves) 45 if (vnode.attrs.update) vnode.attrs.update.map(val => { 46 input_rough.value = Math.round(val * 100) / 100; 47 input_fine.value = (val - input_rough.value) * 100; 48 }); 49 50 zoom.map(z => canvas.style.transform = 51 "translate(-50%, -50%) scale(" + z + ")" 52 ); 53 54 let SIZE = vnode.attrs.size; 55 let SIZE_CSS = SIZE / window.devicePixelRatio; 56 canvas.style.width = canvas.style.height = SIZE_CSS + "px"; 57 canvas.width = canvas.height = SIZE; 58 59 const ctx = canvas.getContext("2d"); 60 let image_data; 61 if (vnode.attrs.image instanceof HTMLImageElement) { 62 ctx.drawImage(vnode.attrs.image, 0, 0); 63 image_data = ctx.getImageData(0, 0, SIZE, SIZE); 64 } else { 65 image_data = ctx.createImageData(SIZE, SIZE); 66 } 67 for (let x = 0; x < SIZE; x++) { 68 for (let y = 0; y < SIZE; y++) { 69 const pos = (y * SIZE + x) * 4; 70 let val; 71 if (vnode.attrs.image instanceof HTMLImageElement) { 72 val = image_data.data.slice(pos, pos + 3) 73 } else { 74 val = vnode.attrs.image(x / SIZE, y / SIZE); 75 } 76 [ 77 image_data.data[pos], 78 image_data.data[pos + 1], 79 image_data.data[pos + 2], 80 ] = vnode.attrs.noise(x, y, val); 81 image_data.data[pos + 3] = 255; 82 } 83 } 84 ctx.putImageData(image_data, 0, 0); 85 }, 86 view: (vnode) => [ 87 m("div", 88 m("div", { style: { 89 position: "relative", 90 overflow: "hidden", 91 lineHeight: 0, 92 width: "100%", 93 paddingBottom: "100%", 94 }}, 95 m("div", { style: { 96 position: "absolute", 97 width: "100%", 98 height: "100%", 99 }}, 100 m("canvas", { style: { 101 position: "absolute", 102 top: "50%", 103 left: "50%", 104 transform: "", 105 }}) 106 ) 107 ), 108 m("p"), 109 m("input#rough", { 110 type: "range", 111 min: 0, 112 max: 2, 113 step: 0.01, 114 oninput: () => sliderupdate(0), 115 }), 116 m("input#fine", { 117 type: "range", 118 min: -1, 119 max: 1, 120 step: 0.01, 121 oninput: () => sliderupdate(0), 122 }), 123 m("p", m("i", 124 vnode.attrs.size + "x" + vnode.attrs.size + " - " + 125 "Zoom: " + zoom().toFixed(4) + "x - ", 126 m("a", { 127 onclick: () => { 128 const canvas = vnode.dom.querySelector("canvas"); 129 canvas.toBlob(blob => { 130 const url = URL.createObjectURL(blob); 131 window.open(url, '_blank'); 132 }); 133 } 134 }, "open image") 135 )), 136 ), 137 ], 138 }; 139 };