mirror of
https://github.com/webgpu/webgpufundamentals.git
synced 2026-05-16 13:52:31 -04:00
318 lines
8.4 KiB
HTML
318 lines
8.4 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>WebGPU Compute Shaders - Histogram (JavaScript</title>
|
|
<style>
|
|
@import url(resources/webgpu-lesson.css);
|
|
canvas {
|
|
display: block;
|
|
max-width: 256px;
|
|
border: 1px solid #888;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
</body>
|
|
<script type="module">
|
|
import { loadImageBitmap } from '../3rdparty/webgpu-utils.module.js';
|
|
|
|
async function main() {
|
|
const imgBitmap = await loadImageBitmap('resources/images/pexels-francesco-ungaro-96938-mid.jpg'); /* webgpufundamentals: url */
|
|
|
|
const imgData = getImageData(imgBitmap);
|
|
const numBins = 256;
|
|
const histogram = computeHistogram(numBins, imgData);
|
|
|
|
showImageBitmap(imgBitmap);
|
|
|
|
const numEntries = imgData.width * imgData.height;
|
|
drawHistogram(histogram, numEntries);
|
|
|
|
drawLuminance(imgData);
|
|
|
|
const eq = computeEqualization(histogram, numEntries);
|
|
drawEqualized(imgData, eq);
|
|
drawEqualizedColor(imgData, eq);
|
|
}
|
|
|
|
function computeHistogram(numBins, imgData) {
|
|
const {width, height, data} = imgData;
|
|
const bins = new Array(numBins).fill(0);
|
|
for (let y = 0; y < height; ++y) {
|
|
for (let x = 0; x < width; ++x) {
|
|
const offset = (y * width + x) * 4;
|
|
|
|
const r = data[offset + 0] / 255;
|
|
const g = data[offset + 1] / 255;
|
|
const b = data[offset + 2] / 255;
|
|
const v = srgbLuminance(r, g, b);
|
|
|
|
const bin = Math.min(numBins - 1, v * numBins) | 0;
|
|
++bins[bin];
|
|
}
|
|
}
|
|
return bins;
|
|
}
|
|
|
|
function computeEqualization(histogram, numEntries) {
|
|
/*
|
|
for i=1:size(InputIm_normalized_histogram,2)
|
|
normalized_cumulsum=normalized_cumulsum+InputIm_normalized_histogram(i)
|
|
normalized_cumulsumvec(i)= normalized_cumulsum
|
|
output(i)=round(normalized_cumulsumvec(i)*levels) %formula: (L-1)*cdf
|
|
end
|
|
*/
|
|
let sum = 0;
|
|
return histogram.map(v => {
|
|
sum += v / numEntries;
|
|
return Math.round(sum * histogram.length);
|
|
});
|
|
}
|
|
|
|
function drawLuminance(imgData) {
|
|
const newImageData = new ImageData(imgData.width, imgData.height);
|
|
for (let offset = 0; offset < imgData.data.length; offset += 4) {
|
|
const r = imgData.data[offset + 0] / 255;
|
|
const g = imgData.data[offset + 1] / 255;
|
|
const b = imgData.data[offset + 2] / 255;
|
|
const v = srgbLuminance(r, g, b);
|
|
|
|
const newBin = v * 255;
|
|
|
|
newImageData.data[offset + 0] = newBin;
|
|
newImageData.data[offset + 1] = newBin;
|
|
newImageData.data[offset + 2] = newBin;
|
|
newImageData.data[offset + 3] = 255;
|
|
}
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = imgData.width;
|
|
canvas.height = imgData.height;
|
|
document.body.appendChild(canvas);
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.putImageData(newImageData, 0, 0);
|
|
}
|
|
|
|
function drawEqualized(imgData, equalized) {
|
|
const numBins = equalized.length;
|
|
const newImageData = new ImageData(imgData.width, imgData.height);
|
|
for (let offset = 0; offset < imgData.data.length; offset += 4) {
|
|
const r = imgData.data[offset + 0] / 255;
|
|
const g = imgData.data[offset + 1] / 255;
|
|
const b = imgData.data[offset + 2] / 255;
|
|
const v = srgbLuminance(r, g, b);
|
|
|
|
const bin = Math.min(numBins - 1, v * numBins) | 0;
|
|
const newBin = equalized[bin];
|
|
|
|
newImageData.data[offset + 0] = newBin;
|
|
newImageData.data[offset + 1] = newBin;
|
|
newImageData.data[offset + 2] = newBin;
|
|
newImageData.data[offset + 3] = 255;
|
|
}
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = imgData.width;
|
|
canvas.height = imgData.height;
|
|
document.body.appendChild(canvas);
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.putImageData(newImageData, 0, 0);
|
|
}
|
|
|
|
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
|
|
function drawEqualizedColor(imgData, equalized) {
|
|
const numBins = equalized.length;
|
|
const newImageData = new ImageData(imgData.width, imgData.height);
|
|
for (let offset = 0; offset < imgData.data.length; offset += 4) {
|
|
const r = imgData.data[offset + 0] / 255;
|
|
const g = imgData.data[offset + 1] / 255;
|
|
const b = imgData.data[offset + 2] / 255;
|
|
const v = srgbLuminance(r, g, b);
|
|
|
|
const bin = Math.min(numBins - 1, v * numBins) | 0;
|
|
const newV = equalized[bin] / numBins;
|
|
|
|
const [h, s] = rgbToHsl(r, g, b);
|
|
const [newR, newG, newB] = hslToRgb(h, s, newV);
|
|
|
|
//rgb = -gbb * yzz - rrg * xxy + l
|
|
|
|
/*
|
|
l = r * 0.2 + g * 0.7 + b * 0.1
|
|
|
|
0.12 0.34 0.56
|
|
|
|
r = -bz - gy + l
|
|
------------
|
|
x
|
|
|
|
g = -bz - rx + l
|
|
------------
|
|
y
|
|
|
|
b = -gy - rx + l
|
|
____________
|
|
z
|
|
|
|
rgb = -gbb * yzz - rrg * xxy + l
|
|
*/
|
|
//const x = 0.2126;
|
|
//const y = 0.7152;
|
|
//const z = 0.0722;
|
|
//let newR = (-b * z - g * y + newV) / x;
|
|
//let newG = (-b * z - r * x + newV) / y;
|
|
//let newB = (-g * y - r * x + newV) / z;
|
|
|
|
//const l = Math.sqrt(newR * newR + newG * newG + newB * newB);
|
|
//if (l > 1) {
|
|
// newR /= l;
|
|
// newG /= l;
|
|
// newB /= l;
|
|
//}
|
|
|
|
newImageData.data[offset + 0] = clamp(newR, 0, 1) * 255 | 0;
|
|
newImageData.data[offset + 1] = clamp(newG, 0, 1) * 255 | 0;
|
|
newImageData.data[offset + 2] = clamp(newB, 0, 1) * 255 | 0;
|
|
newImageData.data[offset + 3] = 255;
|
|
}
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = imgData.width;
|
|
canvas.height = imgData.height;
|
|
document.body.appendChild(canvas);
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.putImageData(newImageData, 0, 0);
|
|
}
|
|
|
|
function drawHistogram(histogram, numEntries, height = 100) {
|
|
const numBins = histogram.length;
|
|
const max = Math.max(...histogram);
|
|
const scale = Math.max(1 / max);//, 0.2 * numBins / numEntries);
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = numBins;
|
|
canvas.height = height;
|
|
document.body.appendChild(canvas);
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx.fillStyle = '#fff';
|
|
|
|
for (let x = 0; x < numBins; ++x) {
|
|
const v = histogram[x] * scale * height;
|
|
ctx.fillRect(x, height - v, 1, v);
|
|
}
|
|
}
|
|
|
|
// from: https://www.w3.org/WAI/GL/wiki/Relative_luminance
|
|
function srgbLuminance(r, g, b) {
|
|
return rgbToHsl(r, g, b)[2];
|
|
//return r * 0.2126 +
|
|
// g * 0.7152 +
|
|
// b * 0.0722;
|
|
}
|
|
|
|
const euclideanModulo = (x, a) => x - a * Math.floor(x / a);
|
|
|
|
function hslToRgb(hue, sat, light) {
|
|
hue = euclideanModulo(hue, 1);
|
|
const a = sat * Math.min(light, 1 - light);
|
|
|
|
function f(n) {
|
|
const k = (n + hue * 12) % 12;
|
|
return light - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
}
|
|
|
|
return [f(0), f(8), f(4)];
|
|
}
|
|
|
|
/*
|
|
|
|
|
|
*/
|
|
|
|
const t3 = v => v.toFixed(3);
|
|
|
|
for (let i = 0; i < 6; ++i) {
|
|
console.log('hsl:', ...[i / 6, 1, 0.5].map(t3), 'rgb:', ...hslToRgb(i / 6, 1, 0.5).map(t3));
|
|
}
|
|
console.log('--');
|
|
|
|
function rgbToHsl(red, green, blue) {
|
|
const max = Math.max(red, green, blue);
|
|
const min = Math.min(red, green, blue);
|
|
let hue = 0;
|
|
let sat = 0;
|
|
const light = (min + max) / 2;
|
|
const d = max - min;
|
|
|
|
if (d !== 0) {
|
|
sat = (light === 0 || light === 1)
|
|
? 0
|
|
: (max - light) / Math.min(light, 1 - light);
|
|
|
|
switch (max) {
|
|
case red: hue = (green - blue) / d + (green < blue ? 6 : 0); break;
|
|
case green: hue = (blue - red) / d + 2; break;
|
|
case blue: hue = (red - green) / d + 4;
|
|
}
|
|
hue /= 6;
|
|
}
|
|
|
|
return [hue, sat, light];
|
|
}
|
|
|
|
/*
|
|
|
|
fn rgbToHsl(rgb: vec3f) -> vec3f {
|
|
let p = mix(vec4f(rgb.bgr, 2.0 / 3.0), vec4f(rgb.gbr, 1.0 / 3.0), step(rgb.b, rgb.g));
|
|
let q = mix(vec4f(p.xyz, rgb.w), vec4f(rgb.r, p.yz, 0.0), step(p.x, rgb.r));
|
|
let l = (q.x + q.y) * 0.5;
|
|
let d = q.x - q.y;
|
|
let s = select(q.x - l / min(l, 1 - l), 0.0, l > 0.0 && l < 1.0);
|
|
let h = select(
|
|
fract(q.y - q.z) / d + q.w),
|
|
0,
|
|
s > 0.0);
|
|
return vec3f(h, s, l);
|
|
}
|
|
|
|
*/
|
|
|
|
[
|
|
[1, 0, 0],
|
|
[1, 1, 0],
|
|
[0, 1, 0],
|
|
[0, 1, 1],
|
|
[0, 0, 1],
|
|
[1, 0, 1],
|
|
].forEach(rgb => {
|
|
console.log('rgb:', ...rgb.map(t3), 'hsl:', ...rgbToHsl(...rgb).map(t3));
|
|
});
|
|
|
|
function getImageData(imgBitmap) {
|
|
const canvas = document.createElement('canvas');
|
|
|
|
// make the canvas the same size as the image
|
|
canvas.width = imgBitmap.width;
|
|
canvas.height = imgBitmap.height;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.drawImage(imgBitmap, 0, 0);
|
|
return ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
}
|
|
|
|
function showImageBitmap(imageBitmap) {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = imageBitmap.width;
|
|
canvas.height = imageBitmap.height;
|
|
|
|
const bm = canvas.getContext('bitmaprenderer');
|
|
bm.transferFromImageBitmap(imageBitmap);
|
|
document.body.appendChild(canvas);
|
|
}
|
|
|
|
main();
|
|
</script>
|
|
</html>
|