feat(canvas): enhance mixer node functionality with overlay dimensions and cropping support

This commit is contained in:
2026-04-15 08:49:54 +02:00
parent 2679a0cc4e
commit 7a06e0db7f
4 changed files with 383 additions and 10 deletions

View File

@@ -0,0 +1,92 @@
# Mixer Resize/Crop Design
**Goal:** Make mixer overlay resize behave like proportional image scaling, and make crop behave like classic edge-based trimming without changing displayed image size.
## Approved Interaction Model
- `Resize` changes the displayed overlay size only.
- `Resize` keeps aspect ratio locked.
- `Crop` changes only the visible source region.
- `Crop` does not change the displayed overlay frame size.
- `Crop` uses 8 handles: 4 corners and 4 side-midpoints.
- Dragging inside the crop box repositions the crop region.
## Conceptual Split
### 1. Display Frame
Controls where and how large the overlay appears in the mixer.
- `overlayX`
- `overlayY`
- `overlayWidth`
- `overlayHeight`
These fields represent the displayed overlay frame in mixer preview/output space.
### 2. Source Crop Region
Controls which part of the source image is shown inside that frame.
Recommended crop contract:
- `cropLeft`
- `cropTop`
- `cropRight`
- `cropBottom`
All values are normalized source-image trims from the corresponding edge.
Why this model:
- Left handle changes only `cropLeft`
- Top handle changes only `cropTop`
- Corner handles combine two crop edges
- The mental model exactly matches "take content away from edges"
## Rendering Semantics
Preview, compare, and bake must all use the same mapping:
1. Resolve source image.
2. Apply crop trims to derive the sampled source rect.
3. Draw that sampled rect into the displayed overlay frame.
This removes the ambiguous zoom-like behavior from crop mode.
## UX Rules
### Resize Mode
- Handles are anchored to the display frame.
- Corner drag scales proportionally.
- Side handles are either hidden or mapped to proportional scaling from the nearest axis while preserving aspect ratio.
- Resize never mutates crop fields.
### Crop Mode
- Handles are anchored to the crop box.
- Edge handles trim one side.
- Corner handles trim two sides.
- Drag inside crop box repositions the crop window.
- Crop never mutates display frame size.
## Constraints
- Minimum display size must keep handles usable.
- Minimum crop region must prevent inverted or zero-area crop boxes.
- Crop box stays within source bounds.
- Display frame stays within mixer preview bounds.
## Backward Compatibility
- Existing mixer nodes with `contentX/Y/Width/Height` need a migration/default path.
- If a direct field migration is too risky, normalization can temporarily map legacy content fields into equivalent crop trims.
## Non-Goals
- rotation
- masks
- free distortion
- multi-layer cropping
- standalone crop modal

View File

