You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, outlineStrength = 2, outlineColor = "0,0,0", posterizeLevels = 5, goldHueAmount = 0.3, saturationBoost = 0.2) {
// Helper: RGB to HSL
// Converts an RGB color value to HSL. Conversion formula
// adapted from easyrgb.com. Assumes r, g, and b are contained
// in the set [0, 255] and returns h, s, and l in the set [0, 1].
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
let max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
// Helper: HSL to RGB
// Converts an HSL color value to RGB. Conversion formula
// adapted from easyrgb.com. Assumes h, s, and l are contained
// in the set [0, 1] and returns r, g, and b in the set [0, 255].
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
let p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
// 1. Setup Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Use naturalWidth/Height for HTMLImageElement, fallback to width/height
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
// Draw original image to the main canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Get image data for color processing
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let data = imageData.data; // This is a view on imageData.buffer_
// Create a pristine copy of original image data for accurate outline detection
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
tempCtx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const originalPixelDataForEdges = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height).data;
// Parse outlineColor string "R,G,B" into an array of numbers
const [oR, oG, oB] = outlineColor.split(',').map(c => parseInt(c.trim(), 10));
// 2. Pixel Manipulation for color effects (applies to 'data' array)
for (let i = 0; i < data.length; i += 4) {
let r_px = data[i];
let g_px = data[i+1];
let b_px = data[i+2];
// a. Gold Hue / Warm tint application
// goldHueAmount typically 0 (no effect) to 1 (strong effect)
if (goldHueAmount > 0 && goldHueAmount <= 1) {
r_px = Math.min(255, r_px + 30 * goldHueAmount); // Increase Red
g_px = Math.min(255, g_px + 20 * goldHueAmount); // Increase Green (less than Red for gold)
b_px = Math.max(0, b_px - 15 * goldHueAmount); // Decrease Blue for warmth
}
// b. Saturation Boost
// saturationBoost typically 0 (no effect) to 1 (e.g., 0.2 for 20% boost)
if (saturationBoost > 0 && saturationBoost <= 1 ) {
let [h, s, l] = rgbToHsl(r_px, g_px, b_px);
s = Math.min(1, s * (1 + saturationBoost)); // Increase saturation
[r_px, g_px, b_px] = hslToRgb(h, s, l);
}
// c. Posterization
// posterizeLevels = number of color levels per channel (e.g., 2 to 255)
if (posterizeLevels > 1 && posterizeLevels < 256) {
const numLevels = Math.max(2, Math.floor(posterizeLevels)); // Ensure integer levels >= 2
const step = 255 / (numLevels - 1); // Size of each color step
r_px = Math.round(Math.round(r_px / step) * step);
g_px = Math.round(Math.round(g_px / step) * step);
b_px = Math.round(Math.round(b_px / step) * step);
}
data[i] = r_px;
data[i+1] = g_px;
data[i+2] = b_px;
// Alpha (data[i+3]) remains unchanged
}
// At this point, 'data' (and thus 'imageData.data') contains the color-processed pixels.
// 3. Outline Detection using Sobel operator
// Outlines are calculated based on 'originalPixelDataForEdges' and drawn onto 'outputPixelData',
// which starts as a copy of the color-processed 'data'.
let outputPixelData = new Uint8ClampedArray(data); // Initialize with color-processed data
if (outlineStrength > 0) {
const width = canvas.width;
const height = canvas.height;
// Sobel kernels for edge detection
const Gx_kernel = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]];
const Gy_kernel = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];
// Iterate excluding 1px border to simplify kernel application (no boundary checks needed)
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let Gx = 0;
let Gy = 0;
// Apply Sobel kernels to a 3x3 neighborhood
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const pixelX = x + kx;
const pixelY = y + ky;
const offset = (pixelY * width + pixelX) * 4;
// Use luminance of the original image for cleaner edges
const r_edge = originalPixelDataForEdges[offset];
const g_edge = originalPixelDataForEdges[offset + 1];
const b_edge = originalPixelDataForEdges[offset + 2];
const luminance = 0.299 * r_edge + 0.587 * g_edge + 0.114 * b_edge;
Gx += luminance * Gx_kernel[ky + 1][kx + 1];
Gy += luminance * Gy_kernel[ky + 1][kx + 1];
}
}
const magnitude = Math.sqrt(Gx * Gx + Gy * Gy);
const currentOffset = (y * width + x) * 4;
// Threshold for edge detection, adjustable by outlineStrength
// Higher strength means a lower threshold, detecting more (fainter) edges.
const threshold = Math.max(10, 150 - outlineStrength * 15);
if (magnitude > threshold) {
// If an edge is detected, color the pixel using the specified outlineColor
outputPixelData[currentOffset] = oR;
outputPixelData[currentOffset + 1] = oG;
outputPixelData[currentOffset + 2] = oB;
outputPixelData[currentOffset + 3] = 255; // Ensure outline is opaque
}
// Else, the pixel from color-processed data (already in outputPixelData) is kept.
}
}
imageData.data.set(outputPixelData); // Update imageData with the outlined version
}
// If outlineStrength is 0, imageData still holds the color-processed data, which is correct.
// 4. Put final image data (with color Fx and outlines) onto the main 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 Illuminated Manuscript Filter Effect Tool allows users to apply artistic effects to their images, mimicking the appearance of illuminated manuscript art. This tool features customizable parameters, enabling users to adjust outline strength and color, posterization levels, as well as enhance warmth and saturation. Ideal for creative professionals, graphic designers, or hobbyists looking to add a unique, vintage flair to images for use in projects such as invitations, art prints, or digital artwork.