Files
odysseus/static/js/editor/canvas-transforms.js
T
pewdiepie-archdaemonandClaude Opus 5.5 5fac693c1c feat(photo-editor): Ajax Image Editor client (Photoshop-parity tools)
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>
2026-10-15 11:57:35 +00:00

112 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Whole-document transforms: rotate by 90/180/270° or flip horizontal/
* vertical. These mutate every layer's canvas + the offset map + the
* document's overall width/height so the result feels like the whole
* image rotated as one piece.
*
* Pure-ish — reads/writes shared state directly; the factory takes a
* small dep bag for the orchestration plumbing (undo snapshot, canvas
* loading overlay, fit-zoom-to-viewport, composite redraw).
*
* @param {{
* saveState: (label?: string) => void,
* composite: () => void,
* fitZoom: () => void,
* showCanvasLoading: (label: string) => void,
* hideCanvasLoading: () => void,
* renderLayerPanel: () => void,
* }} deps
*/
import { state } from './state.js';
import { flipDocument, rotateDocument, rotateDocumentArbitrary, flipLayer as flipLayerGeometry, rotateLayer as rotateLayerGeometry } from './document-geometry.js';
export function createCanvasTransforms({
saveState, composite, fitZoom, showCanvasLoading, hideCanvasLoading, renderLayerPanel,
}) {
return {
/**
* Rotate the entire document by `deg` (90 / 180 / 270). 90 and 270
* swap canvas dimensions. Each layer is rotated around its own
* centre, then its centre is rotated around the old image centre
* and translated into the new image's frame.
*
* Wrapped in requestAnimationFrame because the rotation pass can
* block the UI for 0.5–2 s on big images — the spinner overlay
* paints before we block.
*/
rotateAll(deg) {
if (!state.layers.length) return;
saveState(`Rotate ${deg}°`);
showCanvasLoading('Rotating…');
requestAnimationFrame(() => {
try {
const result = rotateDocument(state, deg);
const sizeLabel = document.getElementById('ge-canvas-size');
if (sizeLabel && result) sizeLabel.textContent = `${result.width}×${result.height}`;
fitZoom();
composite();
renderLayerPanel?.();
} finally {
hideCanvasLoading();
}
});
},
/**
* Rotate the entire document by an arbitrary angle in degrees
* (clockwise; negative for counter-clockwise), expanding the canvas
* so nothing is clipped. Right angles use the exact integer path.
*/
rotateAllArbitrary(deg) {
if (!state.layers.length) return;
saveState(`Rotate ${deg}°`);
showCanvasLoading('Rotating…');
requestAnimationFrame(() => {
try {
const result = rotateDocumentArbitrary(state, deg);
const sizeLabel = document.getElementById('ge-canvas-size');
if (sizeLabel && result) sizeLabel.textContent = `${result.width}×${result.height}`;
fitZoom();
composite();
renderLayerPanel?.();
} finally {
hideCanvasLoading();
}
});
},
/**
* Mirror every layer horizontally ('h') or vertically ('v').
* Canvas dimensions don't change. Each layer offset is reflected
* around the image centre.
*/
flipAll(axis) {
if (!state.layers.length) return;
saveState(axis === 'h' ? 'Flip horizontal' : 'Flip vertical');
flipDocument(state, axis);
composite();
renderLayerPanel?.();
},
/** Flip only the active layer, in place (Edit ▸ Transform ▸ Flip). */
flipLayer(axis) {
const layer = state.layers.find((item) => item.id === state.activeLayerId);
if (!layer) return;
saveState(axis === 'h' ? 'Flip layer horizontal' : 'Flip layer vertical');
flipLayerGeometry(state, layer, axis);
composite();
renderLayerPanel?.();
},
/** Rotate only the active layer by 90/180/270°, centre preserved. */
rotateLayer(deg) {
const layer = state.layers.find((item) => item.id === state.activeLayerId);
if (!layer) return;
saveState(`Rotate layer ${deg}°`);
rotateLayerGeometry(state, layer, deg);
composite();
renderLayerPanel?.();
},
};
}