Levels & LevelsAuto

This commit is contained in:
Michal Kolář
2024-04-17 00:30:14 +02:00
committed by Stanislav Fifik
parent 66478060b1
commit 03512d3cce
3 changed files with 200 additions and 0 deletions

113
src/nodes/WebGL/Levels.ts Normal file
View File

@@ -0,0 +1,113 @@
import WebGL from './WebGl';
import {
ImageVar,
NumberVar,
} from '../io/AbstractIOSet';
import {canvasPool2D} from "graphfx/src/canvas/CanvasPool";
import {Variables} from "graphfx/src/nodes/io/AbstractIOSet";
import {mediaSize} from "../canvas";
const inputs = {
image: {
type: 'Image'
} as ImageVar,
shadow: {
type: 'Number',
min: 0,
max: 255,
default: 0
} as NumberVar,
gamma: {
type: 'Number',
min: 0.1,
max: 9.9,
default: 1,
step: 0.1,
} as NumberVar,
highlight: {
type: 'Number',
min: 0,
max: 255,
default: 255
} as NumberVar,
dark: {
type: 'Number',
min: 0,
max: 255,
default: 0
} as NumberVar,
light: {
type: 'Number',
min: 0,
max: 255,
default: 255
} as NumberVar,
}
export abstract class LevelsAbstract<INPUTS extends Variables> extends WebGL<INPUTS> {
protected constructor(name: string, inputs: INPUTS) {
super(name, inputs);
}
get frag() {
return `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_resolution;
uniform float u_shadow;
uniform float u_highlight;
uniform float u_gamma;
uniform float u_dark;
uniform float u_light;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
uv.y = 1.0 - uv.y; // flip the y-coordinate
vec4 color = texture2D(u_image, uv);
color.rgb = (color.rgb - vec3(u_shadow / 255.0)) * (1.0 / ((u_highlight - u_shadow) / 255.0));
color.rgb = (color.rgb - 0.5) * u_gamma + 0.5;
color.rgb = color.rgb * ((u_light - u_dark) / 255.0) + vec3(u_dark / 255.0);
color.rgb = clamp(color.rgb, 0.0, 1.0);
gl_FragColor = color;
}
`;
}
_setParams(gl: WebGLRenderingContext, program: WebGLProgram) {
const image = this.in.image.value;
// @ts-ignore
const {width, height} = mediaSize(image)
const canvas = canvasPool2D.createCanvas();
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
context.drawImage(image as CanvasImageSource | OffscreenCanvas, 0, 0);
this._setParamsWithCanvas(gl, program, canvas);
}
_setParamsWithCanvas(gl: WebGLRenderingContext, program: WebGLProgram, canvas: OffscreenCanvas | HTMLCanvasElement) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
gl.uniform1i(gl.getUniformLocation(program, 'u_image'), 0);
gl.uniform2f(gl.getUniformLocation(program, 'u_resolution'), canvas.width, canvas.height);
}
}
export default class Levels extends LevelsAbstract<typeof inputs> {
constructor() {
super('Levels', inputs)
}
_setParamsWithCanvas(gl: WebGLRenderingContext, program: WebGLProgram, canvas: OffscreenCanvas | HTMLCanvasElement) {
super._setParamsWithCanvas(gl, program, canvas);
gl.uniform1f(gl.getUniformLocation(program, 'u_shadow'), this.in.shadow.value);
gl.uniform1f(gl.getUniformLocation(program, 'u_highlight'), this.in.highlight.value);
gl.uniform1f(gl.getUniformLocation(program, 'u_gamma'), this.in.gamma.value);
gl.uniform1f(gl.getUniformLocation(program, 'u_dark'), this.in.dark.value);
gl.uniform1f(gl.getUniformLocation(program, 'u_light'), this.in.light.value);
}
}

View File

@@ -0,0 +1,85 @@
import {LevelsAbstract} from './Levels';
import {
ImageVar,
} from '../io/AbstractIOSet';
const inputs = {
image: {
type: 'Image'
} as ImageVar,
}
export default class LevelsAuto extends LevelsAbstract<typeof inputs> {
constructor() {
super('LevelsAuto', inputs)
}
_setParamsWithCanvas(gl: WebGLRenderingContext, program: WebGLProgram, canvas: HTMLCanvasElement) {
const context = canvas.getContext('2d');
const imageData = context.getImageData(0, 0, canvas.width, canvas.height).data;
const histogram = this.createHistogram(imageData);
const threshold = this.calculateThreshold(histogram, imageData);
// Find shadow and highlight
let shadow = histogram.findIndex(value => value > threshold);
let highlight = 255 - histogram.slice().reverse().findIndex(value => value > threshold);
super._setParamsWithCanvas(gl, program, canvas);
gl.uniform1f(gl.getUniformLocation(program, 'u_shadow'), shadow);
gl.uniform1f(gl.getUniformLocation(program, 'u_highlight'), highlight);
gl.uniform1f(gl.getUniformLocation(program, 'u_gamma'), 1);
gl.uniform1f(gl.getUniformLocation(program, 'u_dark'), 0);
gl.uniform1f(gl.getUniformLocation(program, 'u_light'), 255);
}
private createHistogram(imageData: Uint8ClampedArray): number[] {
const histogram = new Array(256).fill(0);
for (let i = 0; i < imageData.length; i += 4) {
if (imageData[i + 3] > 0) { // Check if the pixel is not transparent
let brightness = Math.round(0.299 * imageData[i] + 0.587 * imageData[i + 1] + 0.114 * imageData[i + 2]);
histogram[brightness]++;
}
}
return histogram;
}
private calculateThreshold(histogram: number[], imageData: Uint8ClampedArray) {
let total = imageData.length / 4; // total number of pixels
let sum = 0;
for (let i = 0; i < 256; i++) {
sum += i * histogram[i];
}
let sumB = 0;
let wB = 0;
let wF = 0;
let varMax = 0;
let threshold = 0;
for (let i = 0; i < 256; i++) {
wB += histogram[i]; // Weight Background
if (wB === 0) continue;
wF = total - wB; // Weight Foreground
if (wF === 0) break;
sumB += i * histogram[i];
let mB = sumB / wB; // Mean Background
let mF = (sum - sumB) / wF; // Mean Foreground
// Calculate Between Class Variance
let varBetween = wB * wF * (mB - mF) * (mB - mF);
// Check if new maximum found
if (varBetween > varMax) {
varMax = varBetween;
threshold = i;
}
}
return threshold;
}
}

View File

@@ -20,6 +20,8 @@ export {default as Blur} from './WebGL/Blur';
export {default as SoftEdge} from './WebGL/SoftEdge'; export {default as SoftEdge} from './WebGL/SoftEdge';
export {default as Edge} from './WebGL/Edge'; export {default as Edge} from './WebGL/Edge';
export {default as KernelPreset} from './WebGL/KernelPreset'; export {default as KernelPreset} from './WebGL/KernelPreset';
export {default as Levels} from './WebGL/Levels';
export {default as LevelsAuto} from './WebGL/LevelsAuto';
// Math // Math
export {default as Numbers} from './Math/Numbers'; export {default as Numbers} from './Math/Numbers';
export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation'; export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation';