You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg) {
// Configuration for the Fujifilm FP-100C effect
const config = {
contrastValue: 15, // How much to increase contrast (0-100 is a typical range for such formulas)
brightnessValue: 8, // How much to increase brightness (pixel value adjustment)
// Base color tint multipliers for a general cool cast
baseTintR: 0.96, // Multiplier for Red channel
baseTintG: 0.99, // Multiplier for Green channel
baseTintB: 1.08, // Multiplier for Blue channel (boosts blue)
// Highlight tinting (creamy/yellowish)
highlightLumThreshold: 170, // Luminance value above which highlight tint applies (0-255)
highlightTintAmount: 12, // Max amount of tint value to add/subtract for highlights
// Shadow tinting (blue/cyan)
shadowLumThreshold: 90, // Luminance value below which shadow tint applies (0-255)
shadowTintAmount: 18, // Max amount of tint value to add/subtract for shadows
// Desaturation
desaturationAmount: 0.12, // 0 to 1 (0% to 100% desaturation)
// Vignette effect
vignetteStrength: 0.5, // 0 to 1 (0% to 100% darkening at edges)
vignetteFalloff: 2.0 // Exponent for vignette falloff curve (e.g., 1.0 for linear, 2.0 for quadratic)
};
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error getting imageData (e.g., canvas tainted): ", e);
// In case of error (e.g. tainted canvas), return the canvas with the original image drawn.
return canvas;
}
const data = imageData.data;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Max distance from center to a corner (used for vignette normalization)
const maxDist = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// Pre-calculate contrast factor
const contrastFactor = (259 * (config.contrastValue + 259)) / (259 * (259 - config.contrastValue));
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Contrast adjustment
r = contrastFactor * (r - 128) + 128;
g = contrastFactor * (g - 128) + 128;
b = contrastFactor * (b - 128) + 128;
// 2. Brightness adjustment
r += config.brightnessValue;
g += config.brightnessValue;
b += config.brightnessValue;
// Clamp values after contrast/brightness before further color calculations
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// Calculate luminance for conditioning highlight/shadow tints
const lumPreTint = 0.2126 * r + 0.7152 * g + 0.0722 * b;
// 3. Color Tinting
let tintedR = r * config.baseTintR;
let tintedG = g * config.baseTintG;
let tintedB = b * config.baseTintB;
// Apply creamy/yellowish highlights
if (lumPreTint > config.highlightLumThreshold) {
const highlightFactor = Math.min(1, (lumPreTint - config.highlightLumThreshold) / (255 - config.highlightLumThreshold));
tintedR += config.highlightTintAmount * highlightFactor;
tintedG += config.highlightTintAmount * highlightFactor * 0.7; // More R+G = yellow
tintedB -= config.highlightTintAmount * highlightFactor * 0.2; // Reduce blue slightly for warmth
}
// Apply blue/cyan shadows
if (lumPreTint < config.shadowLumThreshold) {
const shadowFactor = Math.min(1, (config.shadowLumThreshold - lumPreTint) / config.shadowLumThreshold);
tintedR -= config.shadowTintAmount * shadowFactor * 0.4; // Reduce red for cyan feel
tintedG += config.shadowTintAmount * shadowFactor * 0.1; // Slight green boost in shadows for cyan
tintedB += config.shadowTintAmount * shadowFactor; // Boost blue in shadows
}
r = tintedR;
g = tintedG;
b = tintedB;
// Clamp values after tinting
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
if (config.desaturationAmount > 0) {
const currentGray = 0.299 * r + 0.587 * g + 0.114 * b; // Luminance for desaturation
const desat = config.desaturationAmount;
r = r * (1 - desat) + currentGray * desat;
g = g * (1 - desat) + currentGray * desat;
b = b * (1 - desat) + currentGray * desat;
}
// Clamp values after desaturation
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. Vignette effect
if (config.vignetteStrength > 0 && maxDist > 0) {
const currentX = (i / 4) % canvas.width;
const currentY = Math.floor((i / 4) / canvas.width);
const dx = currentX - centerX;
const dy = currentY - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
const normalizedDist = dist / maxDist; // 0 at center, 1 at corners
const vignetteAmount = Math.pow(normalizedDist, config.vignetteFalloff) * config.vignetteStrength;
const vignetteFactor = 1.0 - vignetteAmount;
r *= vignetteFactor;
g *= vignetteFactor;
b *= vignetteFactor;
}
// Final Clamp & assignment
data[i] = Math.max(0, Math.min(255, r));
data[i + 1] = Math.max(0, Math.min(255, g));
data[i + 2] = Math.max(0, Math.min(255, b));
// Alpha channel (data[i+3]) is preserved
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes