import * as glMatrix from "https://esm.sh/gl-matrix@2.1.0"; import bresenham from "./bresenham.ts"; import earcut from "https://esm.sh/earcut@2.2.3"; import Canvas from "./drawille.ts"; var mat2d = glMatrix.mat2d; var vec2 = glMatrix.vec2; interface Path { point: number[]; stroke: boolean; } export interface ImageData { data: string; width: number; height: number; } export default class Context extends Canvas { _matrix = mat2d.create(); _stack: unknown[] = []; _currentPath: Path[] = []; constructor(width?: number, height?: number) { super(width, height); } save() { this._stack.push(mat2d.clone(mat2d.create(), this._matrix)); } restore() { var top = this._stack.pop(); if (!top) return; this._matrix = top; } translate(x: number, y: number) { mat2d.translate(this._matrix, this._matrix, vec2.fromValues(x, y)); } rotate(a: number) { mat2d.rotate(this._matrix, this._matrix, a / 180 * Math.PI); } scale(x: number, y: number) { mat2d.scale(this._matrix, this._matrix, vec2.fromValues(x, y)); } beginPath() { this._currentPath = []; } closePath() { this._currentPath.push({ point: this._currentPath[0].point, stroke: false, }); } stroke() { var set = this.set.bind(this); for (var i = 0; i < this._currentPath.length - 1; i++) { var cur = this._currentPath[i]; var nex = this._currentPath[i + 1]; if (nex.stroke) { bresenham(cur.point[0], cur.point[1], nex.point[0], nex.point[1], set); } } } // Web compatibility :D getContext(t: string) { return this; } clearRect(x: number, y: number, w: number, h: number) { quad( this._matrix, x, y, w, h, this.unset.bind(this), [0, 0, this.width, this.height], ); } fillRect(x: number, y: number, w: number, h: number) { quad( this._matrix, x, y, w, h, this.set.bind(this), [0, 0, this.width, this.height], ); } fill() { if ( this._currentPath[this._currentPath.length - 1].point !== this._currentPath[0].point ) { this.closePath(); } var vertices: number[] = []; this._currentPath.forEach(function (pt) { vertices.push(pt.point[0], pt.point[1]); }); var triangleIndices = earcut(vertices); var p1, p2, p3; for (var i = 0; i < triangleIndices.length; i = i + 3) { p1 = [ vertices[triangleIndices[i] * 2], vertices[triangleIndices[i] * 2 + 1], ]; p2 = [ vertices[triangleIndices[i + 1] * 2], vertices[triangleIndices[i + 1] * 2 + 1], ]; p3 = [ vertices[triangleIndices[i + 2] * 2], vertices[triangleIndices[i + 2] * 2 + 1], ]; triangle( p1, p2, p3, this.set.bind(this), [0, 0, this.width, this.height], ); } } toString() { return this.frame(); } moveTo(x: number, y: number) { addPoint(this._matrix, this._currentPath, x, y, false); } strokeRect(x: number, y: number, w: number, h: number) { var fromX = clamp(x, 0, this.width), fromY = clamp(y, 0, this.height), toX = clamp(x + w, 0, this.width), toY = clamp(y + h, 0, this.height); let set = this.set.bind(this); bresenham(fromX, fromY, toX, fromY, set); bresenham(toX, fromY, toX, toY, set); bresenham(toX, toY, fromX, toY, set); bresenham(fromX, toY, fromX, fromY, set); } lineTo(x: number, y: number) { addPoint(this._matrix, this._currentPath, x, y, true); } arc( h: number, k: number, r: number, th1: number, th2: number, anticlockwise?: boolean, ) { var x: number, y: number; var dth = Math.abs(Math.acos(1 / r) - Math.acos(2 / r)); if (anticlockwise) { var tempth = th2; th2 = th1 + 2 * Math.PI; th1 = tempth; } th1 = th1 % (2 * Math.PI); if (th2 < th1) th2 = th2 + 2 * Math.PI; for (var th = th1; th <= th2; th = th + dth) { y = clamp(r * Math.sin(th) + k, 0, this.height); x = clamp(r * Math.cos(th) + h, 0, this.width); addPoint(this._matrix, this._currentPath, x, y, true); } } // Currently here for web compatibility :D fillText(text: string, x: number, y: number, maxWidth: number) {} getImageData(sx?: number, sy?: number, sw?: number, sh?: number): ImageData { if (!sx) sx = 0; if (!sy) sy = 0; if (!sw) sw = this.width; if (!sh) sh = this.height; sx = Math.floor(sx / 2); sw = Math.floor(sw / 2); sy = Math.floor(sy / 4); sh = Math.floor(sh / 4); let delimiter = "\n"; let imgdata: string[] = []; let data = this.toString().split(delimiter); for (var i = 0; i < sh; i++) { imgdata.push(data[sy + i].slice(sx, sx + sw)); } return { data: imgdata.join(delimiter), width: sw, height: sh, } as ImageData; } putImageData( imageData: ImageData, dx: number, dy: number, dirtyX?: number, dirtyY?: number, dirtyWidth?: number, dirtyHeight?: number, ) { let delimiter = "\n"; let data = imageData.data.split(delimiter); let height = imageData.height; let width = imageData.width; dirtyX = dirtyX || 0; dirtyY = dirtyY || 0; dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width; dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height; dirtyX = Math.floor(dirtyX / 2); dirtyY = Math.floor(dirtyY / 4); width = Math.floor(width / 2); height = Math.floor(height / 4); dirtyWidth = Math.floor(dirtyWidth / 2); dirtyHeight = Math.floor(dirtyHeight / 4); var limitBottom = dirtyY + dirtyHeight; var limitRight = dirtyX + dirtyWidth; for (var y = dirtyY; y < limitBottom; y++) { for (var x = dirtyX; x < limitRight; x++) { if (data[y][x] !== " ") { this.fillRect(x + dx, y + dy, 1, 1); } } } } } function addPoint(m: number, p: Path[], x: number, y: number, s: boolean) { var v = vec2.transformMat2d(vec2.create(), vec2.fromValues(x, y), m); p.push({ point: [Math.floor(v[0]), Math.floor(v[1])], stroke: s, }); } function clamp(value: number, min: number, max: number) { return Math.round(Math.min(Math.max(value, min), max)); } /** * Returns a Point type **/ function br(p1: number[], p2: number[]) { return bresenham( Math.floor(p1[0]), Math.floor(p1[1]), Math.floor(p2[0]), Math.floor(p2[1]), ); } /** * Triangle **/ function triangle( pointB: number[], pointA: number[], pointC: number[], f: any, clip: number[], ) { var a = br(pointB, pointC); var b = br(pointA, pointC); var c = br(pointA, pointB); var s = a.concat(b).concat(c) .filter(function (point) { return point.y < clip[3] && point.y > clip[1]; }) .sort(function (a, b) { if (a.y == b.y) { return a.x - b.x; } return a.y - b.y; }); for (var i = 0; i < s.length - 1; i++) { var cur = s[i]; var nex = s[i + 1]; var left = Math.max(clip[0], cur.x); var right = Math.min(clip[2], nex.x); if (cur.y == nex.y) { for (var j = left; j <= right; j++) { f(j, cur.y); } } else { f(cur.x, cur.y); } } } /** * Quadrilateral **/ function quad( m: number, x: number, y: number, w: number, h: number, f: any, clip: number[], ) { var p1 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x, y), m); var p2 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x + w, y), m); var p3 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x, y + h), m); var p4 = vec2.transformMat2d(vec2.create(), vec2.fromValues(x + w, y + h), m); triangle(p1, p2, p3, f, clip); triangle(p3, p2, p4, f, clip); }