import { Shader } from "./Shader";
/**
* Fragment Shader Details
*
* The shader performs these steps:
* 1. Centers color values around 127
* 2. Applies rotation matrix transformation
* 3. Normalizes results using min/max values
* 4. Outputs transformed and normalized color
*
* Uniforms:
* @property {mat4} rotation - Color transformation matrix
* @property {vec3} min - Minimum RGB values for normalization
* @property {vec3} max - Maximum RGB values for normalization
* @property {sampler2D} image - Input image texture
*/
/**
* ShaderDStretch implements the GPU-accelerated portion of the DStretch algorithm.
* Handles color space transformation and normalization for image enhancement.
*
* Features:
* - Real-time color space transformation
* - Dynamic range computation
* - Euler angle-based rotation control
* - Automatic statistics computation
* - WebGL 2.0+
*
* Technical Implementation:
* - Uses 4x4 matrices for color space transformation
* - Implements color normalization based on sample statistics
* - Supports dynamic update of transformation parameters
* - Handles WebGL uniform management
* @extends Shader
*/
class ShaderDstretch extends Shader {
/**
* Creates a new ShaderDStretch instance
* @param {Object} [options] - Configuration options (inherited from Shader)
*/
constructor(options) {
super(options);
}
/**
* Initializes the shader with configuration data
* @param {ShaderDStretch~Config} json - Configuration data
* @property {Array<Array<number>>} json.samples - Color samples for statistics
*/
init(json) {
this.rotationMatrix = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
// Store samples, compute min / max on the fly
this.samples = json["samples"];
this.samplers.push({ id: 0, name: 'image', type: 'vec3' });
this.setMinMax();
}
/**
* Computes and updates color range statistics
* Calculates min/max values in transformed color space
* @private
*/
setMinMax() {
if (this.samples == undefined)
return;
let min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
for (let i = 0; i < this.samples.length; i++) {
let transformedSample = this.transformSample(this.matToArray(this.transpose(this.rotationMatrix)),
this.transformSample(
this.matToArray(this.rotationMatrix),
this.samples[i].concat(1)));
for (let j = 0; j < 3; j++) {
if (transformedSample[j] < min[j])
min[j] = transformedSample[j];
if (transformedSample[j] > max[j])
max[j] = transformedSample[j];
}
}
this.min = min;
this.max = max;
this.uniforms = {
rotation: { type: 'mat4', needsUpdate: true, size: 16, value: this.matToArray(this.rotationMatrix) },
min: { type: 'vec3', needsUpdate: true, size: 3, value: this.min },
max: { type: 'vec3', needsUpdate: true, size: 3, value: this.max }
}
}
/**
* Transposes a 4x4 matrix
* @param {Array<Array<number>>} mat - Input matrix
* @returns {Array<Array<number>>} Transposed matrix
* @private
*/
transpose(mat) {
let ret = [];
for (let i = 0; i < 4; i++) {
let arr = [];
for (let j = 0; j < 4; j++)
arr.push(mat[j][i]);
ret.push(arr);
}
return ret;
}
/**
* Updates the color transformation matrix based on Euler angles
* @param {number[]} eulerRotation - Rotation angles [x,y,z] in radians
*/
updateRotationMatrix(eulerRotation) {
let x = [[1, 0, 0, 0],
[0, Math.cos(eulerRotation[0]), -Math.sin(eulerRotation[0]), 0],
[0, Math.sin(eulerRotation[0]), Math.cos(eulerRotation[0]), 0],
[0, 0, 0, 1]];
let y = [
[Math.cos(eulerRotation[1]), 0, Math.sin(eulerRotation[1]), 0],
[0, 1, 0, 0],
[Math.sin(eulerRotation[1]), 0, Math.cos(eulerRotation[1]), 0],
[0, 0, 0, 1]];
let z = [
[Math.cos(eulerRotation[2]), -Math.sin(eulerRotation[2]), 0, 0],
[Math.sin(eulerRotation[2]), Math.cos(eulerRotation[2]), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
let mat = this.multiplyMatrices(y, x);
mat = this.multiplyMatrices(z, mat);
this.rotationMatrix = mat;
this.setMinMax();
}
/**
* Multiplies two 4x4 matrices
* @param {Array<Array<number>>} mat1 - First matrix
* @param {Array<Array<number>>} mat2 - Second matrix
* @returns {Array<Array<number>>} Result matrix
* @private
*/
multiplyMatrices(mat1, mat2) {
var res = [];
let i, j, k;
for (i = 0; i < 4; i++) {
res[i] = [];
for (j = 0; j < 4; j++) {
res[i][j] = 0;
for (k = 0; k < 4; k++) {
res[i][j] += mat1[i][k] * mat2[k][j];
}
}
}
return res;
}
/**
* Converts a 4x4 matrix to flat array format for WebGL
* @param {Array<Array<number>>} mat - Input matrix
* @returns {number[]} Flat array of matrix values
* @private
*/
matToArray(mat) {
let arr = [];
for (let i = 0; i < 4; i++)
arr = arr.concat(mat[i]);
return arr;
}
/**
* Transforms a color sample using 4x4 matrix
* @param {number[]} matrix - Transformation matrix in flat array format
* @param {number[]} point - Color point as [r,g,b,1]
* @returns {number[]} Transformed color point
* @private
*/
transformSample(matrix, point) {
let c0r0 = matrix[0], c1r0 = matrix[1], c2r0 = matrix[2], c3r0 = matrix[3];
let c0r1 = matrix[4], c1r1 = matrix[5], c2r1 = matrix[6], c3r1 = matrix[7];
let c0r2 = matrix[8], c1r2 = matrix[9], c2r2 = matrix[10], c3r2 = matrix[11];
let c0r3 = matrix[12], c1r3 = matrix[13], c2r3 = matrix[14], c3r3 = matrix[15];
let x = point[0] - 127, y = point[1] - 127, z = point[2] - 127, w = point[3];
let resultX = (x * c0r0) + (y * c0r1) + (z * c0r2) + (w * c0r3);
let resultY = (x * c1r0) + (y * c1r1) + (z * c1r2) + (w * c1r3);
let resultZ = (x * c2r0) + (y * c2r1) + (z * c2r2) + (w * c2r3);
let resultW = (x * c3r0) + (y * c3r1) + (z * c3r2) + (w * c3r3);
return [resultX + 127, resultY + 127, resultZ + 127, resultW];
}
/**
* Generates fragment shader source code
* Implements DStretch color transformation and normalization
* @param {WebGLRenderingContext} gl - WebGL context
* @returns {string} Fragment shader source code
* @private
*/
fragShaderSrc(gl) {
let gl2 = !(gl instanceof WebGLRenderingContext);
let str = `
${gl2 ? 'in' : 'varying'} vec2 v_texcoord;
uniform mat4 rotation;
uniform vec3 min;
uniform vec3 max;
uniform sampler2D image;
vec4 data() {
vec3 ret = vec3(127.0, 127.0, 127.0) + (transpose(rotation) * (rotation * 255.0 * (texture(image, v_texcoord) - vec4(0.5, 0.5, 0.5, 0.0)))).xyz;
ret = (ret - min) / (max - min);
return vec4(ret, 1.0);
}`;
return str;
}
}
export { ShaderDstretch }