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>
This commit is contained in:
pewdiepie-archdaemon
2026-10-15 11:57:35 +00:00
co-authored by Claude Opus 5.5
parent c74f475679
commit 5fac693c1c
290 changed files with 34358 additions and 766 deletions
+25 -7
View File
@@ -144,21 +144,24 @@ export function createPrecisionGuides({ canvasArea, composite, saveState, schedu
function drawDocumentOverlay(ctx) {
if (!ctx || !state.imgWidth || !state.imgHeight) return;
ctx.save();
if (state.gridVisible) {
const base = Math.max(2, Number(state.gridSize) || 16);
const multiplier = Math.max(1, Math.ceil(5 / Math.max(0.0001, base * state.zoom)));
const pixelBrush = !!state.brushPixel && (state.tool === 'brush' || state.tool === 'eraser');
if (state.gridVisible || pixelBrush) {
const base = pixelBrush
? Math.max(1, Math.round(Number(state.brushSize) || 1))
: Math.max(2, Number(state.gridSize) || 16);
const multiplier = pixelBrush ? 1 : Math.max(1, Math.ceil(5 / Math.max(0.0001, base * state.zoom)));
const spacing = base * multiplier;
const strong = pixelBrush ? 'rgba(122, 208, 255, 0.55)' : 'rgba(110, 180, 220, 0.42)';
const weak = pixelBrush ? 'rgba(122, 208, 255, 0.30)' : 'rgba(110, 180, 220, 0.22)';
ctx.lineWidth = 1 / state.zoom;
for (let x = spacing; x < state.imgWidth; x += spacing) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, state.imgHeight);
ctx.strokeStyle = Math.round(x / spacing) % 4 === 0
? 'rgba(110, 180, 220, 0.42)' : 'rgba(110, 180, 220, 0.22)';
ctx.strokeStyle = Math.round(x / spacing) % 4 === 0 ? strong : weak;
ctx.stroke();
}
for (let y = spacing; y < state.imgHeight; y += spacing) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(state.imgWidth, y);
ctx.strokeStyle = Math.round(y / spacing) % 4 === 0
? 'rgba(110, 180, 220, 0.42)' : 'rgba(110, 180, 220, 0.22)';
ctx.strokeStyle = Math.round(y / spacing) % 4 === 0 ? strong : weak;
ctx.stroke();
}
}
@@ -239,6 +242,21 @@ export function createPrecisionGuides({ canvasArea, composite, saveState, schedu
return {
drawDocumentOverlay,
redrawRulers,
addGuide(orientation, position) {
const axis = orientation === 'horizontal' ? 'horizontal' : 'vertical';
const limit = axis === 'horizontal' ? state.imgHeight : state.imgWidth;
const value = Math.max(0, Math.min(limit, Math.round(Number(position))));
if (!Number.isFinite(value)) return null;
const guides = normalizedGuides();
if (guides[axis].some(existing => Math.abs(existing - value) < 0.5)) return value;
saveState?.('Add guide');
guides[axis].push(value);
guides[axis].sort((a, b) => a - b);
schedulePersist?.();
composite();
redrawRulers();
return value;
},
syncVisibility() { redrawRulers(); composite(); },
clearGuides() {
const guides = normalizedGuides();