The canvas below renders a white diagonal edge on a dark background. Switch between None, MSAA and FXAA to see what each technique does to the boundary pixels.
<!-- Layout: mode buttons + two canvases (zoomed pixel view + full diagonal) -->
<div class="controls">
<span class="label">Mode:</span>
<button id="btn-none" class="mode-btn active" data-mode="none">None</button>
<button id="btn-msaa" class="mode-btn" data-mode="msaa">MSAA 4×</button>
<button id="btn-fxaa" class="mode-btn" data-mode="fxaa">FXAA</button>
</div>
<div class="canvases">
<div class="panel">
<div class="panel-label">Zoomed (10×)</div>
<canvas id="zoom" width="200" height="200"></canvas>
</div>
<div class="panel">
<div class="panel-label">Full view</div>
<canvas id="full" width="320" height="200"></canvas>
</div>
</div>
<div id="info" class="info">None — each pixel is binary: fully lit or fully dark. The staircase is sharp.</div>
/* Styles for controls, canvas panels, and info bar */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; background: #f5f7fa; color: #222; }
.controls { display: flex; align-items: center; gap: .5rem; padding: .6rem 0 .8rem; flex-wrap: wrap; }
.label { font-size: .85rem; font-weight: 600; color: #555; }
.mode-btn { font: 600 13px system-ui; padding: .35rem .8rem; border: 1.5px solid #1d3557;
background: #fff; color: #1d3557; border-radius: 6px; cursor: pointer; transition: all .15s; }
.mode-btn.active { background: #1d3557; color: #fff; }
.canvases { display: flex; gap: 1rem; flex-wrap: wrap; }
.panel { display: flex; flex-direction: column; gap: .3rem; }
.panel-label { font-size: .78rem; font-weight: 600; color: #666; text-align: center; }
canvas { display: block; border: 1px solid #cdd5dd; border-radius: 6px; background: #111;
image-rendering: pixelated; }
#zoom { width: 200px; height: 200px; }
#full { width: 320px; height: 200px; }
.info { margin-top: .7rem; font-size: .85rem; color: #444; line-height: 1.5; min-height: 2.5em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Grab canvas elements and their 2D contexts
const zoomCanvas = document.getElementById('zoom');
const fullCanvas = document.getElementById('full');
const zCtx = zoomCanvas.getContext('2d');
const fCtx = fullCanvas.getContext('2d');
const infoEl = document.getElementById('info');
// Scene parameters
// Diagonal edge defined by y = EDGE_SLOPE * x - EDGE_OFFSET (pixels below the line are bright)
const EDGE_SLOPE = 1.4;
const EDGE_OFFSET = 30;
// Coverage sampling helper
// Returns fraction [0,1] of sub-pixel samples inside the bright half of the scene
function coverageAt(px, py, subSamples) {
let inside = 0;
const step = 1 / subSamples;
const half = step / 2;
for (let sy = 0; sy < subSamples; sy++) {
for (let sx = 0; sx < subSamples; sx++) {
const x = px + half + sx * step;
const y = py + half + sy * step;
// Point is inside (bright) if below the diagonal line
if (y > EDGE_SLOPE * x - EDGE_OFFSET) inside++;
}
}
return inside / (subSamples * subSamples);
}
// Render without anti-aliasing (binary coverage)
function renderNone(ctx, w, h) {
const img = ctx.createImageData(w, h);
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
// Binary inside/outside test for each pixel center
const inside = (py > EDGE_SLOPE * px - EDGE_OFFSET) ? 1 : 0;
const bright = inside * 255;
const i = (py * w + px) * 4;
img.data[i] = bright; img.data[i+1] = bright; img.data[i+2] = bright; img.data[i+3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
// Render with MSAA using NxN sub-pixel grid per pixel
function renderMSAA(ctx, w, h, samples) {
const img = ctx.createImageData(w, h);
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
const cov = coverageAt(px, py, samples);
const bright = Math.round(cov * 255);
const i = (py * w + px) * 4;
img.data[i] = bright; img.data[i+1] = bright; img.data[i+2] = bright; img.data[i+3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
// Render with FXAA (post-process luminance edge detection + blend)
function renderFXAA(ctx, w, h) {
// Step 1 - render the aliased base image
renderNone(ctx, w, h);
const src = ctx.getImageData(0, 0, w, h);
const dst = ctx.createImageData(w, h);
const lum = (r, g, b) => 0.299 * r + 0.587 * g + 0.114 * b;
// Step 2 - detect edges by local luminance contrast and blur along them
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
const idx = (py * w + px) * 4;
const lC = lum(src.data[idx], src.data[idx+1], src.data[idx+2]);
// Sample luminance of the 4 cardinal neighbors
const getL = (x, y) => {
if (x < 0 || x >= w || y < 0 || y >= h) return lC;
const ii = (y * w + x) * 4;
return lum(src.data[ii], src.data[ii+1], src.data[ii+2]);
};
const lN = getL(px, py-1), lS = getL(px, py+1);
const lE = getL(px+1, py), lW = getL(px-1, py);
const maxL = Math.max(lC, lN, lS, lE, lW);
const minL = Math.min(lC, lN, lS, lE, lW);
const contrast = maxL - minL;
// Only blur pixels with contrast above this threshold (0-255 scale)
const THRESHOLD = 40;
let blended;
if (contrast < THRESHOLD) {
blended = src.data[idx];
} else {
// Blur along the dominant edge axis (horizontal or vertical)
const isHoriz = Math.abs(lN + lS - 2 * lC) >= Math.abs(lE + lW - 2 * lC);
const blend = isHoriz
? (src.data[((Math.max(py-1,0)) * w + px) * 4] + src.data[((Math.min(py+1,h-1)) * w + px) * 4]) / 2
: (src.data[(py * w + Math.max(px-1,0)) * 4] + src.data[(py * w + Math.min(px+1,w-1)) * 4]) / 2;
blended = (src.data[idx] + blend) / 2;
}
const out = Math.round(blended);
dst.data[idx] = out; dst.data[idx+1] = out; dst.data[idx+2] = out; dst.data[idx+3] = 255;
}
}
ctx.putImageData(dst, 0, 0);
}
// Zoom renderer
// Magnifies a 20x20 tile from the full render at 10x scale to show pixel structure
const ZOOM_W = 20, ZOOM_H = 20, ZOOM_SCALE = 10;
const ZOOM_X = 18;
function renderZoom(srcCtx, srcW) {
const src = srcCtx.getImageData(ZOOM_X, 0, ZOOM_W, ZOOM_H);
zCtx.clearRect(0, 0, 200, 200);
for (let py = 0; py < ZOOM_H; py++) {
for (let px = 0; px < ZOOM_W; px++) {
const i = (py * ZOOM_W + px) * 4;
const r = src.data[i], g = src.data[i+1], b = src.data[i+2];
zCtx.fillStyle = `rgb(${r},${g},${b})`;
zCtx.fillRect(px * ZOOM_SCALE, py * ZOOM_SCALE, ZOOM_SCALE, ZOOM_SCALE);
}
}
// Draw pixel grid overlay on the zoom view
zCtx.strokeStyle = 'rgba(100,150,200,0.25)';
zCtx.lineWidth = 0.5;
for (let i = 0; i <= ZOOM_W; i++) { zCtx.beginPath(); zCtx.moveTo(i*ZOOM_SCALE,0); zCtx.lineTo(i*ZOOM_SCALE,200); zCtx.stroke(); }
for (let i = 0; i <= ZOOM_H; i++) { zCtx.beginPath(); zCtx.moveTo(0,i*ZOOM_SCALE); zCtx.lineTo(200,i*ZOOM_SCALE); zCtx.stroke(); }
}
const INFO = {
none: 'None — each pixel is binary: fully lit or fully dark. The staircase is sharp.',
msaa: 'MSAA 4× — 16 sub-pixel samples per pixel. Boundary pixels blend proportionally to edge coverage.',
fxaa: 'FXAA — post-process luminance edge detection + directional blur. Fast, but may soften fine detail.'
};
let currentMode = 'none';
function draw(mode) {
currentMode = mode;
const W = fullCanvas.width, H = fullCanvas.height;
if (mode === 'none') {
renderNone(fCtx, W, H);
} else if (mode === 'msaa') {
renderMSAA(fCtx, W, H, 4);
} else {
renderFXAA(fCtx, W, H);
}
renderZoom(fCtx, W);
infoEl.textContent = INFO[mode];
}
// Wire up mode toggle buttons
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
draw(btn.dataset.mode);
});
});
draw('none');
<!-- Layout: mode buttons + two canvases (zoomed pixel view + full diagonal) -->
<div class="controls">
<span class="label">Mode:</span>
<button id="btn-none" class="mode-btn active" data-mode="none">None</button>
<button id="btn-msaa" class="mode-btn" data-mode="msaa">MSAA 4×</button>
<button id="btn-fxaa" class="mode-btn" data-mode="fxaa">FXAA</button>
</div>
<div class="canvases">
<div class="panel">
<div class="panel-label">Zoomed (10×)</div>
<canvas id="zoom" width="200" height="200"></canvas>
</div>
<div class="panel">
<div class="panel-label">Full view</div>
<canvas id="full" width="320" height="200"></canvas>
</div>
</div>
<div id="info" class="info">None — each pixel is binary: fully lit or fully dark. The staircase is sharp.</div>
/* Styles for controls, canvas panels, and info bar */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; background: #f5f7fa; color: #222; }
.controls { display: flex; align-items: center; gap: .5rem; padding: .6rem 0 .8rem; flex-wrap: wrap; }
.label { font-size: .85rem; font-weight: 600; color: #555; }
.mode-btn { font: 600 13px system-ui; padding: .35rem .8rem; border: 1.5px solid #1d3557;
background: #fff; color: #1d3557; border-radius: 6px; cursor: pointer; transition: all .15s; }
.mode-btn.active { background: #1d3557; color: #fff; }
.canvases { display: flex; gap: 1rem; flex-wrap: wrap; }
.panel { display: flex; flex-direction: column; gap: .3rem; }
.panel-label { font-size: .78rem; font-weight: 600; color: #666; text-align: center; }
canvas { display: block; border: 1px solid #cdd5dd; border-radius: 6px; background: #111;
image-rendering: pixelated; }
#zoom { width: 200px; height: 200px; }
#full { width: 320px; height: 200px; }
.info { margin-top: .7rem; font-size: .85rem; color: #444; line-height: 1.5; min-height: 2.5em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Grab canvas elements and their 2D contexts
const zoomCanvas = document.getElementById('zoom');
const fullCanvas = document.getElementById('full');
const zCtx = zoomCanvas.getContext('2d');
const fCtx = fullCanvas.getContext('2d');
const infoEl = document.getElementById('info');
// Scene parameters
// Diagonal edge defined by y = EDGE_SLOPE * x - EDGE_OFFSET (pixels below the line are bright)
const EDGE_SLOPE = 1.4;
const EDGE_OFFSET = 30;
// Coverage sampling helper
// Returns fraction [0,1] of sub-pixel samples inside the bright half of the scene
function coverageAt(px, py, subSamples) {
let inside = 0;
const step = 1 / subSamples;
const half = step / 2;
for (let sy = 0; sy < subSamples; sy++) {
for (let sx = 0; sx < subSamples; sx++) {
const x = px + half + sx * step;
const y = py + half + sy * step;
// Point is inside (bright) if below the diagonal line
if (y > EDGE_SLOPE * x - EDGE_OFFSET) inside++;
}
}
return inside / (subSamples * subSamples);
}
// Render without anti-aliasing (binary coverage)
function renderNone(ctx, w, h) {
const img = ctx.createImageData(w, h);
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
// Binary inside/outside test for each pixel center
const inside = (py > EDGE_SLOPE * px - EDGE_OFFSET) ? 1 : 0;
const bright = inside * 255;
const i = (py * w + px) * 4;
img.data[i] = bright; img.data[i+1] = bright; img.data[i+2] = bright; img.data[i+3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
// Render with MSAA using NxN sub-pixel grid per pixel
function renderMSAA(ctx, w, h, samples) {
const img = ctx.createImageData(w, h);
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
const cov = coverageAt(px, py, samples);
const bright = Math.round(cov * 255);
const i = (py * w + px) * 4;
img.data[i] = bright; img.data[i+1] = bright; img.data[i+2] = bright; img.data[i+3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
// Render with FXAA (post-process luminance edge detection + blend)
function renderFXAA(ctx, w, h) {
// Step 1 - render the aliased base image
renderNone(ctx, w, h);
const src = ctx.getImageData(0, 0, w, h);
const dst = ctx.createImageData(w, h);
const lum = (r, g, b) => 0.299 * r + 0.587 * g + 0.114 * b;
// Step 2 - detect edges by local luminance contrast and blur along them
for (let py = 0; py < h; py++) {
for (let px = 0; px < w; px++) {
const idx = (py * w + px) * 4;
const lC = lum(src.data[idx], src.data[idx+1], src.data[idx+2]);
// Sample luminance of the 4 cardinal neighbors
const getL = (x, y) => {
if (x < 0 || x >= w || y < 0 || y >= h) return lC;
const ii = (y * w + x) * 4;
return lum(src.data[ii], src.data[ii+1], src.data[ii+2]);
};
const lN = getL(px, py-1), lS = getL(px, py+1);
const lE = getL(px+1, py), lW = getL(px-1, py);
const maxL = Math.max(lC, lN, lS, lE, lW);
const minL = Math.min(lC, lN, lS, lE, lW);
const contrast = maxL - minL;
// Only blur pixels with contrast above this threshold (0-255 scale)
const THRESHOLD = 40;
let blended;
if (contrast < THRESHOLD) {
blended = src.data[idx];
} else {
// Blur along the dominant edge axis (horizontal or vertical)
const isHoriz = Math.abs(lN + lS - 2 * lC) >= Math.abs(lE + lW - 2 * lC);
const blend = isHoriz
? (src.data[((Math.max(py-1,0)) * w + px) * 4] + src.data[((Math.min(py+1,h-1)) * w + px) * 4]) / 2
: (src.data[(py * w + Math.max(px-1,0)) * 4] + src.data[(py * w + Math.min(px+1,w-1)) * 4]) / 2;
blended = (src.data[idx] + blend) / 2;
}
const out = Math.round(blended);
dst.data[idx] = out; dst.data[idx+1] = out; dst.data[idx+2] = out; dst.data[idx+3] = 255;
}
}
ctx.putImageData(dst, 0, 0);
}
// Zoom renderer
// Magnifies a 20x20 tile from the full render at 10x scale to show pixel structure
const ZOOM_W = 20, ZOOM_H = 20, ZOOM_SCALE = 10;
const ZOOM_X = 18;
function renderZoom(srcCtx, srcW) {
const src = srcCtx.getImageData(ZOOM_X, 0, ZOOM_W, ZOOM_H);
zCtx.clearRect(0, 0, 200, 200);
for (let py = 0; py < ZOOM_H; py++) {
for (let px = 0; px < ZOOM_W; px++) {
const i = (py * ZOOM_W + px) * 4;
const r = src.data[i], g = src.data[i+1], b = src.data[i+2];
zCtx.fillStyle = `rgb(${r},${g},${b})`;
zCtx.fillRect(px * ZOOM_SCALE, py * ZOOM_SCALE, ZOOM_SCALE, ZOOM_SCALE);
}
}
// Draw pixel grid overlay on the zoom view
zCtx.strokeStyle = 'rgba(100,150,200,0.25)';
zCtx.lineWidth = 0.5;
for (let i = 0; i <= ZOOM_W; i++) { zCtx.beginPath(); zCtx.moveTo(i*ZOOM_SCALE,0); zCtx.lineTo(i*ZOOM_SCALE,200); zCtx.stroke(); }
for (let i = 0; i <= ZOOM_H; i++) { zCtx.beginPath(); zCtx.moveTo(0,i*ZOOM_SCALE); zCtx.lineTo(200,i*ZOOM_SCALE); zCtx.stroke(); }
}
const INFO = {
none: 'None — each pixel is binary: fully lit or fully dark. The staircase is sharp.',
msaa: 'MSAA 4× — 16 sub-pixel samples per pixel. Boundary pixels blend proportionally to edge coverage.',
fxaa: 'FXAA — post-process luminance edge detection + directional blur. Fast, but may soften fine detail.'
};
let currentMode = 'none';
function draw(mode) {
currentMode = mode;
const W = fullCanvas.width, H = fullCanvas.height;
if (mode === 'none') {
renderNone(fCtx, W, H);
} else if (mode === 'msaa') {
renderMSAA(fCtx, W, H, 4);
} else {
renderFXAA(fCtx, W, H);
}
renderZoom(fCtx, W);
infoEl.textContent = INFO[mode];
}
// Wire up mode toggle buttons
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
draw(btn.dataset.mode);
});
});
draw('none');
Comments
Loading comments...