You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, intensity = 1.0) {
const canvas = document.createElement('canvas');
// Use { willReadFrequently: true } for potential performance optimization if supported by the browser.
// This hints to the browser that we'll be using getImageData/putImageData frequently.
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Use naturalWidth/Height for the true image dimensions, fallback to width/height.
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
// If image dimensions are not valid, return an empty canvas.
if (!imgWidth || !imgHeight) {
console.error("Image Fujichrome Velvia 50 Filter: Image has no dimensions or is not loaded. Returning an empty canvas.");
canvas.width = 0;
canvas.height = 0;
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
// Draw the original image onto the canvas.
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let imageData;
try {
// Get the pixel data from the canvas.
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// This can happen due to cross-origin issues if the image is from another domain and CORS isn't set up.
console.error("Image Fujichrome Velvia 50 Filter: Could not getImageData. Canvas may be tainted by cross-origin data.", e);
// In case of an error (e.g., tainted canvas), return the canvas with the original image drawn but unprocessed.
return canvas;
}
const data = imageData.data; // This is a Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]
// Ensure intensity is a number and clamped to a reasonable range.
// Affects the strength of the filter:
// 0.0 = no effect (original image characteristics)
// 1.0 = standard "Velvia 50" effect as defined by base parameters
// > 1.0 = exaggerated effect (clamped at 2.0 to prevent extreme results)
let numIntensity = Number(intensity);
if (isNaN(numIntensity)) {
numIntensity = 1.0; // Default to 1.0 if input is not a valid number
}
const currentIntensity = Math.max(0, Math.min(2.0, numIntensity));
// Base parameters for Fujichrome Velvia 50 emulation.
// These values are chosen to approximate the film's characteristics:
// high contrast, high saturation, and specific color responses.
const base_contrast = 1.2; // Velvia is known for higher contrast. (1.0 = no change)
const base_saturation = 1.4; // Velvia has strong, vivid colors. (1.0 = no change)
// Gamma adjustments for R, G, B channels to mimic film's color rendition.
// Gamma < 1.0 generally brightens/intensifies that color component.
// Gamma > 1.0 generally darkens/deepens that color component.
const base_red_gamma = 0.95; // Enhances reds.
const base_green_gamma = 0.93; // Enhances greens, making them lush.
const base_blue_gamma = 1.05; // Deepens blues.
// Calculate effective filter parameters based on the current intensity.
// If currentIntensity is 0, parameters will result in no change.
// If currentIntensity is 1, parameters will be their base values.
const actual_contrast = 1.0 + (base_contrast - 1.0) * currentIntensity;
const actual_saturation = 1.0 + (base_saturation - 1.0) * currentIntensity;
const derive_final_gamma = (base_gamma_val, current_intensity_val) => {
// If base_gamma is 1.0 (no change), or intensity is 0, final gamma is 1.0.
if (base_gamma_val === 1.0 || current_intensity_val === 0) return 1.0;
// Interpolate the gamma adjustment: (base_gamma - 1.0) is the "amount" of gamma shift.
return 1.0 + (base_gamma_val - 1.0) * current_intensity_val;
};
const final_red_gamma = derive_final_gamma(base_red_gamma, currentIntensity);
const final_green_gamma = derive_final_gamma(base_green_gamma, currentIntensity);
const final_blue_gamma = derive_final_gamma(base_blue_gamma, currentIntensity);
// Standard luminance coefficients (Rec. 601 / NTSC) for saturation calculation.
const lumR = 0.299;
const lumG = 0.587;
const lumB = 0.114;
// Iterate over each pixel (each pixel consists of 4 values: R, G, B, A).
for (let i = 0; i < data.length; i += 4) {
// Get current pixel's RGB values. Alpha (data[i+3]) is preserved.
let r_val = data[i];
let g_val = data[i + 1];
let b_val = data[i + 2];
// Normalize RGB values to the [0, 1] range for calculations.
let r_n = r_val / 255;
let g_n = g_val / 255;
let b_n = b_val / 255;
// 1. Apply Contrast adjustment.
if (actual_contrast !== 1.0) { // Optimization: skip if no change.
r_n = (r_n - 0.5) * actual_contrast + 0.5;
g_n = (g_n - 0.5) * actual_contrast + 0.5;
b_n = (b_n - 0.5) * actual_contrast + 0.5;
}
// Clamp values to [0, 1] after contrast.
r_n = Math.max(0, Math.min(1, r_n));
g_n = Math.max(0, Math.min(1, g_n));
b_n = Math.max(0, Math.min(1, b_n));
// 2. Apply Saturation adjustment.
if (actual_saturation !== 1.0) { // Optimization: skip if no change.
// Calculate luminance of the current pixel.
const L = lumR * r_n + lumG * g_n + lumB * b_n;
// Adjust saturation: push color components further from (or closer to) luminance.
r_n = L + actual_saturation * (r_n - L);
g_n = L + actual_saturation * (g_n - L);
b_n = L + actual_saturation * (b_n - L);
}
// Clamp values to [0, 1] after saturation.
r_n = Math.max(0, Math.min(1, r_n));
g_n = Math.max(0, Math.min(1, g_n));
b_n = Math.max(0, Math.min(1, b_n));
// 3. Apply Velvia-like color gamma adjustments to individual channels.
// Math.pow(0, positive_gamma) is 0. If gamma becomes non-positive (not expected here due to intensity clamp),
// Math.pow(0, non_positive_gamma) could be Infinity or NaN.
// The (val > 0) check is a safeguard, though current clamping ensures gamma is positive.
if (final_red_gamma !== 1.0) {
r_n = (r_n > 0) ? Math.pow(r_n, final_red_gamma) : 0;
}
if (final_green_gamma !== 1.0) {
g_n = (g_n > 0) ? Math.pow(g_n, final_green_gamma) : 0;
}
if (final_blue_gamma !== 1.0) {
b_n = (b_n > 0) ? Math.pow(b_n, final_blue_gamma) : 0;
}
// Final clamp, though values should generally be within [0,1] already.
r_n = Math.max(0, Math.min(1, r_n));
g_n = Math.max(0, Math.min(1, g_n));
b_n = Math.max(0, Math.min(1, b_n));
// Convert normalized [0, 1] values back to [0, 255] and update pixel data.
data[i] = r_n * 255;
data[i + 1] = g_n * 255;
data[i + 2] = b_n * 255;
// data[i + 3] (alpha) remains unchanged.
}
// Write the modified pixel data back to the canvas.
ctx.putImageData(imageData, 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 Fujichrome Velvia 50 Filter Effect Application allows users to apply a Velvia 50 film simulation effect to their images. This tool enhances the saturation and contrast of the images, mimicking the vibrant colors and distinct tonal characteristics associated with Fujichrome Velvia 50 film. Users can adjust the intensity of the effect to achieve subtle or dramatic enhancements, making it suitable for photographers, graphic designers, and anyone looking to enhance their images with a classic film look. Common use cases include enhancing landscape photography, creating vivid visuals for social media, and adding artistic effects to personal projects.