You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, reflectionHeightFactor = 0.5, reflectionOpacity = 0.6, rippleAmplitude = 4, rippleFrequency = 3) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure originalImg is usable (e.g., loaded).
// The problem statement implies originalImg is a ready-to-use Image object.
const imgWidth = originalImg.width;
const imgHeight = originalImg.height;
// Sanitize and validate parameters
reflectionHeightFactor = Number(reflectionHeightFactor);
reflectionOpacity = Number(reflectionOpacity);
rippleAmplitude = Number(rippleAmplitude);
rippleFrequency = Number(rippleFrequency);
// Clamp reflectionHeightFactor to [0, 1] and reflectionOpacity to [0, 1]
reflectionHeightFactor = Math.max(0, Math.min(1, reflectionHeightFactor));
reflectionOpacity = Math.max(0, Math.min(1, reflectionOpacity));
// Ensure amplitude and frequency are non-negative
rippleAmplitude = Math.max(0, rippleAmplitude);
rippleFrequency = Math.max(0, rippleFrequency);
const reflectionActualHeight = Math.floor(imgHeight * reflectionHeightFactor);
canvas.width = imgWidth;
canvas.height = imgHeight + reflectionActualHeight;
// 1. Draw original image
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Proceed only if there's a reflection to draw
if (reflectionActualHeight > 0) {
// 2. Draw the reflected part of the image
// The reflection should mirror the top part of the original image.
ctx.save();
// Translate to the bottom-left of where the reflection will be drawn.
// The Y-axis will be flipped, so (0,0) in transformed space becomes the bottom-left of reflection.
ctx.translate(0, imgHeight + reflectionActualHeight);
ctx.scale(1, -1); // Flip the Y-axis. Positive Y now goes upwards from the new origin.
// Source rectangle from originalImg: (sx, sy, sWidth, sHeight)
// We take the top 'reflectionActualHeight' pixels of the original image.
// sx=0, sy=0 (top-left of original image)
// sWidth=imgWidth
// sHeight=reflectionActualHeight (depth of original image to reflect)
// Destination rectangle in transformed canvas space: (dx, dy, dWidth, dHeight)
// dx=0, dy=0 (relative to the transformed origin)
// dWidth=imgWidth
// dHeight=reflectionActualHeight
ctx.drawImage(originalImg,
0, 0, imgWidth, reflectionActualHeight, // Source rectangle
0, 0, imgWidth, reflectionActualHeight // Destination rectangle
);
ctx.restore(); // Restore context to original state (before translate and scale)
// 3. Apply a fading gradient to the reflection
// The gradient will make the reflection appear to fade out with distance.
const grad = ctx.createLinearGradient(
0, imgHeight, // Gradient start (top of reflection area)
0, imgHeight + reflectionActualHeight // Gradient end (bottom of reflection area)
);
// Define color stops for the gradient.
// Using rgba(0,0,0,alpha) for destination-in: only alpha matters.
grad.addColorStop(0, `rgba(0,0,0, ${reflectionOpacity})`); // Reflection is most opaque near the original image
grad.addColorStop(0.7, `rgba(0,0,0, ${reflectionOpacity * 0.3})`); // Mid-point for a smoother falloff
grad.addColorStop(1, `rgba(0,0,0, 0)`); // Reflection is fully transparent at the farthest point
// Use 'destination-in' composite operation. This means the existing reflection pixels
// (destination) will be kept where they overlap with the new shape (gradient-filled rectangle),
// and their alpha will be multiplied by the alpha of the new shape.
ctx.globalCompositeOperation = 'destination-in';
ctx.fillStyle = grad;
ctx.fillRect(0, imgHeight, imgWidth, reflectionActualHeight); // Apply gradient mask
ctx.globalCompositeOperation = 'source-over'; // Reset composite operation to default
// 4. Apply ripple effect to the reflection (if amplitude and frequency are positive)
if (rippleAmplitude > 0 && rippleFrequency > 0) {
const reflectionImageData = ctx.getImageData(0, imgHeight, imgWidth, reflectionActualHeight);
const pixels = reflectionImageData.data;
// Work on a copy of pixel data to avoid read-modify-write issues on the same array during iteration.
const outputPixels = new Uint8ClampedArray(pixels.length);
// 'rippleFrequency' is the number of full wave cycles across the reflection's height.
// 'wavelength' is the height in pixels of one full sine wave cycle.
const wavelength = reflectionActualHeight / rippleFrequency;
for (let y = 0; y < reflectionActualHeight; y++) {
// Ripple amplitude can decrease with distance (y) from the original image.
// This makes ripples appear stronger/wider closer to the "shoreline".
const amplitudeFactor = (1 - y / reflectionActualHeight); // Linearly decreases from 1 to 0
const currentRippleAmplitude = rippleAmplitude * amplitudeFactor;
// Calculate horizontal displacement (dx) for pixels in this row (y) using a sine wave.
const displacementX = currentRippleAmplitude * Math.sin(2 * Math.PI * y / wavelength);
for (let x = 0; x < imgWidth; x++) {
let srcX = Math.round(x + displacementX);
// Clamp srcX to be within the image bounds [0, imgWidth - 1]
// to prevent reading pixels from outside the image data.
srcX = Math.max(0, Math.min(imgWidth - 1, srcX));
const destIdx = (y * imgWidth + x) * 4; // Index for the destination pixel (current x,y)
const srcIdx = (y * imgWidth + srcX) * 4; // Index for the source pixel (displaced x, same y)
// Copy RGBA values from source pixel to destination pixel
outputPixels[destIdx] = pixels[srcIdx];
outputPixels[destIdx + 1] = pixels[srcIdx + 1];
outputPixels[destIdx + 2] = pixels[srcIdx + 2];
outputPixels[destIdx + 3] = pixels[srcIdx + 3];
}
}
// Copy the modified pixel data from outputPixels back to the ImageData object.
reflectionImageData.data.set(outputPixels);
// Put the modified ImageData (with ripples) back onto the canvas.
ctx.putImageData(reflectionImageData, 0, imgHeight);
}
}
return canvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Water Reflections Filter is an online tool that allows users to create realistic water reflections of images. By applying adjustable parameters such as reflection height, opacity, and ripple effects, users can enhance their images to give them a reflective water surface appearance. This tool is particularly useful for graphic designers, photographers, and content creators who wish to add a creative touch to their visuals, making them more engaging and visually striking.