You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, pixelSize = "4", dithering = "1", scanlineOpacity = "0.15") {
// Treat "Apple Major 2" as an "Apple II" (Apple 2) retro vintage computer effect,
// reducing the image colors to the classic 6-color Apple II Hi-Res palette,
// downscaling for pixelation, applying optional Floyd-Steinberg dithering,
// and rendering an authentic CRT scanline effect.
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const pSize = Math.max(1, parseInt(pixelSize));
const useDither = parseInt(dithering) !== 0;
const sOpacity = parseFloat(scanlineOpacity);
// Calculate downscaled dimensions for pixelation
const smWidth = Math.max(1, Math.floor(canvas.width / pSize));
const smHeight = Math.max(1, Math.floor(canvas.height / pSize));
const tempCanvas = document.createElement('canvas');
tempCanvas.width = smWidth;
tempCanvas.height = smHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(originalImg, 0, 0, smWidth, smHeight);
// Fetch pixels for manipulation
const imgData = tempCtx.getImageData(0, 0, smWidth, smHeight);
const data = imgData.data;
// Classic Apple II Hi-Res Palette
const palette = [
[0, 0, 0], // Black
[255, 255, 255], // White
[20, 223, 20], // Green
[202, 36, 202], // Purple
[224, 64, 0], // Orange
[57, 178, 233] // Blue
];
// Euclid distance function to locate the closest palette color
function closestColor(r, g, b) {
let minDistance = Infinity;
let closest = palette[0];
for (let i = 0; i < palette.length; i++) {
const dr = r - palette[i][0];
const dg = g - palette[i][1];
const db = b - palette[i][2];
const dist = dr * dr + dg * dg + db * db;
if (dist < minDistance) {
minDistance = dist;
closest = palette[i];
}
}
return closest;
}
const getIdx = (x, y) => (y * smWidth + x) * 4;
for (let y = 0; y < smHeight; y++) {
for (let x = 0; x < smWidth; x++) {
const idx = getIdx(x, y);
const oldR = data[idx];
const oldG = data[idx+1];
const oldB = data[idx+2];
// Quantize color to Apple 2 Palette
const newC = closestColor(oldR, oldG, oldB);
data[idx] = newC[0];
data[idx+1] = newC[1];
data[idx+2] = newC[2];
// Apply Floyd-Steinberg error diffusion dithering to retain visual nuance
if (useDither) {
const errR = oldR - newC[0];
const errG = oldG - newC[1];
const errB = oldB - newC[2];
const distributeError = (dx, dy, weight) => {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < smWidth && ny >= 0 && ny < smHeight) {
const nIdx = getIdx(nx, ny);
data[nIdx] = Math.min(255, Math.max(0, data[nIdx] + errR * weight));
data[nIdx+1] = Math.min(255, Math.max(0, data[nIdx+1] + errG * weight));
data[nIdx+2] = Math.min(255, Math.max(0, data[nIdx+2] + errB * weight));
}
};
distributeError(1, 0, 7/16);
distributeError(-1, 1, 3/16);
distributeError(0, 1, 5/16);
distributeError(1, 1, 1/16);
}
}
}
tempCtx.putImageData(imgData, 0, 0);
// Disable image smoothing for sharp, crisp retro pixels when scaling up
ctx.imageSmoothingEnabled = false;
ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height);
// Overlay CRT scanlines
if (sOpacity > 0 && pSize > 1) {
ctx.fillStyle = `rgba(0, 0, 0, ${sOpacity})`;
const lineH = Math.max(1, Math.floor(pSize * 0.25));
for (let y = 0; y < canvas.height; y += pSize) {
ctx.fillRect(0, y, canvas.width, lineH);
}
}
return canvas;
}
Apply Changes