80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
// deno run --allow-write mandelbrot.ts
|
|
import { createCanvas } from "https://deno.land/x/canvas@v1.4.2/mod.ts";
|
|
|
|
const WIDTH = 1024*2;
|
|
const HEIGHT = 1024*2;
|
|
const MAX_ITER = 1000;
|
|
const ZOOM = 0.8;
|
|
const X_CENTER = -0.75;
|
|
const Y_CENTER = 0.0;
|
|
|
|
// Converts HSV to RGB [0..1] input/output
|
|
function hsvToRgb(h: number, s: number, v: number): [number, number, number] {
|
|
let c = v * s;
|
|
let x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
let m = v - c;
|
|
let r = 0, g = 0, b = 0;
|
|
if (0 <= h && h < 60) [r, g, b] = [c, x, 0];
|
|
else if (60 <= h && h < 120) [r, g, b] = [x, c, 0];
|
|
else if (120 <= h && h < 180) [r, g, b] = [0, c, x];
|
|
else if (180 <= h && h < 240) [r, g, b] = [0, x, c];
|
|
else if (240 <= h && h < 300) [r, g, b] = [x, 0, c];
|
|
else if (300 <= h && h < 360) [r, g, b] = [c, 0, x];
|
|
return [
|
|
Math.round((r + m) * 255),
|
|
Math.round((g + m) * 255),
|
|
Math.round((b + m) * 255),
|
|
];
|
|
}
|
|
|
|
// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
|
|
if (import.meta.main) {
|
|
const canvas = createCanvas(WIDTH, HEIGHT);
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
/* deno-canvas has broader compatibility with putImageData than node-canvas, so we can use it as below. */
|
|
const imageData = ctx.createImageData(WIDTH, HEIGHT);
|
|
const data = imageData.data;
|
|
|
|
const start = new Date();
|
|
|
|
for (let y = 0; y < HEIGHT; y++) {
|
|
for (let x = 0; x < WIDTH; x++) {
|
|
const cx = (x - WIDTH / 2) * (4 / WIDTH) * ZOOM + X_CENTER;
|
|
const cy = (y - HEIGHT / 2) * (4 / HEIGHT) * ZOOM + Y_CENTER;
|
|
let zx = 0, zy = 0;
|
|
let iter = 0;
|
|
while (zx * zx + zy * zy < 4 && iter < MAX_ITER) {
|
|
const tmp = zx * zx - zy * zy + cx;
|
|
zy = 2 * zx * zy + cy;
|
|
zx = tmp;
|
|
iter++;
|
|
}
|
|
let pixelIndex = 4 * (y * WIDTH + x);
|
|
if (iter === MAX_ITER) {
|
|
data[pixelIndex + 0] = 0;
|
|
data[pixelIndex + 1] = 0;
|
|
data[pixelIndex + 2] = 0;
|
|
data[pixelIndex + 3] = 255;
|
|
} else {
|
|
let zn = Math.sqrt(zx * zx + zy * zy);
|
|
let nu = Math.log(Math.log(zn)) / Math.log(2);
|
|
let smoothIter = iter + 1 - nu;
|
|
let hue = 360 * smoothIter * smoothIter / MAX_ITER;
|
|
let rgb = hsvToRgb(hue, 1, 1);
|
|
data[pixelIndex + 0] = rgb[0];
|
|
data[pixelIndex + 1] = rgb[1];
|
|
data[pixelIndex + 2] = rgb[2];
|
|
data[pixelIndex + 3] = 255;
|
|
}
|
|
}
|
|
}
|
|
const stop = new Date();
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
// Save PNG
|
|
await Deno.writeFile("mandelbrot.png", canvas.toBuffer("image/png"));
|
|
const doneSecs = (stop.getTime()-start.getTime())/1000;
|
|
console.log(`Done! in ${doneSecs} sec`);
|
|
}
|