hazo_images 1.8.0 → 1.9.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 +10 -0
- package/README.md +35 -2
- package/dist/index.d.ts +22 -1
- package/dist/index.js +28 -1
- package/dist/ui/index.d.ts +14 -1
- package/dist/ui/index.js +18 -0
- package/package.json +7 -7
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# hazo_images Change Log
|
|
2
2
|
|
|
3
|
+
## 1.9.0 — 2026-07-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`bboxIoU(a, b)`** — new root export (`hazo_images`, isomorphic). Computes Intersection-over-Union for two normalized (0..1) axis-aligned bounding boxes. Returns `0` when either input is `null`/`undefined`/malformed instead of throwing — callers commonly iterate user-tagged boxes that may legitimately be missing a region. New export: `BBox` type. Ported from a downstream app's photo face-tag dedup logic; pure, no dependencies.
|
|
8
|
+
- **`canDecodeImage(file)`** — new `hazo_images/ui` export (client-only). Probes whether the current browser can decode a given `File` by loading it into an `Image` and resolving `true`/`false` on `load`/`error` (revoking the created object URL either way). Ported from a downstream app's crop-dialog gating logic (e.g. skip the cropper and fall back to direct upload for formats like HEIC that some browsers can't render). Not a React component — plain function, no `hazo_ui`/React import.
|
|
9
|
+
- Demo: `test-app /bbox-and-decode` page exercising both utilities (draggable-value box overlay for `bboxIoU`, file-picker probe for `canDecodeImage`).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
3
13
|
## 1.8.0 — 2026-07-11
|
|
4
14
|
|
|
5
15
|
### Added
|
package/README.md
CHANGED
|
@@ -91,6 +91,39 @@ const result = await processImage(buffer, {
|
|
|
91
91
|
// thumbnail extension in uploadProcessedImage: __thumb_256.webp
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### bboxIoU — bounding-box overlap (isomorphic)
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { bboxIoU } from 'hazo_images';
|
|
98
|
+
import type { BBox } from 'hazo_images';
|
|
99
|
+
|
|
100
|
+
const a: BBox = { x: 0.1, y: 0.1, w: 0.4, h: 0.4 };
|
|
101
|
+
const b: BBox = { x: 0.3, y: 0.3, w: 0.4, h: 0.4 };
|
|
102
|
+
|
|
103
|
+
bboxIoU(a, b); // 0..1, where 1 = perfect overlap
|
|
104
|
+
bboxIoU(a, null); // 0 — null/undefined/malformed input never throws
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Coordinates are normalized `0..1` relative to the containing image. Useful
|
|
108
|
+
for deduping or matching user-tagged regions (e.g. face-tag boxes) against
|
|
109
|
+
detector output.
|
|
110
|
+
|
|
111
|
+
### canDecodeImage — browser decode probe (client-only)
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { canDecodeImage } from 'hazo_images/ui';
|
|
115
|
+
|
|
116
|
+
const ok = await canDecodeImage(file); // File → Promise<boolean>
|
|
117
|
+
if (!ok) {
|
|
118
|
+
// e.g. HEIC on a non-Safari browser — skip the crop dialog and
|
|
119
|
+
// fall back to direct upload instead.
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Loads the file into an `Image` and resolves `true`/`false` based on
|
|
124
|
+
`load`/`error`; the created object URL is revoked either way. Browser-only —
|
|
125
|
+
relies on global `Image`/`URL`/`File`, so it must run in a client component.
|
|
126
|
+
|
|
94
127
|
## Error Types
|
|
95
128
|
|
|
96
129
|
All errors extend `HazoError` subclasses from `hazo_core` (Wave 2). The
|
|
@@ -136,9 +169,9 @@ try {
|
|
|
136
169
|
|
|
137
170
|
| Import | Contents |
|
|
138
171
|
|---|---|
|
|
139
|
-
| `hazo_images` | Shared types + error classes (isomorphic, no Node deps) |
|
|
172
|
+
| `hazo_images` | Shared types + error classes + `bboxIoU` (isomorphic, no Node deps) |
|
|
140
173
|
| `hazo_images/server` | `processImage`, `uploadProcessedImage`, `createImageProcessHandler` (Node.js only) |
|
|
141
|
-
| `hazo_images/ui` | `ImageUploader`, `ImageViewer`, `ImageEditorDialog`, `ImageAnnotator` + control-schema helpers (
|
|
174
|
+
| `hazo_images/ui` | `ImageUploader`, `ImageViewer`, `ImageEditorDialog`, `ImageAnnotator`, `canDecodeImage` + control-schema helpers (client-only; React components require `hazo_ui`, `canDecodeImage` does not) |
|
|
142
175
|
| `hazo_images/runware` | `createRunwareClient`, `assemblePrompts`, 4 error classes (Node.js only) |
|
|
143
176
|
|
|
144
177
|
## Supported formats
|
package/dist/index.d.ts
CHANGED
|
@@ -152,4 +152,25 @@ declare class ImageUploadError extends HazoExternalError {
|
|
|
152
152
|
constructor(message: string, context?: Record<string, unknown>);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Normalized axis-aligned bounding box: coordinates and dimensions in the
|
|
157
|
+
* 0..1 range relative to the containing image.
|
|
158
|
+
*/
|
|
159
|
+
type BBox = {
|
|
160
|
+
x: number;
|
|
161
|
+
y: number;
|
|
162
|
+
w: number;
|
|
163
|
+
h: number;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Compute Intersection over Union for two axis-aligned bounding boxes.
|
|
167
|
+
* All coordinates are normalized 0..1.
|
|
168
|
+
*
|
|
169
|
+
* Returns a value in [0..1] where 1 means perfect overlap. Returns 0 when
|
|
170
|
+
* either input is null/undefined or otherwise malformed — callers commonly
|
|
171
|
+
* iterate user-tagged bboxes that may legitimately be null (e.g. a tag
|
|
172
|
+
* created without a drawn region carries no bbox).
|
|
173
|
+
*/
|
|
174
|
+
declare function bboxIoU(a: BBox | null | undefined, b: BBox | null | undefined): number;
|
|
175
|
+
|
|
176
|
+
export { type BBox, ImageProcessingError, ImageUploadError, type ProcessImageOptions, type ProcessImageResult, SharpMissingError, UnsupportedFormatError, type UploadProcessedImageResult, bboxIoU };
|
package/dist/index.js
CHANGED
|
@@ -45,9 +45,36 @@ var ImageUploadError = class extends HazoExternalError {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
+
|
|
49
|
+
// src/bbox-iou.ts
|
|
50
|
+
function isValidBBox(b) {
|
|
51
|
+
if (!b || typeof b !== "object") return false;
|
|
52
|
+
const r = b;
|
|
53
|
+
return typeof r.x === "number" && typeof r.y === "number" && typeof r.w === "number" && typeof r.h === "number";
|
|
54
|
+
}
|
|
55
|
+
function bboxIoU(a, b) {
|
|
56
|
+
if (!isValidBBox(a) || !isValidBBox(b)) return 0;
|
|
57
|
+
const ax2 = a.x + a.w;
|
|
58
|
+
const ay2 = a.y + a.h;
|
|
59
|
+
const bx2 = b.x + b.w;
|
|
60
|
+
const by2 = b.y + b.h;
|
|
61
|
+
const interX1 = Math.max(a.x, b.x);
|
|
62
|
+
const interY1 = Math.max(a.y, b.y);
|
|
63
|
+
const interX2 = Math.min(ax2, bx2);
|
|
64
|
+
const interY2 = Math.min(ay2, by2);
|
|
65
|
+
const interW = interX2 - interX1;
|
|
66
|
+
const interH = interY2 - interY1;
|
|
67
|
+
if (interW <= 0 || interH <= 0) return 0;
|
|
68
|
+
const intersection = interW * interH;
|
|
69
|
+
const areaA = a.w * a.h;
|
|
70
|
+
const areaB = b.w * b.h;
|
|
71
|
+
const union = areaA + areaB - intersection;
|
|
72
|
+
return union <= 0 ? 0 : intersection / union;
|
|
73
|
+
}
|
|
48
74
|
export {
|
|
49
75
|
ImageProcessingError,
|
|
50
76
|
ImageUploadError,
|
|
51
77
|
SharpMissingError,
|
|
52
|
-
UnsupportedFormatError
|
|
78
|
+
UnsupportedFormatError,
|
|
79
|
+
bboxIoU
|
|
53
80
|
};
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -393,4 +393,17 @@ declare function ResizeHandleOverlay({ box, onResize, onResizeEnd, containerWidt
|
|
|
393
393
|
|
|
394
394
|
declare function cn(...parts: Array<string | false | null | undefined>): string;
|
|
395
395
|
|
|
396
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Probes whether the current browser can decode a given image File.
|
|
398
|
+
*
|
|
399
|
+
* Resolves `true` if the browser loads the file successfully, `false` if
|
|
400
|
+
* it fires an error (e.g. HEIC on non-Safari). Callers use this to decide
|
|
401
|
+
* whether to open a crop/edit dialog or fall back to direct upload.
|
|
402
|
+
*
|
|
403
|
+
* Browser-only — relies on the global `Image`, `URL.createObjectURL`, and
|
|
404
|
+
* `File` APIs. Never call this from server code or Node.js tests without
|
|
405
|
+
* stubbing those globals first.
|
|
406
|
+
*/
|
|
407
|
+
declare function canDecodeImage(file: File): Promise<boolean>;
|
|
408
|
+
|
|
409
|
+
export { type AnnotatorShape, type AnnotatorTool, type ArrowShape, type CircleShape, DEFAULT_IMAGE_EDIT_CONTROLS, ImageAnnotator, type ImageAnnotatorProps, type ImageEditControls, type ImageEditTab, ImageEditorDialog, type ImageEditorDialogProps, type ImageProcessOptions, type ImageProcessResult, type ImageProcessThumbnail, ImageUploader, type ImageUploaderProps, ImageViewer, type ImageViewerProps, type InputOption, type PenShape, type ProcessFn, type ResizeBox, type ResizeHandle, ResizeHandleOverlay, type ResizeHandleOverlayProps, type TabElement, type ValidationError, type ValidationResult, addShape, canDecodeImage, clearShapes, cn, controlValuesToOptions, defaultControlValues, undoShape, validate_image_edit_controls };
|
package/dist/ui/index.js
CHANGED
|
@@ -1894,6 +1894,23 @@ function ImageAnnotator({
|
|
|
1894
1894
|
)
|
|
1895
1895
|
] }) });
|
|
1896
1896
|
}
|
|
1897
|
+
|
|
1898
|
+
// src/ui/can-decode-image.ts
|
|
1899
|
+
function canDecodeImage(file) {
|
|
1900
|
+
return new Promise((resolve) => {
|
|
1901
|
+
const url = URL.createObjectURL(file);
|
|
1902
|
+
const img = new Image();
|
|
1903
|
+
img.onload = () => {
|
|
1904
|
+
URL.revokeObjectURL(url);
|
|
1905
|
+
resolve(true);
|
|
1906
|
+
};
|
|
1907
|
+
img.onerror = () => {
|
|
1908
|
+
URL.revokeObjectURL(url);
|
|
1909
|
+
resolve(false);
|
|
1910
|
+
};
|
|
1911
|
+
img.src = url;
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1897
1914
|
export {
|
|
1898
1915
|
DEFAULT_IMAGE_EDIT_CONTROLS,
|
|
1899
1916
|
ImageAnnotator,
|
|
@@ -1902,6 +1919,7 @@ export {
|
|
|
1902
1919
|
ImageViewer,
|
|
1903
1920
|
ResizeHandleOverlay,
|
|
1904
1921
|
addShape,
|
|
1922
|
+
canDecodeImage,
|
|
1905
1923
|
clearShapes,
|
|
1906
1924
|
cn,
|
|
1907
1925
|
controlValuesToOptions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_images",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Image processing pipeline for the hazo ecosystem — Sharp wrapper, thumbnail generation, EXIF handling, and hazo_files integration helper",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
"zod": "^4.1.12"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"hazo_core": "^1.
|
|
54
|
+
"hazo_core": "^1.3.0",
|
|
55
55
|
"hazo_files": "^3.1.1",
|
|
56
|
-
"hazo_theme": "^1.0.
|
|
57
|
-
"hazo_ui": "^6.
|
|
56
|
+
"hazo_theme": "^1.0.1",
|
|
57
|
+
"hazo_ui": "^6.3.1",
|
|
58
58
|
"konva": "^10.3.0",
|
|
59
59
|
"lucide-react": "^0.553.0",
|
|
60
60
|
"react": "^18.0.0 || ^19.0.0",
|
|
61
61
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
62
|
-
"react-konva": "^19.2.5",
|
|
62
|
+
"react-konva": "^18.2.16 || ^19.2.5",
|
|
63
63
|
"sharp": "^0.33.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependenciesMeta": {
|
|
@@ -95,12 +95,12 @@
|
|
|
95
95
|
"@types/jest": "^30.0.0",
|
|
96
96
|
"@types/node": "^22.10.0",
|
|
97
97
|
"@types/react": "^18.3.3",
|
|
98
|
-
"hazo_core": "^1.
|
|
98
|
+
"hazo_core": "^1.3.0",
|
|
99
99
|
"jest": "^30.2.0",
|
|
100
100
|
"jest-environment-node": "^30.2.0",
|
|
101
101
|
"konva": "^10.3.0",
|
|
102
102
|
"lucide-react": "^0.553.0",
|
|
103
|
-
"react-konva": "^
|
|
103
|
+
"react-konva": "^18.2.16",
|
|
104
104
|
"ts-jest": "^29.4.5",
|
|
105
105
|
"tsup": "^8.0.0",
|
|
106
106
|
"typescript": "^5.7.2"
|