mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 11:52:25 +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>
50 lines
1.9 KiB
JavaScript
50 lines
1.9 KiB
JavaScript
/** Shape boolean operations via canvas compositing. */
|
|
|
|
export const SHAPE_BOOLEAN_OPS = Object.freeze(['unite', 'subtract', 'intersect', 'exclude']);
|
|
|
|
const OP_COMPOSITE = Object.freeze({
|
|
unite: 'source-over',
|
|
subtract: 'destination-out',
|
|
intersect: 'destination-in',
|
|
exclude: 'xor',
|
|
});
|
|
|
|
/**
|
|
* Combine layer canvases into a single document-sized canvas.
|
|
* @param {Array<{canvas: HTMLCanvasElement}>} layers bottom-to-top order
|
|
* @param {string} op one of SHAPE_BOOLEAN_OPS
|
|
* @param {{width:number, height:number, offsets?: Array<{x:number,y:number}>}} options
|
|
* @returns {HTMLCanvasElement|null}
|
|
*/
|
|
export function combineLayers(layers, op, { width = 0, height = 0, offsets = [] } = {}) {
|
|
const list = Array.isArray(layers) ? layers.filter(layer => layer && layer.canvas) : [];
|
|
if (!list.length) return null;
|
|
const mode = SHAPE_BOOLEAN_OPS.includes(op) ? op : 'unite';
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = Math.max(1, Math.round(width) || list[0].canvas.width);
|
|
canvas.height = Math.max(1, Math.round(height) || list[0].canvas.height);
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.imageSmoothingEnabled = true;
|
|
ctx.imageSmoothingQuality = 'high';
|
|
const base = list[0];
|
|
const baseOffset = offsets[0] || { x: 0, y: 0 };
|
|
if (mode === 'unite') {
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
list.forEach((layer, index) => {
|
|
const offset = offsets[index] || { x: 0, y: 0 };
|
|
ctx.drawImage(layer.canvas, offset.x, offset.y);
|
|
});
|
|
} else {
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
ctx.drawImage(base.canvas, baseOffset.x, baseOffset.y);
|
|
ctx.globalCompositeOperation = OP_COMPOSITE[mode];
|
|
for (let index = 1; index < list.length; index += 1) {
|
|
const layer = list[index];
|
|
const offset = offsets[index] || { x: 0, y: 0 };
|
|
ctx.drawImage(layer.canvas, offset.x, offset.y);
|
|
}
|
|
}
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
return canvas;
|
|
}
|