tldraw
Version:
A tiny little drawing editor.
102 lines (101 loc) • 3.1 kB
JavaScript
import { Image } from "@tldraw/editor";
const TRANSPARENT_IMAGE_MIMETYPES = [
"image/png",
"image/webp",
"image/gif",
"image/avif"
];
function mapToImageCoords(config, point, bounds) {
let nx = Math.max(0, Math.min(1, (point.x - bounds.minX) / bounds.w));
let ny = Math.max(0, Math.min(1, (point.y - bounds.minY) / bounds.h));
if (config.crop) {
const { topLeft, bottomRight } = config.crop;
nx = topLeft.x + nx * (bottomRight.x - topLeft.x);
ny = topLeft.y + ny * (bottomRight.y - topLeft.y);
}
if (config.flipX) nx = 1 - nx;
if (config.flipY) ny = 1 - ny;
return { nx, ny };
}
function isImagePointTransparent(config, point, bounds) {
const data = config.alphaDataGetter();
if (!data) return false;
const { nx, ny } = mapToImageCoords(config, point, bounds);
return isPointTransparent(data, nx, ny);
}
const MAX_SIZE = 256;
const alphaCache = /* @__PURE__ */ new Map();
const pending = /* @__PURE__ */ new Set();
let offscreenCanvas = null;
function getOffscreenCanvas(w, h) {
if (!offscreenCanvas) {
offscreenCanvas = new OffscreenCanvas(w, h);
} else {
offscreenCanvas.width = w;
offscreenCanvas.height = h;
}
return offscreenCanvas;
}
function extractAlphas(ctx, w, h) {
const imageData = ctx.getImageData(0, 0, w, h);
const pixels = new Uint32Array(imageData.data.buffer);
const alphas = new Uint8Array(w * h);
for (let i = 0; i < alphas.length; i++) {
alphas[i] = pixels[i] >>> 24;
}
return alphas;
}
function preloadAlphaData(url, cacheKey) {
const key = cacheKey ?? url;
if (alphaCache.has(key) || pending.has(key)) return;
pending.add(key);
const img = Image();
img.crossOrigin = "anonymous";
img.onload = async () => {
pending.delete(key);
const { width: origW, height: origH } = img;
if (origW === 0 || origH === 0) return;
const scale = Math.min(1, MAX_SIZE / Math.max(origW, origH));
const w = Math.max(1, Math.round(origW * scale));
const h = Math.max(1, Math.round(origH * scale));
let bitmap = null;
try {
bitmap = await createImageBitmap(img, {
resizeWidth: w,
resizeHeight: h,
resizeQuality: "low"
});
} catch {
}
const canvas = getOffscreenCanvas(w, h);
const ctx = canvas.getContext("2d");
if (!ctx) return;
if (bitmap) {
ctx.drawImage(bitmap, 0, 0);
bitmap.close();
} else {
ctx.drawImage(img, 0, 0, w, h);
}
alphaCache.set(key, { width: w, height: h, alphas: extractAlphas(ctx, w, h) });
};
img.onerror = () => {
pending.delete(key);
};
img.src = url;
}
function getAlphaData(src) {
return alphaCache.get(src) ?? null;
}
function isPointTransparent(data, nx, ny, threshold = 10) {
const ix = Math.min(Math.floor(nx * data.width), data.width - 1);
const iy = Math.min(Math.floor(ny * data.height), data.height - 1);
return data.alphas[iy * data.width + ix] < threshold;
}
export {
TRANSPARENT_IMAGE_MIMETYPES,
getAlphaData,
isImagePointTransparent,
isPointTransparent,
preloadAlphaData
};
//# sourceMappingURL=ImageAlphaCache.mjs.map