mirror of
https://github.com/andrewstephens75/as-dithered-image.git
synced 2026-04-14 12:29:30 -07:00
Added cutoff
This commit is contained in:
@@ -20,17 +20,20 @@ class ASDitheredImage extends HTMLElement {
|
|||||||
this.image_loading_ = false
|
this.image_loading_ = false
|
||||||
this.ignore_next_resize_ = false
|
this.ignore_next_resize_ = false
|
||||||
this.worker_ = new Worker("ditherworker.js")
|
this.worker_ = new Worker("ditherworker.js")
|
||||||
|
this.cutoff_ = 0.5
|
||||||
|
|
||||||
this.worker_.onmessage = ((e) => {
|
this.worker_.onmessage = ((e) => {
|
||||||
const imageData = e.data.imageData
|
const imageData = e.data.imageData
|
||||||
|
console.log("Image painted ", imageData.width, imageData.height)
|
||||||
this.context_.putImageData(imageData, 0, 0)
|
this.context_.putImageData(imageData, 0, 0)
|
||||||
}).bind(this)
|
}).bind(this)
|
||||||
|
|
||||||
this.resizing_timeout_ = undefined
|
this.resizing_timeout_ = undefined
|
||||||
|
|
||||||
|
this.last_draw_state_ = { width: 0, height: 0, crunchFactor: 0, imageSrc: "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
console.log("connectedCallback")
|
|
||||||
if (!this.isConnected) {
|
if (!this.isConnected) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -55,7 +58,6 @@ class ASDitheredImage extends HTMLElement {
|
|||||||
|
|
||||||
if (entries.length > 0) {
|
if (entries.length > 0) {
|
||||||
if (entries[0].contentBoxSize) {
|
if (entries[0].contentBoxSize) {
|
||||||
console.log("contentRect=", entries[0].contentRect)
|
|
||||||
|
|
||||||
if (this.ignore_next_resize_ == true) {
|
if (this.ignore_next_resize_ == true) {
|
||||||
this.ignore_next_resize_ = false
|
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) {
|
attributeChangedCallback(name, oldValue, newValue) {
|
||||||
@@ -110,6 +112,14 @@ class ASDitheredImage extends HTMLElement {
|
|||||||
this.canvas.setAttribute("aria-label", newValue)
|
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
|
// all drawing is funneled through requestUpdate so that multiple calls are coalesced to prevent
|
||||||
// processing the image multiple times for no good reason
|
// processing the image multiple times for no good reason
|
||||||
requestUpdate() {
|
requestUpdate() {
|
||||||
console.log("requestUpdate")
|
|
||||||
window.requestAnimationFrame(((timestamp) => {
|
window.requestAnimationFrame(((timestamp) => {
|
||||||
console.log(this.force_refresh_, this.isConnected)
|
|
||||||
if ((this.force_refresh_ == false)) {
|
if ((this.force_refresh_ == false)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log("update happening")
|
|
||||||
if (this.original_image_ == undefined) {
|
if (this.original_image_ == undefined) {
|
||||||
this.loadImage()
|
this.loadImage()
|
||||||
return
|
return
|
||||||
@@ -159,35 +166,47 @@ class ASDitheredImage extends HTMLElement {
|
|||||||
this.image_loading_ = false
|
this.image_loading_ = false
|
||||||
this.original_image_ = image
|
this.original_image_ = image
|
||||||
this.ignore_next_resize_ = true
|
this.ignore_next_resize_ = true
|
||||||
console.log("set aspect ratio")
|
|
||||||
this.style.aspectRatio = this.original_image_.width + "/" + this.original_image_.height
|
this.style.aspectRatio = this.original_image_.width + "/" + this.original_image_.height
|
||||||
this.force_refresh_ = true
|
this.force_refresh_ = true
|
||||||
this.requestUpdate()
|
this.requestUpdate()
|
||||||
console.log("Imaged Loaded")
|
|
||||||
}).bind(this)
|
}).bind(this)
|
||||||
image.onerror = (() => {
|
image.onerror = (() => {
|
||||||
this.image_loading_ == false
|
this.image_loading_ == false
|
||||||
this.original_image_ = undefined
|
this.original_image_ = undefined
|
||||||
}).bind(this)
|
}).bind(this)
|
||||||
this.image_loading_ = true
|
this.image_loading_ = true
|
||||||
console.log("Loading ", this.getAttribute("src"))
|
|
||||||
image.src = this.getAttribute("src")
|
image.src = this.getAttribute("src")
|
||||||
}
|
}
|
||||||
|
|
||||||
repaintImage() {
|
repaintImage() {
|
||||||
const rect = this.canvas_.getBoundingClientRect()
|
const rect = this.canvas_.getBoundingClientRect()
|
||||||
|
|
||||||
let screenPixelsToBackingStorePixels = this.getDevicePixelRatio()
|
let screenPixelsToBackingStorePixels = this.getDevicePixelRatio()
|
||||||
let fractionalPart = screenPixelsToBackingStorePixels - Math.floor(screenPixelsToBackingStorePixels)
|
let fractionalPart = screenPixelsToBackingStorePixels - Math.floor(screenPixelsToBackingStorePixels)
|
||||||
if (fractionalPart != 0) {
|
if (fractionalPart != 0) {
|
||||||
screenPixelsToBackingStorePixels = Math.round(screenPixelsToBackingStorePixels * Math.round(1.0 / fractionalPart))
|
screenPixelsToBackingStorePixels = Math.round(screenPixelsToBackingStorePixels * Math.round(1.0 / fractionalPart))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calculatedWidth = rect.width * screenPixelsToBackingStorePixels
|
||||||
|
const calculatedHeight = rect.height * screenPixelsToBackingStorePixels
|
||||||
let adjustedPixelSize = screenPixelsToBackingStorePixels * this.crunchFactor_
|
let adjustedPixelSize = screenPixelsToBackingStorePixels * this.crunchFactor_
|
||||||
|
|
||||||
// this has to change for fractional device pixel ratios
|
// double check - we may have already painted this image
|
||||||
this.canvas_.width = rect.width * screenPixelsToBackingStorePixels
|
if ((this.last_draw_state_.width == calculatedWidth) &&
|
||||||
this.canvas_.height = rect.height * screenPixelsToBackingStorePixels
|
(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_.imageSmoothingEnabled = true
|
||||||
this.context_.drawImage(this.original_image_, 0, 0, this.canvas_.width / adjustedPixelSize, this.canvas_.height / adjustedPixelSize)
|
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 = {}
|
const msg = {}
|
||||||
msg.imageData = originalData
|
msg.imageData = originalData
|
||||||
msg.pixelSize = adjustedPixelSize
|
msg.pixelSize = adjustedPixelSize
|
||||||
|
msg.cutoff = this.cutoff_
|
||||||
this.worker_.postMessage(msg)
|
this.worker_.postMessage(msg)
|
||||||
|
|
||||||
this.force_refresh_ = false
|
this.force_refresh_ = false
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
onmessage = function (e) {
|
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 = {}
|
const reply = {}
|
||||||
reply.imageData = result
|
reply.imageData = result
|
||||||
|
reply.pixelSize = e.data.pixelSize
|
||||||
|
reply.cutoff = e.data.cutoff
|
||||||
postMessage(reply)
|
postMessage(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
function dither(imageData, scaleFactor) {
|
function dither(imageData, scaleFactor, cutoff) {
|
||||||
let output = new ImageData(imageData.width * scaleFactor, imageData.height * scaleFactor)
|
let output = new ImageData(imageData.width * scaleFactor, imageData.height * scaleFactor)
|
||||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
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)
|
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 accumulatedError = Math.floor(slidingErrorWindow[0][x])
|
||||||
let expectedMono = imageData.data[i] + accumulatedError
|
let expectedMono = imageData.data[i] + accumulatedError
|
||||||
let monoValue = expectedMono
|
let monoValue = expectedMono
|
||||||
if (monoValue <= 127) {
|
if (monoValue <= Math.floor(cutoff * 255)) {
|
||||||
monoValue = 0
|
monoValue = 0
|
||||||
} else {
|
} else {
|
||||||
monoValue = 255
|
monoValue = 255
|
||||||
|
|||||||
13
test.html
13
test.html
@@ -17,15 +17,17 @@
|
|||||||
<as-dithered-image class="setSize" id="picture"
|
<as-dithered-image class="setSize" id="picture"
|
||||||
src="Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"></as-dithered-image>
|
src="Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg"></as-dithered-image>
|
||||||
|
|
||||||
<p>devicePixelRatio = <span id=dpr>0</span></p>
|
<div>devicePixelRatio = <span id=dpr>0</span></p>
|
||||||
<p><select id="crunchselect">
|
<select id="crunchselect">
|
||||||
<option value="auto">Automatic</option>
|
<option value="auto">Automatic</option>
|
||||||
<option value="pixel">Pixel</option>
|
<option value="pixel">Pixel</option>
|
||||||
<option value="1">1</option>
|
<option value="1">1</option>
|
||||||
<option value="2">2</option>
|
<option value="2">2</option>
|
||||||
<option value="3">3</option>
|
<option value="3">3</option>
|
||||||
<option value="4">4</option>
|
<option value="4">4</option>
|
||||||
</select></p>
|
</select>
|
||||||
|
<input id="cutoff" type="range" min="0.0" max="1.0" step="0.05" value="any" />
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -35,6 +37,11 @@
|
|||||||
console.log("Crunch = ", e.target.value)
|
console.log("Crunch = ", e.target.value)
|
||||||
document.getElementById("picture").setAttribute("crunch", e.target.value)
|
document.getElementById("picture").setAttribute("crunch", e.target.value)
|
||||||
})
|
})
|
||||||
|
document.getElementById("cutoff").addEventListener("change", e => {
|
||||||
|
console.log("Value = ", e.target.value)
|
||||||
|
document.getElementById("picture").setAttribute("cutoff", e.target.value)
|
||||||
|
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user