tongong.net

personal website
git clone https://tongong.net/git/tongong.net.git
Log | Files | Refs

commit 6ec423e6eb22a4311f203499955c3d6540a15bd5
parent 802ce7730d7c91c1b77e47de79900b201e2be3e4
Author: tongong <tongong@gmx.net>
Date:   Thu,  8 Dec 2022 21:14:42 +0100

[zoom-interference] transferred content

from js/mithril to html

Diffstat:
Mcontent/writing/zoom-interference/Makefile | 2+-
Mcontent/writing/zoom-interference/src/index.html | 223++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcontent/writing/zoom-interference/src/main.js | 487++++++++++++++++++++++++++++---------------------------------------------------
Mtemplate/index.html | 1+
4 files changed, 398 insertions(+), 315 deletions(-)

diff --git a/content/writing/zoom-interference/Makefile b/content/writing/zoom-interference/Makefile @@ -3,4 +3,4 @@ build: src/* tacker src/index.html index.html watch: - find . | entr -s "make build" + find . | entr -s "make build && cd ../../.. && make" diff --git a/content/writing/zoom-interference/src/index.html b/content/writing/zoom-interference/src/index.html @@ -1,3 +1,224 @@ <link rel="stylesheet" href="./styles.css"/> -<div id="container"></div> + + +<h1>zoom interference</h1> +<p class="page-intro"> + A playful exploration into the world of image rendering. +</p> +<p> + I recently discovered that pixel interference effects can occur by zooming + in on gray image areas. This got me thinking: Can we construct an image + that initially looks uniformly gray but reveals information on specific + zoom levels? Is it maybe even possible to show different grayscale images + depending on the zoom level? +</p> +<p> + The following tests show client-side generated images using the + <code>&lt;canvas&gt;</code> element. However the image data is not manipulated + during zoom - just the CSS <code>transform: scale()</code> property changes. + The demos need pixel perfect sizes to work. Therefore the images can be too + small or too big on some devices - I apologize. +</p> +<p> + Please also note that I do not have any prior knowledge in the field of + image rendering. I was just playing around and documenting things I found + interesting. I hope you find this little tour interesting as well :). +</p> + + +<h2>the interference effect</h2> +<p> + The first image is just a checkerboard pattern on pixel scale (this is + clearly visible on <a class="demo1-checker">2x</a>). When zooming in the + image pixel grid and the screen pixel grid are misaligned creating + interference effects in both dimensions. They are especially strong in the + area of + <a class="demo1-checker">0.5x</a>, + <a class="demo1-checker">0.25x</a>, + <a class="demo1-checker">0.1667x</a> and + <a class="demo1-checker">0.125x</a>. + These turn out to be the whole number parts of 1/2. This makes sense: For + two adjacent screen pixels the distance of the image pixels is a multiple + of 2 which locally creates a uniformly colored area. +</p> +<!-- demo1-checker --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> +<p> + Another observation about browser differences can be made here: In Chromium + I can zoom smoothly at this microscopic scale, Firefox only allows zoom + steps. Interestingly for every zoom step the image size is an integer + multiple of 1/2 of the interference period. I do not know why. Also on + Firefox for Android there are graphic glitches across the whole screen for + some zoom levels. I guess this is a graphics driver issue of my device but + I am not sure. +</p> +<p> + On Chromium the image is completely black on some zoom levels like + <a class="demo1-checker">0.5x</a>. However this bug does not occur when + opening the image in a new tab and zooming to + <a class="demo1-checker">0.5x</a>. +</p> +<p> + For the second test I inverted the checkerboard pattern for some areas of + the image. There are zoom levels like <a class="demo2-checkerinvert">1x</a> + where just parts of the border of the shape are visible. For other zoom + levels like <a clas="demo2-checkerinvert">0.5x</a> the whole shape area is + clearly visible. +</p> +<!-- demo2-checkerinvert --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> + + +<h2>other types of colorings</h2> +<p> + Let's explore some other types of local colorings. I chose uniform gray and + random pixel values for the next demo. As expected the gray area does not + show any interference effects at all. The random area on the other hand has + them but they are hardly visible. You can see them if you keep the rough + slider at <a class="demo3-grayrandom">1x</a> and just move the fine slider. +</p> +<!-- demo3-grayrandom --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> +<p> + Now let's combine different types of colorings. The random noise seems too + different but a combination of checkerboard and gray could work. It does in + fact: For <a class="demo4-checkergray">0.999x</a> the difference is just + barely visible on Firefox and invisible on Chromium. +</p> +<!-- demo4-checkergray --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> +<p> + To hide the remaining interference artefacts we bring the random noise back + and add it to our color values. Now things start to get really interesting. + Let's just look at the zoom levels + <a class="demo5-checkergrayrandom">0.999x</a> and + <a class="demo5-checkergrayrandom">0.5x</a> for the next two demos. +</p> +<p> + On <a class="demo5-checkergrayrandom">0.5x</a> the square is sometimes + visible and sometimes invisible. After a bit of testing I found out that it + depends on the parity of the image width. I cannot explain it but I also do + not find it implausible that these interference effects change drastically + on size parity. +</p> +<p> + The real weirdness starts on <a class="demo5-checkergrayrandom">0.999x</a>. + On Firefox the square is visible for images smaller or equal to 500px in + size and invisible for larger ones. I suppose Firefox uses different + rendering techniques for different image sizes. On Chrome the square is + visible if and only if it is also visible on + <a class="demo5-checkergrayrandom">1x</a> + - this means visible for even image sizes and invisible for odd sizes. +</p> +<!-- demo5-checkergrayrandom --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> +<!-- demo5-checkergrayrandom --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> + + +<h2>invisibility on 1x</h2> +<p> + It would be really cool to create an image that is invisible on <a>1x</a> + but arises on zoom. This did already work but just in Chromium. My first + idea for this was to hide the interference area in a random area and match + the grayscale level: +</p> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> +<p> + This does work on Firefox on my specific screen. The difference is however + visible in other browsers and on Firefox on other devices. +</p> +<p> + To create a truly invisible interference area my next idea was to rearrange + the pixels according to their color. This ensures equal color distribution + inside and outside the interference area. It is however rather complicated + to implement. The same effect can be achieved with random colors using + uneven probabilities. For a dark pixel on the checkerboard we generate two + random color values and take the smaller one. This is equivalent to + imagining a neighboring light pixel and swapping their values if necessary. +</p> +<p> + Now the effect really works consistent across devices and screens! Try + zooming to <a class="demo6-reordering">0.5x</a> for example. +</p> +<!-- demo6-reordering --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> +<p> + Now if we think in terms of probability distributions this process can be + easily generalized from hidden black-and-white to grayscale images. Try the + following example on <a class="demo7-reordering-grayscale">0.5x</a> + Unfortunately the interference patterns are only clearly visible in the + center of the image as you can see at the end of the paint brush. (This is + sometimes not the case on Chromium.) +</p> +<!-- demo7-reordering-grayscale --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> +<p> + It would be really cool if the whole area could be used for hidden images. + However i think that is impossible. There are always spots of destructive + interference and for <a class"demo7-reordering-grayscale">0.5x</a> and its + whole number parts these spots are already at the border of the image. +</p> +<p> + Using the following file picker you can try the effect yourself. I respect + your privacy - All images are processed client-side. (You can check that in + the network tab in the debugging tools of your browser. +</p> +<!-- custom file upload --> +<div class="js-container"> + [If you see this text your browser unfortunately does not support + Javascript.] +</div> + + +<h2>todo</h2> +<ul> + <li>do not force square images</li> + <li>Does this work for colored images?</li> + <li>add source code link</li> +</ul> + +<h2>future ideas</h2> +<ul> + <li>different images on different scales?</li> + <li>more browser tests to really understand the different renderings</li> +</ul> + + +<h2>thank you...</h2> +<ul> + <li>... <a href="https://mithril.js.org/">mithril.js</a> for keeping the + fun in this project by abstracting over the Javascript DOM API</li> + <li>... <a href="https://ciechanow.ski/">Bartosz Ciechanowski</a> for being + a great inspiration</li> + <li>... for reading :)</li> +</ul> + <script src="./main.js"></script> diff --git a/content/writing/zoom-interference/src/main.js b/content/writing/zoom-interference/src/main.js @@ -11,318 +11,179 @@ function base64_to_img(txt) { return img; } -const main = () => { - // store all update streams in a container to avoid having a pile of local - // variables - let stream_container = {}; - // select a stream from the container - let zoom_stream = (st) => { - if (stream_container[st] == undefined) - stream_container[st] = stream(); - return stream_container[st]; - }; - let zoom_button = (st, z) => - m("a", {onclick: e => zoom_stream(st)(z)}, z.toFixed(4) + "x"); - return { - view: (vnode) => m("div", - m("h1", "zoom interference"), - m("p.page-intro", - `A playful exploration into the world of image rendering.` - ), - m("p", `I recently discovered that pixel interference effects can - occur by zooming in on gray image areas. This got me thinking: - Can we construct an image that initially looks uniformly gray - but reveals information on specific zoom levels? Is it maybe - even possible to show different grayscale images depending on - the zoom level?`), - m("p", `The following tests show client-side generated images using - the `, m("code", "<canvas>"), ` element. However the image data - is not manipulated during zoom - just the CSS `, m("code", - "transform: scale()"), ` property changes. The demos need pixel - perfect sizes to work. Therefore the images can be too small - or too big on some devices - I apologize.`), - m("p", `Please also note that I do not have any prior knowledge in - the field of image rendering. I was just playing around and - documenting things I found interesting. I hope you find this - little tour interesting as well :).`), - m("h2", "the interference effect"), - m("p", `The first image is just a checkerboard pattern on pixel - scale (this is clearly visible on `, - zoom_button("demo1-checker", 2), `). When zooming in the image - pixel grid and the screen pixel grid are misaligned creating - interference effects in both dimensions. They are especially - strong in the area of `, - zoom_button("demo1-checker", 0.5), `, `, - zoom_button("demo1-checker", 0.25), `, `, - zoom_button("demo1-checker", 0.1667), ` and `, - zoom_button("demo1-checker", 0.125), `. `, - `These turn out to be the whole number parts of 1/2. This makes - sense: For two adjacent screen pixels the distance of the image - pixels is a multiple of 2 which locally creates a uniformly - colored area.`), - m(zimg, { - size: 601, - image: (x, y) => 0, - noise: (x, y, val) => { - let color = 255 * ((x + y) % 2); - return [color, color, color]; - }, - update: zoom_stream("demo1-checker"), - }), - m("p", `Another observation about browser differences can be made - here: In Chromium I can zoom smoothly at this microscopic - scale, Firefox only allows zoom steps. Interestingly for every - zoom step the image size is an integer multiple of 1/2 of - the interference period. I do not know why. Also on Firefox for - Android there are graphic glitches across the whole screen for - some zoom levels. I guess this is a graphics driver issue of my - device but I am not sure.`), - m("p", `On Chromium the image is completely black on some zoom - levels like `, zoom_button("demo1-checker", 0.5), `. However - this bug does not occur when opening the image in a new tab and - zooming to `, zoom_button("demo1-checker", 0.5), `.`), - m("p", `For the second test I inverted the checkerboard pattern for - some areas of the image. There are zoom levels like `, - zoom_button("demo2-checkerinvert", 1), - ` where just parts of the border of the shape are visible. For - other zoom levels like `, - zoom_button("demo2-checkerinvert", 0.5), - ` the whole shape area is clearly visible`), - m(zimg, { - size: 601, - image: (x, y) => { - const square = 0.2 <= x && x < 0.6 && 0.4 <= y && y < 0.8; - const circle = ((x - 0.6) ** 2 + (y - 0.4) ** 2) < 0.2**2; - return (square + circle) % 2; // xor - }, - noise: (x, y, val) => { - let color = 255 * ((x + y + val) % 2); - return [color, color, color]; - }, - update: zoom_stream("demo2-checkerinvert"), - }), - m("h2", "other types of colorings"), - m("p", `Let's explore some other types of local colorings. I chose - uniform gray and random pixel values for the next demo. As - expected the gray area does not show any interference effects - at all. The random area on the other hand has them but they are - hardly visible. You can see them if you keep the rough slider at `, - zoom_button("demo3-grayrandom", 1), - ` and just move the fine slider.`), - m(zimg, { - size: 600, - image: (x, y) => x >= 0.5, - noise: (x, y, val) => { - let color = val? 128 : Math.random() * 255; - return [color, color, color]; - }, - update: zoom_stream("demo3-grayrandom"), - }), - m("p", `Now let's combine different types of colorings. The random - noise seems too different but a combination of checkerboard and - gray could work. It does in fact: For `, - zoom_button("demo4-checkergray", 0.999), - ` the difference is just barely visible on Firefox and - invisible on Chromium.` - ), - m(zimg, { - size: 601, - image: (x, y) => { - return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; - }, - noise: (x, y, val) => { - let color = 128; - if (val) color = (x + y) % 2 * 256; - return [color, color, color]; - }, - update: zoom_stream("demo4-checkergray"), - }), - m("p", `To hide the remaining interference artefacts we bring the - random noise back and add it to our color values. Now things - start to get really interesting. Let's just look at the zoom - levels `, - zoom_button("demo5-checkergrayrandom", 0.999), ` and `, - zoom_button("demo5-checkergrayrandom", 0.5), ` for the next two - demos.`), - m("p", `On `, zoom_button("demo5-checkergrayrandom", 0.5), ` the - square is sometimes visible and sometimes invisible. After a - bit of testing I found out that it depends on the parity of the - image width. I cannot explain it but I also do not find it - implausible that these interference effects change drastically - on size parity.`), - m("p", `The real weirdness starts on `, - zoom_button("demo5-checkergrayrandom", 0.999), `. `, - `On Firefox the square is visible for images smaller or equal - to 500px in size and invisible for larger ones. I suppose - Firefox uses different rendering techniques for different image - sizes. On Chrome the square is visible if and only if it is - also visible on `, zoom_button("demo5-checkergrayrandom", 1), ` - - this means visible for even image sizes and invisible for odd - sizes.`), - m(zimg, { - size: 601, - image: (x, y) => { - return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; - }, - noise: (x, y, val) => { - const RAND = 16; - let color = 128; - if (val) color = RAND + (x + y) % 2 * (256 - RAND * 2); - color = color - RAND + Math.random() * RAND * 2; - return [color, color, color]; - }, - update: zoom_stream("demo5-checkergrayrandom"), - }), - m(zimg, { - size: 401, - image: (x, y) => { - return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; - }, - noise: (x, y, val) => { - const RAND = 16; - let color = 128; - if (val) color = RAND + (x + y) % 2 * (256 - RAND * 2); - color = color - RAND + Math.random() * RAND * 2; - return [color, color, color]; - }, - update: zoom_stream("demo5-checkergrayrandom"), - }), - m("h2", "invisibility on 1x"), - m("p", `It would be really cool to create an image that is - invisible on `, zoom_button("empty", 1), ` but arises on zoom. - This did already work but just in Chromium. My first idea for - this was to hide the interference area in a random area and - match the grayscale level:`), - m(zimg, { - size: 601, - image: (x, y) => { - return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; - }, - noise: (x, y, val) => { - const RAND = 64; - let color = 150; - // let color = 181; // = 256 / sqrt(2) - if (val) color = RAND + (x + y + 1) % 2 * (256 - RAND * 2); - color = color - RAND + Math.random() * RAND * 2; - return [color, color, color]; - }, - }), - m("p", `This does work on Firefox on my specific screen. The - difference is however visible in other browsers and on Firefox - on other devices.`), - m("p", `To create a truly invisible interference area my next idea - was to rearrange the pixels according to their color. This - ensures equal color distribution inside and outside the - interference area. It is however rather complicated to - implement. The same effect can be achieved with random colors - using uneven probabilities. For a dark pixel on the - checkerboard we generate two random color values and take the - smaller one. This is equivalent to imagining a neighboring - light pixel and swapping their values if necessary.`), - m("p", `Now the effect really works consistent across devices and - screens! Try zooming to `, zoom_button("demo6-reordering", - 0.5), ` for example.`), - m(zimg, { - size: 601, - image: (x, y) => { - return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; - }, - noise: (x, y, val) => { - let color = 128; - const RAND = 80; - let colordiff = Math.random(); - if (val) { - if ((x + y + 1) % 2 == 0) - colordiff = Math.max(colordiff, Math.random()); - else - colordiff = Math.min(colordiff, Math.random()); - } - color = color - RAND + colordiff * 2 * RAND; - return [color, color, color]; - }, - update: zoom_stream("demo6-reordering"), - }), - m("p", `Now if we think in terms of probability distributions this - process can be easily generalized from hidden black-and-white - to grayscale images. Try the following example on `, - zoom_button("demo7-reordering-grayscale", 0.5), `. - Unfortunately the interference patterns are only clearly - visible in the center of the image as you can see at the end of - the paint brush. (This is sometimes not the case on - Chromium.)`), - m(zimg, { - size: 601, - // image: (x, y) => { - // if (x < 0.5) return 0.3 <= x && 0.3 <= y && y < 0.7; - // return 1 - y; - // }, - image: base64_to_img(gimpdata), - noise: (x, y, val) => { - val = val[0] / 256; - // val: input color; 0 <= val < 1 - let color = 128; - const RAND = 100; - // imagine a square. in x direction are the gray levels. in - // y direction are the infinitesimal probabilities. the - // square is cut by a point-symmetric line. the area above - // is for dark pixels on the checkerboard - below is for - // light pixels. if we hit randomly in one of those areas - // we get a color with the correct probabilities - let xp = Math.random(); - let yp = Math.random(); - // the dividing line is linear (this was chosen to ease - // computation but it may be a bad choice) - // it can be described by the function: - // -> f(x) = (1-2*val) * x + val - // for val = 0.5: f(x) = 0.5 - // for val = 0: f(x) = x - // for val = 1: f(x) = 1 - x - // if we hit in the wrong area mirror at the central point - // != for xor - if ((x + y + 1) % 2 == 0 != yp < (1-2*val)*xp+val) { - xp = 1 - xp; - // yp = 1 - yp; - } - color = color - RAND + xp * 2 * RAND; - return [color, color, color]; - }, - update: zoom_stream("demo7-reordering-grayscale"), - }), - m("p", `It would be really cool if the whole area could be used for - hidden images. However i think that is impossible. There are - always spots of destructive interference and for `, - zoom_button("demo7-reordering-grayscale", 0.5), ` and whole - number parts these spots are already at the border of the - image.`), - m("p", `Using the following file picker you can try the effect - yourself. I respect your privacy - All images are processed - client-side.`), - m(custom_file_upload), - m("h2", "todo"), - zoom_button("test", 0.5), `, `, - zoom_button("test", 1), `, `, - m("p", `do not force square images`), - m("p", `Does this work for colored images?`), - m("p", `add source code link`), - m("h2", "future ideas"), - m("p", `different images on different scales?`), - m("p", `more browser tests to really understand the different renderings`), - m("h2", "thank you..."), - m("ul", - m("li", "... ", - m("a", {href: "https://mithril.js.org/"}, "mithril.js"), - ` for keeping the fun in this project by abstracting over - the Javascript DOM API`, - ), - m("li", "... ", - m("a", {href: "https://ciechanow.ski/"}, "Bartosz Ciechanowski"), - ` for being a great inspiration`, - ), - m("li", "... for reading :)"), - ), - m("div", { style: "height: 50px" }), - ), - }; +// store all update streams in a container to avoid having a pile of local +// variables +const stream_container = {}; +// select a stream from the container +const zoom_stream = (st) => { + if (stream_container[st] == undefined) + stream_container[st] = stream(); + return stream_container[st]; }; +const zoom_stream_list = () => Object.keys(stream_container); -m.mount(document.getElementById("container"), main); +// list of mithril components to be mounted in their containers +const components = [ + {view: (vnode) => m(zimg, { + size: 601, + image: (x, y) => 0, + noise: (x, y, val) => { + let color = 255 * ((x + y) % 2); + return [color, color, color]; + }, + update: zoom_stream("demo1-checker"), + })}, + {view: (vnode) => m(zimg, { + size: 601, + image: (x, y) => { + const square = 0.2 <= x && x < 0.6 && 0.4 <= y && y < 0.8; + const circle = ((x - 0.6) ** 2 + (y - 0.4) ** 2) < 0.2**2; + return (square + circle) % 2; // xor + }, + noise: (x, y, val) => { + let color = 255 * ((x + y + val) % 2); + return [color, color, color]; + }, + update: zoom_stream("demo2-checkerinvert"), + })}, + {view: (vnode) => m(zimg, { + size: 600, + image: (x, y) => x >= 0.5, + noise: (x, y, val) => { + let color = val? 128 : Math.random() * 255; + return [color, color, color]; + }, + update: zoom_stream("demo3-grayrandom"), + })}, + {view: (vnode) => m(zimg, { + size: 601, + image: (x, y) => { + return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; + }, + noise: (x, y, val) => { + let color = 128; + if (val) color = (x + y) % 2 * 256; + return [color, color, color]; + }, + update: zoom_stream("demo4-checkergray"), + })}, + {view: (vnode) => m(zimg, { + size: 601, + image: (x, y) => { + return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; + }, + noise: (x, y, val) => { + const RAND = 16; + let color = 128; + if (val) color = RAND + (x + y) % 2 * (256 - RAND * 2); + color = color - RAND + Math.random() * RAND * 2; + return [color, color, color]; + }, + update: zoom_stream("demo5-checkergrayrandom"), + })}, + {view: (vnode) => m(zimg, { + size: 401, + image: (x, y) => { + return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; + }, + noise: (x, y, val) => { + const RAND = 16; + let color = 128; + if (val) color = RAND + (x + y) % 2 * (256 - RAND * 2); + color = color - RAND + Math.random() * RAND * 2; + return [color, color, color]; + }, + update: zoom_stream("demo5-checkergrayrandom"), + })}, + {view: (vnode) => m(zimg, { + size: 601, + image: (x, y) => { + return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; + }, + noise: (x, y, val) => { + const RAND = 64; + let color = 150; + // let color = 181; // = 256 / sqrt(2) + if (val) color = RAND + (x + y + 1) % 2 * (256 - RAND * 2); + color = color - RAND + Math.random() * RAND * 2; + return [color, color, color]; + }, + })}, + {view: (vnode) => m(zimg, { + size: 601, + image: (x, y) => { + return 0.3 <= x && x < 0.7 && 0.3 <= y && y < 0.7; + }, + noise: (x, y, val) => { + let color = 128; + const RAND = 80; + let colordiff = Math.random(); + if (val) { + if ((x + y + 1) % 2 == 0) + colordiff = Math.max(colordiff, Math.random()); + else + colordiff = Math.min(colordiff, Math.random()); + } + color = color - RAND + colordiff * 2 * RAND; + return [color, color, color]; + }, + update: zoom_stream("demo6-reordering"), + })}, + {view: (vnode) => m(zimg, { + size: 601, + // image: (x, y) => { + // if (x < 0.5) return 0.3 <= x && 0.3 <= y && y < 0.7; + // return 1 - y; + // }, + image: base64_to_img(gimpdata), + noise: (x, y, val) => { + val = val[0] / 256; + // val: input color; 0 <= val < 1 + let color = 128; + const RAND = 100; + // imagine a square. in x direction are the gray levels. in + // y direction are the infinitesimal probabilities. the + // square is cut by a point-symmetric line. the area above + // is for dark pixels on the checkerboard - below is for + // light pixels. if we hit randomly in one of those areas + // we get a color with the correct probabilities + let xp = Math.random(); + let yp = Math.random(); + // the dividing line is linear (this was chosen to ease + // computation but it may be a bad choice) + // it can be described by the function: + // -> f(x) = (1-2*val) * x + val + // for val = 0.5: f(x) = 0.5 + // for val = 0: f(x) = x + // for val = 1: f(x) = 1 - x + // if we hit in the wrong area mirror at the central point + // != for xor + if ((x + y + 1) % 2 == 0 != yp < (1-2*val)*xp+val) { + xp = 1 - xp; + // yp = 1 - yp; + } + color = color - RAND + xp * 2 * RAND; + return [color, color, color]; + }, + update: zoom_stream("demo7-reordering-grayscale"), + })}, + {view: (vnode) => m(custom_file_upload)}, +]; + +Array.from(document.querySelectorAll("div.js-container")).forEach((c, i) => { + m.mount(c, components[i]); +}); + +// link the links (connect <a> tags and interactive frames) +zoom_stream_list().forEach(st => { + Array.from(document.querySelectorAll("." + st)).forEach(a => { + // if (!a.innerHTML.endsWith("x")) console.error("broken link text"); + const zoomlevel = parseFloat(a.innerHTML.slice(0, -1)); + a.onclick = () => { + zoom_stream(st)(zoomlevel); + // manual redraw is neccessary for handlers defined outside of + // mithril + m.redraw(); + }; + }); +}); diff --git a/template/index.html b/template/index.html @@ -17,5 +17,6 @@ </p> <hr> {{content}} +<div style="height: 50px"></div> </body> </html>