You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, pixelationFactor = 1, brightness = 0, contrast = 0) {
// NES NTSC palette (commonly used, e.g., FCEUX default.pal)
// Source: https://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Nintendo_Entertainment_System
// Also see: http://bisqwit.iki.fi/utils/nespalette.php (default.pal for emulators like FCEUX)
// This palette has 64 entries, with 56 unique colors.
const NES_PALETTE = [
[124,124,124], [ 0, 0,252], [ 0, 0,188], [ 68, 40,188], [148, 0,132], [168, 0, 32], [168, 16, 0], [136, 20, 0],
[ 80, 48, 0], [ 0,120, 0], [ 0,104, 0], [ 0, 88, 0], [ 0, 64, 88], [ 0, 0, 0], [ 0, 0, 0], [ 0, 0, 0],
[188,188,188], [ 0,120,248], [ 0, 88,248], [104, 68,252], [216, 0,204], [228, 0, 88], [248, 56, 0], [228, 92, 16],
[172,124, 0], [ 0,184, 0], [ 0,168, 0], [ 0,168, 68], [ 0,136,136], [ 0, 0, 0], [ 0, 0, 0], [ 0, 0, 0],
[248,248,248], [ 60,188,252], [104,136,252], [152,120,248], [248,120,248], [248, 88,152], [248,120, 88], [252,160, 68],
[248,184, 0], [184,248, 24], [ 88,216, 84], [ 88,248,152], [ 0,232,216], [120,120,120], [ 0, 0, 0], [ 0, 0, 0],
[252,252,252], [164,228,252], [184,184,248], [216,184,248], [248,184,248], [248,164,192], [240,208,176], [252,224,168],
[248,216,120], [216,248,120], [184,248,184], [184,248,216], [ 0,252,252], [248,216,248], [ 0, 0, 0], [ 0, 0, 0]
];
// Helper function to find the closest color in the NES palette
// Uses squared Euclidean distance for efficiency
function findClosestNesColor(r, g, b) {
let minDistSq = Number.MAX_VALUE;
let closestColor = NES_PALETTE[13]; // Default to a black color ([0,0,0]) from the palette
for (const nesColor of NES_PALETTE) {
const dr = r - nesColor[0];
const dg = g - nesColor[1];
const db = b - nesColor[2];
const distSq = dr * dr + dg * dg + db * db;
if (distSq < minDistSq) {
minDistSq = distSq;
closestColor = nesColor;
}
// Optimization: if an exact match is found (distance is 0), no need to search further
if (minDistSq === 0) {
break;
}
}
return closestColor;
}
const outputCanvas = document.createElement('canvas');
const outputCtx = outputCanvas.getContext('2d');
// Validate and sanitize parameters
// Ensure pixelationFactor is a positive number, defaulting to 1.
pixelationFactor = Math.max(1, Number(pixelationFactor) || 1);
// Ensure brightness and contrast are numbers, defaulting to 0, and clamp to typical ranges.
brightness = Number(brightness) || 0;
brightness = Math.max(-255, Math.min(255, brightness));
contrast = Number(contrast) || 0;
contrast = Math.max(-255, Math.min(255, contrast));
// Calculate working dimensions for pixelation effect.
// Ensure width/height are at least 1 to avoid issues with 0-dimension canvases.
const workingWidth = Math.max(1, Math.floor(originalImg.width / pixelationFactor));
const workingHeight = Math.max(1, Math.floor(originalImg.height / pixelationFactor));
// Create a temporary canvas for processing
const tempCanvas = document.createElement('canvas');
tempCanvas.width = workingWidth;
tempCanvas.height = workingHeight;
const tempCtx = tempCanvas.getContext('2d');
// Draw original image to temp canvas, downscaling if pixelationFactor > 1
tempCtx.drawImage(originalImg, 0, 0, originalImg.width, originalImg.height, 0, 0, workingWidth, workingHeight);
// Get image data from the temporary (potentially downscaled) canvas
// Note: This can throw a security error if the originalImg is cross-origin and taints the canvas.
let imageData;
try {
imageData = tempCtx.getImageData(0, 0, workingWidth, workingHeight);
} catch (e) {
console.error("Error getting ImageData, possibly due to CORS policy:", e);
// Return an empty or error indicating canvas. For now, let's re-throw or return original.
// As per requirements, return a canvas. An empty one will do if processing fails.
outputCanvas.width = originalImg.width;
outputCanvas.height = originalImg.height;
outputCtx.fillStyle = "red";
outputCtx.fillRect(0,0, outputCanvas.width, outputCanvas.height);
outputCtx.fillStyle = "white";
outputCtx.textAlign = "center";
outputCtx.fillText("Error processing image (CORS?)", outputCanvas.width/2, outputCanvas.height/2);
return outputCanvas;
}
const data = imageData.data;
// Calculate contrast factor (once, outside the loop for performance)
// Standard contrast formula: Factor = (259 * (contrast + 255)) / (255 * (259 - contrast))
const contrastFactor = (259 * (contrast + 255)) / (255 * (259 - contrast));
// Process each pixel
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// Alpha channel (data[i + 3]) is preserved
// Apply brightness adjustment
if (brightness !== 0) {
r = Math.max(0, Math.min(255, r + brightness));
g = Math.max(0, Math.min(255, g + brightness));
b = Math.max(0, Math.min(255, b + brightness));
}
// Apply contrast adjustment
// If contrast is 0, contrastFactor is 1, so color values remain unchanged.
if (contrast !== 0) {
r = Math.max(0, Math.min(255, contrastFactor * (r - 128) + 128));
g = Math.max(0, Math.min(255, contrastFactor * (g - 128) + 128));
b = Math.max(0, Math.min(255, contrastFactor * (b - 128) + 128));
}
// Quantize the adjusted color to the NES palette
const nesColor = findClosestNesColor(r, g, b);
data[i] = nesColor[0];
data[i + 1] = nesColor[1];
data[i + 2] = nesColor[2];
}
// Write the modified pixel data back to the temporary canvas
tempCtx.putImageData(imageData, 0, 0);
// Prepare the final output canvas with the original image dimensions
outputCanvas.width = originalImg.width;
outputCanvas.height = originalImg.height;
// Disable image smoothing to preserve the blocky, pixelated look when upscaling
outputCtx.imageSmoothingEnabled = false;
// For cross-browser compatibility (though .imageSmoothingEnabled is widely supported)
outputCtx.mozImageSmoothingEnabled = false; // Older Firefox
outputCtx.webkitImageSmoothingEnabled = false; // Older Chrome/Safari/Opera
outputCtx.msImageSmoothingEnabled = false; // IE/Edge (deprecated)
// Draw the processed image from the temporary canvas to the final output canvas.
// This step handles upscaling (if pixelationFactor > 1) using nearest-neighbor interpolation
// due to imageSmoothingEnabled being false.
outputCtx.drawImage(tempCanvas, 0, 0, workingWidth, workingHeight, 0, 0, outputCanvas.width, outputCanvas.height);
return outputCanvas;
}
Apply Changes