Commit Graph
517 Commits
Author SHA1 Message Date
SnapOtter beb94b698c fix(web): redesign image editor icon for better clarity
Replace abstract curve-and-dots icon with a recognizable photo frame
and pencil design that reads clearly as an image editor at all sizes.
2026-05-13 18:18:20 +08:00
SnapOtter c6a5d3f33e refactor: remove content-aware-crop tool
Remove the content-aware-crop tool entirely -- API route, frontend
settings component, e2e and integration tests, and all registry
entries.
2026-05-13 17:55:01 +08:00
SnapOtter 443e9a4ffa fix: replace broken LaMa watermark detection with median filter approach
The luminance anomaly detection + LaMa inpainting approach failed because
watermark signal on the matted foreground was too weak (10-15 units vs
threshold of 25). Median filter with kernel=5 effectively removes
semi-transparent watermark text while preserving the stamp structure.

Pipeline is now: median filter (if toggle on) -> BiRefNet matting -> defringe.
No longer requires object-eraser-colorize bundle for watermark removal.
2026-05-13 17:08:15 +08:00
SnapOtter 6f30dbc09e feat(web): remove mode selector, add colorize strength slider 2026-05-13 17:06:45 +08:00
SnapOtter 54f3867086 feat: add Remove Watermark toggle UI with feature gating and e2e tests 2026-05-13 16:20:40 +08:00
SnapOtter c3dc98834d feat: add quality tier selector to AI canvas expand UI 2026-05-13 15:47:58 +08:00
SnapOtter 3760885342 fix: restore-photo colorize hang and AVIF decode failures
- Fix dispatcher pipe deadlock: drain stdout pipe in a background thread
  to prevent blocking when ONNX runtime output exceeds 64KB pipe buffer
- Add 5-minute SSE stall timeout so the UI shows an error instead of
  hanging forever when async AI processing stalls
- Guard CPU colorization: skip for images >2MP on CPU and when DDColor
  model is not installed, with clear user-facing messages
- Add AVIF decode fallback via ImageMagick for bitstream variants that
  Sharp's bundled libheif cannot decode (affects all tools)
2026-05-13 15:42:24 +08:00
SnapOtter 0b3f46407a fix: convert HDR/EXR to 8-bit before CLAHE in image enhancement
HDR and EXR files decoded by ImageMagick can produce 16-bit PNG buffers.
Sharp's CLAHE operation (hist_local) requires VIPS_FORMAT_UCHAR (8-bit).
Check the buffer depth and convert to 8-bit sRGB before processing.
2026-05-13 14:16:18 +08:00
SnapOtter b413ace031 feat: multi-file eraser with sequential processing and Erase All button 2026-05-13 13:43:13 +08:00
SnapOtter a5a46a5ece feat: wire maskedFileCount through eraser props 2026-05-13 13:38:15 +08:00
SnapOtter bfffe8a6bd fix: gate eraser canvas on previewLoading for exotic formats 2026-05-13 13:37:57 +08:00
SnapOtter 09d494d87d feat: store eraser strokes per-image for multi-file support 2026-05-13 13:32:44 +08:00
SnapOtter 3497cd0c9a fix: show checkerboard pattern for transparent background in remove-bg tool 2026-05-13 12:10:01 +08:00
SnapOtter f46219c730 fix: prevent tall images from being cropped in live-preview wrapper
The wrapper used inline-block for tools without overlay children (e.g.
border). CSS maxHeight: 100% on the img inside an inline-block parent
without explicit height does not resolve, so tall images rendered at
natural height and got clipped by overflow: hidden. Unify both code
paths to always use inline-flex + column, where the flex item properly
shrinks within the container's maxHeight constraint.
2026-05-13 11:45:49 +08:00
SnapOtter d22bebb8ad fix: gracefully handle mixed formats in find-duplicates and fix network errors
The find-duplicates tool failed entirely when any uploaded file couldn't
be processed, returning "Duplicate detection failed" or a format-specific
error that aborted the whole batch. With mixed-format uploads (77 files),
this made the tool unusable.

