Skip to content

Commit

Permalink
Merge pull request #1 from sinanatra/main
Browse files Browse the repository at this point in the history
  • Loading branch information
fidelthomet authored Jan 25, 2025
2 parents d78152e + 4c472c3 commit 9a0ebfa
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 45 deletions.
24 changes: 22 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,32 @@ <h2>video</h2>
<video src="./hermannstrasse.mp4" muted autoplay loop></video>
<dither-dither src="./hermannstrasse.mp4" video></dither-dither>
</div>
<h2>custom threshold map</h2>
<h2>Custom color</h2>
<div>
<dither-dither src="./hermannstrasse.jpg" dark="0,0,255"></dither-dither>
<dither-dither src="./hermannstrasse.jpg" light="0,0,255"></dither-dither>
<dither-dither src="./hermannstrasse.jpg" dark="255,0,0" light="0,0,255"></dither-dither>
</div>
<hr>
<div>
<dither-dither src="./hermannstrasse.mp4" dark="#978893"></dither-dither>
<dither-dither src="./hermannstrasse.mp4" light="#978893"></dither-dither>
<dither-dither src="./hermannstrasse.mp4" dark="#889a97" light="#978893"></dither-dither>
</div>

<h2>Custom size & object fit</h2>
<div>
<dither-dither src="./hermannstrasse.jpg" width="150"></dither-dither>
<dither-dither src="./hermannstrasse.jpg" height="250"></dither-dither>
<dither-dither src="./hermannstrasse.jpg" width="150" height="500" object-fit="contain"></dither-dither>
<dither-dither src="./hermannstrasse.jpg" width="150" height="500" object-fit="cover"></dither-dither>
</div>

<h2>Custom threshold map</h2>
<div>
<img src="./hermannstrasse.jpg" />
<dither-dither src="./hermannstrasse.jpg" threshold-map="./bayer8x8.png" lazy></dither-dither>
</div>

</body>
<style>
div {
Expand Down
16 changes: 9 additions & 7 deletions src/dither.frag
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ precision mediump float;

uniform sampler2D image;
uniform sampler2D threshold;

uniform vec2 resolution;
uniform vec3 darkColor;
uniform vec3 lightColor;

varying vec2 v_texCoord;

vec4 dither(vec2 position, vec4 color) {
float brightness = dot(color.rgb, vec3(0.299, 0.587, 0.114));
vec2 vecMod = vec2(mod(position.x, resolution.x), mod(position.y, resolution.y));
vec2 uv = vecMod / resolution.xy;
vec2 vecMod = mod(position, resolution);
vec2 uv = vecMod / resolution;
vec4 limit = texture2D(threshold, uv);

float dithered = brightness < limit.x ? 0.0 : 1.0;
return vec4(dithered, dithered, dithered, 1.0);
vec3 finalColor = mix(darkColor, lightColor, dithered);
return vec4(finalColor, 1.0);
}

void main() {
vec4 color = texture2D(image, v_texCoord);
gl_FragColor = dither(gl_FragCoord.xy, color);
}
vec4 color = texture2D(image, v_texCoord);
gl_FragColor = dither(gl_FragCoord.xy, color);
}
136 changes: 100 additions & 36 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class DitherDither extends HTMLElement {
this.intersecting = false;
this.lastRestore = 0;
}
static observedAttributes = ["src", "threshold-map"];
static observedAttributes = ["src", "threshold-map", "dark", "light"];

