You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, amplitudeStr = "0.5", frequencyCountStr = "32", effectColorStr = "255,0,0", direction = "horizontal", effectType = "tint", smoothnessStr = "0.2") {
// Helper: Clamp number
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
// Helper: Lerp (Linear Interpolation)
function lerp(a, b, t) {
return a * (1 - t) + b * t;
}
// Helper: Parse color string (e.g., "#RRGGBB", "rgb(r,g,b)", "r,g,b")
function parseColor(colorStr) {
const defaultErrorColor = { r: 255, g: 0, b: 0 }; // Default to red on error
if (typeof colorStr !== 'string') {
return defaultErrorColor;
}
let str = colorStr.replace(/\s+/g, '').toLowerCase();
let r, g, b;
if (str.startsWith('#')) {
if (str.length === 4) { // #RGB format
r = parseInt(str[1] + str[1], 16);
g = parseInt(str[2] + str[2], 16);
b = parseInt(str[3] + str[3], 16);
} else if (str.length === 7) { // #RRGGBB format
r = parseInt(str.substring(1, 3), 16);
g = parseInt(str.substring(3, 5), 16);
b = parseInt(str.substring(5, 7), 16);
} else {
return defaultErrorColor; // Invalid hex format
}
} else if (str.startsWith('rgb(') && str.endsWith(')')) {
const parts = str.substring(4, str.length - 1).split(',');
if (parts.length === 3) {
r = parseInt(parts[0], 10);
g = parseInt(parts[1], 10);
b = parseInt(parts[2], 10);
} else {
return defaultErrorColor; // Invalid rgb() format
}
} else if (str.includes(',')) {
const parts = str.split(',');
if (parts.length === 3) {
r = parseInt(parts[0], 10);
g = parseInt(parts[1], 10);
b = parseInt(parts[2], 10);
} else {
return defaultErrorColor; // Invalid r,g,b format
}
} else {
return defaultErrorColor; // Unsupported format
}
if (isNaN(r) || isNaN(g) || isNaN(b) || r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
return defaultErrorColor; // Invalid color component values
}
return { r, g, b };
}
// 1. Parse and validate parameters
let amplitude = parseFloat(amplitudeStr);
if (isNaN(amplitude)) amplitude = 0.5;
let frequencyCount = parseInt(frequencyCountStr, 10);
if (isNaN(frequencyCount) || frequencyCount <= 0) {
frequencyCount = 32;
}
frequencyCount = Math.min(frequencyCount, 1024); // Cap frequency count for performance
const parsedColor = parseColor(effectColorStr);
if (direction !== 'horizontal' && direction !== 'vertical') {
direction = 'horizontal'; // Default direction
}
if (!['brightness', 'tint', 'color_filter'].includes(effectType)) {
effectType = 'tint'; // Default effect type
}
let smoothness = parseFloat(smoothnessStr);
if (isNaN(smoothness)) smoothness = 0.2;
smoothness = clamp(smoothness, 0, 1);
// 2. Canvas setup
const canvas = document.createElement('canvas');
const iw = originalImg.naturalWidth || originalImg.width;
const ih = originalImg.naturalHeight || originalImg.height;
if (!iw || !ih || iw === 0 || ih === 0) {
console.warn("Image has invalid dimensions or is not loaded. Returning a 1x1 canvas.");
canvas.width = 1;
canvas.height = 1;
const ctxFallback = canvas.getContext('2d');
if (ctxFallback) {
ctxFallback.fillStyle = 'gray';
ctxFallback.fillRect(0,0,1,1);
}
return canvas;
}
canvas.width = iw;
canvas.height = ih;
const ctx = canvas.getContext('2d');
if (!ctx) { // Should not happen in modern browsers
console.error("Could not get 2D context from canvas.");
return canvas; // Return empty canvas
}
ctx.drawImage(originalImg, 0, 0, iw, ih);
let imageData;
try {
imageData = ctx.getImageData(0, 0, iw, ih);
} catch (e) {
// This can happen due to tainted canvas (e.g. cross-origin image without CORS)
console.error("Could not get image data (possibly tainted canvas):", e);
// Draw a simple message on the canvas indicating the error
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0,0,iw,ih);
ctx.fillStyle = "white";
ctx.font = "16px Arial";
ctx.textAlign = "center";
ctx.fillText("Error: Could not process image (security/CORS issue).", iw/2, ih/2);
return canvas;
}
const data = imageData.data;
// 3. Generate raw spectrum values (0-1)
const rawSpectrumValues = [];
if (frequencyCount > 0) {
for (let i = 0; i < frequencyCount; i++) {
rawSpectrumValues.push(Math.random());
}
} else { // Should have been caught by validation, but as a safe guard
return canvas; // No effect if no frequencies
}
// 4. Smooth spectrum values
let spectrumValues = [...rawSpectrumValues];
if (smoothness > 0 && frequencyCount > 1) {
const smoothed = [];
// Window size for smoothing, at least 1
const windowSize = Math.max(1, Math.floor(frequencyCount * smoothness));
const halfWindow = Math.floor(windowSize / 2);
for (let i = 0; i < frequencyCount; i++) {
let sum = 0;
let numInWindow = 0;
for (let j = 0; j < windowSize; j++) {
let idx = i + j - halfWindow;
// Apply effect at edges by clamping index
idx = clamp(idx, 0, frequencyCount - 1);
sum += rawSpectrumValues[idx];
numInWindow++;
}
smoothed[i] = (numInWindow > 0) ? sum / numInWindow : rawSpectrumValues[i];
}
spectrumValues = smoothed;
}
// 5. Loop through pixels and apply effect
const len = data.length;
const w = canvas.width;
const h = canvas.height;
for (let i = 0; i < len; i += 4) {
const rOrig = data[i];
const gOrig = data[i+1];
const bOrig = data[i+2];
const x = (i / 4) % w;
const y = Math.floor((i / 4) / w);
let bandIndex;
if (direction === 'horizontal') { // Spectrum varies along X, bars are vertical
bandIndex = Math.floor((x / w) * frequencyCount);
} else { // direction === 'vertical', Spectrum varies along Y, bars are horizontal
bandIndex = Math.floor((y / h) * frequencyCount);
}
bandIndex = clamp(bandIndex, 0, frequencyCount - 1);
const currentSpectrumValue = spectrumValues[bandIndex]; // Should always be valid (0-1)
let newR = rOrig, newG = gOrig, newB = bOrig;
if (effectType === 'brightness') {
// factor ranges from (1 - amplitude) to (1 + amplitude) if spectrumValue is 0 to 1
const factor = 1 + (currentSpectrumValue - 0.5) * 2 * amplitude;
newR = rOrig * factor;
newG = gOrig * factor;
newB = bOrig * factor;
} else if (effectType === 'tint') {
// mix ranges 0 to 1, controlled by spectrum value and amplitude
const mix = clamp(currentSpectrumValue * amplitude, 0, 1);
newR = lerp(rOrig, parsedColor.r, mix);
newG = lerp(gOrig, parsedColor.g, mix);
newB = lerp(bOrig, parsedColor.b, mix);
} else if (effectType === 'color_filter') {
// t ranges 0 to 1, determines how much of parsedColor (normalized) to multiply by
const t = clamp(currentSpectrumValue * amplitude, 0, 1);
newR = rOrig * lerp(1, parsedColor.r / 255, t);
newG = gOrig * lerp(1, parsedColor.g / 255, t);
newB = bOrig * lerp(1, parsedColor.b / 255, t);
}
data[i] = clamp(newR, 0, 255);
data[i+1] = clamp(newG, 0, 255);
data[i+2] = clamp(newB, 0, 255);
// data[i+3] (alpha) remains unchanged
}
// 6. Put image data back and return canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes