You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, streakLength = 10, brightnessThreshold = 128, direction = "horizontal", varyByBrightnessStr = "false") {
// Validate and normalize parameters
streakLength = Math.max(1, Math.floor(Number(streakLength)));
brightnessThreshold = Math.max(0, Math.min(255, Number(brightnessThreshold)));
const _varyByBrightness = String(varyByBrightnessStr).toLowerCase() === "true";
const canvas = document.createElement('canvas');
// Using { willReadFrequently: true } can be an optimization hint for canvas contexts
// where getImageData/putImageData is used often.
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (width === 0 || height === 0) {
console.error("Image has zero width or height. Cannot process.");
const errCanvas = document.createElement('canvas');
errCanvas.width = Math.max(150, width || 150); // Ensure a minimum displayable size
errCanvas.height = Math.max(50, height || 50);
const errCtx = errCanvas.getContext('2d');
errCtx.fillStyle = 'lightgray';
errCtx.fillRect(0, 0, errCanvas.width, errCanvas.height);
errCtx.fillStyle = 'red';
errCtx.textAlign = 'center';
errCtx.textBaseline = 'middle';
errCtx.font = '12px Arial';
errCtx.fillText('Error: Image dimensions are zero.', errCanvas.width / 2, errCanvas.height / 2);
return errCanvas;
}
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, width, height);
let sourceImageData;
try {
sourceImageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Error getting ImageData (e.g., cross-origin issue):", e);
ctx.clearRect(0, 0, width, height); // Clear previous drawImage
ctx.fillStyle = 'lightcoral';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '14px Arial';
ctx.fillText('Error: Could not process image data.', width / 2, height / 2 - 10);
ctx.font = '12px Arial';
ctx.fillText('(Possibly due to cross-origin restrictions)', width / 2, height / 2 + 10);
return canvas;
}
const sourceData = sourceImageData.data;
// Create a new ImageData for the output, initialized with a copy of the source data.
// This ensures non-streaked pixels and original alpha values are preserved.
const outputDataArray = new Uint8ClampedArray(sourceData);
const outputImageData = new ImageData(outputDataArray, width, height);
const outputData = outputImageData.data; // This is the pixel array we'll modify
if (direction === "horizontal") {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const R_idx = (y * width + x) * 4;
const G_idx = R_idx + 1;
const B_idx = R_idx + 2;
const A_idx = R_idx + 3;
// Use sourceData for original pixel values
const R = sourceData[R_idx];
const G = sourceData[G_idx];
const B = sourceData[B_idx];
const A = sourceData[A_idx]; // Alpha of the source pixel
// Calculate brightness using luminance formula for better perceptual accuracy
const brightness = 0.299 * R + 0.587 * G + 0.114 * B;
if (brightness > brightnessThreshold) {
let currentAppliedStreakLength = streakLength;
if (_varyByBrightness) {
// Scale streak length by brightness: brighter pixels streak further
currentAppliedStreakLength = Math.max(1, Math.floor((brightness / 255.0) * streakLength));
}
// Extend the color of the current bright pixel
// k=0 is the source pixel itself, which is already set in outputData by the initial copy.
// Streaking starts from k=1 (the pixel next to the source).
for (let k = 1; k < currentAppliedStreakLength; k++) {
const streakX = x + k;
if (streakX < width) { // Check bounds
const targetIdx = (y * width + streakX) * 4;
outputData[targetIdx] = R;
outputData[targetIdx + 1] = G;
outputData[targetIdx + 2] = B;
outputData[targetIdx + 3] = A; // Propagate source pixel's alpha
} else {
break; // Streak goes out of image bounds
}
}
}
}
}
} else if (direction === "vertical") {
for (let x = 0; x < width; x++) { // Outer loop: columns
for (let y = 0; y < height; y++) { // Inner loop: rows in current column
const R_idx = (y * width + x) * 4;
const G_idx = R_idx + 1;
const B_idx = R_idx + 2;
const A_idx = R_idx + 3;
const R = sourceData[R_idx];
const G = sourceData[G_idx];
const B = sourceData[B_idx];
const A = sourceData[A_idx];
const brightness = 0.299 * R + 0.587 * G + 0.114 * B;
if (brightness > brightnessThreshold) {
let currentAppliedStreakLength = streakLength;
if (_varyByBrightness) {
currentAppliedStreakLength = Math.max(1, Math.floor((brightness / 255.0) * streakLength));
}
for (let k = 1; k < currentAppliedStreakLength; k++) {
const streakY = y + k;
if (streakY < height) { // Check bounds
const targetIdx = (streakY * width + x) * 4;
outputData[targetIdx] = R;
outputData[targetIdx + 1] = G;
outputData[targetIdx + 2] = B;
outputData[targetIdx + 3] = A;
} else {
break;
}
}
}
}
}
} else {
console.warn(`Invalid direction: "${direction}". Must be "horizontal" or "vertical". No filter applied, returning original image principles.`);
// Since outputData was initialized as a copy of sourceData, doing nothing here
// means the original image (as drawn on canvas and copied to outputImageData) will be returned.
}
// Put the modified pixel data back onto the canvas
ctx.putImageData(outputImageData, 0, 0);
return canvas;
}
Apply Changes