@@ -0,0 +1,198 @@
# Mixer Resize/Crop Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Correct mixer interactions so resize scales the overlay proportionally while crop trims visible content from any side without changing displayed image size.
**Architecture:** Split displayed overlay geometry from source crop geometry. Keep `overlayX/Y/Width/Height` for display frame placement and size. Introduce explicit crop-edge semantics so preview, compare, and bake all trim the same source region and map it into the unchanged frame.
**Tech Stack:** Next.js 16, React 19, `@xyflow/react`, Vitest, local node preview state via `useNodeLocalData`, image pipeline source-loader.
---
### Task 1: Add failing tests for the approved resize/crop semantics
**Files:**
- Modify: `components/canvas/__tests__/mixer-node.test.tsx`
- Modify: `tests/image-pipeline/source-loader.test.ts`
- Modify: `tests/lib/canvas-mixer-preview.test.ts`
**Step 1: Write the failing tests**
Add tests that prove:
- frame resize keeps aspect ratio locked
- crop handle drag trims edges without changing displayed overlay frame size
- crop drag inside crop box repositions crop region only
- resize does not mutate crop fields
- crop does not mutate `overlayWidth` / `overlayHeight`
**Step 2: Run tests to verify RED**
Run:
```bash
pnpm exec vitest run components/canvas/__tests__/mixer-node.test.tsx tests/image-pipeline/source-loader.test.ts tests/lib/canvas-mixer-preview.test.ts
```
Expected: failures showing current crop behavior still behaves like zoom/scale instead of edge trimming.
**Step 3: Commit**
```bash
git add components/canvas/__tests__/mixer-node.test.tsx tests/image-pipeline/source-loader.test.ts tests/lib/canvas-mixer-preview.test.ts
git commit -m "test(canvas): cover mixer resize and crop semantics"
```
---
### Task 2: Replace zoom-like content fields with crop-edge normalization
**Files:**
- Modify: `lib/canvas-mixer-preview.ts`
- Modify: `lib/canvas-utils.ts`
- Modify: `lib/canvas-node-templates.ts`
- Modify: `components/canvas/nodes/mixer-node.tsx`
**Step 1: Implement minimal normalized crop model**
Prefer explicit crop trims:
```ts
type MixerCropData = {
cropLeft: number;
cropTop: number;
cropRight: number;
cropBottom: number;
};
```
Normalization rules:
- clamp each crop edge to `0..1`
- enforce minimum remaining source width/height
- preserve display frame fields separately
- map legacy `contentX/Y/Width/Height` into equivalent crop trims during normalization if needed
**Step 2: Run focused tests**
Run:
```bash
pnpm exec vitest run tests/lib/canvas-mixer-preview.test.ts
```
Expected: GREEN for normalization and backward-compatibility cases.
**Step 3: Commit**
```bash
git add lib/canvas-mixer-preview.ts lib/canvas-utils.ts lib/canvas-node-templates.ts components/canvas/nodes/mixer-node.tsx tests/lib/canvas-mixer-preview.test.ts
git commit -m "feat(canvas): add explicit mixer crop edge model"
```
---
### Task 3: Fix mixer node interactions
**Files:**
- Modify: `components/canvas/nodes/mixer-node.tsx`
- Modify: `components/canvas/__tests__/mixer-node.test.tsx`
**Step 1: Implement proportional resize**
- use display frame aspect ratio as the locked ratio
- corner drag scales frame proportionally
- side handles either hide in resize mode or preserve ratio while scaling
- resize mutates only `overlay*`
**Step 2: Implement classic crop handles**
- render 8 crop handles in crop mode
- edge handles trim one side
- corner handles trim two sides
- dragging inside crop box repositions crop region
- crop mutates only crop fields
**Step 3: Run focused tests**
Run:
```bash
pnpm exec vitest run components/canvas/__tests__/mixer-node.test.tsx
```
Expected: GREEN for resize and crop semantics.
**Step 4: Commit**
```bash
git add components/canvas/nodes/mixer-node.tsx components/canvas/__tests__/mixer-node.test.tsx
git commit -m "feat(canvas): separate mixer resize and crop interactions"
```
---
### Task 4: Align compare and bake semantics
**Files:**
- Modify: `components/canvas/nodes/compare-surface.tsx`
- Modify: `lib/image-pipeline/source-loader.ts`
- Modify: `tests/image-pipeline/source-loader.test.ts`
- Modify: `tests/lib/canvas-render-preview.test.ts`
- Optional modify: `components/canvas/__tests__/compare-node.test.tsx`
**Step 1: Implement crop-edge sampling everywhere**
- compare preview uses crop edges, not zoom-like content scaling
- bake path samples cropped source region into overlay frame
- non-mixer behavior stays unchanged
**Step 2: Run focused tests**
Run:
```bash
pnpm exec vitest run tests/image-pipeline/source-loader.test.ts tests/lib/canvas-render-preview.test.ts components/canvas/__tests__/compare-node.test.tsx
```
Expected: GREEN with preview/bake parity.
**Step 3: Commit**
```bash
git add components/canvas/nodes/compare-surface.tsx lib/image-pipeline/source-loader.ts tests/image-pipeline/source-loader.test.ts tests/lib/canvas-render-preview.test.ts components/canvas/__tests__/compare-node.test.tsx
git commit -m "fix(canvas): align mixer crop semantics across preview and bake"
```
---
### Task 5: Final verification
**Files:**
- Modify only if docs or small follow-up fixes are needed
**Step 1: Run the verification suite**
```bash
pnpm exec vitest run components/canvas/__tests__/mixer-node.test.tsx tests/lib/canvas-mixer-preview.test.ts tests/lib/canvas-render-preview.test.ts tests/image-pipeline/source-loader.test.ts components/canvas/__tests__/compare-node.test.tsx
pnpm lint
pnpm build
```
Expected: all green.
**Step 2: Commit docs/follow-ups if needed**
```bash
git add components/canvas/CLAUDE.md convex/CLAUDE.md
git commit -m "docs(canvas): document mixer resize and crop semantics"
```
---
## Notes
- Keep `nodrag` and `nopan` on every interactive surface and handle.
- Prefer the smallest migration path from legacy `content*` fields into crop trims.
- Do not broaden into rotation, masks, or non-uniform scaling.