You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, reflectionRatio = 0.5, waveAmplitude = 12, waveFrequency = 0.1, waterTint = "rgba(0, 80, 130, 0.45)") {
const width = originalImg.width;
const height = originalImg.height;
// Cap reflectionRatio at 1 to prevent out-of-bounds reflection reading
const safeRatio = Math.max(0.1, Math.min(1.0, reflectionRatio));
const refHeight = Math.floor(height * safeRatio);
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height + refHeight;
const ctx = canvas.getContext('2d');
// Draw the original image at the top
ctx.drawImage(originalImg, 0, 0);
// Generate the "Regatta" water reflection
for (let y = 0; y < refHeight; y++) {
// Perspective adjustment:
// y represents depth into the reflection (distance from the "horizon" line splitting the image)
// Waves appear wider and more profound closer to the viewer (higher y)
const depth = y / refHeight;
const currentAmplitude = waveAmplitude * (0.5 + 2 * depth);
const currentFrequency = waveFrequency / (1 + depth * 2);
// Calculate horizontal offset for the wave distortion
let offsetX = Math.sin(y * currentFrequency + (y * 0.1)) * currentAmplitude;
// Reflect the corresponding bottom part of the original image line by line
const sourceY = height - 1 - y;
// Draw the sliced line with the wave offset
ctx.drawImage(
originalImg,
0, sourceY, width, 1,
offsetX, height + y, width, 1
);
// Fill edge gaps created by the horizontal wave displacement to avoid seeing transparency
if (offsetX > 0) {
// Fill left gap
ctx.drawImage(originalImg, 0, sourceY, 1, 1, 0, height + y, offsetX, 1);
} else if (offsetX < 0) {
// Fill right gap
ctx.drawImage(originalImg, width - 1, sourceY, 1, 1, width + offsetX, height + y, -offsetX, 1);
}
}
// Apply water color tint overlay on the reflection
ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = waterTint;
ctx.fillRect(0, height, width, refHeight);
// Add specular highlights/ripples on the water
ctx.globalCompositeOperation = 'screen';
ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
for (let y = 0; y < refHeight; y += Math.floor(2 + Math.random() * 8)) {
const depth = y / refHeight;
if (Math.random() > 0.4) {
const currentAmplitude = waveAmplitude * (0.5 + 2 * depth);
const currentFrequency = waveFrequency / (1 + depth * 2);
let offsetX = Math.sin(y * currentFrequency + (y * 0.1)) * currentAmplitude;
// Generate a randomly sized water ripple
const rippleWidth = width * (0.05 + Math.random() * 0.2);
const startX = offsetX + Math.random() * (width - rippleWidth);
const rippleHeight = 1 + depth * 3;
ctx.fillRect(startX, height + y, rippleWidth, rippleHeight);
}
}
// Reset compositing mode
ctx.globalCompositeOperation = 'source-over';
// Draw a subtle dark border separating the image and reflection (shore/horizonline)
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx.fillRect(0, height, width, 2);
return canvas;
}
Apply Changes