feat(gif-tools): SOTA upgrade with 6 processing modes (#52)

* feat(find-duplicates): upgrade to 128-bit dHash with metadata and thumbnails

* feat(find-duplicates): add custom-results display mode and duplicate store

* feat(find-duplicates): add results overview grid and detail comparison view

* feat(find-duplicates): overhaul settings with sensitivity presets and download actions

* feat(find-duplicates): update i18n description

* chore: replace jsqr with zxing-wasm for barcode reading

* feat(barcode-read): rewrite backend with zxing-wasm for all barcode types

* feat(barcode-read): rewrite frontend with multi-file, results table, progress, export

- Multi-file sequential processing with per-file progress
- Structured results table with type badges and copy per-result
- Copy All and Export CSV functionality
- Thorough scan toggle (maps to tryHarder in zxing-wasm)
- Before/after view shows annotated image with bounding boxes
- Updated tool description in constants and i18n

* feat(stitch): update tool name and description for redesign

* feat(stitch): add grid layout, alignment, border, radius, quality, and new resize modes

* feat(stitch): redesign settings UI with grid, alignment, border, radius, quality

* test(stitch): add stitch to e2e tool navigation suite

* feat(vectorize): redesign with dual-engine backend and preset-driven UI

- Backend: potrace for B&W, VTracer (@neplex/vectorizer) for full-color vectorization
- Frontend: 5 presets (logo, illustration, photo, sketch, custom)
- Settings: color precision, gradient step, detail, smoothing, corner threshold, invert
- Updated OpenAPI spec and i18n description

* feat(border): redesign with presets, shadow, padding color, swatches

- Add 8 one-click presets (Clean White, Gallery Black, Shadow, Rounded, Polaroid, Vintage, Minimal, Cinematic)
- Implement proper shadow rendering with blur, offset X/Y, color, opacity
- Add padding color control (was hardcoded white)
- Add color swatches for quick color selection
- Wrap in form for Enter key submission
- Add smart validation (requires at least one effect active)
- Align frontend/backend slider ranges
- Organize UI with sections and collapsible shadow toggle

* feat(split): overhaul image splitting with live grid overlay and tile preview

- Add interactive-split display mode with SplitCanvas component
- Live SVG grid overlay on uploaded image showing split boundaries
- Two split modes: Grid (NxM) and Tile Size (px dimensions)
- 9 grid presets (2x1, 1x2, 2x2, 3x1, 1x3, 3x3, 2x3, 3x2, 4x4)
- Output format selection (original/PNG/JPG/WebP) with quality slider
- Post-split tile preview thumbnails with individual download
- Download All as ZIP button
- HEIC/HEIF preview with loading spinner
- Backend: tile-size mode, output format conversion, quality control
- Zustand store for split state management

* feat(split): rewrite backend and frontend settings

Backend: tile-size mode, output format conversion, quality control.
Frontend: split modes, presets, format selector, tile preview grid.

* feat(border): add live CSS preview and remove before/after slider

- Add imageWrapperStyle prop to ImageViewer for live border preview
- Add onImageStyle callback through tool-page to settings components
- Change border displayMode to no-comparison (no slider)
- BorderControls sends live CSS styles (border, padding, radius, shadow)
- Preview updates instantly as user adjusts sliders or clicks presets

* fix: repair i18n file corrupted by formatter during merge conflict resolution

* feat(border): enable live CSS preview in right pane as settings change

* fix(border): keep CSS preview visible after processing for WYSIWYG consistency

* chore(gif-tools): scaffold for SOTA upgrade

- Add animated GIF test fixture (3 frames, 100x100)
- Update tool description to reflect new capabilities
- Add fflate dependency to API for ZIP creation

* feat(gif-tools): rewrite backend with 6 processing modes

Modes: resize (with percentage), optimize (colors/dither/effort),
speed (delay manipulation), reverse (frame reorder), extract
(single/range/all with ZIP), rotate (90/180/270 + flip).

Adds /api/v1/tools/gif-tools/info metadata endpoint.

* test(gif-tools): add integration tests for all 6 modes

Tests metadata endpoint, resize (pixel + percentage), optimize,
speed, reverse, extract (single/range/all), and rotate (angle + flip).

Fix animated.gif fixture to be a real 3-frame animation (was a single
100x300 frame). Fix reverse and rotate modes to process frames
individually and reassemble via GIF binary concatenation, since
Sharp 0.33.x loses page-height metadata when reconstructing from raw
pixel data.

* feat(gif-tools): rewrite frontend with tabbed 6-mode UI

- useGifInfo hook for metadata (frame count, dimensions, duration)
- Info bar showing GIF properties
- 3x2 mode grid: Resize, Optimize, Speed, Reverse, Extract, Rotate
- Animation modes disabled for static images
- Loop control (infinite/once/custom)
- Batch processing support

* test(gif-tools): add to representative tools in e2e suite

---------

Co-authored-by: Siddharth Kumar Sah <siddharth123sk@gmail.com>
This commit is contained in:
stirling-image
2026-04-13 16:23:07 +08:00
committed by GitHub
co-authored by Siddharth Kumar Sah
parent 4e99150a08
commit a1e11dff74
65 changed files with 6293 additions and 774 deletions
+199 -6
View File
@@ -83,6 +83,9 @@ importers:
'@fastify/static':
specifier: ^8.1.0
version: 8.3.0
'@neplex/vectorizer':
specifier: ^0.0.5
version: 0.0.5
'@scalar/fastify-api-reference':
specifier: ^1.49.5
version: 1.49.5
@@ -113,12 +116,12 @@ importers:
fastify:
specifier: ^5.2.0
version: 5.8.2
fflate:
specifier: ^0.8.2
version: 0.8.2
js-yaml:
specifier: ^4.1.1
version: 4.1.1
jsqr:
specifier: ^1.4.0
version: 1.4.0
mupdf:
specifier: ^1.27.0
version: 1.27.0
@@ -146,6 +149,9 @@ importers:
zod:
specifier: ^3.24.0
version: 3.25.76
zxing-wasm:
specifier: ^3.0.2
version: 3.0.2(@types/emscripten@1.41.5)
devDependencies:
'@types/archiver':
specifier: ^7.0.0
@@ -198,6 +204,9 @@ importers:
fflate:
specifier: ^0.8.2
version: 0.8.2
jszip:
specifier: ^3.10.1
version: 3.10.1
leaflet:
specifier: ^1.9.4
version: 1.9.4
@@ -1978,6 +1987,89 @@ packages:
resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==}
engines: {node: '>= 10'}
'@neplex/vectorizer-android-arm-eabi@0.0.5':
resolution: {integrity: sha512-UqT68l9qjG1w5qeZ8g9FMGZ1YKkejyE7mHIh4otIj2lXYWQ0z0cfH1gZn4SQb47l2SLGEGJsGdqQLEM1nKdIwQ==}
engines: {node: '>= 10'}
cpu: [arm]
os: [android]
'@neplex/vectorizer-android-arm64@0.0.5':
resolution: {integrity: sha512-OFdfqI00O1oOokeREv0EzKRQVok3Pngsrk9AbUwkV/kpoKY0bYNaRIK1JnddW3nCwgQQxzXxUnahyhPXhoL0xA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@neplex/vectorizer-darwin-arm64@0.0.5':
resolution: {integrity: sha512-1Z3PjAufy20eZsy8BOq6u32Gz2JNAWgaqNO1IDhZpA5CBZzGvpk77GM7FtcOsC/O1Tb3Q/puKHIDCYs8tKFYyQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@neplex/vectorizer-darwin-x64@0.0.5':
resolution: {integrity: sha512-rOASaDXqbTvipNFk2DjTjaiiqXteE54Y9DyWz4OfEDT8oOdOL66qqKM6rfn5fUEiDstxPmb30BzPDKz74JCJ3g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@neplex/vectorizer-freebsd-x64@0.0.5':
resolution: {integrity: sha512-jVC1v3645mSLwqBVuXeEY9FWas9253zwV5CsFcDNPL8Eues81m2npt/FBMD/+MluaASAAXrQG0Ree41ACBbwzw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@neplex/vectorizer-linux-arm-gnueabihf@0.0.5':
resolution: {integrity: sha512-iF6npMyuJv75860m7Pb8mwvcBpzoVLddSPJoIpq9ocxeABgWiqt8oaxLnt9fby+OheRLMAu/+hRGT9Kvkf7Pfg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@neplex/vectorizer-linux-arm64-gnu@0.0.5':
resolution: {integrity: sha512-qByFaIeOecgoywgyYc5KyvfeQVtTvazjSZkOegdWDCyK9KmuFps9NHS2gJIYcd/gIUnlwnkkXWCYgd7ClSBM8g==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@neplex/vectorizer-linux-arm64-musl@0.0.5':
resolution: {integrity: sha512-r2a85bAkgwSxAbQTSHnzXaDZCyABgVTYf6f0OSh1oGHHIc9pC97VUZbmQLtGFeIQLQR9j4nKjF1MlOHmnV4EDA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@neplex/vectorizer-linux-x64-gnu@0.0.5':
resolution: {integrity: sha512-8pdPe27RNXHwkvYiK3vj5b3/Yi8rWgJzUsBdT/Jm2bjk5c32wiV454yT0fLZQjRB1DCAK2DvyHjf6eZ0R9HaJg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@neplex/vectorizer-linux-x64-musl@0.0.5':
resolution: {integrity: sha512-VP/DHuX40I/9KzSFRctxksXzJBGwbPE/E30NCAcPA1mS6iApovWsZe3la5dA9A5kStaKh9wTJcZuVEGL8tGIMg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@neplex/vectorizer-win32-arm64-msvc@0.0.5':
resolution: {integrity: sha512-VfQRITnqvjABiIcnx5b/9XjyktTbpDHzY2nVt5wplOqGM88f6fPn2JYiia7IEdv2BA/1+oN/Bcw75eq12mW8Ug==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@neplex/vectorizer-win32-ia32-msvc@0.0.5':
resolution: {integrity: sha512-q1R7Hnz3DK6Ls/XqRoanWEmRj3Goj5BKeayroaIzJy0uMAvpBhzcKw8hbkwIo0Cusm71Pnuninl+8MzfeQRo1w==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@neplex/vectorizer-win32-x64-msvc@0.0.5':
resolution: {integrity: sha512-At6NLzjv5pXHnXAgDjhhC7qV4suuCWK+lYBH05mdxY4dnvPyhIA3J+uvqNbvI2D5951fTXkF8JoLf6TyAQleVQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@neplex/vectorizer@0.0.5':
resolution: {integrity: sha512-lGXsEYK3xqAQ1U/kKNr3m7ml5cFZMYPfuCD3gj/g+1fqRjO7ornjS39TjY89EgvZFLWOWQEUzo2QCEgIUPlGyg==}
engines: {node: '>= 10'}
hasBin: true
'@noble/ciphers@1.3.0':
resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==}
engines: {node: ^14.21.3 || >=16}
@@ -2495,6 +2587,9 @@ packages:
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/emscripten@1.41.5':
resolution: {integrity: sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -3080,6 +3175,10 @@ packages:
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
commander@13.1.0:
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
engines: {node: '>=18'}
commander@14.0.3:
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
engines: {node: '>=20'}
@@ -3803,6 +3902,9 @@ packages:
image-q@4.0.0:
resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==}
immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@@ -4011,8 +4113,8 @@ packages:
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
engines: {node: '>=0.10.0'}
jsqr@1.4.0:
resolution: {integrity: sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==}
jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
kind-of@6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
@@ -4029,6 +4131,9 @@ packages:
resolution: {integrity: sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
lie@3.3.0:
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
light-my-request@6.6.0:
resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==}
@@ -5084,6 +5189,9 @@ packages:
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@@ -5878,6 +5986,11 @@ packages:
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
zxing-wasm@3.0.2:
resolution: {integrity: sha512-2YMAriaYHX9wrBY2k7H0epSo+dyCaCZg/vOtt+nEDXM9ul480gkXz/9SkwpOeHcD2H5qqDG8lWDSBwpTcZpa6w==}
peerDependencies:
'@types/emscripten': '>=1.39.6'
snapshots:
'@actions/core@3.0.0':
@@ -7373,6 +7486,64 @@ snapshots:
'@napi-rs/nice-win32-x64-msvc': 1.1.1
optional: true
'@neplex/vectorizer-android-arm-eabi@0.0.5':
optional: true
'@neplex/vectorizer-android-arm64@0.0.5':
optional: true
'@neplex/vectorizer-darwin-arm64@0.0.5':
optional: true
'@neplex/vectorizer-darwin-x64@0.0.5':
optional: true
'@neplex/vectorizer-freebsd-x64@0.0.5':
optional: true
'@neplex/vectorizer-linux-arm-gnueabihf@0.0.5':
optional: true
'@neplex/vectorizer-linux-arm64-gnu@0.0.5':
optional: true
'@neplex/vectorizer-linux-arm64-musl@0.0.5':
optional: true
'@neplex/vectorizer-linux-x64-gnu@0.0.5':
optional: true
'@neplex/vectorizer-linux-x64-musl@0.0.5':
optional: true
'@neplex/vectorizer-win32-arm64-msvc@0.0.5':
optional: true
'@neplex/vectorizer-win32-ia32-msvc@0.0.5':
optional: true
'@neplex/vectorizer-win32-x64-msvc@0.0.5':
optional: true
'@neplex/vectorizer@0.0.5':
dependencies:
commander: 13.1.0
picocolors: 1.1.1
optionalDependencies:
'@neplex/vectorizer-android-arm-eabi': 0.0.5
'@neplex/vectorizer-android-arm64': 0.0.5
'@neplex/vectorizer-darwin-arm64': 0.0.5
'@neplex/vectorizer-darwin-x64': 0.0.5
'@neplex/vectorizer-freebsd-x64': 0.0.5
'@neplex/vectorizer-linux-arm-gnueabihf': 0.0.5
'@neplex/vectorizer-linux-arm64-gnu': 0.0.5
'@neplex/vectorizer-linux-arm64-musl': 0.0.5
'@neplex/vectorizer-linux-x64-gnu': 0.0.5
'@neplex/vectorizer-linux-x64-musl': 0.0.5
'@neplex/vectorizer-win32-arm64-msvc': 0.0.5
'@neplex/vectorizer-win32-ia32-msvc': 0.0.5
'@neplex/vectorizer-win32-x64-msvc': 0.0.5
'@noble/ciphers@1.3.0': {}
'@noble/hashes@1.8.0': {}
@@ -7911,6 +8082,8 @@ snapshots:
'@types/deep-eql@4.0.2': {}
'@types/emscripten@1.41.5': {}
'@types/estree@1.0.8': {}
'@types/geojson@7946.0.16': {}
@@ -8537,6 +8710,8 @@ snapshots:
comma-separated-tokens@2.0.3: {}
commander@13.1.0: {}
commander@14.0.3: {}
compare-func@2.0.0:
@@ -9298,6 +9473,8 @@ snapshots:
dependencies:
'@types/node': 16.9.1
immediate@3.0.6: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@@ -9499,7 +9676,12 @@ snapshots:
jsonpointer@5.0.1: {}
jsqr@1.4.0: {}
jszip@3.10.1:
dependencies:
lie: 3.3.0
pako: 1.0.11
readable-stream: 2.3.8
setimmediate: 1.0.5
kind-of@6.0.3: {}
@@ -9511,6 +9693,10 @@ snapshots:
leven@4.1.0: {}
lie@3.3.0:
dependencies:
immediate: 3.0.6
light-my-request@6.6.0:
dependencies:
cookie: 1.1.1
@@ -10623,6 +10809,8 @@ snapshots:
set-cookie-parser@2.7.2: {}
setimmediate@1.0.5: {}
setprototypeof@1.2.0: {}
sharp@0.33.5:
@@ -11485,3 +11673,8 @@ snapshots:
react: 19.2.4
zwitch@2.0.4: {}
zxing-wasm@3.0.2(@types/emscripten@1.41.5):
dependencies:
'@types/emscripten': 1.41.5
type-fest: 5.5.0