You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, lineColorStr = "0,255,0", lineWidth = 3, intensity = 1.5, scanHeight = 0.5, beatFrequency = 5, amplitude = 50, tintAmount = 0.3) {
// 1. Setup main canvas
const canvas = document.createElement('canvas');
// Use { willReadFrequently: true } for potential performance optimization if available/supported
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
console.error("Image dimensions are zero or invalid.");
// Optionally draw a small indicator on the canvas
canvas.width = canvas.width || 100; // Ensure some size for error message
canvas.height = canvas.height || 30;
ctx.font = "12px Arial";
ctx.fillStyle = "red";
ctx.fillText("Invalid image dimensions", 5, 15);
return canvas; // Return (Potentially small) canvas
}
// Draw original image onto the main canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// 2. Get original image data
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Could not get ImageData. This might be due to CORS policy if the image is from an external domain.", e);
// Fallback: return the canvas with the original image and an error message
// The original image is already drawn. Add text warning.
ctx.font = "bold 16px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
const errText = "Error: Cannot process image (CORS issue or insecure canvas).";
ctx.strokeText(errText, canvas.width / 2, canvas.height / 2);
ctx.fillText(errText, canvas.width / 2, canvas.height / 2);
return canvas;
}
const data = imageData.data;
// 3. Parse and validate parameters
let [rStr, gStr, bStr] = lineColorStr.split(',');
let lr = parseInt(rStr, 10);
let lg = parseInt(gStr, 10);
let lb = parseInt(bStr, 10);
if (isNaN(lr) || isNaN(lg) || isNaN(lb) || lr < 0 || lr > 255 || lg < 0 || lg > 255 || lb < 0 || lb > 255) {
[lr, lg, lb] = [0, 255, 0]; // Default to green on parse error or invalid color values
}
lineWidth = Math.max(1, Number(lineWidth));
intensity = Math.max(1.0, Number(intensity));
scanHeight = Math.max(0.0, Math.min(1.0, Number(scanHeight)));
beatFrequency = Math.max(1, Number(beatFrequency));
amplitude = Math.max(0, Number(amplitude));
tintAmount = Math.max(0.0, Math.min(1.0, Number(tintAmount)));
const baselineY = canvas.height * scanHeight;
// 4. ECG Y-coordinate function
function getEcgY(xPos, currentCanvasWidth, currentBaselineY, currentAmplitude, currentBeatFrequency) {
const period = currentCanvasWidth / currentBeatFrequency;
const xInPeriod = xPos % period;
const currentPhase = xInPeriod / period; // Normalized phase within a beat cycle (0 to 1)
let yOffset = 0; // Positive yOffset means upward deflection from baseline
// P-wave: Example phase 0.1 to 0.2
if (currentPhase >= 0.1 && currentPhase < 0.2) {
const pPhase = (currentPhase - 0.1) / 0.1; // Normalize phase for P-wave duration
yOffset = currentAmplitude * 0.2 * Math.sin(pPhase * Math.PI); // Sine curve for P-wave
}
// QRS complex (simplified as R-spike): Example phase 0.25 to 0.35
else if (currentPhase >= 0.25 && currentPhase < 0.35) {
const qrsPhase = (currentPhase - 0.25) / 0.1; // Normalize phase for QRS duration
const rPeakPhase = 0.5; // R-spike peak is at midpoint of QRS phase
if (qrsPhase < rPeakPhase) { // Rising edge of R-spike
yOffset = currentAmplitude * (qrsPhase / rPeakPhase);
} else { // Falling edge of R-spike
yOffset = currentAmplitude * (1 - (qrsPhase - rPeakPhase) / (1 - rPeakPhase));
}
}
// T-wave: Example phase 0.4 to 0.6
else if (currentPhase >= 0.4 && currentPhase < 0.6) {
const tPhase = (currentPhase - 0.4) / 0.2; // Normalize phase for T-wave duration
yOffset = currentAmplitude * 0.3 * Math.sin(tPhase * Math.PI); // Sine curve for T-wave
}
return Math.round(currentBaselineY - yOffset); // Canvas Y is downwards, so subtract offset
}
// 5. Create an offscreen canvas to draw the ECG line
const lineCanvas = document.createElement('canvas');
lineCanvas.width = canvas.width;
lineCanvas.height = canvas.height;
const lineCtx = lineCanvas.getContext('2d', { willReadFrequently: true });
// Draw the ECG line on the offscreen canvas
if (canvas.width > 0) {
lineCtx.beginPath();
lineCtx.moveTo(0, getEcgY(0, canvas.width, baselineY, amplitude, beatFrequency));
for (let x = 1; x < canvas.width; x++) {
lineCtx.lineTo(x, getEcgY(x, canvas.width, baselineY, amplitude, beatFrequency));
}
lineCtx.lineWidth = lineWidth;
lineCtx.strokeStyle = `rgb(${lr},${lg},${lb})`;
lineCtx.lineCap = 'round';
lineCtx.lineJoin = 'round';
// Add a glow effect to the line for a softer filter application
lineCtx.shadowColor = `rgba(${lr},${lg},${lb},0.7)`;
lineCtx.shadowBlur = Math.max(1, lineWidth * 1.5); // Ensure blur is at least 1
lineCtx.stroke();
}
// Get image data of the drawn line (this includes the line, its color, glow, and anti-aliasing)
const lineImageData = lineCtx.getImageData(0, 0, lineCanvas.width, lineCanvas.height);
const linePixelData = lineImageData.data;
// 6. Apply the filter effect to the original image data
// Iterate through each pixel of the original image
for (let i = 0; i < data.length; i += 4) {
const lineAlpha = linePixelData[i + 3]; // Alpha value of the ECG line/glow at this pixel
if (lineAlpha > 0) { // If there's any part of the ECG line/glow at this pixel
const lineStrength = lineAlpha / 255; // Normalized strength (0 to 1)
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// Apply intensity: Brighten the underlying image pixels based on lineStrength
const intensityFactor = 1 + (intensity - 1) * lineStrength;
r = Math.min(255, r * intensityFactor);
g = Math.min(255, g * intensityFactor);
b = Math.min(255, b * intensityFactor);
// Apply tint: Shift color towards the specified line color
if (tintAmount > 0) {
const currentTintFactor = lineStrength * tintAmount;
// Blend current (brightened) color with the line color parameters (lr, lg, lb)
r = Math.min(255, Math.round(r * (1 - currentTintFactor) + lr * currentTintFactor));
g = Math.min(255, Math.round(g * (1 - currentTintFactor) + lg * currentTintFactor));
b = Math.min(255, Math.round(b * (1 - currentTintFactor) + lb * currentTintFactor));
}
// Update pixel data
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
// Alpha (data[i+3]) remains unchanged
}
}
// 7. Put the modified image data back onto the main canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes