├── .gitignore ├── examples ├── line.ts ├── circle.ts ├── http_benchmark.ts ├── mouse_draw.ts ├── visualizer.ts ├── sine.ts └── bar_chart.ts ├── README.md ├── LICENSE ├── example.ts ├── bresenham.ts ├── drawille.ts └── canvas.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .deno_plugins/ 2 | 3 | -------------------------------------------------------------------------------- /examples/line.ts: -------------------------------------------------------------------------------- 1 | import Canvas from "../canvas.ts"; 2 | 3 | let c = new Canvas(); 4 | let ctx = c.getContext("2d"); 5 | 6 | ctx.moveTo(0, 0); 7 | ctx.lineTo(200, 100); 8 | ctx.stroke(); 9 | 10 | console.log(ctx.toString()); 11 | -------------------------------------------------------------------------------- /examples/circle.ts: -------------------------------------------------------------------------------- 1 | import Canvas from "../canvas.ts"; 2 | 3 | let c = new Canvas(); 4 | let ctx = c.getContext("2d"); 5 | 6 | ctx.beginPath(); 7 | ctx.arc(95, 50, 40, 0, 2 * Math.PI); 8 | ctx.stroke(); 9 | 10 | console.log(ctx.toString()); 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## `drawille` 3 | 4 | [HTML5](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) [Canvas](canvas.ts) on the terminal. Based on [drawille](https://github.com/asciimoo/drawille) [implementation](drawille.ts). 5 | 6 | https://user-images.githubusercontent.com/34997667/127607186-8eb7ac23-ab00-48ed-a01a-90e5e262f122.mp4 7 | 8 | ### Usage 9 | 10 | Take a look at various examples located at [`examples/`](./examples/) 11 | 12 | ```typescript 13 | import Canvas from "https://deno.land/x/drawille/drawille.ts"; 14 | 15 | const canvas = new Canvas(); 16 | let ctx = canvas.getContext("2d"); 17 | // ... 18 | ``` 19 | 20 | > Requires --unstable feature to be enabled for `Deno.consoleSize`. 21 | 22 | ### License 23 | 24 | [MIT license](LICENSE) 25 | -------------------------------------------------------------------------------- /examples/http_benchmark.ts: -------------------------------------------------------------------------------- 1 | import Canvas from "../canvas.ts"; 2 | 3 | let start: number, end: number; 4 | let canvas = new Canvas(); 5 | let ctx = canvas.getContext("2d"); 6 | 7 | let dataArray: number[] = []; 8 | 9 | let WIDTH = canvas.width; 10 | let HEIGHT = canvas.height; 11 | 12 | let barWidth = WIDTH * 2.5; 13 | let barHeight; 14 | let x = 0; 15 | 16 | async function renderFrame() { 17 | // Clear canvas for next frame. 18 | ctx.clearRect(0, 0, canvas.width, canvas.height); 19 | ctx.save(); 20 | barWidth = (WIDTH / dataArray.length) * 2.5; 21 | x = 0; 22 | start = Date.now(); 23 | await fetch("https://google.com"); 24 | end = Date.now(); 25 | dataArray.push(end - start); 26 | for (var i = 0; i < dataArray.length; i++) { 27 | barHeight = dataArray[i]; 28 | ctx.strokeRect(x, HEIGHT - barHeight, barWidth, barHeight); 29 | x += barWidth + 1; 30 | } 31 | console.log(ctx.toString()); 32 | console.log(`Response time: ${end - start} ms`); 33 | } 34 | 35 | setInterval(renderFrame, 200); 36 | -------------------------------------------------------------------------------- /examples/mouse_draw.ts: -------------------------------------------------------------------------------- 1 | // NOTE: autopilot is an --unstable plugin. We're using it for global mouse movement detection. 2 | import { init } from "https://deno.land/x/mouse/mod.ts"; 3 | import Pilot from "https://deno.land/x/autopilot/mod.ts"; 4 | import Canvas from "../canvas.ts"; 5 | 6 | const canvas = new Canvas(); 7 | const ctx = canvas.getContext("2d"); 8 | 9 | const pilot = new Pilot(); 10 | const dimensions = pilot.screenSize(); 11 | 12 | let isDrawing = true; 13 | let mouseDown = false; 14 | 15 | window.addEventListener("click", (e) => { 16 | // @ts-ignore 17 | if(e.buttons == 1) isDrawing = !isDrawing; 18 | }); 19 | 20 | window.addEventListener("mousemove", (e) => { 21 | if(isDrawing) { 22 | // @ts-ignore Types for MouseEvent are missing 23 | let x = (e.screenX / dimensions.width) * canvas.width; 24 | // @ts-ignore Types for MouseEvent are missing 25 | let y = (e.screenY / dimensions.height) * canvas.height; 26 | ctx.fillRect(x, y, 2, 2); 27 | ctx.stroke(); 28 | console.log(ctx.toString()); 29 | } 30 | }); 31 | 32 | await init(); 33 | -------------------------------------------------------------------------------- /examples/visualizer.ts: -------------------------------------------------------------------------------- 1 | // Random noise visualizer. Works on Deno and the browser. 2 | import Canvas from "../canvas.ts"; 3 | 4 | let canvas = new Canvas(120, 120); 5 | let ctx = canvas.getContext("2d"); 6 | 7 | let bufferLength = 128; 8 | let dataArray = []; 9 | 10 | let WIDTH = canvas.width; 11 | let HEIGHT = canvas.height; 12 | 13 | let barWidth = (WIDTH / bufferLength) * 2.5; 14 | let barHeight; 15 | let x = 0; 16 | 17 | function renderFrame() { 18 | // Clear canvas for next frame. 19 | ctx.clearRect(0, 0, canvas.width, canvas.height); 20 | ctx.save(); 21 | 22 | x = 0; 23 | // Generate random data 24 | dataArray = [...Array(bufferLength)].map(() => 25 | Math.floor(Math.random() * bufferLength) 26 | ); 27 | for (var i = 0; i < bufferLength; i++) { 28 | barHeight = dataArray[i]; 29 | ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight); 30 | x += barWidth + 1; 31 | } 32 | 33 | ctx.restore(); 34 | ctx.strokeRect(0, 0, canvas.width, canvas.height); 35 | console.log(ctx.toString()); 36 | } 37 | 38 | setInterval(renderFrame, 140); 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-21 Divy Srivastava 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /example.ts: -------------------------------------------------------------------------------- 1 | import Canvas from "./canvas.ts"; 2 | 3 | let canvas = new Canvas(); 4 | let c = canvas.getContext("2d"); 5 | 6 | var n = 20; 7 | var a = 40; 8 | var t = 2; 9 | var pi = Math.PI; 10 | var pi2 = pi / 2; 11 | var sin = Math.sin; 12 | var cos = Math.cos; 13 | 14 | var sunX = canvas.width - 20; 15 | var sunData = c.getImageData(sunX, 1, 15, 20); 16 | 17 | function draw() { 18 | var w = canvas.width / 2; 19 | var start = performance.now(); 20 | 21 | c.clearRect(0, 0, canvas.width, canvas.height); 22 | c.save(); 23 | c.translate(w, w); 24 | for (var i = 1; i < n; i++) { 25 | var r = i * (w / n); 26 | c.beginPath(); 27 | c.moveTo(-r, 0); 28 | var tt = start * pi / 1000 / t; 29 | var p = (sin(tt - pi * (cos(pi * i / n) + 1) / 2) + 1) * pi2; 30 | for (var j = 0; j < a; j++) { 31 | var ca = pi * j / (a - i); 32 | if (p > ca) { 33 | c.lineTo(-cos(ca) * r, -sin(ca) * r); 34 | } else { 35 | c.lineTo(-cos(p) * r, -sin(p) * r); 36 | } 37 | } 38 | c.stroke(); 39 | } 40 | c.restore(); 41 | 42 | c.strokeRect(0, 0, canvas.width, canvas.height); 43 | 44 | sunX = (sunX + 1) % canvas.width; 45 | c.putImageData(sunData, sunX, 1); 46 | 47 | console.log(c.toString()); 48 | } 49 | 50 | setInterval(draw, 1000 / 20); 51 | -------------------------------------------------------------------------------- /examples/sine.ts: -------------------------------------------------------------------------------- 1 | import Canvas from "../canvas.ts"; 2 | 3 | let c = new Canvas(); 4 | let ctx = c.getContext("2d"); 5 | let w = c.width; 6 | let h = c.height / 2; 7 | let f = 2; 8 | 9 | function calcSineY(x: number) { 10 | return h - h * Math.sin(x * 2 * Math.PI * (f / w)); 11 | } 12 | 13 | function drawSine(x: number) { 14 | ctx.clear(); 15 | ctx.clearRect(0, 0, w, h * 2); 16 | 17 | ctx.beginPath(); 18 | ctx.moveTo(0, h); 19 | ctx.lineTo(w, h); 20 | ctx.stroke(); 21 | 22 | ctx.beginPath(); 23 | ctx.moveTo(0, h); 24 | 25 | for (var i = 0; i < x; i++) { 26 | let y = calcSineY(x); 27 | ctx.moveTo(i, y); 28 | ctx.lineTo(x, y); 29 | } 30 | ctx.stroke(); 31 | 32 | ctx.beginPath(); 33 | for (var i = 0; i < x; i++) { 34 | let y = calcSineY(x); 35 | ctx.moveTo(x, h); 36 | ctx.lineTo(x, y); 37 | } 38 | ctx.stroke(); 39 | 40 | ctx.beginPath(); 41 | 42 | for (var i = 0; i < x; i++) { 43 | if (i / 3 == Math.round(i / 3)) { 44 | let y = calcSineY(i); 45 | ctx.moveTo(i, h); 46 | ctx.lineTo(i, y); 47 | } 48 | } 49 | ctx.stroke(); 50 | 51 | ctx.beginPath(); 52 | 53 | for (var i = 0; i < x; i++) { 54 | let y = calcSineY(i); 55 | ctx.lineTo(i, y); 56 | } 57 | ctx.stroke(); 58 | console.log(ctx.toString()); 59 | } 60 | 61 | let x = 0; 62 | let interval = setInterval(function () { 63 | drawSine(x); 64 | x++; 65 | if (x > w) x = 0; 66 | }, 20); 67 | -------------------------------------------------------------------------------- /bresenham.ts: -------------------------------------------------------------------------------- 1 | // Bresenham's Line Algorithm https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm 2 | // hand ported to Typescript from https://github.com/madbence/node-bresenham 3 | // MIT License (c) 2014 Bence Dányi 4 | // MIT License (c) 2021 Divy Srivastava 5 | 6 | /** 7 | * Custom function to create a point from known points 8 | **/ 9 | export type RecvFn = (x: number, y: number) => void; 10 | 11 | /** 12 | * Represents a point. 13 | **/ 14 | export interface Point { 15 | // The abscissa. 16 | x: number; 17 | // The ordinate. 18 | y: number; 19 | } 20 | 21 | /** 22 | * Bresenham's Line Drawing algorithm. 23 | **/ 24 | export default function ( 25 | x0: number, 26 | y0: number, 27 | x1: number, 28 | y1: number, 29 | fn?: RecvFn, 30 | ): Point[] { 31 | if (!fn) { 32 | var arr = []; 33 | fn = function (x, y) { 34 | arr.push({ x: x, y: y }); 35 | }; 36 | } 37 | var dx = x1 - x0; 38 | var dy = y1 - y0; 39 | var adx = Math.abs(dx); 40 | var ady = Math.abs(dy); 41 | var eps = 0; 42 | var sx = dx > 0 ? 1 : -1; 43 | var sy = dy > 0 ? 1 : -1; 44 | if (adx > ady) { 45 | for (var x = x0, y = y0; sx < 0 ? x >= x1 : x <= x1; x += sx) { 46 | fn(x, y); 47 | eps += ady; 48 | if ((eps << 1) >= adx) { 49 | y += sy; 50 | eps -= adx; 51 | } 52 | } 53 | } else { 54 | for (var x = x0, y = y0; sy < 0 ? y >= y1 : y <= y1; y += sy) { 55 | fn(x, y); 56 | eps += adx; 57 | if ((eps << 1) >= ady) { 58 | x += sx; 59 | eps -= ady; 60 | } 61 | } 62 | } 63 | return arr || []; 64 | } 65 | -------------------------------------------------------------------------------- /drawille.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "https://deno.land/std/node/buffer.ts"; 2 | 3 | const map = [ 4 | [0x1, 0x8], 5 | [0x2, 0x10], 6 | [0x4, 0x20], 7 | [0x40, 0x80], 8 | ]; 9 | 10 | class Canvas { 11 | private _width: number = 0; 12 | private _height: number = 0; 13 | 14 | public content: Buffer; 15 | constructor(width?: number, height?: number) { 16 | // XXX(UNSTABLE): This is a Deno unstable feature. 17 | let { columns, rows } = Deno.consoleSize(Deno.stdout.rid); 18 | this.width = width || columns * 2 - 2; 19 | this.height = height || rows * 4; 20 | this.content = Buffer.alloc((this.width * this.height) / 8); 21 | } 22 | 23 | set width(width: number) { 24 | this._width = Math.floor(width / 2) * 2; 25 | this.content = Buffer.alloc((this.width * this.height) / 8); 26 | this.clear(); 27 | } 28 | 29 | get width() { 30 | return this._width; 31 | } 32 | 33 | get height() { 34 | return this._height; 35 | } 36 | 37 | set height(height: number) { 38 | this._height = Math.floor(height / 4) * 4; 39 | this.content = Buffer.alloc((this.width * this.height) / 8); 40 | this.clear(); 41 | } 42 | 43 | clear() { 44 | this.content.fill(0); 45 | } 46 | 47 | frame(delimiter: string = "\n"): string { 48 | const frameWidth = this.width / 2; 49 | const result = this.content.reduce( 50 | (acc: string[], cur: number, i: number) => { 51 | if (i % frameWidth === 0) { 52 | acc.push(delimiter); 53 | } 54 | acc.push(cur ? String.fromCharCode(0x2800 + cur) : " "); 55 | return acc; 56 | }, 57 | [], 58 | ); 59 | result.push(delimiter); 60 | return result.join(""); 61 | } 62 | 63 | set(x: number, y: number) { 64 | if (!(x >= 0 && x < this.width && y >= 0 && y < this.height)) { 65 | return; 66 | } 67 | x = Math.floor(x); 68 | y = Math.floor(y); 69 | const nx = Math.floor(x / 2); 70 | const ny = Math.floor(y / 4); 71 | const coord = nx + (this.width / 2) * ny; 72 | const mask = map[y % 4][x % 2]; 73 | this.content[coord] |= mask; 74 | } 75 | 76 | unset(x: number, y: number) { 77 | if (!(x >= 0 && x < this.width && y >= 0 && y < this.height)) { 78 | return; 79 | } 80 | x = Math.floor(x); 81 | y = Math.floor(y); 82 | const nx = Math.floor(x / 2); 83 | const ny = Math.floor(y / 4); 84 | const coord = nx + (this.width / 2) * ny; 85 | const mask = map[y % 4][x % 2]; 86 | this.content[coord] &= ~mask; 87 | } 88 | 89 | toggle(x: number, y: number) { 90 | if (!(x >= 0 && x < this.width && y >= 0 && y < this.height)) { 91 | return; 92 | } 93 | x = Math.floor(x); 94 | y = Math.floor(y); 95 | const nx = Math.floor(x / 2); 96 | const ny = Math.floor(y / 4); 97 | const coord = nx + (this.width / 2) * ny; 98 | const mask = map[y % 4][x % 2]; 99 | this.content[coord] ^= mask; 100 | } 101 | } 102 | 103 | export default Canvas; 104 | -------------------------------------------------------------------------------- /examples/bar_chart.ts: -------------------------------------------------------------------------------- 1 | // Static bar chart example. Works on Deno & Browser. 2 | import Canvas from "../canvas.ts"; 3 | 4 | let myCanvas = new Canvas(); 5 | let ctx = myCanvas.getContext("2d"); 6 | 7 | function drawLine( 8 | ctx: Canvas, 9 | startX: number, 10 | startY: number, 11 | endX: number, 12 | endY: number, 13 | ) { 14 | ctx.save(); 15 | ctx.beginPath(); 16 | ctx.moveTo(startX, startY); 17 | ctx.lineTo(endX, endY); 18 | ctx.stroke(); 19 | ctx.restore(); 20 | } 21 | 22 | function drawBar( 23 | ctx: Canvas, 24 | upperLeftCornerX: number, 25 | upperLeftCornerY: number, 26 | width: number, 27 | height: number, 28 | ) { 29 | ctx.save(); 30 | ctx.fillRect(upperLeftCornerX, upperLeftCornerY, width, height); 31 | ctx.restore(); 32 | } 33 | 34 | // Random data to plot 35 | const data = [ 36 | 1, 37 | 8, 38 | 1, 39 | 5, 40 | 4, 41 | 8, 42 | 7, 43 | 2, 44 | 5, 45 | 4, 46 | 2, 47 | 2, 48 | 9, 49 | 7, 50 | 0, 51 | 0, 52 | 9, 53 | 6, 54 | 8, 55 | 1, 56 | 0, 57 | 3, 58 | 4, 59 | 6, 60 | 8, 61 | 3, 62 | 4, 63 | 3, 64 | 8, 65 | 1, 66 | 3, 67 | 6, 68 | 6, 69 | 7, 70 | 5, 71 | 5, 72 | 7, 73 | 7, 74 | 5, 75 | 0, 76 | 7, 77 | 9, 78 | 0, 79 | 6, 80 | 9, 81 | 6, 82 | 5, 83 | 8, 84 | 6, 85 | 8, 86 | 6, 87 | 8, 88 | 9, 89 | 1, 90 | 8, 91 | 7, 92 | 5, 93 | 5, 94 | 9, 95 | 5, 96 | 8, 97 | 9, 98 | 0, 99 | 4, 100 | 4, 101 | 7, 102 | 7, 103 | 5, 104 | 5, 105 | 0, 106 | 7, 107 | 9, 108 | 7, 109 | 2, 110 | 2, 111 | 5, 112 | 7, 113 | 9, 114 | 8, 115 | 7, 116 | 1, 117 | 7, 118 | 2, 119 | 6, 120 | 0, 121 | 0, 122 | 1, 123 | 3, 124 | 2, 125 | 4, 126 | 3, 127 | 4, 128 | 5, 129 | 3, 130 | 2, 131 | 7, 132 | 9, 133 | 4, 134 | 6, 135 | 8, 136 | 5, 137 | 9, 138 | 5, 139 | 6, 140 | 0, 141 | 3, 142 | 8, 143 | 0, 144 | 1, 145 | 0, 146 | 4, 147 | 5, 148 | 4, 149 | 5, 150 | 7, 151 | 3, 152 | 8, 153 | 0, 154 | 3, 155 | 1, 156 | 6, 157 | 2, 158 | 4, 159 | 9, 160 | 3, 161 | 2, 162 | 2, 163 | 2, 164 | 9, 165 | 3, 166 | 2, 167 | 8, 168 | 0, 169 | ]; 170 | 171 | let padding = 10; 172 | 173 | function draw() { 174 | var maxValue = 0; 175 | for (var categ in data) { 176 | maxValue = Math.max(maxValue, data[categ]); 177 | } 178 | var canvasActualHeight = ctx.height - padding * 2; 179 | var canvasActualWidth = ctx.width - padding * 2; 180 | 181 | var barIndex = 0; 182 | var numberOfBars = Object.keys(data).length; 183 | var barSize = (canvasActualWidth) / numberOfBars; 184 | 185 | for (categ in data) { 186 | var val = data[categ]; 187 | var barHeight = Math.round(canvasActualHeight * val / maxValue); 188 | drawBar( 189 | ctx, 190 | padding + barIndex * barSize, 191 | ctx.height - barHeight - padding, 192 | barSize, 193 | barHeight, 194 | ); 195 | 196 | barIndex++; 197 | } 198 | 199 | ctx.save(); 200 | console.log(ctx.toString()); 201 | } 202 | 203 | draw(); 204 | -------------------------------------------------------------------------------- /canvas.ts: -------------------------------------------------------------------------------- 1 | import * as glMatrix from "https://esm.sh/gl-matrix@2.1.0"; 2 | import bresenham from "./bresenham.ts"; 3 | import earcut from "https://esm.sh/earcut@2.2.3"; 4 | import Canvas from "./drawille.ts"; 5 | 6 | var mat2d = glMatrix.mat2d; 7 | var vec2 = glMatrix.vec2; 8 | 9 | interface Path { 10 | point: number[]; 11 | stroke: boolean; 12 | } 13 | 14 | export interface ImageData { 15 | data: string; 16 | width: number; 17 | height: number; 18 | } 19 | 20 | export default class Context extends Canvas { 21 | _matrix = mat2d.create(); 22 | _stack: unknown[] = []; 23 | _currentPath: Path[] = []; 24 | 25 | constructor(width?: number, height?: number) { 26 | super(width, height); 27 | } 28 | save() { 29 | this._stack.push(mat2d.clone(mat2d.create(), this._matrix)); 30 | } 31 | restore() { 32 | var top = this._stack.pop(); 33 | if (!top) return; 34 | this._matrix = top; 35 | } 36 | translate(x: number, y: number) { 37 | mat2d.translate(this._matrix, this._matrix, vec2.fromValues(x, y)); 38 | } 39 | rotate(a: number) { 40 | mat2d.rotate(this._matrix, this._matrix, a / 180 * Math.PI); 41 | } 42 | scale(x: number, y: number) { 43 | mat2d.scale(this._matrix, this._matrix, vec2.fromValues(x, y)); 44 | } 45 | beginPath() { 46 | this._currentPath = []; 47 | } 48 | closePath() { 49 | this._currentPath.push({ 50 | point: this._currentPath[0].point, 51 | stroke: false, 52 | }); 53 | } 54 | stroke() { 55 | var set = this.set.bind(this); 56 | for (var i = 0; i < this._currentPath.length - 1; i++) { 57 | var cur = this._currentPath[i]; 58 | var nex = this._currentPath[i + 1]; 59 | if (nex.stroke) { 60 | bresenham(cur.point[0], cur.point[1], nex.point[0], nex.point[1], set); 61 | } 62 | } 63 | } 64 | 65 | // Web compatibility :D 66 | getContext(t: string) { 67 | return this; 68 | } 69 | 70 | clearRect(x: number, y: number, w: number, h: number) { 71 | quad( 72 | this._matrix, 73 | x, 74 | y, 75 | w, 76 | h, 77 | this.unset.bind(this), 78 | [0, 0, this.width, this.height], 79 | ); 80 | } 81 | 82 | fillRect(x: number, y: number, w: number, h: number) { 83 | quad( 84 | this._matrix, 85 | x, 86 | y, 87 | w, 88 | h, 89 | this.set.bind(this), 90 | [0, 0, this.width, this.height], 91 | ); 92 | } 93 | 94 | fill() { 95 | if ( 96 | this._currentPath[this._currentPath.length - 1].point !== 97 | this._currentPath[0].point 98 | ) { 99 | this.closePath(); 100 | } 101 | var vertices: number[] = []; 102 | this._currentPath.forEach(function (pt) { 103 | vertices.push(pt.point[0], pt.point[1]); 104 | }); 105 | var triangleIndices = earcut(vertices); 106 | var p1, p2, p3; 107 | for (var i = 0; i < triangleIndices.length; i = i + 3) { 108 | p1 = [ 109 | vertices[triangleIndices[i] * 2], 110 | vertices[triangleIndices[i] * 2 + 1], 111 | ]; 112 | p2 = [ 113 | vertices[triangleIndices[i + 1] * 2], 114 | vertices[triangleIndices[i + 1] * 2 + 1], 115 | ]; 116 | p3 = [ 117 | vertices[triangleIndices[i + 2] * 2], 118 | vertices[triangleIndices[i + 2] * 2 + 1], 119 | ]; 120 | triangle( 121 | p1, 122 | p2, 123 | p3, 124 | this.set.bind(this), 125 | [0, 0, this.width, this.height], 126 | ); 127 | } 128 | } 129 | 130 | toString() { 131 | return this.frame(); 132 | } 133 | 134 | moveTo(x: number, y: number) { 135 | addPoint(this._matrix, this._currentPath, x, y, false); 136 | } 137 | 138 | strokeRect(x: number, y: number, w: number, h: number) { 139 | var fromX = clamp(x, 0, this.width), 140 | fromY = clamp(y, 0, this.height), 141 | toX = clamp(x + w, 0, this.width), 142 | toY = clamp(y + h, 0, this.height); 143 | let set = this.set.bind(this); 144 | bresenham(fromX, fromY, toX, fromY, set); 145 | bresenham(toX, fromY, toX, toY, set); 146 | bresenham(toX, toY, fromX, toY, set); 147 | bresenham(fromX, toY, fromX, fromY, set); 148 | } 149 | 150 | lineTo(x: number, y: number) { 151 | addPoint(this._matrix, this._currentPath, x, y, true); 152 | } 153 | 154 | arc( 155 | h: number, 156 | k: number, 157 | r: number, 158 | th1: number, 159 | th2: number, 160 | anticlockwise?: boolean, 161 | ) { 162 | var x: number, y: number; 163 | var dth = Math.abs(Math.acos(1 / r) - Math.acos(2 / r)); 164 | if (anticlockwise) { 165 | var tempth = th2; 166 | th2 = th1 + 2 * Math.PI; 167 | th1 = tempth; 168 | } 169 | th1 = th1 % (2 * Math.PI); 170 | if (th2 < th1) th2 = th2 + 2 * Math.PI; 171 | for (var th = th1; th <= th2; th = th + dth) { 172 | y = clamp(r * Math.sin(th) + k, 0, this.height); 173 | x = clamp(r * Math.cos(th) + h, 0, this.width); 174 | addPoint(this._matrix, this._currentPath, x, y, true); 175 | } 176 | } 177 | 178 | // Currently here for web compatibility :D 179 | fillText(text: string, x: number, y: number, maxWidth: number) {} 180 | getImageData(sx?: number, sy?: number, sw?: number, sh?: number): ImageData { 181 | if (!sx) sx = 0; 182 | if (!sy) sy = 0; 183 | if (!sw) sw = this.width; 184 | if (!sh) sh = this.height; 185 | 186 | sx = Math.floor(sx / 2); 187 | sw = Math.floor(sw / 2); 188 | sy = Math.floor(sy / 4); 189 | sh = Math.floor(sh / 4); 190 | 191 | let delimiter = "\n"; 192 | let imgdata: string[] = []; 193 | let data = this.toString().split(delimiter); 194 | 195 | for (var i = 0; i < sh; i++) { 196 | imgdata.push(data[sy + i].slice(sx, sx + sw)); 197 | } 198 | 199 | return { 200 | data: imgdata.join(delimiter), 201 | width: sw, 202 | height: sh, 203 | } as ImageData; 204 | } 205 | 206 | putImageData( 207 | imageData: ImageData, 208 | dx: number, 209 | dy: number, 210 | dirtyX?: number, 211 | dirtyY?: number, 212 | dirtyWidth?: number, 213 | dirtyHeight?: number, 214 | ) { 215 | let delimiter = "\n"; 216 | let data = imageData.data.split(delimiter); 217 | let height = imageData.height; 218 | let width = imageData.width; 219 | dirtyX = dirtyX || 0; 220 | dirtyY = dirtyY || 0; 221 | dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width; 222 | dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height; 223 | 224 | dirtyX = Math.floor(dirtyX / 2); 225 | dirtyY = Math.floor(dirtyY / 4); 226 | width = Math.floor(width / 2); 227 | height = Math.floor(height / 4); 228 | dirtyWidth = Math.floor(dirtyWidth / 2); 229 | dirtyHeight = Math.floor(dirtyHeight / 4); 230 | 231 | var limitBottom = dirtyY + dirtyHeight; 232 | var limitRight = dirtyX + dirtyWidth; 233 | for (var y = dirtyY; y < limitBottom; y++) { 234 | for (var x = dirtyX; x < limitRight; x++) { 235 | if (data[y][x] !== " ") { 236 | this.fillRect(x + dx, y + dy, 1, 1); 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | function addPoint(m: number, p: Path[], x: number, y: number, s: boolean) { 244 | var v = vec2.transformMat2d(vec2.create(), vec2.fromValues(x, y), m); 245 | p.push({ 246 | point: [Math.floor(v[0]), Math.floor(v[1])], 247 | stroke: s, 248 | }); 249 | } 250 | 251 | function clamp(value: number, min: number, max: number) { 252 | return Math.round(Math.min(Math.max(value, min), max)); 253 | } 254 | 255 | /** 256 | * Returns a Point type 257 | **/ 258 | function br(p1: number[], p2: number[]) { 259 | return bresenham( 260 | Math.floor(p1[0]), 261 | Math.floor(p1[1]), 262 | Math.floor(p2[0]), 263 | Math.floor(p2[1]), 264 | ); 265 | } 266 | 267 | /** 268 | * Triangle 269 | **/ 270 | function triangle( 271 | pointB: number[], 272 | pointA: number[], 273 | pointC: number[], 274 | f: any, 275 | clip: number[], 276 | ) { 277 | var a = br(pointB, pointC); 278 | var b = br(pointA, pointC); 279 | var c = br(pointA, pointB); 280 | 281 | var s = a.concat(b).concat(c) 282 | .filter(function (point) { 283 | return point.y < clip[3] && point.y > clip[1]; 284 | }) 285 | .sort(function (a, b) { 286 | if (a.y == b.y) { 287 | return a.x - b.x; 288 | } 289 | return a.y - b.y; 290 | }); 291 | 292 | for (var i = 0; i < s.length - 1; i++) { 293 | var cur = s[i]; 294 | var nex = s[i + 1]; 295 | var left = Math.max(clip[0], cur.x); 296 | var right = Math.min(clip[2], nex.x); 297 | if (cur.y == nex.y) { 298 | for (var j = left; j <= right; j++) { 299 | f(j, cur.y); 300 | } 301 | } else { 302 | f(cur.x, cur.y); 303 | } 304 | } 305 | } 306 | 307 | /** 308 | * Quadrilateral 309 | **/ 310 | function quad( 311 | m: number, 312 | x: number, 313 | y: number, 314 | w: number, 315 | h: number, 316 | f: any, 317 | clip: number[], 318 | ) { 319 | var p1 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x, y), m); 320 | var p2 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x + w, y), m); 321 | var p3 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x, y + h), m); 322 | var p4 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x + w, y + h), m); 323 | triangle(p1, p2, p3, f, clip); 324 | triangle(p3, p2, p4, f, clip); 325 | } 326 | --------------------------------------------------------------------------------