You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

301 lines
8.5 KiB

function disableImageSmoothing({ctx}) {
ctx.imageSmoothingEnabled = false;
if (ctx.imageSmoothingEnabled !== false) {
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
}
};
function makeCanvas({height, width}) {
const canvas = document.createElement("canvas");
canvas.save = function() { // {{{
canvas.temp.ctx.clearRect(0, 0, canvas.temp.width, canvas.temp.height);
canvas.temp.ctx.drawImage(canvas, 0, 0);
return canvas;
} // }}}
canvas.restore = function({x=0, y=0}={}) { // {{{
canvas.ctx.drawImage(canvas.temp, x, y);
return canvas;
} // }}}
canvas.add = function({canvas2}) { // {{{
canvas.ctx.drawImage(canvas2, 0, 0);
return canvas;
} // }}}
canvas.getData = function() { // {{{
return canvas.ctx.getImageData(0, 0, canvas.width, canvas.height).data;
}; // }}}
canvas.fromDataUrl = function({dataUrl}) { // {{{
return new Promise((resolve, reject) => {
const image = new Image();
image.src = dataUrl;
image.onload = () => {
canvas.width = image.width;
canvas.height = image.height;
canvas.style.width = `${image.width}px`;
canvas.style.height = `${image.height}px`;
canvas.ctx.drawImage(image, 0, 0);
disableImageSmoothing({ctx: canvas.ctx});
resolve(canvas);
};
image.onerror = (error) => {
reject(error); // Reject the promise if an error occurs
}
});
} // }}}
canvas.toDataUrl = function() { // {{{
return canvas.toDataURL();
}; // }}}
canvas.resize = function({height, width}) { // {{{
canvas.save();
canvas.clear();
canvas.height = height;
canvas.width = width;
canvas.style.height = `${height}px`;
canvas.style.width = `${width}px`;
disableImageSmoothing({ctx: canvas.ctx});
canvas.restore();
canvas.temp.height = height;
canvas.temp.width = width;
canvas.temp.style.height = `${height}px`;
canvas.temp.style.width = `${width}px`;
disableImageSmoothing({ctx: canvas.temp.ctx});
return canvas;
}; // }}}
canvas.drawRect = function({x, y, width, height, color=makeColor({r: 0, g: 0, b: 0, a: 255}), erase=false}) { // {{{
x = Math.round(x);
y = Math.round(y);
width = Math.round(width);
height = Math.round(height);
if (erase === true) {
canvas.ctx.clearRect(x, y, width, height);
} else {
canvas.ctx.fillStyle = color.toRgbaString();
canvas.ctx.fillRect(x, y, width, height);
}
return canvas;
}; // }}}
canvas.clear = function() { // {{{
canvas.drawRect({
x: 0,
y: 0,
width: canvas.width,
height: canvas.height,
erase: true,
});
return canvas;
}; // }}}
canvas.fill = function({color=makeColor({r: 0, g: 0, b: 0, a: 255})}) { // {{{
canvas.drawRect({
x: 0,
y: 0,
width: canvas.width,
height: canvas.height,
color: color,
});
return canvas;
}; // }}}
canvas.getPixel = function({x, y}) { // {{{
if (x < 0 || x >= canvas.width || y < 0 || y >= canvas.height) {
return makeColor({r: 0, g: 0, b: 0, a: 0});
}
const data = canvas.getData();
const index = (y * canvas.width + x) * 4;
const r = data[index];
const g = data[index + 1];
const b = data[index + 2];
const a = data[index + 3];
return makeColor({r: data[index], g: data[index + 1], b: data[index + 2], a: data[index + 3]});
}; // }}}
canvas.drawPixel = function({x, y, color=makeColor({r: 0, g: 0, b: 0, a: 255}), erase=false}) { // {{{
canvas.drawRect({
x,
y,
width: 1,
height: 1,
color,
erase,
});
return canvas;
}; // }}}
canvas.drawLineWithPixels = function({x1, y1, x2, y2, color=makeColor({r: 0, g: 0, b: 0, a: 255}), erase=false}) { // {{{
canvas.ctx.fillStyle = color.toRgbaString();
const dx = Math.abs(x2 - x1);
const dy = Math.abs(y2 - y1);
const sx = x1 < x2 ? 1 : -1;
const sy = y1 < y2 ? 1 : -1;
let err = dx - dy;
while (true) {
canvas.drawPixel({x: x1, y: y1, color, erase});
if (x1 === x2 && y1 === y2) break;
const e2 = err * 2;
if (e2 > -dy) { err -= dy; x1 += sx; }
if (e2 < dx) { err += dx; y1 += sy; }
}
return canvas;
}; // }}}
canvas.drawEmptyRectangle = function({x, y, width, height, color=makeColor({r: 0, g: 0, b: 0, a: 255}), erase=false}) { // {{{
width = Math.round(width);
height = Math.round(height);
canvas.rect({x: x, y: y, width: width, height: 1, color, erase});
canvas.rect({x: x, y: y, width: 1, height: height, color, erase});
canvas.rect({x: x, y: y + height, width: width, height: 1, color, erase});
canvas.rect({x: x + width, y: y, width: 1, height: height, color, erase});
return canvas;
}; // }}}
canvas.drawCircle = function({x, y, diameter, color=makeColor({r: 0, g: 0, b: 0, a: 255}), erase=false}) { // {{{
if (diameter === 1) {
canvas.drawPixel({x, y, color, erase});
}
let radius = Math.floor(diameter / 2);
let radiusSquared = radius * radius;
for (let y1 = -radius; y1 <= radius; y1++) {
for (let x1 = -radius; x1 <= radius; x1++) {
if ((x1 * x1 + y1 * y1) <= radiusSquared - radius) {
canvas.drawRect({x: x + x1, y: y + y1, width: 1, height: 1, color, erase});
}
}
}
return canvas;
}; // }}}
canvas.drawEmptyCircle = function({x, y, diameter, color = makeColor({r: 0, g: 0, b: 0, a: 255}), erase = false}) { // {{{
if (diameter === 1) {
canvas.drawPixel({x, y, color, erase});
return canvas;
}
let radius = Math.floor(diameter / 2);
let radiusSquared = radius * radius;
for (let y1 = -radius; y1 <= radius; y1++) {
for (let x1 = -radius; x1 <= radius; x1++) {
let distanceSquared = x1 * x1 + y1 * y1;
// Check if the point is on the circumference
if (distanceSquared >= radiusSquared - radius && distanceSquared <= radiusSquared + radius) {
canvas.drawRect({x: x + x1, y: y + y1, width: 1, height: 1, color, erase});
}
}
}
return canvas;
}; // }}}
canvas.drawLineWithCircles = function({x1, y1, x2, y2, diameter, color=makeColor({r: 0, g: 0, b: 0, a: 255}), erase=false}) { // {{{
if (diameter === 1) {
canvas.drawLineWithPixels({x1, y1, x2, y2, color, erase});
return canvas;
}
const dx = x2 - x1;
const dy = y2 - y1;
const distance = Math.sqrt(dx * dx + dy * dy);
const steps = Math.ceil(distance / (diameter / 3));
for (let i = 0; i <= steps; i++) {
const x = Math.round(x1 + (dx * i) / steps);
const y = Math.round(y1 + (dy * i) / steps);
canvas.drawCircle({x, y, diameter, color, erase});
}
return canvas;
}; // }}}
canvas.drawCrossHairs = function({x, y, length=5, color=makeColor({r: 0, g: 0, b: 0, a: 255}), erase=false}) { // {{{
canvas.drawRect({x: x - length, y, width: length * 2, height: 1, color, erase});
canvas.drawRect({x, y: y - length, width: 1, height: length * 2, color, erase});
return canvas;
} // }}}
canvas.floodFill = function({x, y, color=makeColor({r: 0, g: 0, b: 0, a: 255})}) { // {{{
const targetColor = canvas.getPixel({x, y});
const fillColor = color;
if (targetColor.match({color2: fillColor})) {
return;
}
const targetColorArray = targetColor.toRgbaArray();
const fillColorArray = fillColor.toRgbaArray();
const data = canvas.getData();
const stack = [{x, y}];
while (stack.length > 0) {
const {x, y} = stack.pop();
const index = (y * canvas.width + x) * 4;
const currentColorArray = [data[index], data[index + 1], data[index + 2], data[index + 3]];
if (currentColorArray[0] === targetColorArray[0] &&
currentColorArray[1] === targetColorArray[1] &&
currentColorArray[2] === targetColorArray[2] &&
currentColorArray[3] === targetColorArray[3]
) {
data[index] = fillColorArray[0];
data[index + 1] = fillColorArray[1];
data[index + 2] = fillColorArray[2];
data[index + 3] = fillColorArray[3];
if (x > 0) stack.push({x: x - 1, y});
if (x < canvas.width - 1) stack.push({x: x + 1, y});
if (y > 0) stack.push({x, y: y - 1});
if (y < canvas.height - 1) stack.push({x, y: y + 1});
}
}
canvas.ctx.putImageData(new ImageData(data, canvas.width, canvas.height), 0, 0);
return canvas;
}; // }}}
canvas.getPositionOnCanvas = function(e) { // {{{
const rect = canvas.getBoundingClientRect();
return {
x: Math.round((e.clientX - rect.left) / zoom),
y: Math.round((e.clientY - rect.top) / zoom),
};
} // }}}
canvas.init = function() { // {{{
canvas.style.imageRendering = "pixelated";
canvas.ctx = canvas.getContext("2d", { willReadFrequently: true });
canvas.temp = document.createElement("canvas");
canvas.temp.ctx = canvas.temp.getContext("2d", { willReadFrequently: true });
canvas.resize({height, width});
return canvas;
} // }}}
canvas.init();
return canvas;
}