You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, amplitude = 10, frequency = 0.05, phase = 0, direction = "horizontal") {
// Ensure parameters are numbers, provide defaults if conversion fails
const numAmplitude = Number(amplitude);
const finalAmplitude = isNaN(numAmplitude) ? 10 : numAmplitude;
const numFrequency = Number(frequency);
const finalFrequency = isNaN(numFrequency) ? 0.05 : numFrequency;
const numPhase = Number(phase);
const finalPhase = isNaN(numPhase) ? 0 : numPhase;
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = 0;
emptyCanvas.height = 0;
return emptyCanvas;
}
const outputCanvas = document.createElement('canvas');
outputCanvas.width = imgWidth;
outputCanvas.height = imgHeight;
// { willReadFrequently: true } can improve performance of getImageData/putImageData in some browsers
const outputCtx = outputCanvas.getContext('2d', { willReadFrequently: true });
// Create a temporary source canvas to draw the original image
// and reliably get its pixel data.
const sourceCanvas = document.createElement('canvas');
sourceCanvas.width = imgWidth;
sourceCanvas.height = imgHeight;
const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });
try {
sourceCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error drawing original image to source canvas:", e);
// Draw error message on the output canvas
outputCtx.fillStyle = 'rgba(200,0,0,0.5)'; // Semi-transparent red background
outputCtx.fillRect(0,0,imgWidth,imgHeight);
outputCtx.font = "16px Arial";
outputCtx.fillStyle = 'white';
outputCtx.textAlign = 'center';
outputCtx.fillText("Error: Could not draw original image.", imgWidth/2, imgHeight/2 - 10);
if (e && e.message) {
outputCtx.fillText(e.message, imgWidth/2, imgHeight/2 + 10);
}
return outputCanvas;
}
let sourceImageData;
try {
sourceImageData = sourceCtx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error getting ImageData from source canvas (e.g., CORS issue):", e);
// Draw error message on the output canvas
outputCtx.fillStyle = 'rgba(200,0,0,0.5)';
outputCtx.fillRect(0,0,imgWidth,imgHeight);
outputCtx.font = "16px Arial";
outputCtx.fillStyle = 'white';
outputCtx.textAlign = 'center';
outputCtx.fillText("Error: Could not access image pixels.", imgWidth/2, imgHeight/2 - 20);
outputCtx.fillText("This may be due to CORS restrictions", imgWidth/2, imgHeight/2);
if (e && e.message) {
outputCtx.fillText(e.message, imgWidth/2, imgHeight/2 + 20);
}
return outputCanvas;
}
const sourceData = sourceImageData.data;
const outputImageData = outputCtx.createImageData(imgWidth, imgHeight);
const outputData = outputImageData.data;
// Bilinear interpolation helper function.
// It captures sourceData, imgWidth, imgHeight from the outer scope for efficiency.
function getBilinearPixel(sourceSampleX, sourceSampleY) {
const x_floor = Math.floor(sourceSampleX);
const y_floor = Math.floor(sourceSampleY);
// Calculate fractional parts for interpolation
const u = sourceSampleX - x_floor;
const v = sourceSampleY - y_floor;
const resultPixel = [0,0,0,0]; // RGBA array for the interpolated pixel
// Iterate over R, G, B, A channels
for (let channel = 0; channel < 4; channel++) {
let interpolatedValue = 0;
// Get values of the 4 surrounding pixels (p00, p10, p01, p11)
// P00 is (x_floor, y_floor)
const px00 = Math.max(0, Math.min(x_floor, imgWidth - 1));
const py00 = Math.max(0, Math.min(y_floor, imgHeight - 1));
const val00 = sourceData[(py00 * imgWidth + px00) * 4 + channel];
interpolatedValue += val00 * (1 - u) * (1 - v);
// P10 is (x_floor + 1, y_floor)
const px10 = Math.max(0, Math.min(x_floor + 1, imgWidth - 1));
const py10 = Math.max(0, Math.min(y_floor, imgHeight - 1));
const val10 = sourceData[(py10 * imgWidth + px10) * 4 + channel];
interpolatedValue += val10 * u * (1 - v);
// P01 is (x_floor, y_floor + 1)
const px01 = Math.max(0, Math.min(x_floor, imgWidth - 1));
const py01 = Math.max(0, Math.min(y_floor + 1, imgHeight - 1));
const val01 = sourceData[(py01 * imgWidth + px01) * 4 + channel];
interpolatedValue += val01 * (1 - u) * v;
// P11 is (x_floor + 1, y_floor + 1)
const px11 = Math.max(0, Math.min(x_floor + 1, imgWidth - 1));
const py11 = Math.max(0, Math.min(y_floor + 1, imgHeight - 1));
const val11 = sourceData[(py11 * imgWidth + px11) * 4 + channel];
interpolatedValue += val11 * u * v;
resultPixel[channel] = Math.round(interpolatedValue);
}
return resultPixel;
}
// Iterate over each pixel of the destination canvas
for (let y = 0; y < imgHeight; y++) { // y is the destination y-coordinate
for (let x = 0; x < imgWidth; x++) { // x is the destination x-coordinate
let sourcePixelX, sourcePixelY; // Coordinates in the source image to sample from
if (String(direction).toLowerCase() === "vertical") {
// Vertical waves: y-coordinate is displaced based on x and wave function
const displacement = finalAmplitude * Math.sin(x * finalFrequency + finalPhase);
sourcePixelX = x;
sourcePixelY = y - displacement; // Apply inverse displacement
} else { // Default to horizontal waves
// Horizontal waves: x-coordinate is displaced based on y and wave function
const displacement = finalAmplitude * Math.sin(y * finalFrequency + finalPhase);
sourcePixelX = x - displacement; // Apply inverse displacement
sourcePixelY = y;
}
// Get the color from the source image using bilinear interpolation
const newPixelColor = getBilinearPixel(sourcePixelX, sourcePixelY);
const destIndex = (y * imgWidth + x) * 4;
outputData[destIndex] = newPixelColor[0]; // R
outputData[destIndex + 1] = newPixelColor[1]; // G
outputData[destIndex + 2] = newPixelColor[2]; // B
outputData[destIndex + 3] = newPixelColor[3]; // A
}
}
outputCtx.putImageData(outputImageData, 0, 0);
return outputCanvas;
}
Apply Changes