OCR_DATASET_MAKER / web /lib /gpu-augmentation.ts
Omarrran's picture
Performance optimizations: GPU acceleration, parallel processing, pause/resume
599349f
// GPU-accelerated augmentation using WebGL
// Applies brightness, contrast, and noise operations on the GPU
// Check if WebGL is available
let webglAvailable: boolean | null = null
let glContext: WebGLRenderingContext | null = null
let glCanvas: HTMLCanvasElement | null = null
export function isWebGLAvailable(): boolean {
if (webglAvailable !== null) return webglAvailable
try {
const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
webglAvailable = gl !== null
return webglAvailable
} catch {
webglAvailable = false
return false
}
}
// Shader source code
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
`
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_image;
uniform float u_brightness;
uniform float u_contrast;
uniform float u_noiseAmount;
uniform float u_seed;
varying vec2 v_texCoord;
// Simple pseudo-random function
float random(vec2 co) {
return fract(sin(dot(co.xy + u_seed, vec2(12.9898, 78.233))) * 43758.5453);
}
void main() {
vec4 color = texture2D(u_image, v_texCoord);
// Apply brightness (additive)
color.rgb += u_brightness;
// Apply contrast (multiplicative from midpoint)
color.rgb = (color.rgb - 0.5) * u_contrast + 0.5;
// Apply noise
if (u_noiseAmount > 0.0) {
float noise = (random(v_texCoord) - 0.5) * u_noiseAmount;
color.rgb += noise;
}
// Clamp values
color.rgb = clamp(color.rgb, 0.0, 1.0);
gl_FragColor = color;
}
`
// Compile shader
function compileShader(gl: WebGLRenderingContext, source: string, type: number): WebGLShader | null {
const shader = gl.createShader(type)
if (!shader) return null
gl.shaderSource(shader, source)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(shader))
gl.deleteShader(shader)
return null
}
return shader
}
// Create shader program
function createProgram(gl: WebGLRenderingContext): WebGLProgram | null {
const vertexShader = compileShader(gl, vertexShaderSource, gl.VERTEX_SHADER)
const fragmentShader = compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER)
if (!vertexShader || !fragmentShader) return null
const program = gl.createProgram()
if (!program) return null
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program link error:', gl.getProgramInfoLog(program))
gl.deleteProgram(program)
return null
}
return program
}
// Initialize WebGL context and program
let program: WebGLProgram | null = null
let positionBuffer: WebGLBuffer | null = null
let texCoordBuffer: WebGLBuffer | null = null
function initWebGL(width: number, height: number): boolean {
if (glCanvas && glContext && program) {
// Resize if needed
if (glCanvas.width !== width || glCanvas.height !== height) {
glCanvas.width = width
glCanvas.height = height
glContext.viewport(0, 0, width, height)
}
return true
}
try {
glCanvas = document.createElement('canvas')
glCanvas.width = width
glCanvas.height = height
glContext = glCanvas.getContext('webgl', { preserveDrawingBuffer: true }) as WebGLRenderingContext
if (!glContext) {
console.warn('WebGL not available')
return false
}
program = createProgram(glContext)
if (!program) {
console.warn('Failed to create WebGL program')
return false
}
// Create buffers
const gl = glContext
// Position buffer (full screen quad)
positionBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1, 1, -1, -1, 1,
-1, 1, 1, -1, 1, 1
]), gl.STATIC_DRAW)
// Texture coordinate buffer
texCoordBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 0
]), gl.STATIC_DRAW)
return true
} catch (err) {
console.warn('WebGL initialization failed:', err)
return false
}
}
export interface GPUAugmentOptions {
brightness?: number // -1 to 1 (0 = no change)
contrast?: number // 0 to 2 (1 = no change)
noiseAmount?: number // 0 to 1
seed?: number
}
/**
* Apply GPU-accelerated augmentation to an image
* Returns the augmented canvas, or null if WebGL is not available
*/
export function applyGPUAugmentation(
sourceCanvas: HTMLCanvasElement,
options: GPUAugmentOptions
): HTMLCanvasElement | null {
const { brightness = 0, contrast = 1, noiseAmount = 0, seed = Math.random() } = options
// Skip if no augmentation needed
if (brightness === 0 && contrast === 1 && noiseAmount === 0) {
return sourceCanvas
}
if (!initWebGL(sourceCanvas.width, sourceCanvas.height)) {
return null // Fallback to CPU
}
const gl = glContext!
try {
gl.useProgram(program!)
// Create texture from source canvas
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.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sourceCanvas)
// Set up attributes
const positionLocation = gl.getAttribLocation(program!, 'a_position')
const texCoordLocation = gl.getAttribLocation(program!, 'a_texCoord')
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer!)
gl.enableVertexAttribArray(positionLocation)
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer!)
gl.enableVertexAttribArray(texCoordLocation)
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0)
// Set uniforms
gl.uniform1f(gl.getUniformLocation(program!, 'u_brightness'), brightness)
gl.uniform1f(gl.getUniformLocation(program!, 'u_contrast'), contrast)
gl.uniform1f(gl.getUniformLocation(program!, 'u_noiseAmount'), noiseAmount)
gl.uniform1f(gl.getUniformLocation(program!, 'u_seed'), seed)
// Draw
gl.drawArrays(gl.TRIANGLES, 0, 6)
// Clean up texture
gl.deleteTexture(texture)
return glCanvas!
} catch (err) {
console.warn('GPU augmentation failed:', err)
return null
}
}
/**
* Apply GPU augmentation and return the result as ImageData
*/
export function applyGPUAugmentationToImageData(
imageData: ImageData,
options: GPUAugmentOptions
): ImageData | null {
// Create temporary canvas with the image data
const tempCanvas = document.createElement('canvas')
tempCanvas.width = imageData.width
tempCanvas.height = imageData.height
const ctx = tempCanvas.getContext('2d')
if (!ctx) return null
ctx.putImageData(imageData, 0, 0)
const resultCanvas = applyGPUAugmentation(tempCanvas, options)
if (!resultCanvas) return null
const resultCtx = resultCanvas.getContext('2d') || resultCanvas.getContext('webgl')
// For WebGL canvas, we need to read pixels differently
if (glContext) {
const pixels = new Uint8Array(imageData.width * imageData.height * 4)
glContext.readPixels(0, 0, imageData.width, imageData.height, glContext.RGBA, glContext.UNSIGNED_BYTE, pixels)
// Flip Y axis (WebGL has origin at bottom-left)
const flipped = new Uint8ClampedArray(pixels.length)
const rowSize = imageData.width * 4
for (let y = 0; y < imageData.height; y++) {
const srcRow = (imageData.height - 1 - y) * rowSize
const dstRow = y * rowSize
flipped.set(pixels.subarray(srcRow, srcRow + rowSize), dstRow)
}
return new ImageData(flipped, imageData.width, imageData.height)
}
return null
}
/**
* Clean up WebGL resources
*/
export function cleanupGPU() {
if (glContext && program) {
glContext.deleteProgram(program)
if (positionBuffer) glContext.deleteBuffer(positionBuffer)
if (texCoordBuffer) glContext.deleteBuffer(texCoordBuffer)
}
program = null
positionBuffer = null
texCoordBuffer = null
glContext = null
glCanvas = null
}