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
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;
|
|
|
|
}
|
|
|