mcp-painter
by flrngel
Verified
import { PNG } from 'pngjs';
interface Color {
r: number;
g: number;
b: number;
a: number;
}
interface Pixel extends Color {} // Pixel is currently same as Color, can be extended if needed
class Canvas {
width: number;
height: number;
pixels: Pixel[][];
constructor(width: number, height: number) {
if (typeof width !== 'number' || width <= 0 || typeof height !== 'number' || height <= 0) {
throw new Error("Canvas dimensions must be positive numbers.");
}
this.width = width;
this.height = height;
this.pixels = [];
for (let y = 0; y < height; y++) {
this.pixels[y] = [];
for (let x = 0; x < width; x++) {
// Default to white background
this.pixels[y][x] = { r: 255, g: 255, b: 255, a: 255 };
}
}
}
fillRectangle(x: number, y: number, width: number, height: number, color: Color): void {
if (!this.isValidCoordinate(x, y) || !this.isValidCoordinate(x + width - 1, y + height - 1)) {
throw new Error("Rectangle coordinates are out of canvas bounds.");
}
if (typeof width !== 'number' || width <= 0 || typeof height !== 'number' || height <= 0) {
throw new Error("Rectangle dimensions must be positive numbers.");
}
if (!this.isValidColor(color)) {
throw new Error("Invalid color format. Color should be an object with {r, g, b, a} values (0-255 for r, g, b and 0-255 for a).");
}
for (let rectY = y; rectY < y + height; rectY++) {
for (let rectX = x; rectX < x + width; rectX++) {
this.pixels[rectY][rectX] = { ...color }; // Spread to avoid modifying the original color object
}
}
}
getCanvasData(): Pixel[][] { // Renamed from getCanvas to getCanvasData for clarity
return this.pixels;
}
async getCanvasPngBase64(): Promise<string> {
const png = new PNG({
width: this.width,
height: this.height,
bitDepth: 8,
colorType: 6, // truecolor with alpha
inputColorType: 6,
});
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const pixel = this.pixels[y][x];
const idx = (y * this.width + x) << 2; // Faster than * 4
png.data[idx] = pixel.r;
png.data[idx + 1] = pixel.g;
png.data[idx + 2] = pixel.b;
png.data[idx + 3] = pixel.a;
}
}
return new Promise<string>((resolve, reject) => {
const chunks: Buffer[] = [];
png.pack()
.on('data', function(chunk) {
chunks.push(chunk);
})
.on('end', function() {
const pngBuffer = Buffer.concat(chunks);
const base64String = pngBuffer.toString('base64');
resolve(base64String);
})
.on('error', function(error) {
reject(error);
});
});
}
isValidCoordinate(x: number, y: number): boolean {
return x >= 0 && x < this.width && y >= 0 && y < this.height;
}
isValidColor(color: any): color is Color {
if (!color || typeof color !== 'object') return false;
const { r, g, b, a } = color as Color; // Type assertion
if (typeof r !== 'number' || typeof g !== 'number' || typeof b !== 'number' || typeof a !== 'number') return false;
return r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 255;
}
}
function generateCanvas(width: number, height: number): Canvas {
return new Canvas(width, height);
}
function fillRectangle(canvas: Canvas, x: number, y: number, width: number, height: number, color: Color): void {
if (!(canvas instanceof Canvas)) {
throw new Error("Invalid canvas object provided.");
}
canvas.fillRectangle(x, y, width, height, color);
}
function getCanvasPngBase64(canvas: Canvas): Promise<string> {
if (!(canvas instanceof Canvas)) {
throw new Error("Invalid canvas object provided.");
}
return canvas.getCanvasPngBase64();
}
function getCanvasData(canvas: Canvas): Pixel[][] { // Exporting getCanvasData instead of getCanvas
if (!(canvas instanceof Canvas)) {
throw new Error("Invalid canvas object provided.");
}
return canvas.getCanvasData();
}
export {
generateCanvas,
fillRectangle,
getCanvasPngBase64,
getCanvasData, // Exporting getCanvasData
Canvas, // Export Canvas class if needed elsewhere
Color, // Export Color interface if needed elsewhere
};