From a581dbe19c77f3277a299fea38d8838812ccb4f6 Mon Sep 17 00:00:00 2001 From: Andrew Stephens Date: Mon, 2 Jan 2023 16:59:16 -0500 Subject: [PATCH] Added cutoff --- as-dithered-image.js | 46 +++++++++++++++++++++++++++++++------------- ditherworker.js | 10 ++++++---- test.html | 13 ++++++++++--- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/as-dithered-image.js b/as-dithered-image.js index e8738c6..317b67e 100644 --- a/as-dithered-image.js +++ b/as-dithered-image.js @@ -20,17 +20,20 @@ class ASDitheredImage extends HTMLElement { this.image_loading_ = false this.ignore_next_resize_ = false this.worker_ = new Worker("ditherworker.js") + this.cutoff_ = 0.5 this.worker_.onmessage = ((e) => { const imageData = e.data.imageData + console.log("Image painted ", imageData.width, imageData.height) this.context_.putImageData(imageData, 0, 0) }).bind(this) this.resizing_timeout_ = undefined + + this.last_draw_state_ = { width: 0, height: 0, crunchFactor: 0, imageSrc: "" } } connectedCallback() { - console.log("connectedCallback") if (!this.isConnected) { return } @@ -55,7 +58,6 @@ class ASDitheredImage extends HTMLElement { if (entries.length > 0) { if (entries[0].contentBoxSize) { - console.log("contentRect=", entries[0].contentRect) if (this.ignore_next_resize_ == true) { this.ignore_next_resize_ = false @@ -80,7 +82,7 @@ class ASDitheredImage extends HTMLElement { } - static get observedAttributes() { return ["src", "crunch", "alt"] } + static get observedAttributes() { return ["src", "crunch", "alt", "cutoff"] } attributeChangedCallback(name, oldValue, newValue) { @@ -110,6 +112,14 @@ class ASDitheredImage extends HTMLElement { this.canvas.setAttribute("aria-label", newValue) } } + } else if (name === "cutoff") { + this.cutoff_ = parseFloat(newValue) + if (isNaN(this.cutoff_)) { + this.cutoff_ = 0.5 + } + this.cutoff_ = Math.min(1.0, Math.max(0.0, this.cutoff_)) + this.force_refresh_ = true + this.requestUpdate() } } @@ -132,13 +142,10 @@ class ASDitheredImage extends HTMLElement { // all drawing is funneled through requestUpdate so that multiple calls are coalesced to prevent // processing the image multiple times for no good reason requestUpdate() { - console.log("requestUpdate") window.requestAnimationFrame(((timestamp) => { - console.log(this.force_refresh_, this.isConnected) if ((this.force_refresh_ == false)) { return } - console.log("update happening") if (this.original_image_ == undefined) { this.loadImage() return @@ -159,35 +166,47 @@ class ASDitheredImage extends HTMLElement { this.image_loading_ = false this.original_image_ = image this.ignore_next_resize_ = true - console.log("set aspect ratio") this.style.aspectRatio = this.original_image_.width + "/" + this.original_image_.height this.force_refresh_ = true this.requestUpdate() - console.log("Imaged Loaded") }).bind(this) image.onerror = (() => { this.image_loading_ == false this.original_image_ = undefined }).bind(this) this.image_loading_ = true - console.log("Loading ", this.getAttribute("src")) image.src = this.getAttribute("src") } repaintImage() { const rect = this.canvas_.getBoundingClientRect() - let screenPixelsToBackingStorePixels = this.getDevicePixelRatio() let fractionalPart = screenPixelsToBackingStorePixels - Math.floor(screenPixelsToBackingStorePixels) if (fractionalPart != 0) { screenPixelsToBackingStorePixels = Math.round(screenPixelsToBackingStorePixels * Math.round(1.0 / fractionalPart)) } + const calculatedWidth = rect.width * screenPixelsToBackingStorePixels + const calculatedHeight = rect.height * screenPixelsToBackingStorePixels let adjustedPixelSize = screenPixelsToBackingStorePixels * this.crunchFactor_ - // this has to change for fractional device pixel ratios - this.canvas_.width = rect.width * screenPixelsToBackingStorePixels - this.canvas_.height = rect.height * screenPixelsToBackingStorePixels + // double check - we may have already painted this image + if ((this.last_draw_state_.width == calculatedWidth) && + (this.last_draw_state_.height == calculatedHeight) && + (this.last_draw_state_.adjustedPixelSize == adjustedPixelSize) && + (this.last_draw_state_.imageSrc == this.original_image_.currentSrc) && + (this.last_draw_state_.cutoff == this.cutoff_)) { + return; // nothing to do + } + + this.canvas_.width = calculatedWidth + this.canvas_.height = calculatedHeight + + this.last_draw_state_.width = this.canvas_.width + this.last_draw_state_.height = this.canvas_.height + this.last_draw_state_.adjustedPixelSize = adjustedPixelSize + this.last_draw_state_.imageSrc = this.original_image_.currentSrc + this.last_draw_state_.cutoff = this.cutoff_ this.context_.imageSmoothingEnabled = true this.context_.drawImage(this.original_image_, 0, 0, this.canvas_.width / adjustedPixelSize, this.canvas_.height / adjustedPixelSize) @@ -199,6 +218,7 @@ class ASDitheredImage extends HTMLElement { const msg = {} msg.imageData = originalData msg.pixelSize = adjustedPixelSize + msg.cutoff = this.cutoff_ this.worker_.postMessage(msg) this.force_refresh_ = false diff --git a/ditherworker.js b/ditherworker.js index 43a0e56..fdaf979 100644 --- a/ditherworker.js +++ b/ditherworker.js @@ -1,13 +1,15 @@ onmessage = function (e) { - console.log("Worker: start", e.data.imageData) + console.log("Worker: start", e.data) - const result = dither(e.data.imageData, e.data.pixelSize) + const result = dither(e.data.imageData, e.data.pixelSize, e.data.cutoff) const reply = {} reply.imageData = result + reply.pixelSize = e.data.pixelSize + reply.cutoff = e.data.cutoff postMessage(reply) } -function dither(imageData, scaleFactor) { +function dither(imageData, scaleFactor, cutoff) { let output = new ImageData(imageData.width * scaleFactor, imageData.height * scaleFactor) for (let i = 0; i < imageData.data.length; i += 4) { imageData.data[i] = imageData.data[i + 1] = imageData.data[i + 2] = Math.floor(imageData.data[i] * 0.3 + imageData.data[i + 1] * 0.59 + imageData.data[i + 2] * 0.11) @@ -25,7 +27,7 @@ function dither(imageData, scaleFactor) { let accumulatedError = Math.floor(slidingErrorWindow[0][x]) let expectedMono = imageData.data[i] + accumulatedError let monoValue = expectedMono - if (monoValue <= 127) { + if (monoValue <= Math.floor(cutoff * 255)) { monoValue = 0 } else { monoValue = 255 diff --git a/test.html b/test.html index da5aef7..a4782e6 100644 --- a/test.html +++ b/test.html @@ -17,15 +17,17 @@ -

devicePixelRatio = 0

-

-

+ + + \ No newline at end of file