commit e4ea0b0b99de63138e5630d5af6635fed2458ef0 Author: Christian Lawson-Perfect Date: Fri Apr 4 08:21:57 2025 +0000 First commit. It filters the colours by doing matrix multiplication in JS on the pixel array. It could almost definitely be made faster using a WebGL shader. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bc0f76 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.make.* \ No newline at end of file diff --git a/clp-2023.jpg b/clp-2023.jpg new file mode 100644 index 0000000..d47b875 Binary files /dev/null and b/clp-2023.jpg differ diff --git a/colourful-monotiles.jpg b/colourful-monotiles.jpg new file mode 100644 index 0000000..1149b76 Binary files /dev/null and b/colourful-monotiles.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..f0c4d34 --- /dev/null +++ b/index.html @@ -0,0 +1,48 @@ + + + + + + CVD simulator + + + + +
+

Colour vision deficiency simulator

+

This page aims to give you a rough idea of what people with different colour vision see.

+
+
+
+

+ + +

+
+
+
+ +
Unmodified
+
+
+ +
Simulated protanomaly
+
+
+ +
Simulated deuteranomaly
+
+
+ +
Simulated tritanomaly
+
+
+ +

This is based on the method described in A Physiologically-based Model for Simulation of Color Vision Deficiency.

+

To simulate a colour vision deficiency, the (r,g,b) vector for each pixel is multiplied by a matrix representing the eye's response in each kind of colour receptor.

+
+ + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..e0cb4db --- /dev/null +++ b/script.js @@ -0,0 +1,98 @@ +const img = document.querySelector('img'); + +function matmul(m,v) { + return m.map(r => r[0]*v[0] + r[1]*v[1] + r[2]*v[2]); +} + +/* From https://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html#Tutorial + */ +const protan = [ + [0.152286 , 1.052583 , -0.204868], + [0.114503 , 0.786281 , 0.099216], + [-0.003882 , -0.048116 , 1.051998], + ]; + +const deuteran = [ + [0.367322 , 0.860646 , -0.227968], + [0.280085 , 0.672501 , 0.047413], + [-0.011820 , 0.042940 , 0.968881], +] + +const tritan = [ + [1.255528 , -0.076749 , -0.178779], + [-0.078411 , 0.930809 , 0.147602], + [0.004733 , 0.691367 , 0.303900], +]; + +async function filter(canvas, matrix) { + const {naturalWidth, naturalHeight} = img; + const width = Math.min(2*img.width, naturalWidth); + const height = naturalHeight/naturalWidth * width; + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img,0,0,width,height); + const {data} = ctx.getImageData(0,0,width,height); + for(let i=0;i { + const canvas = document.getElementById(k).querySelector('canvas'); + const blob = await filter(canvas, m); + sim_img.src = URL.createObjectURL(blob); + }) +} + +if(img.complete) { + go(); +} else { + img.addEventListener('load', go, {once:true}); +} + +document.body.addEventListener('dragover', e => { + e.preventDefault(); + e.stopPropagation(); +}) + +function load_file(file) { + img.src = URL.createObjectURL(file); + img.addEventListener('load', go, {once:true}); +} + +document.body.addEventListener('drop', e => { + e.preventDefault(); + e.stopPropagation(); + + const [file] = e.dataTransfer.files; + if(!file) { + return; + } + load_file(file); +}) + +document.getElementById('file').addEventListener('change', ({target}) => { + const [file] = target.files; + console.log(file); + if(!file) { + return; + } + load_file(file); +}) \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..59f0f13 --- /dev/null +++ b/style.css @@ -0,0 +1,36 @@ +:root { + --spacing: 1em; + + color-scheme: light dark; +} + +body { + font-family: sans-serif; +} + +#grid { + margin: 5svh 0; + display: grid; + grid-template: repeat(2,1fr) / repeat(2,1fr); + width: 90svw; + height: 90svh; + gap: var(--spacing); +} + +figure { + max-height: 100%; + display: grid; + overflow: hidden; + grid-template-rows: 1fr min-content; + margin: 0; +} + +figcaption { + text-align: center; +} + +img, canvas { + width: 100%; + height: 100%; + object-fit: cover; +} \ No newline at end of file