start .
me .
HTML Document File : improved-antialiasing.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Improved Anti-aliasing for Pixel Graphics</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
margin-bottom: 10px;
}
h3 {
color: #555;
margin-top: 0;
margin-bottom: 20px;
font-weight: normal;
}
.canvas-container {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
canvas {
border: 1px solid #333;
background-color: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.controls {
display: flex;
gap: 15px;
margin: 20px 0;
}
button {
padding: 8px 15px;
cursor: pointer;
background-color: #4a90e2;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
transition: background-color 0.2s;
}
button:hover {
background-color: #3a7bc8;
}
.canvas-label {
text-align: center;
font-weight: bold;
margin-top: 5px;
color: #555;
}
.description {
max-width: 800px;
margin-top: 20px;
line-height: 1.5;
color: #555;
font-size: 14px;
text-align: center;
}
</style>
</head>
<body>
<h1>Improved Anti-aliasing for Pixel Graphics</h1>
<h3>Supersampling & Multi-pass Edge Detection</h3>
<div class="controls">
<button onclick="drawCircle()">Circle</button>
<button onclick="drawStar()">Star</button>
<button onclick="drawCustomShape()">Custom Shape</button>
<button onclick="toggleZoom()">Toggle Zoom</button>
</div>
<div class="canvas-container">
<div>
<canvas id="basicCanvas" width="200" height="200"></canvas>
<div class="canvas-label">Standard Pixels</div>
</div>
<div>
<canvas id="aaCanvas" width="200" height="200"></canvas>
<div class="canvas-label">With Anti-aliasing</div>
</div>
</div>
<div class="description">
This demo uses a supersampling technique with 4x4 subpixels and edge detection to create smooth anti-aliased shapes.
The left canvas shows standard pixel-by-pixel rendering, while the right shows the anti-aliased version.
</div>
<script>
// Get the canvases and contexts
const basicCanvas = document.getElementById('basicCanvas');
const aaCanvas = document.getElementById('aaCanvas');
const basicCtx = basicCanvas.getContext('2d');
const aaCtx = aaCanvas.getContext('2d');
// Set initial scale
let scale = 1;
let zoomMode = false;
// Clear both canvases
function clearCanvases() {
basicCtx.clearRect(0, 0, basicCanvas.width, basicCanvas.height);
aaCtx.clearRect(0, 0, aaCanvas.width, aaCanvas.height);
// Reset transformations
basicCtx.setTransform(1, 0, 0, 1, 0, 0);
aaCtx.setTransform(1, 0, 0, 1, 0, 0);
// Apply zoom if enabled
if (zoomMode) {
basicCtx.scale(2, 2);
aaCtx.scale(2, 2);
}
}
// Toggle zoom mode
function toggleZoom() {
zoomMode = !zoomMode;
// Redraw current shape with new zoom setting
redrawCurrentShape();
}
// Keep track of current shape
let currentShape = 'circle';
function redrawCurrentShape() {
if (currentShape === 'circle') drawCircle();
else if (currentShape === 'star') drawStar();
else if (currentShape === 'custom') drawCustomShape();
}
// Function to set a pixel on the basic canvas (without anti-aliasing)
function setBasicPixel(x, y, r, g, b) {
basicCtx.fillStyle = `rgb(${r}, ${g}, ${b})`;
basicCtx.fillRect(x, y, 1, 1);
}
// Function to set a pixel on the anti-aliased canvas with opacity
function setAAPixel(x, y, r, g, b, a) {
aaCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;
aaCtx.fillRect(x, y, 1, 1);
}
// Create a supersampled buffer (4x4 subpixels per pixel)
function createSupersampledBuffer(width, height) {
const buffer = new Array(width * 4);
for (let x = 0; x < width * 4; x++) {
buffer[x] = new Array(height * 4).fill(0);
}
return buffer;
}
// Draw a shape to the supersampled buffer using a shape function
function drawShapeToBuffer(buffer, width, height, shapeFunc) {
// Draw to supersampled buffer (4x resolution)
const bufferWidth = width * 4;
const bufferHeight = height * 4;
// Use the provided shape function to determine if a point is inside or outside
for (let y = 0; y < bufferHeight; y++) {
for (let x = 0; x < bufferWidth; x++) {
const realX = x / 4;
const realY = y / 4;
if (shapeFunc(realX, realY)) {
buffer[x][y] = 1;
}
}
}
return buffer;
}
// Create a Gaussian kernel for smoother anti-aliasing
function createGaussianKernel(size, sigma) {
const kernel = [];
const center = Math.floor(size / 2);
let sum = 0;
// Generate kernel values
for (let y = 0; y < size; y++) {
kernel[y] = [];
for (let x = 0; x < size; x++) {
const dx = x - center;
const dy = y - center;
// Gaussian function
const g = Math.exp(-(dx*dx + dy*dy) / (2 * sigma * sigma));
kernel[y][x] = g;
sum += g;
}
}
// Normalize the kernel
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
kernel[y][x] /= sum;
}
}
return kernel;
}
// Apply Gaussian blur to buffer for smoother edges
function applyGaussianBlur(buffer, width, height) {
const blurredBuffer = new Array(width);
for (let i = 0; i < width; i++) {
blurredBuffer[i] = new Array(height).fill(0);
}
const kernel = createGaussianKernel(5, 1.0);
const kernelSize = 5;
const kernelRadius = Math.floor(kernelSize / 2);
// Apply kernel to each point
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let sum = 0;
// Apply kernel
for (let ky = -kernelRadius; ky <= kernelRadius; ky++) {
for (let kx = -kernelRadius; kx <= kernelRadius; kx++) {
const sampleX = Math.min(Math.max(x + kx, 0), width - 1);
const sampleY = Math.min(Math.max(y + ky, 0), height - 1);
const kernelValue = kernel[ky + kernelRadius][kx + kernelRadius];
sum += buffer[sampleX][sampleY] * kernelValue;
}
}
blurredBuffer[x][y] = sum;
}
}
return blurredBuffer;
}
// Improved edge detection for anti-aliasing
function detectEdges(buffer, width, height) {
const edgeBuffer = new Array(width);
for (let i = 0; i < width; i++) {
edgeBuffer[i] = new Array(height).fill(0);
}
const sobelX = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]];
const sobelY = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let gx = 0;
let gy = 0;
// Apply Sobel operators
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const val = buffer[x + kx][y + ky];
gx += val * sobelX[ky + 1][kx + 1];
gy += val * sobelY[ky + 1][kx + 1];
}
}
// Calculate gradient magnitude
const magnitude = Math.sqrt(gx * gx + gy * gy);
edgeBuffer[x][y] = Math.min(magnitude, 1);
}
}
return edgeBuffer;
}
// Downsample the supersampled buffer to actual pixels with anti-aliasing
function downsampleBuffer(buffer, width, height, r, g, b) {
// Clear both canvases first
clearCanvases();
// Apply Gaussian blur for edge smoothing
const smoothedBuffer = applyGaussianBlur(buffer, width * 4, height * 4);
// Edge detection to enhance boundaries
const edgeBuffer = detectEdges(smoothedBuffer, width * 4, height * 4);
// Combine original buffer with edge information
for (let y = 0; y < height * 4; y++) {
for (let x = 0; x < width * 4; x++) {
// Enhance edges while keeping interior solid
if (smoothedBuffer[x][y] > 0.1) {
// Apply edge enhancement
let value = smoothedBuffer[x][y];
// If it's an edge pixel, adjust its value for smoother transitions
if (edgeBuffer[x][y] > 0.1) {
// Apply a more gradual transition at edges
value = Math.min(value + edgeBuffer[x][y] * 0.2, 1.0);
}
smoothedBuffer[x][y] = value;
}
}
}
// Downsample and draw to both canvases
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Calculate average value from 4x4 supersampled grid
let sum = 0;
for (let sy = 0; sy < 4; sy++) {
for (let sx = 0; sx < 4; sx++) {
sum += smoothedBuffer[x * 4 + sx][y * 4 + sy];
}
}
// Average of all subpixels
const avgValue = sum / 16;
// Draw to basic canvas (aliased - either on or off)
if (avgValue > 0.5) {
setBasicPixel(x, y, r, g, b);
}
// Draw to anti-aliased canvas with proper alpha
if (avgValue > 0) {
setAAPixel(x, y, r, g, b, avgValue);
}
}
}
}
// Function to draw a circle
function drawCircle() {
currentShape = 'circle';
const width = basicCanvas.width / (zoomMode ? 2 : 1);
const height = basicCanvas.height / (zoomMode ? 2 : 1);
const centerX = width / 2;
const centerY = height / 2;
const radius = Math.min(width, height) * 0.4;
// Create buffer
const buffer = createSupersampledBuffer(width, height);
// Define circle shape function
const circleFunc = (x, y) => {
const dx = x - centerX;
const dy = y - centerY;
return (dx * dx + dy * dy) <= (radius * radius);
};
// Draw to buffer and render
const filledBuffer = drawShapeToBuffer(buffer, width, height, circleFunc);
downsampleBuffer(filledBuffer, width, height, 0, 127, 255);
}
// Function to draw a star
function drawStar() {
currentShape = 'star';
const width = basicCanvas.width / (zoomMode ? 2 : 1);
const height = basicCanvas.height / (zoomMode ? 2 : 1);
const centerX = width / 2;
const centerY = height / 2;
const outerRadius = Math.min(width, height) * 0.4;
const innerRadius = outerRadius * 0.4;
const points = 5;
// Create buffer
const buffer = createSupersampledBuffer(width, height);
// Define star shape function
const starFunc = (x, y) => {
const dx = x - centerX;
const dy = y - centerY;
const angle = Math.atan2(dy, dx);
const distToCenter = Math.sqrt(dx * dx + dy * dy);
// Determine radius at this angle
const angleToPoint = (angle % (Math.PI * 2 / points)) / (Math.PI * 2 / points);
const pointiness = Math.abs(angleToPoint - 0.5) * 2;
const radius = innerRadius + (outerRadius - innerRadius) * pointiness;
return distToCenter <= radius;
};
// Draw to buffer and render
const filledBuffer = drawShapeToBuffer(buffer, width, height, starFunc);
downsampleBuffer(filledBuffer, width, height, 255, 127, 0);
}
// Function to draw a custom shape
function drawCustomShape() {
currentShape = 'custom';
const width = basicCanvas.width / (zoomMode ? 2 : 1);
const height = basicCanvas.height / (zoomMode ? 2 : 1);
// Create buffer
const buffer = createSupersampledBuffer(width, height);
// Define a custom shape - a heart
const heartFunc = (x, y) => {
// Adjust coordinates to center and scale
const scale = Math.min(width, height) * 0.3;
const nx = (x - width/2) / scale;
const ny = (y - height/2) / scale + 0.4;
// Heart equation
const nx2 = nx * nx;
const ny2 = ny * ny;
return ny2 < (nx2 * (1 - nx2 - ny2 + Math.abs(nx)));
};
// Draw to buffer and render
const filledBuffer = drawShapeToBuffer(buffer, width, height, heartFunc);
downsampleBuffer(filledBuffer, width, height, 255, 0, 127);
}
// Initial drawing
drawCircle();
</script>
</body>
</html>