You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, directionAngle = "45", trailLength = "60", trailDensity = "2", heatmapOpacity = "0.75", sensitivity = "60") {
// 1. Parse and sanitize input parameters
const angleRad = parseFloat(directionAngle) * Math.PI / 180;
const length = Math.max(1, parseInt(trailLength, 10)); // Number of accumulation steps
const stepSize = Math.max(0.1, parseFloat(trailDensity)); // Distance per step
const opacity = Math.max(0.0, Math.min(1.0, parseFloat(heatmapOpacity)));
// Sensitivity maps to difference threshold (0 to 100+). Higher sensitivity = lower threshold.
const sensPcl = Math.max(0, Math.min(100, parseFloat(sensitivity)));
const thresholdVal = 100 - sensPcl;
// Retrieve image dimensions
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
// Set up the main output canvas
const mainCanvas = document.createElement('canvas');
mainCanvas.width = width;
mainCanvas.height = height;
const ctx = mainCanvas.getContext('2d');
// Draw the original image as base
ctx.drawImage(originalImg, 0, 0);
// 2. Create an "Activity Map" based on luminance contrast to simulate prominent features
const threshCanvas = document.createElement('canvas');
threshCanvas.width = width;
threshCanvas.height = height;
const tCtx = threshCanvas.getContext('2d');
tCtx.drawImage(originalImg, 0, 0);
const imgData = tCtx.getImageData(0, 0, width, height);
const data = imgData.data;
// Calculate Average Luma to find relative prominent features
let sumLuma = 0;
const numPixels = width * height;
for (let i = 0; i < data.length; i += 4) {
sumLuma += 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
}
const avgLuma = sumLuma / numPixels;
// Apply brightness thresholding
const activeRange = 255.0 - thresholdVal;
for (let i = 0; i < data.length; i += 4) {
const luma = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
const diff = Math.abs(luma - avgLuma);
// If a pixel stands out from the average, record it in the activity map's alpha
if (diff > thresholdVal && activeRange > 0) {
const intensity = Math.min(1.0, (diff - thresholdVal) / activeRange);
data[i] = 255;
data[i+1] = 255;
data[i+2] = 255;
data[i+3] = Math.floor(intensity * 255);
} else {
data[i+3] = 0; // Transparent
}
}
tCtx.putImageData(imgData, 0, 0);
// 3. Accumulate "Activity" in a directional smear (motion blur approach)
const accCanvas = document.createElement('canvas');
accCanvas.width = width;
accCanvas.height = height;
const accCtx = accCanvas.getContext('2d');
const dx = Math.cos(angleRad) * stepSize;
const dy = Math.sin(angleRad) * stepSize;
// Additive blending for accumulation logic
accCtx.globalCompositeOperation = 'lighter';
// Tune alpha based on trail length so accumulation heats up properly without blowing out instantly
accCtx.globalAlpha = Math.max(0.01, 3.5 / length);
// Smear in the reverse direction of the angle to simulate forward motion
for (let i = 0; i <= length; i++) {
accCtx.drawImage(threshCanvas, -i * dx, -i * dy);
}
// 4. Map the accumulated directional intensities into a thermal Heatmap gradient
const accImgData = accCtx.getImageData(0, 0, width, height);
const aData = accImgData.data;
const overlayData = new ImageData(width, height);
const oData = overlayData.data;
// Standard Thermal/Jet stops
const stops = [
[0, 0, 0, 0], // 0.0 - Transparent base
[0, 0, 255, 120], // 0.16 - Deep Blue
[0, 255, 255, 200], // 0.33 - Cyan
[0, 255, 0, 255], // 0.50 - Green
[255, 255, 0, 255], // 0.66 - Yellow
[255, 0, 0, 255], // 0.83 - Red
[255, 255, 255, 255] // 1.00 - White Hot Core
];
const numStops = stops.length - 1;
for (let i = 0; i < aData.length; i += 4) {
// Evaluate heat from the greyscale accumulation (R channel suffices)
const t = Math.min(1.0, aData[i] / 255.0);
if (t <= 0.01) {
oData[i] = 0; oData[i+1] = 0; oData[i+2] = 0; oData[i+3] = 0;
continue;
}
const scaledT = t * numStops;
const idx = Math.floor(scaledT);
if (idx >= numStops) {
const endCoord = stops[numStops];
oData[i] = endCoord[0]; oData[i+1] = endCoord[1]; oData[i+2] = endCoord[2]; oData[i+3] = endCoord[3];
} else {
const frac = scaledT - idx;
const c1 = stops[idx];
const c2 = stops[idx + 1];
// Interpolate colors to create smooth temperature gradients
oData[i] = c1[0] + (c2[0] - c1[0]) * frac;
oData[i+1] = c1[1] + (c2[1] - c1[1]) * frac;
oData[i+2] = c1[2] + (c2[2] - c1[2]) * frac;
oData[i+3] = c1[3] + (c2[3] - c1[3]) * frac;
}
}
// 5. Apply the heatmap overlay symmetrically atop the base layer
const overlayCanvas = document.createElement('canvas');
overlayCanvas.width = width;
overlayCanvas.height = height;
const oCtx = overlayCanvas.getContext('2d');
oCtx.putImageData(overlayData, 0, 0);
ctx.globalAlpha = opacity;
// Overlay logic, using 'screen' or generic over composition handles nice blending
ctx.globalCompositeOperation = 'screen';
ctx.drawImage(overlayCanvas, 0, 0);
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
return mainCanvas;
}
Apply Changes