mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 10:15:31 +00:00
Experiment with WebGL image filtering
This commit is contained in:
parent
7b69ff2b02
commit
710041ff34
2 changed files with 245 additions and 1 deletions
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
239
packages/webamp-modern-2/src/skin/GammaWebGL.ts
Normal file
239
packages/webamp-modern-2/src/skin/GammaWebGL.ts
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue