Levels & LevelsAuto
This commit is contained in:
113
src/nodes/WebGL/Levels.ts
Normal file
113
src/nodes/WebGL/Levels.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
85
src/nodes/WebGL/LevelsAuto.ts
Normal file
85
src/nodes/WebGL/LevelsAuto.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ export {default as Blur} from './WebGL/Blur';
|
||||
export {default as SoftEdge} from './WebGL/SoftEdge';
|
||||
export {default as Edge} from './WebGL/Edge';
|
||||
export {default as KernelPreset} from './WebGL/KernelPreset';
|
||||
export {default as Levels} from './WebGL/Levels';
|
||||
export {default as LevelsAuto} from './WebGL/LevelsAuto';
|
||||
// Math
|
||||
export {default as Numbers} from './Math/Numbers';
|
||||
export {default as NumberBinaryOperation} from './Math/NumberBinaryOperation';
|
||||
|
||||
Reference in New Issue
Block a user