async attributeChangedCallback(name, oldValue, newValue) {
if (!this.initialized) return;
switch (name) {
Expand All @@ -40,6 +41,12 @@ class DitherDither extends HTMLElement {
}
this.initGL();
break;
case "dark":
case "light":
if (this.initialized) {
this.initGL();
}
break;
default:
break;
}
Expand Down Expand Up @@ -72,19 +79,8 @@ class DitherDither extends HTMLElement {
this.canvas.setAttribute("aria-label", this.getAttribute("alt"));

this.resizeCanvas();

// if (this.restore) {
// this.canvas.addEventListener("webglcontextlost", (e) => {
// e.preventDefault();
// if (this.intersecting) {
// this.restoreContext();
// }
// });
// this.canvas.addEventListener("webglcontextrestored", (e) => {
// if (this.gl.isContextLost()) this.restoreContext();
// });
// }
}

loadMedia(url, isVideo) {
return new Promise((resolve) => {
const el = isVideo ? document.createElement("video") : new Image();
Expand Down Expand Up @@ -115,16 +111,56 @@ class DitherDither extends HTMLElement {
}
async initMedia() {
this.media = await this.loadMedia(this.mediaSrc, this.isVideo());

this.width = this.media.videoWidth ?? this.media.width;
this.height = this.media.videoHeight ?? this.media.height;
}
async initThreshold() {
this.threshold = await this.loadMedia(this.thresholdSrc ?? thresholdMap);
}
async resizeCanvas() {
this.canvas.width = this.width;
this.canvas.height = this.height;
const customWidth = this.getAttribute("width") ? parseInt(this.getAttribute("width")) : null;
const customHeight = this.getAttribute("height") ? parseInt(this.getAttribute("height")) : null;
const ObjectFitType = this.getAttribute("object-fit") || "contain";

const mediaObjectFit = this.width / this.height;
let canvasWidth, canvasHeight;

if (customWidth && customHeight) {
canvasWidth = customWidth;
canvasHeight = customHeight;
}
else if (customWidth) {
canvasWidth = customWidth;
canvasHeight = customWidth / mediaObjectFit;
}
else if (customHeight) {
canvasHeight = customHeight;
canvasWidth = customHeight * mediaObjectFit;
}
else {
canvasWidth = this.width;
canvasHeight = this.height;
}

let renderWidth, renderHeight;
if (ObjectFitType === "contain") {
const scale = Math.min(canvasWidth / this.width, canvasHeight / this.height);
renderWidth = this.width * scale;
renderHeight = this.height * scale;
} else if (ObjectFitType === "cover") {
const scale = Math.max(canvasWidth / this.width, canvasHeight / this.height);
renderWidth = this.width * scale;
renderHeight = this.height * scale;
} else {
renderWidth = canvasWidth;
renderHeight = canvasHeight;
}

this.canvas.width = canvasWidth;
this.canvas.height = canvasHeight;

this.renderWidth = renderWidth;
this.renderHeight = renderHeight;
}

restoreContext() {
Expand All @@ -142,30 +178,26 @@ class DitherDither extends HTMLElement {
}
async initGL() {
const gl = (this.gl = this.canvas.getContext("webgl"));
// if (!gl) return;

const program = createProgram(gl, vs, fs);
gl.useProgram(program);

const mediaTexture = createTexture(gl, this.media);
const thresholdTexture = createTexture(gl, this.threshold);

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const xOffset = (this.renderWidth - this.canvas.width) / 2.0;
const yOffset = (this.renderHeight - this.canvas.height) / 2.0;
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
0,
0,
this.width,
0,
0,
this.height,
0,
this.height,
this.width,
0,
this.width,
this.height,
-xOffset, -yOffset,
this.renderWidth - xOffset, -yOffset,
-xOffset, this.renderHeight - yOffset,
-xOffset, this.renderHeight - yOffset,
this.renderWidth - xOffset, -yOffset,
this.renderWidth - xOffset, this.renderHeight - yOffset
]),
gl.STATIC_DRAW
);
Expand All @@ -174,7 +206,14 @@ class DitherDither extends HTMLElement {
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]),
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0
]),
gl.STATIC_DRAW
);

Expand All @@ -184,6 +223,8 @@ class DitherDither extends HTMLElement {
const imageLocation = gl.getUniformLocation(program, "image");
const thresholdLocation = gl.getUniformLocation(program, "threshold");
const resolutionThresholdLocation = gl.getUniformLocation(program, "resolution");
const darkColorLocation = gl.getUniformLocation(program, "darkColor");
const lightColorLocation = gl.getUniformLocation(program, "lightColor");

gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(program);
Expand All @@ -196,6 +237,16 @@ class DitherDither extends HTMLElement {
gl.uniform1i(imageLocation, 0);
gl.uniform1i(thresholdLocation, 1);

const darkColor = this.getAttribute("dark")
? parseColor(this.getAttribute("dark"))
: [0.0, 0.0, 0.0];
const lightColor = this.getAttribute("light")
? parseColor(this.getAttribute("light"))
: [1.0, 1.0, 1.0];

gl.uniform3fv(darkColorLocation, darkColor);
gl.uniform3fv(lightColorLocation, lightColor);

function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
Expand All @@ -204,7 +255,6 @@ class DitherDither extends HTMLElement {
if (success) {
return shader;
}

console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
Expand All @@ -220,21 +270,17 @@ class DitherDither extends HTMLElement {
if (success) {
return program;
}

console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}

function createTexture(gl, image) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

return texture;
}

Expand Down Expand Up @@ -286,7 +332,7 @@ class DitherDither extends HTMLElement {
this.img.src = url;
this.removeCanvas();
this.root.appendChild(this.img);
this.gl.getExtension("WEBGL_lose_context").loseContext();
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
});
}

Expand All @@ -312,6 +358,24 @@ class DitherDither extends HTMLElement {
if (this.observer?.unobserve) this.observer.unobserve(this);
}
}
function parseColor(colorString) {
if (colorString.startsWith("#")) {
let hex = colorString.slice(1);
if (hex.length === 3) {
hex = hex.split("").map(c => c + c).join("");
}
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return [r / 255, g / 255, b / 255];
}

if (colorString.includes(",")) {
return colorString.split(",").map(Number);
}
return [0.0, 0.0, 0.0];
}

customElements.define("dither-dither", DitherDither);
export default DitherDither;

0 comments on commit 9a0ebfa

Please sign in to comment.