- Skip unprocessable files instead of aborting; return skippedFiles in response
- Switch from fetch() to XHR with upload progress tracking (Uploading X%)
- Add Vite proxy timeout config (5min) to prevent connection drops on large uploads
- Add "Download Grouped" button: ZIP with each duplicate group in its own folder
- Add collapsible skipped-files section in the results UI
- Add 3 integration tests for skip behavior (43 total)
2026-05-13 11:35:33 +08:00
SnapOtter 3c3682661e fix: prevent image collapse in beautify preview when frame or watermark is active
The image element used flex: 1 1 0 (basis 0) inside the inline-flex
preview wrapper, causing it to collapse to zero height whenever a
device frame or watermark overlay was present. Changed to flex: 0 1 auto
so the image uses its intrinsic height and can shrink if needed.

Added regression e2e tests verifying every preset keeps the image
visible, and that adding a watermark does not collapse the image
regardless of whether a frame is active.
2026-05-13 11:23:54 +08:00
SnapOtter 48b795d073 fix: percent-encode X-File-Results header for non-ASCII filenames (#133)
Closes #133
2026-05-13 11:07:12 +08:00
SnapOtter abfad0de56 fix: progress bar not showing during stitch processing
setError() in the file store unconditionally set processing to false,
even when called with null to clear a previous error. This meant
setProcessing(true) followed by setError(null) at the start of
processing would immediately kill the processing state, preventing the
progress bar from ever rendering.

Fix setError to only reset processing when there is an actual error.
This also fixes the same latent bug in 10+ other custom tool settings
components that call setProcessing before setError.
2026-05-13 11:04:35 +08:00
SnapOtter 8637105bce fix: add exotic format decoding to image-to-pdf tool 2026-05-13 10:39:38 +08:00
SnapOtter f93e864094 fix: add progress bar and batch download to vectorize tool
The vectorize tool had a custom processing flow that bypassed the
standard useToolProcessor hook -- no progress indication, no server-side
batch, and the Download All ZIP relied on a client-side sequential loop.

Backend: extract core logic into vectorizeBuffer(), register via
registerToolProcessFn() so the /batch endpoint works with p-queue
concurrency and SSE progress events.

Frontend: replace custom fetch loop with useToolProcessor hook and
ProgressCard, giving upload progress, per-file batch status, and
automatic Download All ZIP via the existing tool-page infrastructure.

Also set image/svg+xml MIME type on SVG blobs during batch ZIP
extraction to ensure reliable rendering in <img> tags across browsers.
2026-05-13 10:36:35 +08:00
SnapOtter ac7d9cd591 fix: restrict SVG-to-raster dropzone to only accept SVG files
Adds fileFilter and acceptDescription props to Dropzone so tools can
restrict accepted file types across all entry paths (file picker,
drag-and-drop, paste, single URL import, bulk URL import).
2026-05-13 10:32:18 +08:00
SnapOtter bf01dcd47e fix: make beautify live preview reflect all settings in real time
Background images, device frames, custom shadows, and watermark text
were not rendering in the right-pane preview. The preview now updates
in real time for all settings: gradient/solid/image backgrounds, macOS/
Windows/Browser frame chrome, iPhone/MacBook/iPad frame indicators,
custom shadow parameters, and watermark text overlay.

Also fixes a React StrictMode effect-ordering race where the parent
tool-page reset cleared preview state set by the child Settings
component on initial mount.
2026-05-13 10:28:44 +08:00
SnapOtter 0874cfc809 fix: add exotic format decoding and upload progress to stitch tool
The stitch route only decoded HEIC via ensureSharpCompat, so exotic
formats (DNG, PSD, TGA, BMP, JXL, HDR, QOI, DDS, ICO, JP2, DPX, etc.)
crashed Sharp at metadata read time, causing "Stitch creation failed".
Add the full CLI decode pipeline (decodeToSharpCompat) matching the
tool-factory pattern, plus SVG sanitization with proper error handling
for each format category.

Replace fetch() with XMLHttpRequest in the frontend to surface upload
progress via onprogress events, and add a progress bar that shows upload
percentage then pulses during server-side stitching.
2026-05-13 10:27:59 +08:00
SnapOtter ac8c585beb fix: percent-encode X-File-Results header to support non-ASCII filenames
The X-File-Results header contained raw JSON with non-ASCII characters
from filenames (Chinese, Japanese, etc.), violating RFC 7230. Node.js
threw ERR_INVALID_CHAR on writeHead(). Fixed by wrapping the JSON in
encodeURIComponent() on the backend and decodeURIComponent() on the
frontend, ensuring only ASCII goes into the header while preserving
the original filenames after decoding.

Closes #133
2026-05-12 23:19:20 +08:00
SnapOtter 2d17fd4903 fix: show real image dimensions in right pane for DNG/RAW files
The preview endpoint now returns X-Original-Width/Height headers with
dimensions read from Sharp metadata (or ExifTool for RAW files). The
frontend stores these in FileEntry and the ImageViewer prefers them over
the browser's naturalWidth/naturalHeight, which reflects the resized
preview rather than the original sensor dimensions.
2026-05-12 22:13:58 +08:00
SnapOtter 7a3ec93847 fix: clear error when navigating to cached image info and pass RAW extension
Error from a failed image (e.g. DNG) persisted when navigating to a
previously cached image because the cache path skipped setError(null).
Also pass the original file extension to decodeToSharpCompat so RAW
variants get the correct temp file suffix for ExifTool/ImageMagick.
2026-05-12 21:02:35 +08:00
SnapOtter 449f5dc97e fix: add exotic format decoding to image composition tool
The compose route only decoded HEIC/HEIF via ensureSharpCompat, causing
EPS, PSD, BMP, RAW, and other exotic formats to fail with
"Processing failed". Now uses the same full format pipeline as the
tool-factory for both base and overlay buffers.
2026-05-12 19:41:23 +08:00
SnapOtter d3d50c92be fix: update image info panel when navigating between files
Info panel always read files[0] and stored results in a single useState,
so it never changed when switching images. Now tracks selectedIndex,
auto-fetches on navigation after initial "Read Info" click, caches
results per-index, and aborts stale requests on rapid navigation.
2026-05-12 18:40:33 +08:00
SnapOtter da692e68b3 fix: wire up URL import to home page dropzone and prevent click-through
Pass onUrlImport handler through AppLayout to Dropzone so URL-imported
files are loaded on the home page. Add stopPropagation on the modal
overlay to prevent clicks from reaching elements underneath.
2026-05-11 23:35:26 +08:00
SnapOtter 7a2ea2069b fix: clean up crop mode toggle, extend timeouts for long-running tools
- Remove content-aware crop mode toggle from tool-page (now handled internally)
- Add content-aware-resize/crop to LONG_RUNNING_TOOLS for extended XHR timeout
- Remove unused onModeChange prop from CropProps
- Bump HEIC converter test timeouts to 60s to fix flaky failures
2026-05-11 22:48:05 +08:00
SnapOtter 095e3d9488 feat: add URL-based image import (single + bulk)
Add a fourth image ingestion path: importing images by URL.

Backend:
- POST /api/v1/fetch-urls endpoint with SSRF protection, image validation,
  preview generation, and p-queue concurrency
- SSRF utility blocking private IPs, validating redirect hops, with
  comprehensive IPv4/IPv6 range coverage

Frontend:
- Always-visible URL input in the dropzone for quick single-image import
- Bulk URL import modal with smart URL parsing (lists, markdown, HTML),
  per-URL progress tracking, retry on failure, and batch add
- useUrlImport hook managing the full fetch lifecycle

Tests: 48 new tests (23 SSRF unit, 10 URL parser unit, 15 integration)
2026-05-11 22:41:47 +08:00
SnapOtter b06906025c refactor: improve tool processing, dropzone, seam carving, and format encoding
- Refactor use-tool-processor and use-pipeline-processor hooks
- Enhance dropzone component with improved UX
- Improve seam carving with better error handling and tests
- Add JXL format encoding support to format-encoders
- Update tool routes for consistent format handling
- Add dropzone unit tests
2026-05-11 21:57:40 +08:00
SnapOtter b9272977df fix: add accessibility attributes to URL import modal 2026-05-11 21:43:54 +08:00
SnapOtter c901e29528 fix: address code review issues in useUrlImport hook 2026-05-11 21:34:43 +08:00
SnapOtter b7af25862a feat: add inline URL input to dropzone 2026-05-11 21:32:55 +08:00
SnapOtter d8941a2bc8 feat: add bulk URL import modal component 2026-05-11 21:32:40 +08:00
SnapOtter a85ca54feb refactor: remove redundant trim in URL parser 2026-05-11 21:28:24 +08:00
SnapOtter 4a430abf2f feat: add useUrlImport hook for URL fetch lifecycle 2026-05-11 21:27:43 +08:00
SnapOtter 9763a10db4 fix: add tool registry entry and manifest for content-aware-crop 2026-05-11 21:23:47 +08:00
SnapOtter a2be47bd68 feat: add smart URL parser for bulk import 2026-05-11 21:18:45 +08:00
SnapOtter 8ed0700a04 feat: add Content-Aware tab to crop settings with expansion controls 2026-05-11 21:15:03 +08:00
SnapOtter aa87c7e5e1 feat: add expansion preview for content-aware crop mode 2026-05-11 21:14:17 +08:00
SnapOtter 50d1f532d1 fix: block PDF uploads, fix PBM/PGM/PPM batch failures, add tests
- Added isImageFile() filter to all drop handlers (dropzone, collage,
  file-upload-area) so PDFs and non-image files are rejected on drop.
  Previously only the file picker's accept attribute filtered; drag-and-
  drop accepted anything.

- Added ppm, pgm, pbm to CLI_DECODED_FORMATS in file-validation.ts.
  These formats were missing, causing Sharp metadata checks to fail for
  some files during batch validation, which silently dropped them from
  results ("File not found in batch results").

- Added integration tests for PBM, PGM, PPM, TIFF, QOI, JP2, SVGZ
  single-file processing, plus a test confirming PDF is rejected.
2026-05-11 17:33:51 +08:00
SnapOtter a52a3b7e87 fix: generate server-side previews for all non-browser formats
SERVER_PREVIEW_EXTENSIONS was missing TIFF, DDS, DPX, EPS, FITS, JP2,
PBM/PGM/PPM, QOI, SVGZ, CUR, and many RAW variants. The server
already had decoders for all of them but the client never triggered
the preview fetch, so users saw "Preview not available" instead.
2026-05-11 17:25:26 +08:00
SnapOtter 1d7a7188bf fix: replace editor PenTool icon with image-edit icon
Custom SVG showing a landscape image frame with a pencil overlay,
clearly communicating "edit image" instead of the vector pen tool
which read more as Illustrator than Photoshop.
2026-05-11 17:20:14 +08:00
SnapOtter 0f1fd2050b fix: add +/- stepper buttons to quality slider in compress tool
Small bordered buttons with Minus/Plus icons flank the range slider,
allowing single-step quality adjustments. Disabled at min (1) and
max (100) boundaries.
2026-05-11 17:12:24 +08:00
SnapOtter aa614cb53d fix: constrain thumbnail strip to right pane and handle non-previewable formats
The thumbnail strip expanded beyond the right pane because the flex
section lacked min-w-0, causing a page-level horizontal scrollbar.
Added min-w-0 to both mobile and desktop image area sections.

For non-previewable formats (DDS, TIFF, EXR, PSD, RAW, etc.), show a
file info card with format and size instead of passing the blob URL to
an img tag that fails and shows an infinite "Loading preview..." spinner.
Also changed the ImageViewer error state from a misleading spinner to a
static "Preview not available" message.
2026-05-11 17:10:42 +08:00
SnapOtter aec1e91c5a fix: add .avif to all file picker accept strings
AVIF was missing from expandAccept() and every inline accept attribute,
causing the OS file picker to gray out .avif files. Drag-and-drop was
unaffected because it bypasses the accept filter. Added .avif to
dropzone.tsx expandAccept (covers all Dropzone pickers) plus 12 inline
accept strings across tool settings, pages, and file-upload-area.
2026-05-11 16:59:49 +08:00
SnapOtter 72de10b844 fix: clean up files section and add KB/MB unit selector to compress
- Remove duplicate "Files" heading (section header + inline count)
- Space "Show more" and "Clear all" apart on the same row
- Replace bare number input with number + KB/MB dropdown for target
  size, defaulting to KB; MB values are converted to KB for the API
- Hide native number input spin buttons for a cleaner look
2026-05-11 16:54:13 +08:00
SnapOtter 0c8e8c075c fix: remove checkerboard bleed in before-after slider and image viewer
The before-after slider hardcoded a checkerboard background on the
entire "after" overlay, which filled letterbox/pillarbox space around
non-transparent images. Replaced with a neutral bg-muted/30.

In image-viewer, moved the checkerboard from the outer container
(which included padding) to the img element itself, so it only
shows through transparent pixels rather than the surrounding area.
2026-05-11 16:48:51 +08:00