mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-21 23:53:08 +01:00
Decaffeinate Canvas
This commit is contained in:
parent
4122e8826b
commit
09142e7a8f
@ -1,161 +0,0 @@
|
|||||||
###
|
|
||||||
termap - Terminal Map Viewer
|
|
||||||
by Michael Strassburger <codepoet@cpan.org>
|
|
||||||
|
|
||||||
Canvas-like painting abstraction for BrailleBuffer
|
|
||||||
|
|
||||||
Implementation inspired by node-drawille-canvas (https://github.com/madbence/node-drawille-canvas)
|
|
||||||
* added support for filled polygons
|
|
||||||
* improved text rendering
|
|
||||||
|
|
||||||
Will most likely be turned into a stand alone module at some point
|
|
||||||
###
|
|
||||||
|
|
||||||
bresenham = require 'bresenham'
|
|
||||||
simplify = require 'simplify-js'
|
|
||||||
|
|
||||||
earcut = require 'earcut'
|
|
||||||
BrailleBuffer = require './BrailleBuffer'
|
|
||||||
utils = require './utils'
|
|
||||||
|
|
||||||
module.exports = class Canvas
|
|
||||||
stack: []
|
|
||||||
|
|
||||||
constructor: (@width, @height) ->
|
|
||||||
@buffer = new BrailleBuffer @width, @height
|
|
||||||
|
|
||||||
frame: ->
|
|
||||||
@buffer.frame()
|
|
||||||
|
|
||||||
clear: ->
|
|
||||||
@buffer.clear()
|
|
||||||
|
|
||||||
text: (text, x, y, color, center = false) ->
|
|
||||||
@buffer.writeText text, x, y, color, center
|
|
||||||
|
|
||||||
line: (from, to, color, width = 1) ->
|
|
||||||
@_line from.x, from.y, to.x, to.y, color, width
|
|
||||||
|
|
||||||
polyline: (points, color, width = 1) ->
|
|
||||||
for i in [1...points.length]
|
|
||||||
@_line points[i-1].x, points[i-1].y, points[i].x, points[i].y, width, color
|
|
||||||
|
|
||||||
setBackground: (color) ->
|
|
||||||
@buffer.setGlobalBackground color
|
|
||||||
|
|
||||||
background: (x, y, color) ->
|
|
||||||
@buffer.setBackground x, y, color
|
|
||||||
|
|
||||||
polygon: (rings, color) ->
|
|
||||||
vertices = []
|
|
||||||
holes = []
|
|
||||||
|
|
||||||
for ring in rings
|
|
||||||
if vertices.length
|
|
||||||
continue if ring.length < 3
|
|
||||||
holes.push vertices.length/2
|
|
||||||
else
|
|
||||||
return false if ring.length < 3
|
|
||||||
|
|
||||||
for point in ring
|
|
||||||
vertices.push point.x
|
|
||||||
vertices.push point.y
|
|
||||||
|
|
||||||
try
|
|
||||||
triangles = earcut vertices, holes
|
|
||||||
catch e
|
|
||||||
return false
|
|
||||||
|
|
||||||
for i in [0...triangles.length] by 3
|
|
||||||
pa = @_polygonExtract vertices, triangles[i]
|
|
||||||
pb = @_polygonExtract vertices, triangles[i+1]
|
|
||||||
pc = @_polygonExtract vertices, triangles[i+2]
|
|
||||||
|
|
||||||
@_filledTriangle pa, pb, pc, color
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
_polygonExtract: (vertices, pointId) ->
|
|
||||||
[vertices[pointId*2], vertices[pointId*2+1]]
|
|
||||||
|
|
||||||
# Inspired by Alois Zingl's "The Beauty of Bresenham's Algorithm"
|
|
||||||
# -> http://members.chello.at/~easyfilter/bresenham.html
|
|
||||||
_line: (x0, y0, x1, y1, width, color) ->
|
|
||||||
|
|
||||||
# Fall back to width-less bresenham algorithm if we dont have a width
|
|
||||||
unless width = Math.max 0, width-1
|
|
||||||
return bresenham x0, y0, x1, y1,
|
|
||||||
(x, y) => @buffer.setPixel x, y, color
|
|
||||||
|
|
||||||
dx = Math.abs x1-x0
|
|
||||||
sx = if x0 < x1 then 1 else -1
|
|
||||||
dy = Math.abs y1-y0
|
|
||||||
sy = if y0 < y1 then 1 else -1
|
|
||||||
|
|
||||||
err = dx-dy
|
|
||||||
|
|
||||||
ed = if dx+dy is 0 then 1 else Math.sqrt dx*dx+dy*dy
|
|
||||||
|
|
||||||
width = (width+1)/2
|
|
||||||
loop
|
|
||||||
@buffer.setPixel x0, y0, color
|
|
||||||
e2 = err
|
|
||||||
x2 = x0
|
|
||||||
|
|
||||||
if 2*e2 >= -dx
|
|
||||||
e2 += dy
|
|
||||||
y2 = y0
|
|
||||||
while e2 < ed*width && (y1 != y2 || dx > dy)
|
|
||||||
@buffer.setPixel x0, y2 += sy, color
|
|
||||||
e2 += dx
|
|
||||||
break if x0 is x1
|
|
||||||
e2 = err
|
|
||||||
err -= dy
|
|
||||||
x0 += sx
|
|
||||||
|
|
||||||
if 2*e2 <= dy
|
|
||||||
e2 = dx-e2
|
|
||||||
while e2 < ed*width && (x1 != x2 || dx < dy)
|
|
||||||
@buffer.setPixel x2 += sx, y0, color
|
|
||||||
e2 += dy
|
|
||||||
break if y0 is y1
|
|
||||||
err += dx
|
|
||||||
y0 += sy
|
|
||||||
|
|
||||||
_filledRectangle: (x, y, width, height, color) ->
|
|
||||||
pointA = [x, y]
|
|
||||||
pointB = [x+width, y]
|
|
||||||
pointC = [x, y+height]
|
|
||||||
pointD = [x+width, y+height]
|
|
||||||
|
|
||||||
@_filledTriangle pointA, pointB, pointC, color
|
|
||||||
@_filledTriangle pointC, pointB, pointD, color
|
|
||||||
|
|
||||||
_bresenham: (pointA, pointB) ->
|
|
||||||
bresenham pointA[0], pointA[1],
|
|
||||||
pointB[0], pointB[1]
|
|
||||||
|
|
||||||
# Draws a filled triangle
|
|
||||||
_filledTriangle: (pointA, pointB, pointC, color) ->
|
|
||||||
a = @_bresenham pointB, pointC
|
|
||||||
b = @_bresenham pointA, pointC
|
|
||||||
c = @_bresenham pointA, pointB
|
|
||||||
|
|
||||||
points = a.concat(b).concat(c)
|
|
||||||
.filter (point) => 0 <= point.y < @height
|
|
||||||
.sort (a, b) -> if a.y is b.y then a.x - b.x else a.y-b.y
|
|
||||||
|
|
||||||
for i in [0...points.length]
|
|
||||||
point = points[i]
|
|
||||||
next = points[i*1+1]
|
|
||||||
|
|
||||||
if point.y is next?.y
|
|
||||||
left = Math.max 0, point.x
|
|
||||||
right = Math.min @width-1, next.x
|
|
||||||
if left >= 0 and right <= @width
|
|
||||||
@buffer.setPixel x, point.y, color for x in [left..right]
|
|
||||||
|
|
||||||
else
|
|
||||||
@buffer.setPixel point.x, point.y, color
|
|
||||||
|
|
||||||
break unless next
|
|
200
src/Canvas.js
Normal file
200
src/Canvas.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
termap - Terminal Map Viewer
|
||||||
|
by Michael Strassburger <codepoet@cpan.org>
|
||||||
|
|
||||||
|
Canvas-like painting abstraction for BrailleBuffer
|
||||||
|
|
||||||
|
Implementation inspired by node-drawille-canvas (https://github.com/madbence/node-drawille-canvas)
|
||||||
|
* added support for filled polygons
|
||||||
|
* improved text rendering
|
||||||
|
|
||||||
|
Will most likely be turned into a stand alone module at some point
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
const bresenham = require('bresenham');
|
||||||
|
const earcut = require('earcut');
|
||||||
|
const BrailleBuffer = require('./BrailleBuffer');
|
||||||
|
|
||||||
|
class Canvas {
|
||||||
|
constructor(width, height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.buffer = new BrailleBuffer(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame() {
|
||||||
|
return this.buffer.frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.buffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
text(text, x, y, color, center = false) {
|
||||||
|
this.buffer.writeText(text, x, y, color, center);
|
||||||
|
}
|
||||||
|
|
||||||
|
line(from, to, color, width = 1) {
|
||||||
|
this._line(from.x, from.y, to.x, to.y, color, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
polyline(points, color, width = 1) {
|
||||||
|
for (let i = 1; i < points.length; i++) {
|
||||||
|
const x1 = points[i - 1].x;
|
||||||
|
const y1 = points[i - 1].y;
|
||||||
|
this._line(x1, y1, points[i].x, points[i].y, width, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setBackground(color) {
|
||||||
|
this.buffer.setGlobalBackground(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
background(x, y, color) {
|
||||||
|
this.buffer.setBackground(x, y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
polygon(rings, color) {
|
||||||
|
const vertices = [];
|
||||||
|
const holes = [];
|
||||||
|
for (const ring of rings) {
|
||||||
|
if (vertices.length) {
|
||||||
|
if (ring.length < 3) continue;
|
||||||
|
holes.push(vertices.length / 2);
|
||||||
|
} else {
|
||||||
|
if (ring.length < 3) return false;
|
||||||
|
}
|
||||||
|
for (const point of ring) {
|
||||||
|
vertices.push(point.x);
|
||||||
|
vertices.push(point.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let triangles;
|
||||||
|
try {
|
||||||
|
triangles = earcut(vertices, holes);
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < triangles.length; i += 3) {
|
||||||
|
const pa = this._polygonExtract(vertices, triangles[i]);
|
||||||
|
const pb = this._polygonExtract(vertices, triangles[i + 1]);
|
||||||
|
const pc = this._polygonExtract(vertices, triangles[i + 2]);
|
||||||
|
this._filledTriangle(pa, pb, pc, color);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_polygonExtract(vertices, pointId) {
|
||||||
|
return [vertices[pointId * 2], vertices[pointId * 2 + 1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspired by Alois Zingl's "The Beauty of Bresenham's Algorithm"
|
||||||
|
// -> http://members.chello.at/~easyfilter/bresenham.html
|
||||||
|
_line(x0, y0, x1, y1, width, color) {
|
||||||
|
// Fall back to width-less bresenham algorithm if we dont have a width
|
||||||
|
if (!(width = Math.max(0, width - 1))) {
|
||||||
|
return bresenham(x0, y0, x1, y1, (x, y) => {
|
||||||
|
return this.buffer.setPixel(x, y, color);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dx = Math.abs(x1 - x0);
|
||||||
|
const sx = x0 < x1 ? 1 : -1;
|
||||||
|
const dy = Math.abs(y1 - y0);
|
||||||
|
const sy = y0 < y1 ? 1 : -1;
|
||||||
|
|
||||||
|
let err = dx - dy;
|
||||||
|
|
||||||
|
const ed = dx + dy === 0 ? 1 : Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
width = (width + 1) / 2;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
this.buffer.setPixel(x0, y0, color);
|
||||||
|
let e2 = err;
|
||||||
|
let x2 = x0;
|
||||||
|
if (2 * e2 >= -dx) {
|
||||||
|
e2 += dy;
|
||||||
|
let y2 = y0;
|
||||||
|
while (e2 < ed * width && (y1 !== y2 || dx > dy)) {
|
||||||
|
this.buffer.setPixel(x0, y2 += sy, color);
|
||||||
|
e2 += dx;
|
||||||
|
}
|
||||||
|
if (x0 === x1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
e2 = err;
|
||||||
|
err -= dy;
|
||||||
|
x0 += sx;
|
||||||
|
}
|
||||||
|
if (2 * e2 <= dy) {
|
||||||
|
e2 = dx - e2;
|
||||||
|
while (e2 < ed * width && (x1 !== x2 || dx < dy)) {
|
||||||
|
this.buffer.setPixel(x2 += sx, y0, color);
|
||||||
|
e2 += dy;
|
||||||
|
}
|
||||||
|
if (y0 === y1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
err += dx;
|
||||||
|
y0 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_filledRectangle(x, y, width, height, color) {
|
||||||
|
const pointA = [x, y];
|
||||||
|
const pointB = [x + width, y];
|
||||||
|
const pointC = [x, y + height];
|
||||||
|
const pointD = [x + width, y + height];
|
||||||
|
this._filledTriangle(pointA, pointB, pointC, color);
|
||||||
|
this._filledTriangle(pointC, pointB, pointD, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
_bresenham(pointA, pointB) {
|
||||||
|
return bresenham(pointA[0], pointA[1], pointB[0], pointB[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws a filled triangle
|
||||||
|
_filledTriangle(pointA, pointB, pointC, color) {
|
||||||
|
const a = this._bresenham(pointB, pointC);
|
||||||
|
const b = this._bresenham(pointA, pointC);
|
||||||
|
const c = this._bresenham(pointA, pointB);
|
||||||
|
|
||||||
|
const points = a.concat(b).concat(c).filter((point) => {
|
||||||
|
var ref;
|
||||||
|
return (0 <= (ref = point.y) && ref < this.height);
|
||||||
|
}).sort(function(a, b) {
|
||||||
|
if (a.y === b.y) {
|
||||||
|
return a.x - b.x;
|
||||||
|
} else {
|
||||||
|
return a.y - b.y;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const point = points[i];
|
||||||
|
const next = points[i * 1 + 1];
|
||||||
|
|
||||||
|
if (point.y === (next || {}).y) {
|
||||||
|
const left = Math.max(0, point.x);
|
||||||
|
const right = Math.min(this.width - 1, next.x);
|
||||||
|
if (left >= 0 && right <= this.width) {
|
||||||
|
for (let x = left; x <= right; x++) {
|
||||||
|
this.buffer.setPixel(x, point.y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.buffer.setPixel(point.x, point.y, color);
|
||||||
|
}
|
||||||
|
if (!next) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas.prototype.stack = [];
|
||||||
|
|
||||||
|
module.exports = Canvas;
|
Loading…
Reference in New Issue
Block a user