Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
7.5 kB · 284 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285// KidLisp Mini Renderer — Software Pixel Buffer// Direct canvas manipulation with Bresenham line and midpoint circle// ~150 lines, produces ~3-4 KB minified
class Renderer { constructor(width, height) { this.width = width; this.height = height; this.pixels = new Uint8ClampedArray(width * height * 4); this.imageData = new ImageData(this.pixels, width, height);
// Current color state this.r = 255; this.g = 255; this.b = 255; this.a = 255;
// Transform stacks this.transforms = []; // { type, value } this.fade = null; this.fadeFrame = 0; }
setColor(r, g, b, a = 255) { this.r = Math.round(r); this.g = Math.round(g); this.b = Math.round(b); this.a = Math.round(a); }
// Clear entire buffer with current color wipe(r, g, b, a = 255) { const [cr, cg, cb, ca] = Array.isArray(r) ? r : [r, g, b, a]; const rgba = [(cr & 255), (cg & 255), (cb & 255), (ca & 255)];
for (let i = 0; i < this.pixels.length; i += 4) { this.pixels[i] = rgba[0]; this.pixels[i + 1] = rgba[1]; this.pixels[i + 2] = rgba[2]; this.pixels[i + 3] = rgba[3]; } }
// Bresenham line algorithm line(x0, y0, x1, y1) { x0 = Math.round(x0); y0 = Math.round(y0); x1 = Math.round(x1); y1 = Math.round(y1);
const dx = Math.abs(x1 - x0); const dy = Math.abs(y1 - y0); const sx = x0 < x1 ? 1 : -1; const sy = y0 < y1 ? 1 : -1; let err = dx - dy;
let x = x0, y = y0;
while (true) { this.plot(x, y); if (x === x1 && y === y1) break;
const e2 = 2 * err; if (e2 > -dy) { err -= dy; x += sx; } if (e2 < dx) { err += dx; y += sy; } } }
// Midpoint circle algorithm circle(cx, cy, r, mode = 'fill') { cx = Math.round(cx); cy = Math.round(cy); r = Math.round(r);
if (mode === 'fill') { // Filled circle for (let y = -r; y <= r; y++) { const yy = cy + y; if (yy < 0 || yy >= this.height) continue;
const x = Math.sqrt(r * r - y * y); const x0 = Math.round(cx - x); const x1 = Math.round(cx + x);
for (let xx = x0; xx <= x1; xx++) { if (xx >= 0 && xx < this.width) { this.plot(xx, yy); } } } } else { // Outlined circle using midpoint let x = 0; let y = r; let d = 3 - 2 * r;
while (x <= y) { this.plot(cx + x, cy + y); this.plot(cx - x, cy + y); this.plot(cx + x, cy - y); this.plot(cx - x, cy - y); this.plot(cx + y, cy + x); this.plot(cx - y, cy + x); this.plot(cx + y, cy - x); this.plot(cx - y, cy - x);
if (d < 0) { d = d + 4 * x + 6; } else { d = d + 4 * (x - y) + 10; y--; } x++; } } }
// Box (filled or outlined) box(x, y, w, h, mode = 'fill') { x = Math.round(x); y = Math.round(y); w = Math.round(w); h = Math.round(h);
if (mode === 'fill') { for (let yy = y; yy < y + h; yy++) { if (yy < 0 || yy >= this.height) continue; for (let xx = x; xx < x + w; xx++) { if (xx >= 0 && xx < this.width) { this.plot(xx, yy); } } } } else { // Outline: four sides this.line(x, y, x + w, y); // top this.line(x + w, y, x + w, y + h); // right this.line(x + w, y + h, x, y + h); // bottom this.line(x, y + h, x, y); // left } }
// Single pixel plot(x, y) { x = Math.round(x); y = Math.round(y);
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
const idx = (y * this.width + x) * 4; this.pixels[idx] = this.r; this.pixels[idx + 1] = this.g; this.pixels[idx + 2] = this.b; this.pixels[idx + 3] = this.a; }
// Scroll (shift buffer in-place) scroll(dx, dy) { dx = Math.round(dx); dy = Math.round(dy);
const copy = new Uint8ClampedArray(this.pixels); this.pixels.fill(0);
for (let y = 0; y < this.height; y++) { for (let x = 0; x < this.width; x++) { const srcX = x - dx; const srcY = y - dy;
if (srcX >= 0 && srcX < this.width && srcY >= 0 && srcY < this.height) { const srcIdx = (srcY * this.width + srcX) * 4; const dstIdx = (y * this.width + x) * 4;
this.pixels[dstIdx] = copy[srcIdx]; this.pixels[dstIdx + 1] = copy[srcIdx + 1]; this.pixels[dstIdx + 2] = copy[srcIdx + 2]; this.pixels[dstIdx + 3] = copy[srcIdx + 3]; } } } }
// Spin (rotate buffer around center) spin(angle) { const copy = new Uint8ClampedArray(this.pixels); this.pixels.fill(0);
const cx = this.width / 2; const cy = this.height / 2; const cos = Math.cos(angle); const sin = Math.sin(angle);
for (let y = 0; y < this.height; y++) { for (let x = 0; x < this.width; x++) { // Rotate backwards to find source pixel const dx = x - cx; const dy = y - cy;
const srcX = cx + (dx * cos + dy * sin); const srcY = cy + (-dx * sin + dy * cos);
const sx = Math.round(srcX); const sy = Math.round(srcY);
if (sx >= 0 && sx < this.width && sy >= 0 && sy < this.height) { const srcIdx = (sy * this.width + sx) * 4; const dstIdx = (y * this.width + x) * 4;
this.pixels[dstIdx] = copy[srcIdx]; this.pixels[dstIdx + 1] = copy[srcIdx + 1]; this.pixels[dstIdx + 2] = copy[srcIdx + 2]; this.pixels[dstIdx + 3] = copy[srcIdx + 3]; } } } }
// Zoom (scale buffer) zoom(factor) { const copy = new Uint8ClampedArray(this.pixels); this.pixels.fill(0);
const cx = this.width / 2; const cy = this.height / 2;
for (let y = 0; y < this.height; y++) { for (let x = 0; x < this.width; x++) { const dx = (x - cx) / factor; const dy = (y - cy) / factor;
const srcX = Math.round(cx + dx); const srcY = Math.round(cy + dy);
if (srcX >= 0 && srcX < this.width && srcY >= 0 && srcY < this.height) { const srcIdx = (srcY * this.width + srcX) * 4; const dstIdx = (y * this.width + x) * 4;
this.pixels[dstIdx] = copy[srcIdx]; this.pixels[dstIdx + 1] = copy[srcIdx + 1]; this.pixels[dstIdx + 2] = copy[srcIdx + 2]; this.pixels[dstIdx + 3] = copy[srcIdx + 3]; } } } }
// Contrast contrast(factor) { const mid = 128; for (let i = 0; i < this.pixels.length; i += 4) { this.pixels[i] = Math.max(0, Math.min(255, mid + (this.pixels[i] - mid) * factor)); this.pixels[i + 1] = Math.max(0, Math.min(255, mid + (this.pixels[i + 1] - mid) * factor)); this.pixels[i + 2] = Math.max(0, Math.min(255, mid + (this.pixels[i + 2] - mid) * factor)); } }
// Fade background (gradient cycling through colors) fadeBackground(colors, frame) { const idx = Math.floor(frame / 20) % colors.length; // cycle through colors const color = colors[idx];
if (Array.isArray(color)) { this.wipe(color[0], color[1], color[2], color[3]); } }
// Present to canvas present(ctx) { this.imageData.data.set(this.pixels); ctx.putImageData(this.imageData, 0, 0); }}
function makeRenderer(width, height) { return new Renderer(width, height);}
export { Renderer, makeRenderer };