dist-mcp / helpers / drawBboxesOnImage.js
dist-mcp / helpers / drawBboxesOnImage.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.drawBboxesOnImage = drawBboxesOnImage;
// Parse a CSS color name or hex string to an RGBA integer (Jimp format: 0xRRGGBBAA).
function cssColorToRgbaInt(color) {
const named = {
pink: [0xff, 0x69, 0xb4],
red: [0xff, 0x00, 0x00],
green: [0x00, 0x80, 0x00],
lime: [0x00, 0xff, 0x00],
blue: [0x00, 0x00, 0xff],
yellow: [0xff, 0xff, 0x00],
cyan: [0x00, 0xff, 0xff],
magenta: [0xff, 0x00, 0xff],
white: [0xff, 0xff, 0xff],
black: [0x00, 0x00, 0x00],
orange: [0xff, 0xa5, 0x00],
purple: [0x80, 0x00, 0x80],
};
const lower = color.trim().toLowerCase();
if (named[lower]) {
const [r, g, b] = named[lower];
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff) >>> 0;
}
const rgb3 = lower.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
if (rgb3) {
const r = parseInt(rgb3[1] + rgb3[1], 16);
const g = parseInt(rgb3[2] + rgb3[2], 16);
const b = parseInt(rgb3[3] + rgb3[3], 16);
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff) >>> 0;
}
const rgb6 = lower.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
if (rgb6) {
const r = parseInt(rgb6[1], 16);
const g = parseInt(rgb6[2], 16);
const b = parseInt(rgb6[3], 16);
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff) >>> 0;
}
return (((0xff & 0xff) << 24) | ((0x69 & 0xff) << 16) | ((0xb4 & 0xff) << 8) | 0xff) >>> 0;
}
async function drawBboxesOnImage(buffer, bboxes, options) {
const sourceDims = options?.sourceDims;
const usePalette = options?.palette !== false;
const lineWeight = options?.lineWeight ?? 2;
const singleColorInt = usePalette ? 0 : cssColorToRgbaInt(options?.color ?? "magenta");
const requireFn = typeof require !== "undefined" ? require : (await import("module")).createRequire(__filename);
const jimpMod = requireFn("jimp");
const Jimp = jimpMod.Jimp ?? jimpMod.default ?? jimpMod;
if (!Jimp || typeof Jimp.read !== "function") {
throw new Error("drawBboxesOnImage: Jimp.read not available");
}
const img = await Jimp.read(buffer);
const imgW = typeof img.getWidth === "function"
? img.getWidth()
: typeof img.width === "number"
? img.width
: img.bitmap?.width || 0;
const imgH = typeof img.getHeight === "function"
? img.getHeight()
: typeof img.height === "number"
? img.height
: img.bitmap?.height || 0;
const scaleX = sourceDims && sourceDims.width > 0 ? imgW / sourceDims.width : 1;
const scaleY = sourceDims && sourceDims.height > 0 ? imgH / sourceDims.height : 1;
const palette = [
[0xff, 0x3b, 0x30, 0xff],
[0x34, 0xc7, 0x59, 0xff],
[0x00, 0x7a, 0xff, 0xff],
[0xff, 0x9f, 0x0a, 0xff],
[0xbf, 0x5a, 0xf2, 0xff],
[0xff, 0xd6, 0x0a, 0xff],
];
for (let bi = 0; bi < bboxes.length; bi++) {
let colorInt;
if (usePalette) {
const [r, g, b, a] = palette[bi % palette.length];
colorInt = (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | (a & 0xff)) >>> 0;
}
else {
colorInt = singleColorInt;
}
const [bx1, by1, bx2, by2] = bboxes[bi];
const x1 = Math.max(0, Math.min(imgW - 1, Math.round(bx1 * scaleX)));
const y1 = Math.max(0, Math.min(imgH - 1, Math.round(by1 * scaleY)));
const x2 = Math.max(0, Math.min(imgW - 1, Math.round(bx2 * scaleX)));
const y2 = Math.max(0, Math.min(imgH - 1, Math.round(by2 * scaleY)));
for (let t = 0; t < lineWeight; t++) {
for (let x = x1; x <= x2; x++) {
if (y1 + t < imgH)
img.setPixelColor(colorInt, x, y1 + t);
if (y2 - t >= 0)
img.setPixelColor(colorInt, x, y2 - t);
}
for (let y = y1; y <= y2; y++) {
if (x1 + t < imgW)
img.setPixelColor(colorInt, x1 + t, y);
if (x2 - t >= 0)
img.setPixelColor(colorInt, x2 - t, y);
}
}
}
const bufResult = typeof img.getBufferAsync === "function"
? img.getBufferAsync("image/png")
: img.getBuffer("image/png");
if (bufResult && typeof bufResult.then === "function") {
return bufResult;
}
return new Promise((resolve, reject) => img.getBuffer("image/png", (err, data) => (err ? reject(err) : resolve(data))));
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.drawBboxesOnImage = drawBboxesOnImage;
// Parse a CSS color name or hex string to an RGBA integer (Jimp format: 0xRRGGBBAA).
function cssColorToRgbaInt(color) {
const named = {
pink: [0xff, 0x69, 0xb4],
red: [0xff, 0x00, 0x00],
green: [0x00, 0x80, 0x00],
lime: [0x00, 0xff, 0x00],
blue: [0x00, 0x00, 0xff],
yellow: [0xff, 0xff, 0x00],
cyan: [0x00, 0xff, 0xff],
magenta: [0xff, 0x00, 0xff],
white: [0xff, 0xff, 0xff],
black: [0x00, 0x00, 0x00],
orange: [0xff, 0xa5, 0x00],
purple: [0x80, 0x00, 0x80],
};
const lower = color.trim().toLowerCase();
if (named[lower]) {
const [r, g, b] = named[lower];
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff) >>> 0;
}
const rgb3 = lower.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
if (rgb3) {
const r = parseInt(rgb3[1] + rgb3[1], 16);
const g = parseInt(rgb3[2] + rgb3[2], 16);
const b = parseInt(rgb3[3] + rgb3[3], 16);
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff) >>> 0;
}
const rgb6 = lower.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
if (rgb6) {
const r = parseInt(rgb6[1], 16);
const g = parseInt(rgb6[2], 16);
const b = parseInt(rgb6[3], 16);
return (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff) >>> 0;
}
return (((0xff & 0xff) << 24) | ((0x69 & 0xff) << 16) | ((0xb4 & 0xff) << 8) | 0xff) >>> 0;
}
async function drawBboxesOnImage(buffer, bboxes, options) {
const sourceDims = options?.sourceDims;
const usePalette = options?.palette !== false;
const lineWeight = options?.lineWeight ?? 2;
const singleColorInt = usePalette ? 0 : cssColorToRgbaInt(options?.color ?? "magenta");
const requireFn = typeof require !== "undefined" ? require : (await import("module")).createRequire(__filename);
const jimpMod = requireFn("jimp");
const Jimp = jimpMod.Jimp ?? jimpMod.default ?? jimpMod;
if (!Jimp || typeof Jimp.read !== "function") {
throw new Error("drawBboxesOnImage: Jimp.read not available");
}
const img = await Jimp.read(buffer);
const imgW = typeof img.getWidth === "function"
? img.getWidth()
: typeof img.width === "number"
? img.width
: img.bitmap?.width || 0;
const imgH = typeof img.getHeight === "function"
? img.getHeight()
: typeof img.height === "number"
? img.height
: img.bitmap?.height || 0;
const scaleX = sourceDims && sourceDims.width > 0 ? imgW / sourceDims.width : 1;
const scaleY = sourceDims && sourceDims.height > 0 ? imgH / sourceDims.height : 1;
const palette = [
[0xff, 0x3b, 0x30, 0xff],
[0x34, 0xc7, 0x59, 0xff],
[0x00, 0x7a, 0xff, 0xff],
[0xff, 0x9f, 0x0a, 0xff],
[0xbf, 0x5a, 0xf2, 0xff],
[0xff, 0xd6, 0x0a, 0xff],
];
for (let bi = 0; bi < bboxes.length; bi++) {
let colorInt;
if (usePalette) {
const [r, g, b, a] = palette[bi % palette.length];
colorInt = (((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | (a & 0xff)) >>> 0;
}
else {
colorInt = singleColorInt;
}
const [bx1, by1, bx2, by2] = bboxes[bi];
const x1 = Math.max(0, Math.min(imgW - 1, Math.round(bx1 * scaleX)));
const y1 = Math.max(0, Math.min(imgH - 1, Math.round(by1 * scaleY)));
const x2 = Math.max(0, Math.min(imgW - 1, Math.round(bx2 * scaleX)));
const y2 = Math.max(0, Math.min(imgH - 1, Math.round(by2 * scaleY)));
for (let t = 0; t < lineWeight; t++) {
for (let x = x1; x <= x2; x++) {
if (y1 + t < imgH)
img.setPixelColor(colorInt, x, y1 + t);
if (y2 - t >= 0)
img.setPixelColor(colorInt, x, y2 - t);
}
for (let y = y1; y <= y2; y++) {
if (x1 + t < imgW)
img.setPixelColor(colorInt, x1 + t, y);
if (x2 - t >= 0)
img.setPixelColor(colorInt, x2 - t, y);
}
}
}
const bufResult = typeof img.getBufferAsync === "function"
? img.getBufferAsync("image/png")
: img.getBuffer("image/png");
if (bufResult && typeof bufResult.then === "function") {
return bufResult;
}
return new Promise((resolve, reject) => img.getBuffer("image/png", (err, data) => (err ? reject(err) : resolve(data))));
}