You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
// Return an empty canvas for invalid image dimensions
canvas.width = 0;
canvas.height = 0;
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
// This can happen due to tainted canvas (e.g. cross-origin image without CORS)
console.error("Error getting ImageData:", e);
// In a real tool, you might want to inform the user.
// For now, return the original image drawn on canvas if pixel manipulation fails.
return canvas;
}
const data = imageData.data;
const centerX = imgWidth / 2;
const centerY = imgHeight / 2;
// Calculate maxDist for vignette; ensure it's not zero to prevent NaN issues.
// For a 1x1 pixel image, maxDist is sqrt(0.5^2 + 0.5^2) = sqrt(0.5) approx 0.707
const maxDist = (imgWidth === 0 && imgHeight === 0) ? 0 : Math.sqrt(centerX * centerX + centerY * centerY);
// Internal constants for the SX-70 effect. These are tuned to give a recognizable style.
// Color Tinting Coefficients: [Red_from_R, Red_from_G, Red_from_B], etc.
const TINT_R_COEFF = [1.12, 0.07, 0.01]; // Emphasize red, add warmth from green/blue.
const TINT_G_COEFF = [0.03, 1.08, 0.03]; // Emphasize green, slight interaction.
const TINT_B_COEFF = [0.08, 0.12, 0.75]; // Reduce blue's direct impact, warm it with R/G.
// This combination aims for warm highlights and slightly shifted, muted blues.
const BRIGHTNESS_ADJUST = 8; // Additive brightness adjustment (0-255 scale).
const CONTRAST_LEVEL = -25; // Percentage contrast adjustment. Negative values reduce contrast (faded look).
const SATURATION_FACTOR = 0.80; // Saturation multiplier. <1.0 desaturates, >1.0 saturates. SX-70s often look a bit muted.
const VIGNETTE_STRENGTH = 0.40; // How dark the vignette is at the edges (0.0 to 1.0).
const VIGNETTE_START = 0.20; // Normalized distance from center where vignette begins to appear (0.0 to 1.0).
// 0.0 means vignette starts from center, 1.0 means no vignette unless strength is 0.
const VIGNETTE_FALLOFF_POWER = 1.7; // Controls the curve of the vignette fade (e.g., 1.0 linear, 2.0 quadratic).
const contrastFactor = (100.0 + CONTRAST_LEVEL) / 100.0;
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Color Tinting
const rOrig = r, gOrig = g, bOrig = b;
r = rOrig * TINT_R_COEFF[0] + gOrig * TINT_R_COEFF[1] + bOrig * TINT_R_COEFF[2];
g = rOrig * TINT_G_COEFF[0] + gOrig * TINT_G_COEFF[1] + bOrig * TINT_G_COEFF[2];
b = rOrig * TINT_B_COEFF[0] + gOrig * TINT_B_COEFF[1] + bOrig * TINT_B_COEFF[2];
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 2. Brightness Adjustment
r += BRIGHTNESS_ADJUST;
g += BRIGHTNESS_ADJUST;
b += BRIGHTNESS_ADJUST;
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 3. Contrast Adjustment
// Formula: NewValue = (OldValue - 128) * Factor + 128
r = (r - 128) * contrastFactor + 128;
g = (g - 128) * contrastFactor + 128;
b = (b - 128) * contrastFactor + 128;
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 4. Saturation Adjustment
// Formula: NewValue = Luminance + Factor * (OldValue - Luminance)
const lum = 0.299 * r + 0.587 * g + 0.114 * b; // Standard luminance calculation
r = lum + SATURATION_FACTOR * (r - lum);
g = lum + SATURATION_FACTOR * (g - lum);
b = lum + SATURATION_FACTOR * (b - lum);
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 5. Vignetting
let reductionFactor = 1.0;
if (maxDist > 0) { // Only apply vignette if image has dimensions
const currentX = (i / 4) % imgWidth;
const currentY = Math.floor((i / 4) / imgWidth);
const dx = currentX - centerX;
const dy = currentY - centerY;
const distNormalized = Math.sqrt(dx * dx + dy * dy) / maxDist; // Distance from center, normalized (0 to 1)
let vignetteAmount = 0;
if (distNormalized > VIGNETTE_START) {
if (VIGNETTE_START < 1.0) { // Ensure VIGNETTE_START is not at or beyond the edge (1.0)
// Scale distance: 0 at VIGNETTE_START, 1 at the image edge
const effectiveDist = (distNormalized - VIGNETTE_START) / (1.0 - VIGNETTE_START);
vignetteAmount = Math.pow(Math.max(0.0, Math.min(1.0, effectiveDist)), VIGNETTE_FALLOFF_POWER);
}
}
reductionFactor = 1.0 - vignetteAmount * VIGNETTE_STRENGTH;
}
r *= reductionFactor;
g *= reductionFactor;
b *= reductionFactor;
// Final assignment to pixel data
data[i] = Math.round(Math.max(0, Math.min(255, r)));
data[i+1] = Math.round(Math.max(0, Math.min(255, g)));
data[i+2] = Math.round(Math.max(0, Math.min(255, b)));
// Alpha channel (data[i+3]) is preserved
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes