Experiment with WebGL image filtering

This commit is contained in:
Jordan Eldredge 2021-07-06 18:44:53 -07:00
parent 7b69ff2b02
commit 710041ff34
2 changed files with 245 additions and 1 deletions

View file

@ -1,4 +1,5 @@
import { clamp, normalizeDomId, num, toBool } from "../utils";
import { clamp, normalizeDomId, num } from "../utils";
import { glTransformImage } from "./GammaWebGL";
// https://www.pawelporwisz.pl/winamp/wct_en.php
export default class GammaGroup {
@ -47,6 +48,10 @@ export default class GammaGroup {
// TODO: Figure out how to actually implement this.
transformImage(img: HTMLImageElement): string {
// Toggle this to play with gl transforming
if (false) {
return glTransformImage(img);
}
const [r, g, b] = this._value.split(",").map((v) => {
return (Number(v) / 4096) * 255;
});

View file

@ -0,0 +1,239 @@
const VERTEXT_SHADER = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points.
v_texCoord = a_texCoord;
}`;
const FRAGMENT_SHADER = `
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord).rrba;
}`;
const canvas: HTMLCanvasElement = document.createElement("canvas");
if (canvas == null) {
throw new Error("Missing Canvas");
}
const gl = canvas.getContext("webgl2");
if (!gl) {
throw new Error("Missing WebGL2 context");
}
// An attempt at doing some kind of image gamma filtering in WebGL.
// A real solution will need:
// * Reuse gl context between images?
// * Handle multiple gammagroups with the same compiled shader code?
//
// Mostly stolen from https://webglfundamentals.org/webgl/lessons/webgl-image-processing.html
export function glTransformImage(image: HTMLImageElement): string {
canvas.width = image.width;
canvas.height = image.height;
// setup GLSL program
const shaders = [
loadShader(gl, VERTEXT_SHADER, gl.VERTEX_SHADER),
loadShader(gl, FRAGMENT_SHADER, gl.FRAGMENT_SHADER),
];
const program = createProgram(gl, shaders);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
// Create a buffer to put three 2d clip space points in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
setRectangle(gl, 0, 0, image.width, image.height);
// provide texture coordinates for the rectangle.
var texcoordBuffer = gl.createBuffer();
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,
]),
gl.STATIC_DRAW
);
// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation,
size,
type,
normalize,
stride,
offset
);
// Turn on the texcoord attribute
gl.enableVertexAttribArray(texcoordLocation);
// bind the texcoord buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Tell the texcoord attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
texcoordLocation,
size,
type,
normalize,
stride,
offset
);
// set the resolution
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
// Draw the rectangle.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
return (gl.canvas as HTMLCanvasElement).toDataURL();
}
function setRectangle(
gl: WebGL2RenderingContext,
x: number,
y: number,
width: number,
height: number
) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2]),
gl.STATIC_DRAW
);
}
function loadShader(
gl: WebGL2RenderingContext,
shaderSource: string,
shaderType: number
): WebGLShader {
// Create the shader object
const shader = gl.createShader(shaderType);
// Load the shader source
gl.shaderSource(shader, shaderSource);
// Compile the shader
gl.compileShader(shader);
// Check the compile status
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
// Something went wrong during compilation; get the error
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl: WebGL2RenderingContext, shaders: WebGLShader[]) {
const program = gl.createProgram();
shaders.forEach(function (shader) {
gl.attachShader(program, shader);
});
gl.linkProgram(program);
// Check the link status
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
// something went wrong with the link
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}