You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, displacementAmount = 20, noiseScaleInput = 50) {
// Parameter sanitization
// Ensure noiseScale is at least 1; otherwise, grid calculations can be problematic.
const noiseScale = Math.max(1, noiseScaleInput);
const W = originalImg.width;
const H = originalImg.height;
// Handle cases where the image might not be loaded or has zero dimensions
if (W === 0 || H === 0) {
console.warn("Original image has zero width or height. Returning a 1x1 transparent canvas.");
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = 1;
emptyCanvas.height = 1;
const emptyCtx = emptyCanvas.getContext('2d');
if (emptyCtx) {
emptyCtx.fillStyle = 'rgba(0,0,0,0)'; // Transparent
emptyCtx.fillRect(0,0,1,1);
}
return emptyCanvas;
}
const canvas = document.createElement('canvas');
canvas.width = W;
canvas.height = H;
const ctx = canvas.getContext('2d');
// Should not happen for '2d', but good practice to check
if (!ctx) {
console.error("Could not get 2D context from canvas. Returning an empty canvas.");
return canvas;
}
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0);
// Get image data
let originalImageData;
try {
originalImageData = ctx.getImageData(0, 0, W, H);
} catch (e) {
// This can happen due to tainted canvas if image is cross-origin and CORS isn't set up
console.error("Could not get ImageData, possibly due to CORS policy. Error:", e);
// Return the canvas with the original image drawn, without the effect
return canvas;
}
const outputImageData = ctx.createImageData(W, H);
const originalData = originalImageData.data;
const outputData = outputImageData.data;
// Helper function for linear interpolation
function lerp(a, b, t) {
return a * (1 - t) + b * t;
}
// Helper function for bilinear interpolation from a grid
// gx, gy are fractional coordinates in the grid
function interpolateFromGrid(grid, gx, gy) {
const x_floor = Math.floor(gx);
const y_floor = Math.floor(gy);
const x_frac = gx - x_floor;
const y_frac = gy - y_floor;
const gridActualHeight = grid.length;
const gridActualWidth = grid[0].length;
// Coordinates of the four grid points surrounding (gx, gy)
// These indices are guaranteed to be within the grid dimensions
// due to how gridW/gridH are calculated and how gx/gy relate to image W/H & noiseScale
const x0 = x_floor;
const x1 = x_floor + 1;
const y0 = y_floor;
const y1 = y_floor + 1;
// Fetch values from the grid, ensuring x1/y1 don't exceed grid boundaries
// (they shouldn't if grid size calculation is correct, but this is safer)
const p00 = grid[y0][x0];
const p10 = grid[y0][Math.min(x1, gridActualWidth - 1)];
const p01 = grid[Math.min(y1, gridActualHeight - 1)][x0];
const p11 = grid[Math.min(y1, gridActualHeight - 1)][Math.min(x1, gridActualWidth - 1)];
const topInterpolation = lerp(p00, p10, x_frac);
const bottomInterpolation = lerp(p01, p11, x_frac);
return lerp(topInterpolation, bottomInterpolation, y_frac);
}
// Calculate dimensions of the noise grid
// Add 1 to ensure there's always a grid cell to interpolate from, even at edges.
const gridW = Math.ceil(W / noiseScale) + 1;
const gridH = Math.ceil(H / noiseScale) + 1;
// Create and populate displacement grids for X and Y offsets
const gridDx = Array(gridH).fill(null).map(() => Array(gridW).fill(0));
const gridDy = Array(gridH).fill(null).map(() => Array(gridW).fill(0));
for (let j = 0; j < gridH; j++) {
for (let i = 0; i < gridW; i++) {
// Random values between -displacementAmount and +displacementAmount
gridDx[j][i] = (Math.random() - 0.5) * 2 * displacementAmount;
gridDy[j][i] = (Math.random() - 0.5) * 2 * displacementAmount;
}
}
// Main loop to process each pixel
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
// Calculate fractional coordinates in the noise grid
const gx = x / noiseScale;
const gy = y / noiseScale;
// Interpolate displacement values from the grids
const finalDx = interpolateFromGrid(gridDx, gx, gy);
const finalDy = interpolateFromGrid(gridDy, gx, gy);
// Calculate source pixel coordinates after displacement
const sourceX = x + finalDx;
const sourceY = y + finalDy;
// Clamp source coordinates to be within image boundaries and round to nearest integer
const clampedSourceX = Math.max(0, Math.min(W - 1, Math.round(sourceX)));
const clampedSourceY = Math.max(0, Math.min(H - 1, Math.round(sourceY)));
// Calculate array indices for source and destination pixels
const sourceIndex = (clampedSourceY * W + clampedSourceX) * 4;
const destIndex = (y * W + x) * 4;
// Copy pixel data from source (original) to destination (output)
outputData[destIndex] = originalData[sourceIndex]; // R
outputData[destIndex + 1] = originalData[sourceIndex + 1]; // G
outputData[destIndex + 2] = originalData[sourceIndex + 2]; // B
outputData[destIndex + 3] = originalData[sourceIndex + 3]; // A
}
}
// Put the modified pixel data back onto the canvas
ctx.putImageData(outputImageData, 0, 0);
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 Molten Glass Filter Effect Application is a tool that allows users to apply a unique molten glass effect to their images. By adjusting parameters such as displacement amount and noise scale, users can create visually striking effects that mimic the look of molten glass. This tool is ideal for artists, graphic designers, and photographers looking to enhance their images with abstract and textured effects, making it suitable for creative projects, social media graphics, and personalized artwork.