hazo_images 1.2.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGE_LOG.md +90 -0
- package/README.md +226 -5
- package/dist/index.d.ts +76 -0
- package/dist/runware/index.js +13 -18
- package/dist/server/index.d.ts +152 -1
- package/dist/server/index.js +329 -5
- package/dist/ui/index.d.ts +395 -1
- package/dist/ui/index.js +1911 -0
- package/package.json +32 -7
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# hazo_images Change Log
|
|
2
2
|
|
|
3
|
+
## 1.8.0 — 2026-07-11
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`ImageAnnotator`** — new `hazo_images/ui` component: a mobile-first image markup tool built on a Konva `Stage`/`Layer` (via `react-konva`). Loads a source image (`File | Blob | string`), scales it to fit within a `maxWidth`/`maxHeight` viewport, and renders it as the bottom Konva `Image` layer beneath drawn annotations.
|
|
8
|
+
- Tools: **pen** (freehand `Line`, points collected on pointer move), **arrow** (drag-stamped `Arrow`), **circle** (drag-stamped `Ellipse`, sized by drag distance) — switched via a large-tap-target toolbar.
|
|
9
|
+
- **Undo** (pops the last shape) and **Clear** (empties the shape list) buttons.
|
|
10
|
+
- Drawing is driven by pointer events (not mouse-only assumptions), so it works with touch/stylus input as well as a mouse.
|
|
11
|
+
- `onSave(blob: Blob) => void` — flattens the stage (source image + markup) via `Konva.Stage#toBlob({ mimeType: 'image/png' })` and calls `onSave` with the resulting PNG `Blob`.
|
|
12
|
+
- `labels` override object (tool names, undo/clear/save, loading text), matching the `ImageEditorDialog` customization pattern.
|
|
13
|
+
- New export: `ImageAnnotatorProps`.
|
|
14
|
+
- **`image-annotator-shapes.ts`** — pure, framework-free shape-list helpers (`addShape`, `undoShape`, `clearShapes`) that back `ImageAnnotator`'s state transitions; unit-tested directly (no DOM/Konva required). New exports: `AnnotatorTool`, `AnnotatorShape`, `PenShape`, `ArrowShape`, `CircleShape`.
|
|
15
|
+
- **`konva ^10.3.0`** and **`react-konva ^19.2.5`** added as optional peer dependencies (mirroring the existing `hazo_ui`/`react`/`react-dom` optional-peer pattern) — only required when importing `ImageAnnotator` from `hazo_images/ui`; the rest of the package is unaffected.
|
|
16
|
+
- Demo: `test-app /annotator` page and a `hazo_images_annotator` autotest scenario exercising the pen/arrow/circle → flatten-to-blob path.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **`createImageProcessHandler` save now overwrites.** The handler's `save` step calls `uploadFile(..., { overwrite: true })`, so re-saving an edited image to the same path replaces it instead of failing with `File already exists`. (Previously a second save of the same filename returned `saved: null` + a `saveError`.)
|
|
21
|
+
|
|
22
|
+
### Test-app
|
|
23
|
+
|
|
24
|
+
- `test-app/next.config.js` externalises `dropbox` + `googleapis` (pulled transitively by `hazo_files`) so Turbopack no longer tries to bundle their `node-fetch` requires and fail the build.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1.7.0 — 2026-06-22
|
|
29
|
+
### Added
|
|
30
|
+
- `deriveVariant(sourceFileId, deps, opts)` — fileId-in/fileId-out convenience over `processImage` + `uploadProcessedImage`
|
|
31
|
+
- New exports: `DeriveVariantOptions`, `DeriveVariantDeps`, `DeriveVariantResult`
|
|
32
|
+
- Autotest scenario + `/derive-variant` test-app page
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 1.5.0 — 2026-06-21
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **Instant live preview for geometry + filters.** The `ImageEditorDialog` stage now renders **rotate**, **flip**, **flop**, **blur**, **brightness**, **saturation**, **hue**, **grayscale**, and **tint** as a CSS transform/filter/blend on the stage image, so they update truly in realtime while dragging (no server round-trip lag). These controls are omitted from the debounced server preview to avoid double-applying; **Save** still bakes them server-side via Sharp, so output is exact. `sharpen`, crop, and output (webp / quality / max-dimension) remain server-rendered. (Earlier in 1.5.0 every control went through the debounced server preview, which made sliders feel like they only updated on release.)
|
|
41
|
+
- **Tint preview** uses a `mix-blend-mode: color` overlay (preserves the image's luminance while applying the tint's hue/chroma — approximates Sharp's `tint()`).
|
|
42
|
+
- **Interactive crop with shapes.** Crop is now a toggle mode (**Crop image** / **Done**) with a draggable box and 8 resize handles (4 corners + 4 edges), replacing the draw-only `CropOverlay`. A shape picker offers **rectangle**, **square** (constrained 1:1), and **circle** (constrained 1:1; corners masked transparent → PNG output). The box renders a circular outline for the circle shape.
|
|
43
|
+
- **Rotate quarter-turn presets.** The rotate control shows **0° / 90° / 180° / 270°** buttons beside the slider (active preset highlighted) for one-click quarter turns.
|
|
44
|
+
- **Tint color picker.** New `'color'` control `input_type` (added to the control schema + validator). The `tint` control renders a native color swatch + editable hex field + **Clear** button instead of a plain text box.
|
|
45
|
+
- **Reset button.** Restores every control to its default and clears the crop region / shape, keeping the loaded image; now rendered with an outlined button border. New `labels.reset` / `labels.crop` / `labels.cropDone` overrides.
|
|
46
|
+
- **`crop.shape`** option on `ProcessImageOptions` (`'rect' | 'square' | 'circle'`, default `'rect'`). `'circle'` composites a circular alpha mask and forces PNG output (overrides `webp` / `jpeg`).
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **Crop now runs before rotate** in the server pipeline, clamped against the **auto-oriented** input dimensions. The crop is baked to a lossless intermediate buffer first, because Sharp otherwise applies `rotate` before a pre-resize `extract` regardless of call order — which cropped the wrong region for rotate+crop combinations. The crop is the region the user drew on the (auto-oriented) original; rotate/flip apply to the result.
|
|
51
|
+
- **`ImageEditorDialog` — "Studio" side-rail layout.** The dialog body was a single vertical stack with the image preview rendered last (below all controls). It is now a two-pane layout: a large image **stage** on the left (`bg-gray-900`, `min-h` 52–64vh, `object-contain`) and a compact scrollable **controls rail** on the right (`lg:w-80`) holding the Geometry / Filters / Output tabs.
|
|
52
|
+
- Stage shows the live processed **Preview** when available, else the **Original** (badge top-left); a processing badge appears top-right while a render is in flight.
|
|
53
|
+
- Control rows are stacked (label above input) to read well in the narrow rail; slider/switch/select behaviour is unchanged.
|
|
54
|
+
- Dialog widened `max-w-3xl` → `max-w-5xl`; panes stack vertically below `lg`. The standalone `ImageViewer` (before/after grid) is no longer used inside the dialog but remains a separate `hazo_images/ui` export.
|
|
55
|
+
- Demo: `test-app /editor` exercises the new layout (no API change).
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 1.4.0 — 2026-06-20
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- **Extended pipeline options** on `processImage` / `ProcessImageOptions` (all optional, default no-op):
|
|
64
|
+
- Geometry: `crop` (extract region, clamped to input dims), `rotate` (degrees), `flip` (vertical), `flop` (horizontal).
|
|
65
|
+
- Filters: `grayscale`, `sharpen` (sigma), `brightness` (modulate multiplier), `saturation` (modulate multiplier), `hue` (degrees), `tint` (hex).
|
|
66
|
+
- Output: `quality` (1–100, applied to WebP + JPEG output).
|
|
67
|
+
- Pipeline order documented: autoOrient → rotate → flip/flop → withMetadata? → crop → resize → modulate → grayscale → sharpen/blur → tint → format+quality → thumbnails.
|
|
68
|
+
- **`createImageProcessHandler(opts?)`** — framework-agnostic `(request: Request) => Promise<Response>` factory exported from `hazo_images/server`. Parses multipart `FormData` (`image` File + `options` JSON + `save` flag + optional `filename`). Returns JSON `{ ok, metadata, preview, originalPreview, thumbnails, saved, saveError? }` or `{ ok: false, error, errorType }` (UnsupportedFormatError → 400, ImageProcessingError → 422, other → 500). Exports companion types `CreateImageProcessHandlerOptions` and `ImageProcessHandlerFileManager`.
|
|
69
|
+
- **`hazo_images/ui`** — real React components (was an empty reserved stub):
|
|
70
|
+
- `ImageUploader` — drag-and-drop / file-picker input component.
|
|
71
|
+
- `ImageViewer` — before/after preview panel.
|
|
72
|
+
- `ImageEditorDialog` — headline component: `hazo_ui` Dialog + Geometry / Filters / Output tabs of Slider / Switch / Select / color controls, debounced live preview, Save action (persists via the Phase-2 handler). Customisation: `controls` declarative schema, `defaultOptions`, `endpoint` | `onProcess`, `onSave`, `labels`, `headerSlot` / `footerSlot`, `className`, `debounceMs`.
|
|
73
|
+
- Helper exports: `DEFAULT_IMAGE_EDIT_CONTROLS`, `defaultControlValues`, `controlValuesToOptions`, `validate_image_edit_controls`.
|
|
74
|
+
- Control-schema types: `ImageEditControl` and related interfaces (source in `src/ui/controls-schema.ts`).
|
|
75
|
+
- **`hazo_ui ^4.4.0`** added as an optional peer dependency (used only by `hazo_images/ui`; server pipeline + handler never import `hazo_ui`).
|
|
76
|
+
|
|
77
|
+
### Notes
|
|
78
|
+
|
|
79
|
+
- `hazo_images/ui` is now a built sub-export with real components. The control-schema types were **copied** (not moved) from the build-excluded `src/components/` into `src/ui/controls-schema.ts`.
|
|
80
|
+
- `src/components/` remains build-excluded and MUST stay in the repo — see "Deferred Components" in `CLAUDE.md`.
|
|
81
|
+
- `createImageProcessHandler` returns a plain `Response` with no hazo_api envelopes — consistent with D-2.1.0-01.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 1.3.0 — 2026-06-20
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- `blur?: number` option on `processImage` — Gaussian blur sigma (applied after `sharpen` in the pipeline, before `tint`).
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
3
93
|
## 1.2.0 — 2026-05-30
|
|
4
94
|
|
|
5
95
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# hazo_images
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Image processing pipeline for the hazo ecosystem. Sharp wrapper with EXIF handling, auto-rotate, resize, thumbnail generation, and a full suite of geometry / filter / output controls. Composes with `hazo_files` via an integration helper. Ships a framework-agnostic route-handler factory and, since `@1.4.0`, a real React editor dialog in `hazo_images/ui`.
|
|
4
4
|
|
|
5
5
|
Image **storage** is owned by `hazo_files`. This package handles **processing only**.
|
|
6
6
|
|
|
@@ -17,6 +17,9 @@ npm install sharp
|
|
|
17
17
|
|
|
18
18
|
# Optional — only needed if using uploadProcessedImage
|
|
19
19
|
npm install hazo_files
|
|
20
|
+
|
|
21
|
+
# Optional — only needed if using hazo_images/ui components
|
|
22
|
+
npm install hazo_ui react react-dom
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
## Usage
|
|
@@ -134,8 +137,9 @@ try {
|
|
|
134
137
|
| Import | Contents |
|
|
135
138
|
|---|---|
|
|
136
139
|
| `hazo_images` | Shared types + error classes (isomorphic, no Node deps) |
|
|
137
|
-
| `hazo_images/server` | `processImage`, `uploadProcessedImage` (Node.js only) |
|
|
138
|
-
| `hazo_images/ui` |
|
|
140
|
+
| `hazo_images/server` | `processImage`, `uploadProcessedImage`, `createImageProcessHandler` (Node.js only) |
|
|
141
|
+
| `hazo_images/ui` | `ImageUploader`, `ImageViewer`, `ImageEditorDialog`, `ImageAnnotator` + control-schema helpers (React, client-only) |
|
|
142
|
+
| `hazo_images/runware` | `createRunwareClient`, `assemblePrompts`, 4 error classes (Node.js only) |
|
|
139
143
|
|
|
140
144
|
## Supported formats
|
|
141
145
|
|
|
@@ -150,8 +154,220 @@ interface ProcessImageOptions {
|
|
|
150
154
|
maxDimension?: number; // default: 4096 — longest side; 0 = disable resize
|
|
151
155
|
webp?: boolean; // default: false — transcode to WebP
|
|
152
156
|
thumbnails?: number[]; // default: [] — pixel sizes for longest side
|
|
157
|
+
|
|
158
|
+
// Geometry (Phase 1)
|
|
159
|
+
crop?: { // Extract region — applied BEFORE rotate, clamped to (auto-oriented) input dims
|
|
160
|
+
left: number; top: number; width: number; height: number;
|
|
161
|
+
shape?: 'rect' | 'square' | 'circle'; // 'circle' masks corners transparent & forces PNG output
|
|
162
|
+
};
|
|
163
|
+
rotate?: number; // Rotate by degrees (Sharp fills gaps with black)
|
|
164
|
+
flip?: boolean; // Flip vertically (top↔bottom)
|
|
165
|
+
flop?: boolean; // Flop horizontally (left↔right)
|
|
166
|
+
|
|
167
|
+
// Filters (Phase 1)
|
|
168
|
+
grayscale?: boolean; // Convert to greyscale
|
|
169
|
+
sharpen?: number; // Unsharp-mask sigma (e.g. 1.5)
|
|
170
|
+
blur?: number; // Gaussian blur sigma (e.g. 1.0)
|
|
171
|
+
brightness?: number; // Modulate multiplier — 1 = unchanged (e.g. 1.2 = +20%)
|
|
172
|
+
saturation?: number; // Modulate multiplier — 1 = unchanged (e.g. 0 = greyscale)
|
|
173
|
+
hue?: number; // Hue rotation in degrees (e.g. 180 = complementary)
|
|
174
|
+
tint?: string; // Hex colour tint applied via Sharp's tint() (e.g. "#ff0000")
|
|
175
|
+
|
|
176
|
+
// Output (Phase 1)
|
|
177
|
+
quality?: number; // JPEG / WebP quality 1–100 (default: 80)
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Pipeline order
|
|
182
|
+
|
|
183
|
+
The Sharp pipeline applies operations in this order:
|
|
184
|
+
|
|
185
|
+
1. `autoOrient` — apply EXIF orientation
|
|
186
|
+
2. `extract` — `crop` region (clamped to the auto-oriented input dims). For `shape: 'circle'` a circular alpha mask is composited (corners → transparent) and output is forced to PNG. The crop is baked to an intermediate buffer so the following rotate is applied **after** the crop (Sharp otherwise reorders `rotate` before a pre-resize `extract`).
|
|
187
|
+
3. `rotate` — explicit rotation (degrees)
|
|
188
|
+
4. `flip` / `flop` — vertical / horizontal mirror
|
|
189
|
+
5. `withMetadata(false)` — strip EXIF (when `stripExif: true`)
|
|
190
|
+
6. `resize` — longest side to `maxDimension` (maintaining aspect ratio)
|
|
191
|
+
7. `modulate` — `brightness`, `saturation`, `hue`
|
|
192
|
+
8. `grayscale`
|
|
193
|
+
9. `sharpen` / `blur`
|
|
194
|
+
10. `tint`
|
|
195
|
+
11. Format encode — `webp` / `jpeg` / `png` with `quality` (PNG forced for circular crops)
|
|
196
|
+
12. Thumbnail generation (each thumbnail runs the same pipeline with a different `resize` target)
|
|
197
|
+
|
|
198
|
+
## Route handler factory (`createImageProcessHandler`)
|
|
199
|
+
|
|
200
|
+
`createImageProcessHandler` returns a framework-agnostic `(request: Request) => Promise<Response>` suitable for mounting directly as a Next.js App-Router route handler (or any other WHATWG `Request`/`Response` environment).
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
// app/api/images/process/route.ts
|
|
204
|
+
import { createImageProcessHandler } from 'hazo_images/server';
|
|
205
|
+
import { createInitializedFileManager } from 'hazo_files/server';
|
|
206
|
+
|
|
207
|
+
export const POST = createImageProcessHandler({
|
|
208
|
+
getFileManager: async () =>
|
|
209
|
+
createInitializedFileManager({ config: { provider: 'local', local: { basePath: './uploads' } } }),
|
|
210
|
+
maxUploadBytes: 15 * 1024 * 1024, // default: 15 MB
|
|
211
|
+
allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
212
|
+
previewMaxDimension: 1600, // default: 1600 — size of preview returned in response
|
|
213
|
+
savePathPrefix: '/hazo-images', // default: '/hazo-images'
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Request format (multipart `FormData`)
|
|
218
|
+
|
|
219
|
+
| Field | Type | Required | Description |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| `image` | `File` | yes | The source image file |
|
|
222
|
+
| `options` | JSON string | no | `ProcessImageOptions` to apply |
|
|
223
|
+
| `save` | `"true"` | no | Persist via the file manager |
|
|
224
|
+
| `filename` | string | no | Override the stored filename (otherwise uses uploaded filename) |
|
|
225
|
+
|
|
226
|
+
### Response JSON
|
|
227
|
+
|
|
228
|
+
**Success:**
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"ok": true,
|
|
232
|
+
"metadata": { "width": 1200, "height": 800, "format": "jpeg" },
|
|
233
|
+
"preview": "data:image/jpeg;base64,…",
|
|
234
|
+
"originalPreview": "data:image/jpeg;base64,…",
|
|
235
|
+
"thumbnails": [{ "size": 256, "format": "jpeg", "dataUrl": "data:image/jpeg;base64,…" }],
|
|
236
|
+
"saved": { "path": "/hazo-images/photo.jpg", "url": "https://…" }
|
|
153
237
|
}
|
|
154
238
|
```
|
|
239
|
+
`saved` is `null` when `save` is not `"true"`. `saveError` (string) is present if the file manager threw but the preview still succeeded.
|
|
240
|
+
|
|
241
|
+
**Error:**
|
|
242
|
+
```json
|
|
243
|
+
{ "ok": false, "error": "Unsupported image format", "errorType": "UnsupportedFormatError" }
|
|
244
|
+
```
|
|
245
|
+
HTTP status: `UnsupportedFormatError` → 400, `ImageProcessingError` → 422, other → 500.
|
|
246
|
+
|
|
247
|
+
### Types
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import type {
|
|
251
|
+
CreateImageProcessHandlerOptions,
|
|
252
|
+
ImageProcessHandlerFileManager,
|
|
253
|
+
} from 'hazo_images/server';
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`ImageProcessHandlerFileManager` is a duck-typed interface (`uploadFile` method) — any `hazo_files` FileManager satisfies it.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## `<ImageEditorDialog>` (`hazo_images/ui`)
|
|
261
|
+
|
|
262
|
+
A full-featured client-side image editor dialog built on `hazo_ui` (shadcn/ui primitives). Requires `hazo_ui ^4.4.0`, `react`, and `react-dom` (all optional peers — install them only if you use this subpath).
|
|
263
|
+
|
|
264
|
+
> **`"use client"` required.** All exports from `hazo_images/ui` are client components. Import them inside `"use client"` files or dynamic-import with `ssr: false`.
|
|
265
|
+
|
|
266
|
+
### Minimal usage (uncontrolled, default endpoint)
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
'use client';
|
|
270
|
+
import { ImageEditorDialog } from 'hazo_images/ui';
|
|
271
|
+
|
|
272
|
+
export function MyPage() {
|
|
273
|
+
return (
|
|
274
|
+
<ImageEditorDialog
|
|
275
|
+
imageUrl="/uploads/photo.jpg"
|
|
276
|
+
endpoint="/api/images/process"
|
|
277
|
+
onSave={(result) => console.log('saved', result.saved?.path)}
|
|
278
|
+
/>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The dialog talks to the `endpoint` URL with the multipart format expected by `createImageProcessHandler`. A live before/after preview updates after each debounced control change.
|
|
284
|
+
|
|
285
|
+
### Customised controls
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { ImageEditorDialog, DEFAULT_IMAGE_EDIT_CONTROLS } from 'hazo_images/ui';
|
|
289
|
+
|
|
290
|
+
<ImageEditorDialog
|
|
291
|
+
imageUrl="/uploads/photo.jpg"
|
|
292
|
+
endpoint="/api/images/process"
|
|
293
|
+
controls={[
|
|
294
|
+
...DEFAULT_IMAGE_EDIT_CONTROLS,
|
|
295
|
+
{ key: 'tint', label: 'Tint', type: 'color', tab: 'filters', default: '#ffffff' },
|
|
296
|
+
]}
|
|
297
|
+
defaultOptions={{ quality: 90, maxDimension: 2000 }}
|
|
298
|
+
debounceMs={400}
|
|
299
|
+
labels={{ save: 'Export', cancel: 'Discard' }}
|
|
300
|
+
/>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Props
|
|
304
|
+
|
|
305
|
+
| Prop | Type | Default | Description |
|
|
306
|
+
|---|---|---|---|
|
|
307
|
+
| `imageUrl` | `string` | — | Source image URL (displayed as "before") |
|
|
308
|
+
| `endpoint` | `string` | — | URL of `createImageProcessHandler` route (use this **or** `onProcess`) |
|
|
309
|
+
| `onProcess` | `(opts: ProcessImageOptions) => Promise<...>` | — | Custom processing callback instead of `endpoint` |
|
|
310
|
+
| `onSave` | `(result) => void` | — | Called after a successful save |
|
|
311
|
+
| `controls` | `ImageEditControl[]` | `DEFAULT_IMAGE_EDIT_CONTROLS` | Declarative control schema (see below) |
|
|
312
|
+
| `defaultOptions` | `ProcessImageOptions` | `{}` | Options pre-applied on open |
|
|
313
|
+
| `debounceMs` | `number` | `300` | Debounce delay (ms) before sending preview request |
|
|
314
|
+
| `labels` | `Partial<EditorLabels>` | — | Override button / tab label strings |
|
|
315
|
+
| `headerSlot` | `React.ReactNode` | — | Extra content in the dialog header |
|
|
316
|
+
| `footerSlot` | `React.ReactNode` | — | Extra content in the dialog footer |
|
|
317
|
+
| `className` | `string` | — | Extra class on the dialog container |
|
|
318
|
+
|
|
319
|
+
The dialog is organised into three tabs: **Geometry** (crop / rotate / flip / flop), **Filters** (grayscale / sharpen / blur / brightness / saturation / hue / tint), and **Output** (format / quality / resize).
|
|
320
|
+
|
|
321
|
+
The stage shows a **live preview**. Geometry (**rotate** / **flip** / **flop**) and most filters (**blur** / **brightness** / **saturation** / **hue** / **grayscale** / **tint**) render **instantly** as a CSS transform / filter / blend on the stage image — they update in realtime while you drag, with no server round-trip. These are omitted from the debounced (`debounceMs`) server preview to avoid double-applying; **Save** bakes them server-side via Sharp so output is exact. `sharpen`, crop, and output (format / quality / resize) are reflected via the debounced server preview.
|
|
322
|
+
|
|
323
|
+
The **rotate** control shows **0° / 90° / 180° / 270°** preset buttons beside the slider for one-click quarter turns. The **tint** control is a native color picker (swatch + editable hex + **Clear**).
|
|
324
|
+
|
|
325
|
+
**Crop** is a toggle mode on the Geometry tab: click **Crop image** to swap the stage to the original with a draggable box (8 resize handles) and a shape picker — **rectangle**, **square** (1:1), or **circle** (1:1, masked to a transparent-cornered PNG) — then **Done** to return to the live preview. A **Reset** button (outlined) restores every control to its default and clears the crop, keeping the loaded image. Override the `Reset`, `Crop image`, and `Done` strings via `labels.reset` / `labels.crop` / `labels.cropDone`.
|
|
326
|
+
|
|
327
|
+
### Helper exports
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import {
|
|
331
|
+
DEFAULT_IMAGE_EDIT_CONTROLS, // ImageEditControl[] — the default control schema
|
|
332
|
+
defaultControlValues, // Record<string, unknown> — zeroed defaults
|
|
333
|
+
controlValuesToOptions, // (values) => ProcessImageOptions
|
|
334
|
+
validate_image_edit_controls // (controls) => string[] — validation errors
|
|
335
|
+
} from 'hazo_images/ui';
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Control schema types
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import type { ImageEditControl } from 'hazo_images/ui';
|
|
342
|
+
// Each control: { key, label, type, tab, default, min?, max?, step? }
|
|
343
|
+
// type: 'slider' | 'switch' | 'select' | 'color'
|
|
344
|
+
// tab: 'geometry' | 'filters' | 'output'
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## `<ImageAnnotator>` (`hazo_images/ui`)
|
|
350
|
+
|
|
351
|
+
Mobile-first image markup tool built on a Konva `Stage`/`Layer` (via `react-konva`). Loads a source image (`File | Blob | string`), scales it to fit a `maxWidth`/`maxHeight` viewport, and lets the user draw over it with **pen** (freehand), **arrow**, and **circle** tools. **Undo** pops the last shape; **Clear** empties them. `onSave` flattens the source image + markup to a PNG `Blob`.
|
|
352
|
+
|
|
353
|
+
> **`"use client"` required** and **optional peer deps** `konva ^10.3.0` + `react-konva ^19.2.5` must be installed to use it (the rest of the package works without them).
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
'use client';
|
|
357
|
+
import { ImageAnnotator } from 'hazo_images/ui';
|
|
358
|
+
|
|
359
|
+
<ImageAnnotator
|
|
360
|
+
source={file} // File | Blob | string (URL)
|
|
361
|
+
maxWidth={800}
|
|
362
|
+
maxHeight={600}
|
|
363
|
+
onSave={(blob) => { /* PNG Blob of image + markup */ }}
|
|
364
|
+
// labels={{ pen: 'Draw', arrow: 'Arrow', circle: 'Circle', undo: 'Undo', clear: 'Clear', save: 'Save', loading: 'Loading…' }}
|
|
365
|
+
/>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Pure, framework-free shape-list helpers back its state and are exported for reuse: `addShape`, `undoShape`, `clearShapes` (+ types `AnnotatorTool`, `AnnotatorShape`, `PenShape`, `ArrowShape`, `CircleShape`).
|
|
369
|
+
|
|
370
|
+
---
|
|
155
371
|
|
|
156
372
|
## Runware (AI image generation)
|
|
157
373
|
|
|
@@ -202,8 +418,13 @@ const { positive, negative } = assemblePrompts({
|
|
|
202
418
|
| Version | Scope |
|
|
203
419
|
|---|---|
|
|
204
420
|
| `@1.0` | Server pipeline: `processImage` + `uploadProcessedImage` |
|
|
205
|
-
| `@1.1
|
|
206
|
-
| `@1.2
|
|
421
|
+
| `@1.1` | Wave 2 standardisation: `HazoError` subclasses, structured logging, INI config |
|
|
422
|
+
| `@1.2` | `hazo_images/runware` sub-export (AI image generation) |
|
|
423
|
+
| `@1.3` | `blur` Gaussian-blur option on `processImage` |
|
|
424
|
+
| `@1.4` | Extended pipeline options (crop / rotate / flip / flop / grayscale / sharpen / brightness / saturation / hue / tint / quality); `createImageProcessHandler` factory; `hazo_images/ui` (`ImageEditorDialog` + helpers) |
|
|
425
|
+
| `@1.5` | `ImageEditorDialog`: instant CSS live preview for geometry + filters (realtime while dragging); interactive crop (drag + 8 handles) with rect / square / circle shapes; rotate 0/90/180/270 presets; tint color picker; Reset button. Server: crop runs before rotate; `crop.shape: 'circle'` masks to a transparent PNG |
|
|
426
|
+
| `@1.7` | `deriveVariant` — fileId-in / fileId-out convenience over `processImage` + `uploadProcessedImage` |
|
|
427
|
+
| `@1.8` | `ImageAnnotator` — Konva-based image markup (pen / arrow / circle → flatten to PNG); `createImageProcessHandler` save now overwrites an existing file at the same path |
|
|
207
428
|
|
|
208
429
|
## License
|
|
209
430
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,82 @@ interface ProcessImageOptions {
|
|
|
6
6
|
maxDimension?: number;
|
|
7
7
|
webp?: boolean;
|
|
8
8
|
thumbnails?: number[];
|
|
9
|
+
blur?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Crop (extract) a region from the image. Crop is applied BEFORE rotate/flip,
|
|
12
|
+
* clamped to the auto-oriented input dimensions — out-of-bounds values are
|
|
13
|
+
* silently adjusted rather than thrown. { left, top, width, height } in pixels.
|
|
14
|
+
*
|
|
15
|
+
* `shape` controls the output silhouette (default 'rect'):
|
|
16
|
+
* - 'rect' — free rectangular region.
|
|
17
|
+
* - 'square' — caller supplies an equal-sided box; output is a square rectangle.
|
|
18
|
+
* - 'circle' — a circular alpha mask is applied (corners become transparent)
|
|
19
|
+
* and the output format is forced to PNG (overrides webp/jpeg).
|
|
20
|
+
*/
|
|
21
|
+
crop?: {
|
|
22
|
+
left: number;
|
|
23
|
+
top: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
shape?: 'rect' | 'square' | 'circle';
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Rotate the image by the given number of degrees clockwise.
|
|
30
|
+
* 0 (or omitting the option) skips rotation.
|
|
31
|
+
* Note: `autoRotate` already handles EXIF orientation — this is an explicit
|
|
32
|
+
* additional rotation on top of that.
|
|
33
|
+
*/
|
|
34
|
+
rotate?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Flip the image vertically (mirror across the horizontal axis).
|
|
37
|
+
* Default: false (no flip).
|
|
38
|
+
*/
|
|
39
|
+
flip?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Flop the image horizontally (mirror across the vertical axis).
|
|
42
|
+
* Default: false (no flop).
|
|
43
|
+
*/
|
|
44
|
+
flop?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Convert the image to grayscale.
|
|
47
|
+
* Default: false.
|
|
48
|
+
*/
|
|
49
|
+
grayscale?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Apply an unsharp-mask sharpen with the given sigma value (0.000001–10).
|
|
52
|
+
* Higher values = stronger sharpening. Omit or 0 to skip.
|
|
53
|
+
*/
|
|
54
|
+
sharpen?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Adjust brightness using Sharp's modulate operation.
|
|
57
|
+
* Multiplier: 1 = unchanged, <1 = darker, >1 = brighter.
|
|
58
|
+
* Omit to leave brightness unchanged.
|
|
59
|
+
*/
|
|
60
|
+
brightness?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Adjust saturation (color intensity) using Sharp's modulate operation.
|
|
63
|
+
* Multiplier: 1 = unchanged, <1 = less saturated, >1 = more saturated.
|
|
64
|
+
* Omit to leave saturation unchanged.
|
|
65
|
+
*/
|
|
66
|
+
saturation?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Rotate the hue of every pixel using Sharp's modulate operation.
|
|
69
|
+
* Degrees: 0 = unchanged, positive = clockwise rotation around the color wheel.
|
|
70
|
+
* Omit to leave hue unchanged.
|
|
71
|
+
*/
|
|
72
|
+
hue?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Tint the image with a hex color string, e.g. "#ff0000" for red.
|
|
75
|
+
* Omit to skip tinting.
|
|
76
|
+
*/
|
|
77
|
+
tint?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Output quality for lossy formats (1–100).
|
|
80
|
+
* Applied to WebP output (always) and JPEG output (when input format is jpeg/jpg).
|
|
81
|
+
* PNG and other lossless formats ignore this option.
|
|
82
|
+
* Omit to use Sharp's default quality per format.
|
|
83
|
+
*/
|
|
84
|
+
quality?: number;
|
|
9
85
|
}
|
|
10
86
|
interface ProcessImageResult {
|
|
11
87
|
buffer: Buffer;
|
package/dist/runware/index.js
CHANGED
|
@@ -13,31 +13,26 @@ function rng() {
|
|
|
13
13
|
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// ../node_modules/uuid/dist/esm-node/regex.js
|
|
17
|
-
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
|
|
18
|
-
|
|
19
|
-
// ../node_modules/uuid/dist/esm-node/validate.js
|
|
20
|
-
function validate(uuid) {
|
|
21
|
-
return typeof uuid === "string" && regex_default.test(uuid);
|
|
22
|
-
}
|
|
23
|
-
var validate_default = validate;
|
|
24
|
-
|
|
25
16
|
// ../node_modules/uuid/dist/esm-node/stringify.js
|
|
26
17
|
var byteToHex = [];
|
|
27
18
|
for (let i = 0; i < 256; ++i) {
|
|
28
|
-
byteToHex.push((i + 256).toString(16).
|
|
19
|
+
byteToHex.push((i + 256).toString(16).slice(1));
|
|
29
20
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
if (!validate_default(uuid)) {
|
|
33
|
-
throw TypeError("Stringified UUID is invalid");
|
|
34
|
-
}
|
|
35
|
-
return uuid;
|
|
21
|
+
function unsafeStringify(arr, offset = 0) {
|
|
22
|
+
return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
|
|
36
23
|
}
|
|
37
|
-
|
|
24
|
+
|
|
25
|
+
// ../node_modules/uuid/dist/esm-node/native.js
|
|
26
|
+
import crypto2 from "crypto";
|
|
27
|
+
var native_default = {
|
|
28
|
+
randomUUID: crypto2.randomUUID
|
|
29
|
+
};
|
|
38
30
|
|
|
39
31
|
// ../node_modules/uuid/dist/esm-node/v4.js
|
|
40
32
|
function v4(options, buf, offset) {
|
|
33
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
34
|
+
return native_default.randomUUID();
|
|
35
|
+
}
|
|
41
36
|
options = options || {};
|
|
42
37
|
const rnds = options.random || (options.rng || rng)();
|
|
43
38
|
rnds[6] = rnds[6] & 15 | 64;
|
|
@@ -49,7 +44,7 @@ function v4(options, buf, offset) {
|
|
|
49
44
|
}
|
|
50
45
|
return buf;
|
|
51
46
|
}
|
|
52
|
-
return
|
|
47
|
+
return unsafeStringify(rnds);
|
|
53
48
|
}
|
|
54
49
|
var v4_default = v4;
|
|
55
50
|
|
package/dist/server/index.d.ts
CHANGED
|
@@ -6,6 +6,82 @@ interface ProcessImageOptions {
|
|
|
6
6
|
maxDimension?: number;
|
|
7
7
|
webp?: boolean;
|
|
8
8
|
thumbnails?: number[];
|
|
9
|
+
blur?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Crop (extract) a region from the image. Crop is applied BEFORE rotate/flip,
|
|
12
|
+
* clamped to the auto-oriented input dimensions — out-of-bounds values are
|
|
13
|
+
* silently adjusted rather than thrown. { left, top, width, height } in pixels.
|
|
14
|
+
*
|
|
15
|
+
* `shape` controls the output silhouette (default 'rect'):
|
|
16
|
+
* - 'rect' — free rectangular region.
|
|
17
|
+
* - 'square' — caller supplies an equal-sided box; output is a square rectangle.
|
|
18
|
+
* - 'circle' — a circular alpha mask is applied (corners become transparent)
|
|
19
|
+
* and the output format is forced to PNG (overrides webp/jpeg).
|
|
20
|
+
*/
|
|
21
|
+
crop?: {
|
|
22
|
+
left: number;
|
|
23
|
+
top: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
shape?: 'rect' | 'square' | 'circle';
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Rotate the image by the given number of degrees clockwise.
|
|
30
|
+
* 0 (or omitting the option) skips rotation.
|
|
31
|
+
* Note: `autoRotate` already handles EXIF orientation — this is an explicit
|
|
32
|
+
* additional rotation on top of that.
|
|
33
|
+
*/
|
|
34
|
+
rotate?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Flip the image vertically (mirror across the horizontal axis).
|
|
37
|
+
* Default: false (no flip).
|
|
38
|
+
*/
|
|
39
|
+
flip?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Flop the image horizontally (mirror across the vertical axis).
|
|
42
|
+
* Default: false (no flop).
|
|
43
|
+
*/
|
|
44
|
+
flop?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Convert the image to grayscale.
|
|
47
|
+
* Default: false.
|
|
48
|
+
*/
|
|
49
|
+
grayscale?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Apply an unsharp-mask sharpen with the given sigma value (0.000001–10).
|
|
52
|
+
* Higher values = stronger sharpening. Omit or 0 to skip.
|
|
53
|
+
*/
|
|
54
|
+
sharpen?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Adjust brightness using Sharp's modulate operation.
|
|
57
|
+
* Multiplier: 1 = unchanged, <1 = darker, >1 = brighter.
|
|
58
|
+
* Omit to leave brightness unchanged.
|
|
59
|
+
*/
|
|
60
|
+
brightness?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Adjust saturation (color intensity) using Sharp's modulate operation.
|
|
63
|
+
* Multiplier: 1 = unchanged, <1 = less saturated, >1 = more saturated.
|
|
64
|
+
* Omit to leave saturation unchanged.
|
|
65
|
+
*/
|
|
66
|
+
saturation?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Rotate the hue of every pixel using Sharp's modulate operation.
|
|
69
|
+
* Degrees: 0 = unchanged, positive = clockwise rotation around the color wheel.
|
|
70
|
+
* Omit to leave hue unchanged.
|
|
71
|
+
*/
|
|
72
|
+
hue?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Tint the image with a hex color string, e.g. "#ff0000" for red.
|
|
75
|
+
* Omit to skip tinting.
|
|
76
|
+
*/
|
|
77
|
+
tint?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Output quality for lossy formats (1–100).
|
|
80
|
+
* Applied to WebP output (always) and JPEG output (when input format is jpeg/jpg).
|
|
81
|
+
* PNG and other lossless formats ignore this option.
|
|
82
|
+
* Omit to use Sharp's default quality per format.
|
|
83
|
+
*/
|
|
84
|
+
quality?: number;
|
|
9
85
|
}
|
|
10
86
|
interface ProcessImageResult {
|
|
11
87
|
buffer: Buffer;
|
|
@@ -39,6 +115,42 @@ interface UploadOptions {
|
|
|
39
115
|
}
|
|
40
116
|
declare function uploadProcessedImage(fm: FileManager, buffer: Buffer, virtualPath: string, opts?: ProcessImageOptions & UploadOptions): Promise<UploadProcessedImageResult>;
|
|
41
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Framework-agnostic Web handler factory for the hazo_images processing pipeline.
|
|
120
|
+
*
|
|
121
|
+
* Uses only Web-standard Request/Response/FormData — works as a Next.js App-Router
|
|
122
|
+
* route POST directly. Does NOT import `next`, `hazo_ui`, or `hazo_files`.
|
|
123
|
+
*
|
|
124
|
+
* @since 1.4.0
|
|
125
|
+
*/
|
|
126
|
+
/**
|
|
127
|
+
* Duck-typed FileManager interface — structurally matches hazo_files FileManager
|
|
128
|
+
* and TrackedFileManager. No compile-time dep on hazo_files.
|
|
129
|
+
*/
|
|
130
|
+
interface ImageProcessHandlerFileManager {
|
|
131
|
+
uploadFile(source: Buffer | string, remotePath: string, options?: Record<string, unknown>): Promise<{
|
|
132
|
+
success: boolean;
|
|
133
|
+
data?: unknown;
|
|
134
|
+
error?: string;
|
|
135
|
+
}>;
|
|
136
|
+
}
|
|
137
|
+
interface CreateImageProcessHandlerOptions {
|
|
138
|
+
/**
|
|
139
|
+
* Provide to enable persistence on `save`.
|
|
140
|
+
* Returns the FileManager (sync or async).
|
|
141
|
+
*/
|
|
142
|
+
getFileManager?: () => Promise<ImageProcessHandlerFileManager> | ImageProcessHandlerFileManager;
|
|
143
|
+
/** Max upload size in bytes. Default 15 MB. */
|
|
144
|
+
maxUploadBytes?: number;
|
|
145
|
+
/** Allowed input MIME types. */
|
|
146
|
+
allowedTypes?: string[];
|
|
147
|
+
/** Cap the longest side of the base64 preview to bound payload size. Default 1600. */
|
|
148
|
+
previewMaxDimension?: number;
|
|
149
|
+
/** Base virtual path prefix used when saving. Default '/hazo-images'. */
|
|
150
|
+
savePathPrefix?: string;
|
|
151
|
+
}
|
|
152
|
+
declare function createImageProcessHandler(opts?: CreateImageProcessHandlerOptions): (request: Request) => Promise<Response>;
|
|
153
|
+
|
|
42
154
|
/**
|
|
43
155
|
* hazo_images error classes.
|
|
44
156
|
*
|
|
@@ -90,4 +202,43 @@ declare class ImageUploadError extends HazoExternalError {
|
|
|
90
202
|
constructor(message: string, context?: Record<string, unknown>);
|
|
91
203
|
}
|
|
92
204
|
|
|
93
|
-
|
|
205
|
+
interface DeriveVariantOptions extends ProcessImageOptions {
|
|
206
|
+
actor_id?: string;
|
|
207
|
+
/**
|
|
208
|
+
* Virtual path suffix appended to the source path to form the variant path.
|
|
209
|
+
* Default: derived from the operation (e.g. '_crop', '_blur3', '_thumb', '_derived').
|
|
210
|
+
*/
|
|
211
|
+
variantSuffix?: string;
|
|
212
|
+
}
|
|
213
|
+
interface DeriveVariantDeps {
|
|
214
|
+
/** Must support: uploadFile, downloadFile */
|
|
215
|
+
fm: {
|
|
216
|
+
uploadFile(source: Buffer | string, remotePath: string, opts?: Record<string, unknown>): Promise<{
|
|
217
|
+
success: boolean;
|
|
218
|
+
data?: unknown;
|
|
219
|
+
error?: string;
|
|
220
|
+
}>;
|
|
221
|
+
downloadFile(fileId: string): Promise<Buffer>;
|
|
222
|
+
};
|
|
223
|
+
metadataService?: {
|
|
224
|
+
findById(id: string): Promise<{
|
|
225
|
+
virtual_path?: string;
|
|
226
|
+
file_name?: string;
|
|
227
|
+
} | null>;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
interface DeriveVariantResult {
|
|
231
|
+
/** The file_id of the newly uploaded variant (from fm.uploadFile data.file_id or similar) */
|
|
232
|
+
file_id: string;
|
|
233
|
+
/** Optional — not known at this layer; caller resolves URL separately */
|
|
234
|
+
url?: string;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* deriveVariant — fileId-in/fileId-out convenience over processImage + uploadProcessedImage.
|
|
238
|
+
*
|
|
239
|
+
* Downloads the source file by ID, processes it with processImage, uploads the
|
|
240
|
+
* result via fm.uploadFile, and returns the new file_id.
|
|
241
|
+
*/
|
|
242
|
+
declare function deriveVariant(sourceFileId: string, deps: DeriveVariantDeps, opts?: DeriveVariantOptions): Promise<DeriveVariantResult>;
|
|
243
|
+
|
|
244
|
+
export { type CreateImageProcessHandlerOptions, type DeriveVariantDeps, type DeriveVariantOptions, type DeriveVariantResult, type ImageProcessHandlerFileManager, ImageProcessingError, ImageUploadError, type ProcessImageOptions, type ProcessImageResult, SharpMissingError, UnsupportedFormatError, type UploadProcessedImageResult, createImageProcessHandler, deriveVariant, processImage, uploadProcessedImage };
|