mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 03:42:20 +02:00
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).
Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.3 KiB
JavaScript
63 lines
2.3 KiB
JavaScript
// Small, dependency-light mask helpers used by the editor-control mask
|
|
// operations (add-mask-from-selection, apply/invert/feather) and per-layer
|
|
// vision ops (get bounds). Pure data helpers are exported so they can be unit
|
|
// tested in Node without a real canvas.
|
|
|
|
// Bounding box of non-transparent pixels. `data` is RGBA (Uint8ClampedArray or
|
|
// plain array), row-major, length width*height*4. Returns {x,y,w,h} or null.
|
|
export function alphaBounds(data, width, height, threshold = 8) {
|
|
if (!data || !(width > 0) || !(height > 0)) return null;
|
|
let minX = width, minY = height, maxX = -1, maxY = -1;
|
|
for (let y = 0; y < height; y++) {
|
|
const row = y * width;
|
|
for (let x = 0; x < width; x++) {
|
|
const a = data[(row + x) * 4 + 3];
|
|
if (a > threshold) {
|
|
if (x < minX) minX = x;
|
|
if (x > maxX) maxX = x;
|
|
if (y < minY) minY = y;
|
|
if (y > maxY) maxY = y;
|
|
}
|
|
}
|
|
}
|
|
if (maxX < 0) return null;
|
|
return { x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1 };
|
|
}
|
|
|
|
// Invert a mask stored as white RGB + alpha (selected = alpha 255). Returns a
|
|
// NEW array; the source is not mutated.
|
|
export function invertMaskData(data) {
|
|
const out = new Uint8ClampedArray(data.length);
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
out[i] = 255;
|
|
out[i + 1] = 255;
|
|
out[i + 2] = 255;
|
|
out[i + 3] = 255 - data[i + 3];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Blur (feather) a mask canvas into a fresh canvas. `createCanvas(w,h)` must
|
|
// return a canvas-like object with getContext('2d'). Returns the new canvas.
|
|
export function featherMaskCanvas(createCanvas, srcCanvas, radius) {
|
|
const w = srcCanvas.width, h = srcCanvas.height;
|
|
const out = createCanvas(w, h);
|
|
const ctx = out.getContext('2d');
|
|
const r = Math.max(0, Number(radius) || 0);
|
|
if (r > 0 && 'filter' in ctx) ctx.filter = `blur(${r}px)`;
|
|
ctx.drawImage(srcCanvas, 0, 0);
|
|
if (r > 0 && 'filter' in ctx) ctx.filter = 'none';
|
|
return out;
|
|
}
|
|
|
|
// Bake a mask into a layer by multiplying the layer alpha (destination-in).
|
|
export function bakeMaskIntoLayer(layerCtx, maskCanvas, offsetX = 0, offsetY = 0) {
|
|
layerCtx.save();
|
|
layerCtx.globalCompositeOperation = 'destination-in';
|
|
layerCtx.drawImage(maskCanvas, offsetX, offsetY);
|
|
layerCtx.restore();
|
|
return true;
|
|
}
|
|
|
|
export default { alphaBounds, invertMaskData, featherMaskCanvas, bakeMaskIntoLayer };
|