You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, strength = 10, angle = 0, facetSize = 50, facetStrengthVariation = 0.2, facetAngleVariation = 15) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const W = originalImg.naturalWidth || originalImg.width;
const H = originalImg.naturalHeight || originalImg.height;
if (W === 0 || H === 0) {
// Image not loaded or zero size, return an empty canvas.
canvas.width = W;
canvas.height = H;
return canvas;
}
canvas.width = W;
canvas.height = H;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, W, H);
// If base strength is zero, no displacement effect will occur.
// Facet variations modify base strength/angle, so if base strength is 0, they also result in 0 displacement.
if (strength === 0) {
return canvas;
}
const imageData = ctx.getImageData(0, 0, W, H);
const pixels = imageData.data;
const outputImageData = ctx.createImageData(W, H);
const outputPixels = outputImageData.data;
// Determine if faceting should be applied based on parameters
const applyFacetStrengthVariation = Math.abs(facetStrengthVariation) > 0.001;
const applyFacetAngleVariation = Math.abs(facetAngleVariation) > 0.001;
const useFacets = facetSize > 0 && (applyFacetStrengthVariation || applyFacetAngleVariation);
// Helper function to get a pseudo-random value (0-1) for a facet cell
// This ensures consistent randomness for each cell.
const getFacetRand = (cellX, cellY) => {
// Constants chosen to create varied patterns.
const val = Math.sin(cellX * 12.9898 + cellY * 78.233 + W * 0.0113 + H * 0.0307) * 43758.5453123;
return val - Math.floor(val);
};
// Helper function to get a pixel channel value from source data,
// with boundary clamping and rounding of source coordinates.
const getPixelChannel = (sourceData, imgW, imgH, sX, sY, channelOffset) => {
// Round coordinates to nearest integer pixel
const srcX = Math.round(sX);
const srcY = Math.round(sY);
// Clamp coordinates to be within image bounds
const clampedX = Math.max(0, Math.min(imgW - 1, srcX));
const clampedY = Math.max(0, Math.min(imgH - 1, srcY));
const index = (clampedY * imgW + clampedX) * 4;
return sourceData[index + channelOffset];
};
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const currentIndex = (y * W + x) * 4;
let currentStrength = strength;
let currentAngleDeg = angle;
if (useFacets) {
const cellX = Math.floor(x / facetSize);
const cellY = Math.floor(y / facetSize);
const facetRand = getFacetRand(cellX, cellY); // Value between 0 and 1
if (applyFacetStrengthVariation) {
// Modulate strength: baseStrength * (1 +/- variation)
const strengthFactor = 1 + (facetRand - 0.5) * 2 * facetStrengthVariation;
currentStrength = strength * strengthFactor;
}
if (applyFacetAngleVariation) {
// Modulate angle: baseAngle +/- variationOffset
const angleOffsetDeg = (facetRand - 0.5) * 2 * facetAngleVariation;
currentAngleDeg = angle + angleOffsetDeg;
}
}
// If, after adjustments, strength is effectively zero for this pixel, copy original.
// Using a small epsilon for floating point comparisons.
if (Math.abs(currentStrength) < 0.001) {
outputPixels[currentIndex + 0] = pixels[currentIndex + 0];
outputPixels[currentIndex + 1] = pixels[currentIndex + 1];
outputPixels[currentIndex + 2] = pixels[currentIndex + 2];
outputPixels[currentIndex + 3] = pixels[currentIndex + 3];
continue;
}
const currentAngleRad = currentAngleDeg * Math.PI / 180;
const cosAngle = Math.cos(currentAngleRad);
const sinAngle = Math.sin(currentAngleRad);
// Calculate the displacement for R and B channels
const offsetX = currentStrength * cosAngle;
const offsetY = currentStrength * sinAngle;
// Source coordinates for Red channel (shifted by +offset)
const rX = x + offsetX;
const rY = y + offsetY;
// Source coordinates for Blue channel (shifted by -offset)
const bX = x - offsetX;
const bY = y - offsetY;
// Green channel and Alpha use the original pixel's location
const gX = x;
const gY = y;
outputPixels[currentIndex + 0] = getPixelChannel(pixels, W, H, rX, rY, 0); // Red
outputPixels[currentIndex + 1] = getPixelChannel(pixels, W, H, gX, gY, 1); // Green
outputPixels[currentIndex + 2] = getPixelChannel(pixels, W, H, bX, bY, 2); // Blue
outputPixels[currentIndex + 3] = getPixelChannel(pixels, W, H, gX, gY, 3); // Alpha
}
}
ctx.putImageData(outputImageData, 0, 0);
return canvas;
}
Apply Changes