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>
91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
import json
|
|
import subprocess
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MODULE = (ROOT / "static/js/editor/layer-hit-test.js").as_uri()
|
|
|
|
|
|
def run_node(body: str):
|
|
script = textwrap.dedent(
|
|
f"""
|
|
import {{ pickTopmostLayerAt, AUTO_SELECT_ALPHA_THRESHOLD }} from {json.dumps(MODULE)};
|
|
{body}
|
|
"""
|
|
)
|
|
result = subprocess.run(
|
|
["node", "--input-type=module", "-e", script],
|
|
cwd=ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_pick_topmost_opaque_layer_skips_transparent_and_special_layers():
|
|
result = run_node(
|
|
"""
|
|
const mk = (id, extra = {}) => ({id, visible: true, canvas: {width: 4, height: 4}, ...extra});
|
|
// Special layer kinds sit ABOVE an opaque raster so the walk must skip them.
|
|
const layers = [
|
|
mk('opaque'),
|
|
mk('transparent'),
|
|
mk('hidden', {visible: false}),
|
|
mk('adjustment', {kind: 'adjustment'}),
|
|
];
|
|
const alpha = {opaque: 255, transparent: 0};
|
|
const over = pickTopmostLayerAt({
|
|
layers,
|
|
point: {x: 1, y: 1},
|
|
sampleAlpha: (layer, x, y) => alpha[layer.id] ?? null,
|
|
});
|
|
// A genuinely topmost opaque layer still wins over a lower opaque one.
|
|
const stacked = pickTopmostLayerAt({
|
|
layers: [mk('under'), mk('over')],
|
|
point: {x: 1, y: 1},
|
|
sampleAlpha: () => 255,
|
|
});
|
|
console.log(JSON.stringify({over: over && over.id, stacked: stacked && stacked.id, threshold: AUTO_SELECT_ALPHA_THRESHOLD}));
|
|
"""
|
|
)
|
|
assert result == {"over": "opaque", "stacked": "over", "threshold": 8}
|
|
|
|
|
|
def test_pick_respects_offsets_bounds_locks_and_hidden_groups():
|
|
result = run_node(
|
|
"""
|
|
const mk = (id) => ({id, visible: true, canvas: {width: 10, height: 10}});
|
|
const layers = [mk('offsetHit'), mk('far'), mk('locked'), mk('inHiddenGroup')];
|
|
const offsets = {offsetHit: {x: 5, y: 5}, far: {x: 0, y: 0}, locked: {x: 0, y: 0}, inHiddenGroup: {x: 0, y: 0}};
|
|
const picked = pickTopmostLayerAt({
|
|
layers,
|
|
point: {x: 6, y: 6},
|
|
isLocked: layer => layer.id === 'locked',
|
|
isHidden: layer => layer.id === 'inHiddenGroup',
|
|
getOffset: layer => offsets[layer.id],
|
|
// 'far' has no rendered pixel under this point.
|
|
sampleAlpha: (layer, x, y) => (layer.id === 'far' ? null : 255),
|
|
});
|
|
console.log(JSON.stringify({picked: picked && picked.id}));
|
|
"""
|
|
)
|
|
assert result == {"picked": "offsetHit"}
|
|
|
|
|
|
def test_pick_returns_null_when_nothing_is_opaque_under_pointer():
|
|
result = run_node(
|
|
"""
|
|
const layers = [{id: 'only', visible: true, canvas: {width: 4, height: 4}}];
|
|
const picked = pickTopmostLayerAt({
|
|
layers,
|
|
point: {x: 100, y: 100},
|
|
sampleAlpha: () => 255,
|
|
});
|
|
console.log(JSON.stringify({picked: picked && picked.id}));
|
|
"""
|
|
)
|
|
assert result == {"picked": None}
|