You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, saturationReduction = 0.5, colorTintStrength = 0.4, tintColorStr = "130,100,70", textureStrength = 20, stitchSize = 3, quantizationLevels = 8) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
// If image has no dimensions, return an empty canvas or handle error
console.error("Image has no dimensions.");
return canvas;
}
// Ensure parameters are of correct type and within reasonable bounds
const currentSaturationReduction = Math.max(0, Math.min(1, Number(saturationReduction)));
const currentColorTintStrength = Math.max(0, Math.min(1, Number(colorTintStrength)));
const currentTextureStrength = Number(textureStrength);
const effectiveStitchSize = Math.max(1, Math.floor(Number(stitchSize))); // Ensure stitchSize is at least 1
const currentQuantizationLevels = Math.floor(Number(quantizationLevels));
let tintR = 130, tintG = 100, tintB = 70; // Default tint color components
if (tintColorStr && typeof tintColorStr === 'string') {
const parsedTint = tintColorStr.split(',').map(Number);
if (parsedTint.length === 3 && parsedTint.every(c => !isNaN(c) && c >= 0 && c <= 255)) {
[tintR, tintG, tintB] = parsedTint;
} else {
console.warn(`Invalid tintColorStr: "${tintColorStr}". Using default tint.`);
}
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Desaturation
if (currentSaturationReduction > 0) {
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
r = r * (1 - currentSaturationReduction) + gray * currentSaturationReduction;
g = g * (1 - currentSaturationReduction) + gray * currentSaturationReduction;
b = b * (1 - currentSaturationReduction) + gray * currentSaturationReduction;
}
// 2. Color Tint
if (currentColorTintStrength > 0) {
r = r * (1 - currentColorTintStrength) + tintR * currentColorTintStrength;
g = g * (1 - currentColorTintStrength) + tintG * currentColorTintStrength;
b = b * (1 - currentColorTintStrength) + tintB * currentColorTintStrength;
}
// 3. Simulated Weave Texture
if (currentTextureStrength !== 0 && effectiveStitchSize > 0) {
const pixelIndex = i / 4;
const x = pixelIndex % width;
const y = Math.floor(pixelIndex / width);
let offset = 0;
// Determine if the current pixel falls into a "light" or "dark" phase for horizontal "threads"
if (Math.floor(y / effectiveStitchSize) % 2 === 0) {
offset += currentTextureStrength / 2;
} else {
offset -= currentTextureStrength / 2;
}
// Determine if the current pixel falls into a "light" or "dark" phase for vertical "threads"
if (Math.floor(x / effectiveStitchSize) % 2 === 0) {
offset += currentTextureStrength / 2;
} else {
offset -= currentTextureStrength / 2;
}
// The combination creates blocks that are either brightened (offset = +textureStrength),
// darkened (offset = -textureStrength), or unchanged (offset = 0), simulating a weave.
r += offset;
g += offset;
b += offset;
}
// 4. Quantization / Posterization
// currentQuantizationLevels defines the number of distinct color values per channel (e.g., 8 means 8 shades from black to white)
if (currentQuantizationLevels >= 2 && currentQuantizationLevels <= 255) {
// Calculate the size of each quantization step
const step = 255.0 / (currentQuantizationLevels - 1);
r = Math.round(r / step) * step;
g = Math.round(g / step) * step;
b = Math.round(b / step) * step;
} else if (currentQuantizationLevels === 1) { // Special case: 1 level means mid-gray (or channel specific mid-value)
r = g = b = 128; // Or use values from tint if specific color output is desired
}
// If currentQuantizationLevels is 0, less than 0, or > 255, no quantization is applied.
// 5. Clamping RGB values to 0-255 range
data[i] = Math.max(0, Math.min(255, Math.round(r)));
data[i+1] = Math.max(0, Math.min(255, Math.round(g)));
data[i+2] = Math.max(0, Math.min(255, Math.round(b)));
// Alpha channel (data[i+3]) is preserved
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes