residualForEllipse computed C = H^T·E·H, but H maps image → output, so
the correct output-space conic is C = H^{-T}·E·H^{-1}. The forward form
represents a geometrically meaningless conic and produced nonsensical
numbers in the per-datum table (a 210mm circle was reported as 2046mm).
The deskewed image itself was correct — only the diagnostic was wrong,
because these residuals are display-only and don't feed back into the
solver. Now invert H once and compute the conic in output space.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Datum editor (step 3):
- Add world-axis role to rectangles (isAxisReference) and lines
(axisRole: "x"|"y"). Exclusive via a new store action that clears
any other axis flag on write. The solver's pickPrimary now honors an
explicit user flag ahead of the type-priority fallback; line-primary
correspondences target world +x or +y depending on the flag.
- Panel UI: checkbox on rect cards, three-way button row on line cards,
and an axis badge in each card header.
- Ellipse datum switches to 8 user-placed points on the circle contour.
New src/lib/ellipse-fit.ts does an algebraic LSQ conic fit (data-
normalised, 5x5 Gaussian solve, f=-1 constraint) and returns the
geometric center + perpendicular conjugate semi-axes, which we cache
on the datum for the solver and renderer. Dragging any handle
refits; an extra center handle translates all 8 points together.
datum-cache migrates legacy 3-handle storage by synthesising 8
samples from the old parametric form.
- ResultViewer auto-scale now floors to an int to match the integer-
only scale input (step=1).
Measurement tool (step 4) — CorrectedImageViewer.vue:
- Three measurement tools: line (length), ellipse (semi-axes + area),
angle (0-180 degrees between two rays).
- Persistent, multi-measurement state. Each has id, colorIndex, and
type-specific geometry; colors cycle via the existing getDatumColor
palette with a monotonic counter so deletion doesn't recolor.
- Selection model with hit-testing on handles, geometry, and labels.
Selected draws on top in white; others render dashed with 0.8/0.5
alpha so the active measurement pops.
- Dragging geometry or label moves the whole measurement; dragging a
handle reshapes just that handle. 3px mouse threshold distinguishes
click from drag.
- Side panel lists measurements with color chip, type, value, and a
delete button; clicking selects on canvas. Delete/Backspace deletes
the selected measurement. Escape cancels in-progress placement.
- Live placement preview + inline hint strip describes what the next
click does. Pinch-zoom and single-finger pan still work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the two-pass closed-form deskew (getPerspectiveTransform +
per-axis scale corrections) with an alternating-minimisation loop around
cv.findHomography (internal Levenberg–Marquardt). Each outer iteration
recomputes per-datum point correspondences from the current H and the
datum's shape constraint, then findHomography refines H. Confidence
drives per-correspondence replication; primary gets a 3× gauge boost.
- Add EllipseDatum type (center + two conjugate semi-axis endpoints +
known diameter) with 3-handle Konva rendering and coin presets.
- Generalise primary selection to any datum type. Priority rect >
ellipse > line; within type, confidence then image size. Warm-start
anchors: rect = 4 axis-aligned corners; ellipse = 4 conjugate-axis
samples on a world circle; line = 2 endpoints + 2 synthetic
perpendicular points (isotropic image-scale assumption).
- Direction-agnostic shape residuals: Procrustes-fit ideal (w × h) rect
to projected corners; midpoint-preserving line rescale; radial-snap
ellipse samples to a circle at projectPoint(H, center).
- Drop the "at least one rectangle" requirement. Any datum combination
works; diagnostics widgets auto-pick a scale reference across types.
- Diagnostics: replace X/Y axis-correction cards with RMS residual +
iteration count; per-datum table shows a residual breakdown column
(edge %, perp Δ°, iso/skew/dia).
- Detect period-2 oscillation in the outer loop and warn to console.
- Relative convergence threshold so the affine and perspective entries
of H are weighted comparably.
- Guard diagnostic diameter via geometric-mean-radius for non-circular
conics; guard collinear-axes ellipses; fix Mat leak in
solveHomography on the exception path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Accept any browser-supported image type (png, webp, gif, …) in addition
to jpg and heic on the upload step.
- Add a swap button between the width and height inputs on rectangle
datums so users can flip dimensions with one click.
- Block the Next action on the datum step when a rectangle has crossed
corners (top below bottom or right left of left), and surface which
datums need fixing in the tooltip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Highlight what sets Skwik apart from existing perspective-correction
and measurement tools: client-side, multi-datum weighting, mm scale,
measurement tools, and scale bar export all in one.
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix scale bar checkbox: replace shadcn Checkbox (broken event
propagation) with native input[type=checkbox] + v-model
- Scale input: use local string ref so user can type freely;
red highlight when invalid, run button disabled until valid
- Auto-compute default scale to match input image dimensions,
capped at 8192px output; cached scale takes priority
Co-Authored-By: Claude <noreply@anthropic.com>
- Mobile: fix pinch-zoom jitter by disabling Konva stage drag during
two-finger gestures so only our zoom handler controls position
- Mobile: fix dot dragging — defer pan start so Konva can claim touch
for shape drag first (isDraggingShape flag)
- Desktop: enable stage drag for click-drag panning, disable during
point drag; filter dragend by nodeType to prevent offset corruption
- Fallback file hash using name+size+lastModified when crypto.subtle
is unavailable (HTTP contexts, some mobile browsers)
Co-Authored-By: Claude <noreply@anthropic.com>
- pnpm 10 + Node 22 for fastest CI
- Frozen lockfile, upload-pages-artifact v3, deploy-pages v4
- Vite base path set to /skwik/ when building in GHA
- Concurrency group cancels in-progress deploys
Co-Authored-By: Claude <noreply@anthropic.com>
Badge Primitive (<span>) was swallowing clicks. Reachable steps now
render as <button> with hover effects, current/unreached as Badge.
Co-Authored-By: Claude <noreply@anthropic.com>
- Squirrel engineer logo (SkwikLogo.vue) with hard hat and ruler
- Matching favicon with squirrel head silhouette
- Gitea fork ribbon (top-left, desktop only, Gitea green)
- Centered header with logo, title, and subtitle
- Footer: "Made by Samuel Prevost" with GitHub link
- Clickable step indicators for previously visited steps
- Smaller datum dots (6/4 base radius with visual cap)
- Engineering-tool styling: monospace for measurements, Geist Mono
font, deeper dark mode colors, instrument-panel header
- EXIF viewer explains why focal length matters
- Upload page describes what Skwik does
Co-Authored-By: Claude <noreply@anthropic.com>
- Add file-hash.ts: SHA-256 hash of uploaded files via Web Crypto API
- Add datum-cache.ts: localStorage save/load/clear for datums by hash
- Add settings-cache.ts: persist scalePxPerMm and includeScaleBar
- Restore datums from cache on re-upload of same file
- Discreet "Clear cache" button on upload page
- Store fileHash and cacheRestoreMessage in Pinia store
- Auto-save datums on every change via deep watcher
- Track maxStepReached for clickable step navigation
Co-Authored-By: Claude <noreply@anthropic.com>
- New CorrectedImageViewer component with dual-canvas (image + overlay)
- Point-to-point measurement tool: click two points, see mm distance
- Toggleable grid overlay with configurable spacing and major lines
- Scale bar export: appends measurement bar to downloaded PNG
- Progress bar with step labels during algorithm execution
- Estimated output size shown before running, blocks if > 512MB
- Actual output dimensions/filesize shown in diagnostics
- Filename changed to originalname-skwik.png
- Collapsible algorithm explanation with numbered steps
- Process New Image button to reset and start over
- Scale bar tooltip explaining the feature
- Add shadcn-vue Checkbox component
Co-Authored-By: Claude <noreply@anthropic.com>
- Add step-by-step console logging throughout the algorithm
- Add onProgress callback for UI progress bar integration
- Fix WASM OOM: clamp output dimensions with proper matrix scaling
(previously clamped size but not the transform, causing cropping)
- Fix waitForOpenCV race condition: probe cv.Mat() instead of
checking constructor existence
- Wrap all OpenCV mats in try/finally for guaranteed cleanup
- Raise MAX_OUTPUT_DIM to 12288 for more leniency
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace placeholder with OpenCV.js WASM perspective correction:
pick highest-confidence rectangle, compute homography, fold
weighted scale corrections from secondary datums, single warpPerspective
- All units now mm throughout (no cm conversion)
- Simplified datum creation: two buttons (+ Rectangle / + Line) with
preset chips, auto-numbered labels (Line 1, Rectangle 2, etc.)
- Dimensions default to 0, user must input manually; Next button
disabled until all datums have valid dimensions with tooltip hint
- Fix image preview (keep object URL alive), fix canvas disappearing
on breakpoint switch (single instance + ResizeObserver re-fit)
- Mobile responsive: bottom sheet for datum panel, full-width canvas
- Spinner on result screen during processing
- Stricter ESLint config, updated Prettier to 4-space/no-semicolons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vue 3 + Vite + TypeScript (strict) app with shadcn-vue, Konva.js canvas,
and Pinia. 4-step wizard: upload JPG/HEIC, view EXIF, place datum
measurements (rectangles/lines with presets), run deskew (placeholder).
Dark mode, mobile-responsive with bottom sheet for datum panel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>