@flighthq/image 0.2.1-next.841.373388f → 0.3.0-edge.1458.0c01b63
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/dist/contract.d.ts +3 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +3 -0
- package/dist/contract.js.map +1 -0
- package/dist/imageResource.d.ts +6 -12
- package/dist/imageResource.d.ts.map +1 -1
- package/dist/imageResource.js +17 -69
- package/dist/imageResource.js.map +1 -1
- package/dist/imageResourceFrom.d.ts +10 -10
- package/dist/imageResourceFrom.d.ts.map +1 -1
- package/dist/imageResourceFrom.js +35 -35
- package/dist/imageResourceFrom.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
- package/src/imageResource.test.ts +42 -190
- package/src/imageResourceFrom.test.ts +70 -36
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
package/dist/imageResource.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function cloneImageResource(resource: Readonly<
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function createImageResource(image
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function hasImageResourceData(resource: Readonly<ImageResource>): boolean;
|
|
8
|
-
export declare function hasImageResourcePixels(resource: Readonly<ImageResource>): boolean;
|
|
9
|
-
export declare function hasImageResourceSource(resource: Readonly<ImageResource>): boolean;
|
|
10
|
-
export declare function invalidateImageResource(resource: ImageResource): void;
|
|
11
|
-
export declare function isImageResourceEmpty(resource: Readonly<ImageResource>): boolean;
|
|
12
|
-
export declare function setImageResourceSource(resource: ImageResource, element: CanvasImageSource | null): void;
|
|
1
|
+
import type { CompressedImage, CompressedImageData, Image } from '@flighthq/types/contract';
|
|
2
|
+
export declare function cloneImageResource(resource: Readonly<Image>): Image;
|
|
3
|
+
export declare function createCompressedImage(compressed: Readonly<CompressedImageData>): CompressedImage;
|
|
4
|
+
export declare function createImageResource(image: CanvasImageSource): Image;
|
|
5
|
+
export declare function invalidateImageResource(resource: Image): void;
|
|
6
|
+
export declare function isImageResourceEmpty(resource: Readonly<Image>): boolean;
|
|
13
7
|
//# sourceMappingURL=imageResource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imageResource.d.ts","sourceRoot":"","sources":["../src/imageResource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"imageResource.d.ts","sourceRoot":"","sources":["../src/imageResource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAK5F,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,KAAK,CAQnE;AAID,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GAAG,eAAe,CAQhG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CAUnE;AAGD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,KAAK,GAAG,IAAI,CAG7D;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAEvE"}
|
package/dist/imageResource.js
CHANGED
|
@@ -1,104 +1,52 @@
|
|
|
1
|
-
import { createEntity } from '@flighthq/entity';
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// states with separate invalidation. Use a Surface copy when you need the pixels themselves duplicated.
|
|
1
|
+
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
+
import { CompressedImageTextureSourceKind, ImageTextureSourceKind } from '@flighthq/types/contract';
|
|
3
|
+
// Allocates a new resource identity over the same borrowed host image. The host handle is shared by
|
|
4
|
+
// reference; the clone owns an independent version counter for renderer cache invalidation.
|
|
6
5
|
export function cloneImageResource(resource) {
|
|
7
6
|
return createEntity({
|
|
8
|
-
alphaType: resource.alphaType,
|
|
9
|
-
compressed: resource.compressed,
|
|
10
|
-
data: resource.data,
|
|
11
|
-
format: resource.format,
|
|
12
7
|
height: resource.height,
|
|
8
|
+
kind: resource.kind,
|
|
13
9
|
source: resource.source,
|
|
14
10
|
version: resource.version,
|
|
15
11
|
width: resource.width,
|
|
16
12
|
});
|
|
17
13
|
}
|
|
18
|
-
// Wraps a parsed block-compressed
|
|
19
|
-
// the container's
|
|
20
|
-
|
|
21
|
-
// caller owns the payload bytes the container's level ranges index into.
|
|
22
|
-
export function createCompressedImageResource(compressed) {
|
|
14
|
+
// Wraps a parsed block-compressed payload as its own GPU-only source. The caller owns the payload
|
|
15
|
+
// bytes indexed by the container's level ranges.
|
|
16
|
+
export function createCompressedImage(compressed) {
|
|
23
17
|
return createEntity({
|
|
24
|
-
alphaType: 'straight',
|
|
25
18
|
compressed,
|
|
26
|
-
data: null,
|
|
27
|
-
format: 'rgba8unorm',
|
|
28
19
|
height: compressed.container.height,
|
|
29
|
-
|
|
20
|
+
kind: CompressedImageTextureSourceKind,
|
|
30
21
|
version: 0,
|
|
31
22
|
width: compressed.container.width,
|
|
32
23
|
});
|
|
33
24
|
}
|
|
34
25
|
export function createImageResource(image) {
|
|
35
26
|
const resource = createEntity({
|
|
36
|
-
alphaType: 'straight',
|
|
37
|
-
compressed: null,
|
|
38
|
-
data: null,
|
|
39
|
-
format: 'rgba8unorm',
|
|
40
27
|
height: 0,
|
|
41
|
-
|
|
28
|
+
kind: ImageTextureSourceKind,
|
|
29
|
+
source: image,
|
|
42
30
|
version: 0,
|
|
43
31
|
width: 0,
|
|
44
32
|
});
|
|
45
|
-
|
|
46
|
-
updateImageResourceSize(resource);
|
|
33
|
+
updateImageResourceSize(resource);
|
|
47
34
|
return resource;
|
|
48
35
|
}
|
|
49
|
-
//
|
|
50
|
-
// changed. This does NOT free the backend GPU texture (that is owned per render state — call the
|
|
51
|
-
// backend's destroy*Texture) and does NOT close an owned ImageBitmap (ownership is ambiguous; close it
|
|
52
|
-
// explicitly if you own it). Dimensions are left intact; setImageResourceSource repopulates the resource.
|
|
53
|
-
export function disposeImageResource(resource) {
|
|
54
|
-
resource.compressed = null;
|
|
55
|
-
resource.data = null;
|
|
56
|
-
resource.source = null;
|
|
57
|
-
invalidateImageResource(resource);
|
|
58
|
-
}
|
|
59
|
-
// Returns the byte footprint of the CPU-side pixel data (`data` array). Returns 0 when `data` is
|
|
60
|
-
// null (element-only resource — the GPU texture footprint is tracked by the render state, not here).
|
|
61
|
-
// For `rgba8unorm` (the default) the result equals width × height × 4; other formats are not yet
|
|
62
|
-
// exercised but the formula still applies (byteLength of the actual buffer is authoritative).
|
|
63
|
-
export function getImageResourceByteSize(resource) {
|
|
64
|
-
return resource.data !== null ? resource.data.byteLength : 0;
|
|
65
|
-
}
|
|
66
|
-
export function hasImageResourceData(resource) {
|
|
67
|
-
return resource.data !== null;
|
|
68
|
-
}
|
|
69
|
-
// True when the resource carries pixels in any representation — a decoded `source` element, raw CPU
|
|
70
|
-
// `data`, or a block-`compressed` payload. The upload-readiness predicate for backends that can consume
|
|
71
|
-
// any of them: an element-only load, a data-only generated Surface, a compressed-only parsed container,
|
|
72
|
-
// or a resource holding several are all uploadable. Prefer this over hasImageResourceSource at any gate
|
|
73
|
-
// that feeds a source-or-data uploader (bindGlTexture, bindWgpuTexture), so a memory-generated Surface
|
|
74
|
-
// or a compressed container is not rejected for lacking an element.
|
|
75
|
-
export function hasImageResourcePixels(resource) {
|
|
76
|
-
return resource.source !== null || resource.data !== null || resource.compressed !== null;
|
|
77
|
-
}
|
|
78
|
-
export function hasImageResourceSource(resource) {
|
|
79
|
-
return resource.source !== null;
|
|
80
|
-
}
|
|
81
|
-
// Bumps the resource content revision so consumers (renderer texture caches) know the pixels behind
|
|
82
|
-
// this image changed even though the object identity is the same. Call after mutating the backing
|
|
83
|
-
// pixels in place; the Surface API calls it for you. Resource-tier analog of invalidateNodeLocalContent.
|
|
36
|
+
// Marks changed pixels behind the same borrowed host handle. The handle itself remains immutable.
|
|
84
37
|
export function invalidateImageResource(resource) {
|
|
38
|
+
updateImageResourceSize(resource);
|
|
85
39
|
resource.version = (resource.version + 1) >>> 0;
|
|
86
40
|
}
|
|
87
41
|
export function isImageResourceEmpty(resource) {
|
|
88
42
|
return resource.width <= 0 || resource.height <= 0;
|
|
89
43
|
}
|
|
90
|
-
//
|
|
91
|
-
// null to clear the element (dimensions are left intact; use disposeImageResource to release for GC).
|
|
92
|
-
export function setImageResourceSource(resource, element) {
|
|
93
|
-
resource.source = element;
|
|
94
|
-
if (element !== null)
|
|
95
|
-
updateImageResourceSize(resource);
|
|
96
|
-
invalidateImageResource(resource);
|
|
97
|
-
}
|
|
98
|
-
// Reads pixel dimensions from the current (non-null) element. Video sources carry their size on
|
|
44
|
+
// Reads pixel dimensions from the current host element. Video sources carry their size on
|
|
99
45
|
// videoWidth/videoHeight; every other CanvasImageSource exposes width/height directly.
|
|
100
46
|
function updateImageResourceSize(resource) {
|
|
101
47
|
const element = resource.source;
|
|
48
|
+
if (element === null)
|
|
49
|
+
return;
|
|
102
50
|
if (element instanceof HTMLVideoElement) {
|
|
103
51
|
resource.width = element.videoWidth;
|
|
104
52
|
resource.height = element.videoHeight;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imageResource.js","sourceRoot":"","sources":["../src/imageResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"imageResource.js","sourceRoot":"","sources":["../src/imageResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,gCAAgC,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEpG,oGAAoG;AACpG,4FAA4F;AAC5F,MAAM,UAAU,kBAAkB,CAAC,QAAyB;IAC1D,OAAO,YAAY,CAAC;QAClB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,KAAK,EAAE,QAAQ,CAAC,KAAK;KACtB,CAAC,CAAC;AACL,CAAC;AAED,kGAAkG;AAClG,iDAAiD;AACjD,MAAM,UAAU,qBAAqB,CAAC,UAAyC;IAC7E,OAAO,YAAY,CAAC;QAClB,UAAU;QACV,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,MAAM;QACnC,IAAI,EAAE,gCAAgC;QACtC,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,KAAK;KAClC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAwB;IAC1D,MAAM,QAAQ,GAAU,YAAY,CAAC;QACnC,MAAM,EAAE,CAAC;QACT,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC;KACT,CAAC,CAAC;IACH,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,uBAAuB,CAAC,QAAe;IACrD,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAClC,QAAQ,CAAC,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAyB;IAC5D,OAAO,QAAQ,CAAC,KAAK,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,0FAA0F;AAC1F,uFAAuF;AACvF,SAAS,uBAAuB,CAAC,QAAe;IAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC;IAChC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO;IAC7B,IAAI,OAAO,YAAY,gBAAgB,EAAE,CAAC;QACxC,QAAQ,CAAC,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC;QACpC,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IACxC,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,OAA6D,CAAC;QAC5E,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAC7B,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACjC,CAAC;AACH,CAAC"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function createImageResourceFromCanvas(canvas: HTMLCanvasElement):
|
|
4
|
-
export declare function createImageResourceFromImageBitmap(bitmap: ImageBitmap):
|
|
5
|
-
export declare function createImageResourceFromImageElement(img: HTMLImageElement):
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function loadImageResourceFromBase64(base64: string, mimeType: string, signal?: AbortSignal): Promise<
|
|
8
|
-
export declare function loadImageResourceFromBlob(blob: Blob, signal?: AbortSignal): Promise<
|
|
9
|
-
export declare function loadImageResourceFromBytes(bytes: Uint8Array, mimeType?: string, signal?: AbortSignal): Promise<
|
|
10
|
-
export declare function loadImageResourceFromUrl(url: string, crossOrigin?:
|
|
1
|
+
import type { Bitmap, Image } from '@flighthq/types/contract';
|
|
2
|
+
export declare function createImageResourceFromBitmap(bitmap: Readonly<Bitmap>): Image;
|
|
3
|
+
export declare function createImageResourceFromCanvas(canvas: HTMLCanvasElement): Image;
|
|
4
|
+
export declare function createImageResourceFromImageBitmap(bitmap: ImageBitmap): Image;
|
|
5
|
+
export declare function createImageResourceFromImageElement(img: HTMLImageElement): Image;
|
|
6
|
+
export declare function isImageUrlSameOrigin(url: string): boolean;
|
|
7
|
+
export declare function loadImageResourceFromBase64(base64: string, mimeType: string, signal?: AbortSignal): Promise<Image>;
|
|
8
|
+
export declare function loadImageResourceFromBlob(blob: Blob, signal?: AbortSignal): Promise<Image>;
|
|
9
|
+
export declare function loadImageResourceFromBytes(bytes: Uint8Array, mimeType?: string, signal?: AbortSignal): Promise<Image>;
|
|
10
|
+
export declare function loadImageResourceFromUrl(url: string, crossOrigin?: 'anonymous' | 'use-credentials', signal?: AbortSignal): Promise<Image>;
|
|
11
11
|
//# sourceMappingURL=imageResourceFrom.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imageResourceFrom.d.ts","sourceRoot":"","sources":["../src/imageResourceFrom.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"imageResourceFrom.d.ts","sourceRoot":"","sources":["../src/imageResourceFrom.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAO9D,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,KAAK,CAQ7E;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,iBAAiB,GAAG,KAAK,CAQ9E;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAQ7E;AAED,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,gBAAgB,GAAG,KAAK,CAQhF;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,KAAK,CAAC,CAEhB;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,CAOhG;AAED,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,KAAK,CAAC,CAOhB;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,EAC7C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,KAAK,CAAC,CA0BhB"}
|
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
import { createEntity } from '@flighthq/entity';
|
|
2
|
-
import { detectImageMimeType } from '@flighthq/image-codec';
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
export function createCanvasFromImageResource(image) {
|
|
10
|
-
if (image.data === null)
|
|
11
|
-
return null;
|
|
1
|
+
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
+
import { detectImageMimeType } from '@flighthq/image-codec/contract';
|
|
3
|
+
import { ImageTextureSourceKind } from '@flighthq/types/contract';
|
|
4
|
+
// Transcodes a Bitmap's raw pixels into an element-backed Image, via a detached canvas.
|
|
5
|
+
// The inverse of captureBitmapFromImageResource. Lives here rather than in @flighthq/bitmap because a
|
|
6
|
+
// conversion belongs with the type it PRODUCES: you look for it under what you want to end up with.
|
|
7
|
+
// Allocates a fresh canvas on every call; callers that draw repeatedly should hold the result.
|
|
8
|
+
export function createImageResourceFromBitmap(bitmap) {
|
|
12
9
|
const canvas = document.createElement('canvas');
|
|
13
|
-
canvas.width =
|
|
14
|
-
canvas.height =
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
canvas.getContext('2d').putImageData(
|
|
18
|
-
return canvas;
|
|
10
|
+
canvas.width = bitmap.width;
|
|
11
|
+
canvas.height = bitmap.height;
|
|
12
|
+
const domImageData = new globalThis.ImageData(bitmap.width, bitmap.height);
|
|
13
|
+
domImageData.data.set(bitmap.data);
|
|
14
|
+
canvas.getContext('2d').putImageData(domImageData, 0, 0);
|
|
15
|
+
return createImageResourceFromCanvas(canvas);
|
|
19
16
|
}
|
|
20
17
|
export function createImageResourceFromCanvas(canvas) {
|
|
21
18
|
return createEntity({
|
|
22
|
-
alphaType: 'straight',
|
|
23
|
-
compressed: null,
|
|
24
|
-
data: null,
|
|
25
|
-
format: 'rgba8unorm',
|
|
26
19
|
height: canvas.height,
|
|
20
|
+
kind: ImageTextureSourceKind,
|
|
27
21
|
source: canvas,
|
|
28
22
|
version: 0,
|
|
29
23
|
width: canvas.width,
|
|
@@ -31,11 +25,8 @@ export function createImageResourceFromCanvas(canvas) {
|
|
|
31
25
|
}
|
|
32
26
|
export function createImageResourceFromImageBitmap(bitmap) {
|
|
33
27
|
return createEntity({
|
|
34
|
-
alphaType: 'straight',
|
|
35
|
-
compressed: null,
|
|
36
|
-
data: null,
|
|
37
|
-
format: 'rgba8unorm',
|
|
38
28
|
height: bitmap.height,
|
|
29
|
+
kind: ImageTextureSourceKind,
|
|
39
30
|
source: bitmap,
|
|
40
31
|
version: 0,
|
|
41
32
|
width: bitmap.width,
|
|
@@ -43,17 +34,14 @@ export function createImageResourceFromImageBitmap(bitmap) {
|
|
|
43
34
|
}
|
|
44
35
|
export function createImageResourceFromImageElement(img) {
|
|
45
36
|
return createEntity({
|
|
46
|
-
alphaType: 'straight',
|
|
47
|
-
compressed: null,
|
|
48
|
-
data: null,
|
|
49
|
-
format: 'rgba8unorm',
|
|
50
37
|
height: img.height,
|
|
38
|
+
kind: ImageTextureSourceKind,
|
|
51
39
|
source: img,
|
|
52
40
|
version: 0,
|
|
53
41
|
width: img.width,
|
|
54
42
|
});
|
|
55
43
|
}
|
|
56
|
-
export function
|
|
44
|
+
export function isImageUrlSameOrigin(url) {
|
|
57
45
|
if (url.startsWith('data:') || url.startsWith('blob:'))
|
|
58
46
|
return true;
|
|
59
47
|
try {
|
|
@@ -89,12 +77,24 @@ export async function loadImageResourceFromUrl(url, crossOrigin, signal) {
|
|
|
89
77
|
if (crossOrigin !== undefined)
|
|
90
78
|
img.crossOrigin = crossOrigin;
|
|
91
79
|
img.src = url;
|
|
92
|
-
// Wire abort to cancel the pending decode
|
|
80
|
+
// Wire abort to cancel the pending decode and reject with the signal's reason. Always remove the
|
|
81
|
+
// listener when the race settles so a long-lived signal does not retain the image and closure.
|
|
93
82
|
if (signal !== undefined) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
let rejectAbort = () => { };
|
|
84
|
+
const abortPromise = new Promise((_, reject) => {
|
|
85
|
+
rejectAbort = reject;
|
|
86
|
+
});
|
|
87
|
+
const onAbort = () => {
|
|
88
|
+
img.src = '';
|
|
89
|
+
rejectAbort(signal.reason);
|
|
90
|
+
};
|
|
91
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
92
|
+
try {
|
|
93
|
+
await Promise.race([img.decode(), abortPromise]);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
signal.removeEventListener('abort', onAbort);
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
99
|
else {
|
|
100
100
|
await img.decode();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imageResourceFrom.js","sourceRoot":"","sources":["../src/imageResourceFrom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"imageResourceFrom.js","sourceRoot":"","sources":["../src/imageResourceFrom.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './imageResourceFrom';
|
|
1
|
+
export { cloneImageResource, createCompressedImage, createImageResource, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, isImageResourceEmpty, isImageUrlSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, } from './contract';
|
|
3
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,6BAA6B,EAC7B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './imageResourceFrom';
|
|
1
|
+
export { cloneImageResource, createCompressedImage, createImageResource, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, isImageResourceEmpty, isImageUrlSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, } from './contract';
|
|
3
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,6BAA6B,EAC7B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/image",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-edge.1458.0c01b63",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./contract": {
|
|
18
|
+
"types": "./dist/contract.d.ts",
|
|
19
|
+
"default": "./dist/contract.js"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
@@ -32,9 +36,9 @@
|
|
|
32
36
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
33
37
|
},
|
|
34
38
|
"dependencies": {
|
|
35
|
-
"@flighthq/entity": "0.
|
|
36
|
-
"@flighthq/image-codec": "0.
|
|
37
|
-
"@flighthq/types": "0.
|
|
39
|
+
"@flighthq/entity": "0.3.0-edge.1458.0c01b63",
|
|
40
|
+
"@flighthq/image-codec": "0.3.0-edge.1458.0c01b63",
|
|
41
|
+
"@flighthq/types": "0.3.0-edge.1458.0c01b63"
|
|
38
42
|
},
|
|
39
43
|
"devDependencies": {
|
|
40
44
|
"typescript": "^5.3.0"
|
|
@@ -1,234 +1,86 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CompressedImageData } from '@flighthq/types/contract';
|
|
2
|
+
import { CompressedImageTextureSourceKind, ImageTextureSourceKind } from '@flighthq/types/contract';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
cloneImageResource,
|
|
5
|
-
|
|
6
|
+
createCompressedImage,
|
|
6
7
|
createImageResource,
|
|
7
|
-
disposeImageResource,
|
|
8
|
-
getImageResourceByteSize,
|
|
9
|
-
hasImageResourceData,
|
|
10
|
-
hasImageResourcePixels,
|
|
11
|
-
hasImageResourceSource,
|
|
12
8
|
invalidateImageResource,
|
|
13
9
|
isImageResourceEmpty,
|
|
14
|
-
setImageResourceSource,
|
|
15
10
|
} from './imageResource';
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
function makeCompressed(): ImageResourceCompressed {
|
|
12
|
+
function makeCompressed(): CompressedImageData {
|
|
19
13
|
return {
|
|
20
14
|
container: {
|
|
15
|
+
depth: 1,
|
|
16
|
+
faces: 1,
|
|
21
17
|
format: 'bc3',
|
|
22
|
-
width: 4,
|
|
23
18
|
height: 4,
|
|
24
|
-
depth: 1,
|
|
25
|
-
mipLevels: 1,
|
|
26
19
|
layers: 1,
|
|
27
|
-
|
|
20
|
+
levels: [{ byteLength: 16, byteOffset: 0, height: 4, width: 4 }],
|
|
21
|
+
mipLevels: 1,
|
|
28
22
|
supercompression: 'None',
|
|
29
|
-
|
|
23
|
+
width: 4,
|
|
30
24
|
},
|
|
31
25
|
payload: new Uint8Array(16),
|
|
32
26
|
};
|
|
33
27
|
}
|
|
34
28
|
|
|
35
29
|
describe('cloneImageResource', () => {
|
|
36
|
-
it('
|
|
37
|
-
const
|
|
38
|
-
|
|
30
|
+
it('shares the host source under an independent identity and version', () => {
|
|
31
|
+
const source = document.createElement('canvas');
|
|
32
|
+
source.width = 4;
|
|
33
|
+
source.height = 5;
|
|
34
|
+
const resource = createImageResource(source);
|
|
39
35
|
resource.version = 3;
|
|
40
|
-
|
|
41
36
|
const copy = cloneImageResource(resource);
|
|
42
|
-
|
|
43
37
|
expect(copy).not.toBe(resource);
|
|
44
|
-
expect(copy.source).toBe(
|
|
45
|
-
expect(copy.
|
|
46
|
-
expect(copy.height).toStrictEqual(5);
|
|
47
|
-
expect(copy.version).toStrictEqual(3);
|
|
48
|
-
|
|
38
|
+
expect(copy.source).toBe(source);
|
|
39
|
+
expect(copy.version).toBe(3);
|
|
49
40
|
copy.version++;
|
|
50
|
-
expect(resource.version).
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('shares the data buffer by reference and copies the format and alphaType', () => {
|
|
54
|
-
const resource = createImageResource();
|
|
55
|
-
resource.data = new Uint8ClampedArray([1, 2, 3, 4]);
|
|
56
|
-
resource.format = 'bgra8unorm';
|
|
57
|
-
resource.alphaType = 'premultiplied';
|
|
58
|
-
|
|
59
|
-
const copy = cloneImageResource(resource);
|
|
60
|
-
|
|
61
|
-
expect(copy.data).toBe(resource.data);
|
|
62
|
-
expect(copy.format).toStrictEqual('bgra8unorm');
|
|
63
|
-
expect(copy.alphaType).toStrictEqual('premultiplied');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('shares the compressed payload by reference', () => {
|
|
67
|
-
const resource = createCompressedImageResource(makeCompressed());
|
|
68
|
-
const copy = cloneImageResource(resource);
|
|
69
|
-
expect(copy.compressed).toBe(resource.compressed);
|
|
41
|
+
expect(resource.version).toBe(3);
|
|
70
42
|
});
|
|
71
43
|
});
|
|
72
44
|
|
|
73
|
-
describe('
|
|
74
|
-
it('wraps the
|
|
45
|
+
describe('createCompressedImage', () => {
|
|
46
|
+
it('wraps the compressed payload as a distinct source', () => {
|
|
75
47
|
const compressed = makeCompressed();
|
|
76
|
-
const
|
|
77
|
-
expect(
|
|
78
|
-
expect(
|
|
79
|
-
expect(
|
|
80
|
-
expect(
|
|
81
|
-
expect(resource.data).toBeNull();
|
|
82
|
-
expect(resource.version).toStrictEqual(0);
|
|
48
|
+
const image = createCompressedImage(compressed);
|
|
49
|
+
expect(image.kind).toBe(CompressedImageTextureSourceKind);
|
|
50
|
+
expect(image.compressed).toBe(compressed);
|
|
51
|
+
expect(image.width).toBe(4);
|
|
52
|
+
expect(image.height).toBe(4);
|
|
83
53
|
});
|
|
84
54
|
});
|
|
85
55
|
|
|
86
56
|
describe('createImageResource', () => {
|
|
87
|
-
it('
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
expect(resource.
|
|
96
|
-
expect(resource.width).toStrictEqual(element.width);
|
|
97
|
-
expect(resource.height).toStrictEqual(element.height);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('starts at version 0', () => {
|
|
101
|
-
expect(createImageResource().version).toBe(0);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('defaults to null data, rgba8unorm format, and straight alphaType', () => {
|
|
105
|
-
const resource = createImageResource();
|
|
106
|
-
expect(resource.data).toBeNull();
|
|
107
|
-
expect(resource.format).toStrictEqual('rgba8unorm');
|
|
108
|
-
expect(resource.alphaType).toStrictEqual('straight');
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('disposeImageResource', () => {
|
|
113
|
-
it('releases the element, data, and compressed payload and marks the resource changed', () => {
|
|
114
|
-
const resource = createImageResource({ width: 4, height: 5 } as HTMLImageElement);
|
|
115
|
-
resource.data = new Uint8ClampedArray(4 * 5 * 4);
|
|
116
|
-
resource.compressed = makeCompressed();
|
|
117
|
-
const before = resource.version;
|
|
118
|
-
|
|
119
|
-
disposeImageResource(resource);
|
|
120
|
-
|
|
121
|
-
expect(resource.source).toBeNull();
|
|
122
|
-
expect(resource.data).toBeNull();
|
|
123
|
-
expect(resource.compressed).toBeNull();
|
|
124
|
-
expect(resource.version).toStrictEqual(before + 1);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('getImageResourceByteSize', () => {
|
|
129
|
-
it('returns 0 when data is null (element-only resource)', () => {
|
|
130
|
-
const resource = createImageResource();
|
|
131
|
-
expect(getImageResourceByteSize(resource)).toBe(0);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('returns the byteLength of the data array when present', () => {
|
|
135
|
-
const resource = createImageResource();
|
|
136
|
-
resource.data = new Uint8ClampedArray(100);
|
|
137
|
-
expect(getImageResourceByteSize(resource)).toBe(100);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('reflects width × height × 4 for a typical rgba8unorm resource', () => {
|
|
141
|
-
const resource = createImageResource();
|
|
142
|
-
resource.width = 4;
|
|
143
|
-
resource.height = 4;
|
|
144
|
-
resource.data = new Uint8ClampedArray(4 * 4 * 4);
|
|
145
|
-
expect(getImageResourceByteSize(resource)).toBe(64);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('hasImageResourceData', () => {
|
|
150
|
-
it('is false without data and true with it', () => {
|
|
151
|
-
const resource = createImageResource();
|
|
152
|
-
expect(hasImageResourceData(resource)).toStrictEqual(false);
|
|
153
|
-
resource.data = new Uint8ClampedArray(4);
|
|
154
|
-
expect(hasImageResourceData(resource)).toStrictEqual(true);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe('hasImageResourcePixels', () => {
|
|
159
|
-
it('is false when neither representation is present', () => {
|
|
160
|
-
expect(hasImageResourcePixels(createImageResource())).toStrictEqual(false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('is true for a data-only resource (a generated Surface)', () => {
|
|
164
|
-
const resource = createImageResource();
|
|
165
|
-
resource.data = new Uint8ClampedArray(4);
|
|
166
|
-
expect(hasImageResourcePixels(resource)).toStrictEqual(true);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('is true for an element-only resource', () => {
|
|
170
|
-
expect(hasImageResourcePixels(createImageResource({ width: 1, height: 1 } as HTMLImageElement))).toStrictEqual(
|
|
171
|
-
true,
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('is true for a compressed-only resource (a parsed container)', () => {
|
|
176
|
-
expect(hasImageResourcePixels(createCompressedImageResource(makeCompressed()))).toStrictEqual(true);
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('hasImageResourceSource', () => {
|
|
181
|
-
it('is false without an element and true with one', () => {
|
|
182
|
-
expect(hasImageResourceSource(createImageResource())).toStrictEqual(false);
|
|
183
|
-
expect(hasImageResourceSource(createImageResource({ width: 1, height: 1 } as HTMLImageElement))).toStrictEqual(
|
|
184
|
-
true,
|
|
185
|
-
);
|
|
57
|
+
it('wraps one host source with its dimensions and declared kind', () => {
|
|
58
|
+
const source = document.createElement('canvas');
|
|
59
|
+
source.width = 8;
|
|
60
|
+
source.height = 6;
|
|
61
|
+
const resource = createImageResource(source);
|
|
62
|
+
expect(resource.kind).toBe(ImageTextureSourceKind);
|
|
63
|
+
expect(resource.source).toBe(source);
|
|
64
|
+
expect(resource.width).toBe(8);
|
|
65
|
+
expect(resource.height).toBe(6);
|
|
186
66
|
});
|
|
187
67
|
});
|
|
188
68
|
|
|
189
69
|
describe('invalidateImageResource', () => {
|
|
190
|
-
it('
|
|
191
|
-
const
|
|
70
|
+
it('refreshes host dimensions and advances the version', () => {
|
|
71
|
+
const source = document.createElement('canvas');
|
|
72
|
+
const resource = createImageResource(source);
|
|
73
|
+
source.width = 12;
|
|
74
|
+
source.height = 9;
|
|
192
75
|
invalidateImageResource(resource);
|
|
76
|
+
expect(resource.width).toBe(12);
|
|
77
|
+
expect(resource.height).toBe(9);
|
|
193
78
|
expect(resource.version).toBe(1);
|
|
194
|
-
invalidateImageResource(resource);
|
|
195
|
-
expect(resource.version).toBe(2);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('wraps around with >>> 0', () => {
|
|
199
|
-
const resource = createImageResource();
|
|
200
|
-
resource.version = 0xffffffff;
|
|
201
|
-
invalidateImageResource(resource);
|
|
202
|
-
expect(resource.version).toBe(0);
|
|
203
79
|
});
|
|
204
80
|
});
|
|
205
81
|
|
|
206
82
|
describe('isImageResourceEmpty', () => {
|
|
207
|
-
it('
|
|
208
|
-
expect(isImageResourceEmpty(createImageResource())).
|
|
209
|
-
expect(isImageResourceEmpty(createImageResource({ width: 2, height: 2 } as HTMLImageElement))).toStrictEqual(false);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe('setImageResourceSource', () => {
|
|
214
|
-
it('swaps the element, re-reads dimensions, and marks the resource changed', () => {
|
|
215
|
-
const resource = createImageResource();
|
|
216
|
-
const before = resource.version;
|
|
217
|
-
const element = { width: 8, height: 6 } as HTMLImageElement;
|
|
218
|
-
|
|
219
|
-
setImageResourceSource(resource, element);
|
|
220
|
-
|
|
221
|
-
expect(resource.source).toBe(element);
|
|
222
|
-
expect(resource.width).toStrictEqual(8);
|
|
223
|
-
expect(resource.height).toStrictEqual(6);
|
|
224
|
-
expect(resource.version).toStrictEqual(before + 1);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('clears the element when passed null', () => {
|
|
228
|
-
const resource = createImageResource({ width: 8, height: 6 } as HTMLImageElement);
|
|
229
|
-
|
|
230
|
-
setImageResourceSource(resource, null);
|
|
231
|
-
|
|
232
|
-
expect(resource.source).toBeNull();
|
|
83
|
+
it('reports a zero-sized host image', () => {
|
|
84
|
+
expect(isImageResourceEmpty(createImageResource(globalThis.document.createElement('img')))).toBe(true);
|
|
233
85
|
});
|
|
234
86
|
});
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { createEntity } from '@flighthq/entity/contract';
|
|
2
|
+
import type { Bitmap } from '@flighthq/types/contract';
|
|
3
|
+
import { BitmapTextureSourceKind } from '@flighthq/types/contract';
|
|
4
|
+
|
|
1
5
|
import { createImageResource } from './imageResource';
|
|
2
6
|
import {
|
|
3
|
-
|
|
7
|
+
createImageResourceFromBitmap,
|
|
4
8
|
createImageResourceFromCanvas,
|
|
5
9
|
createImageResourceFromImageBitmap,
|
|
6
10
|
createImageResourceFromImageElement,
|
|
7
|
-
|
|
11
|
+
isImageUrlSameOrigin,
|
|
8
12
|
loadImageResourceFromBase64,
|
|
9
13
|
loadImageResourceFromBlob,
|
|
10
14
|
loadImageResourceFromBytes,
|
|
@@ -21,29 +25,24 @@ afterEach(() => {
|
|
|
21
25
|
delete (HTMLImageElement.prototype as Partial<HTMLImageElement>).decode;
|
|
22
26
|
});
|
|
23
27
|
|
|
24
|
-
describe('
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
expect(createCanvasFromImageResource(resource)).not.toBe(createCanvasFromImageResource(resource));
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('returns null for an element-only resource with no data', () => {
|
|
46
|
-
expect(createCanvasFromImageResource(createImageResource(document.createElement('img')))).toBeNull();
|
|
28
|
+
describe('createImageResourceFromBitmap', () => {
|
|
29
|
+
it('returns an Image with matching dimensions', () => {
|
|
30
|
+
// Built with createEntity rather than @flighthq/bitmap's createBitmap: bitmap depends on image,
|
|
31
|
+
// so importing it here would force a circular tsconfig project reference.
|
|
32
|
+
const bitmap: Bitmap = createEntity({
|
|
33
|
+
alphaType: 'straight',
|
|
34
|
+
colorSpace: 'srgb',
|
|
35
|
+
data: new Uint8ClampedArray(4 * 4 * 4),
|
|
36
|
+
format: 'rgba8unorm',
|
|
37
|
+
height: 4,
|
|
38
|
+
kind: BitmapTextureSourceKind,
|
|
39
|
+
version: 0,
|
|
40
|
+
width: 4,
|
|
41
|
+
});
|
|
42
|
+
const resource = createImageResourceFromBitmap(bitmap);
|
|
43
|
+
expect(resource.width).toBe(4);
|
|
44
|
+
expect(resource.height).toBe(4);
|
|
45
|
+
expect(resource.source).not.toBeNull();
|
|
47
46
|
});
|
|
48
47
|
});
|
|
49
48
|
|
|
@@ -115,26 +114,26 @@ describe('createImageResourceFromImageElement', () => {
|
|
|
115
114
|
});
|
|
116
115
|
});
|
|
117
116
|
|
|
118
|
-
describe('
|
|
117
|
+
describe('isImageUrlSameOrigin', () => {
|
|
119
118
|
it('returns true for data: URLs', () => {
|
|
120
|
-
expect(
|
|
119
|
+
expect(isImageUrlSameOrigin('data:image/png;base64,abc')).toBe(true);
|
|
121
120
|
});
|
|
122
121
|
|
|
123
122
|
it('returns true for blob: URLs', () => {
|
|
124
|
-
expect(
|
|
123
|
+
expect(isImageUrlSameOrigin('blob:http://localhost/some-id')).toBe(true);
|
|
125
124
|
});
|
|
126
125
|
|
|
127
126
|
it('returns true for relative URLs (same origin)', () => {
|
|
128
|
-
expect(
|
|
127
|
+
expect(isImageUrlSameOrigin('/images/logo.png')).toBe(true);
|
|
129
128
|
});
|
|
130
129
|
|
|
131
130
|
it('returns false for a different-origin absolute URL', () => {
|
|
132
|
-
expect(
|
|
131
|
+
expect(isImageUrlSameOrigin('https://cdn.other-domain.com/image.png')).toBe(false);
|
|
133
132
|
});
|
|
134
133
|
});
|
|
135
134
|
|
|
136
135
|
describe('loadImageResourceFromBase64', () => {
|
|
137
|
-
it('resolves to an
|
|
136
|
+
it('resolves to an Image', async () => {
|
|
138
137
|
const resource = await loadImageResourceFromBase64('abc123', 'image/png');
|
|
139
138
|
expect(resource).not.toBeNull();
|
|
140
139
|
expect(resource.source).toBeInstanceOf(HTMLImageElement);
|
|
@@ -147,7 +146,7 @@ describe('loadImageResourceFromBase64', () => {
|
|
|
147
146
|
});
|
|
148
147
|
|
|
149
148
|
describe('loadImageResourceFromBlob', () => {
|
|
150
|
-
it('resolves to an
|
|
149
|
+
it('resolves to an Image', async () => {
|
|
151
150
|
const blob = new Blob([], { type: 'image/png' });
|
|
152
151
|
const resource = await loadImageResourceFromBlob(blob);
|
|
153
152
|
expect(resource).not.toBeNull();
|
|
@@ -221,12 +220,47 @@ describe('loadImageResourceFromUrl', () => {
|
|
|
221
220
|
);
|
|
222
221
|
});
|
|
223
222
|
|
|
224
|
-
it('
|
|
223
|
+
it('clears the image source when aborted during decode', async () => {
|
|
224
|
+
let capturedImg: HTMLImageElement | undefined;
|
|
225
|
+
const origImage = globalThis.Image;
|
|
226
|
+
globalThis.Image = new Proxy(origImage, {
|
|
227
|
+
construct(Target, args) {
|
|
228
|
+
const img = new Target(...(args as []));
|
|
229
|
+
capturedImg = img;
|
|
230
|
+
return img;
|
|
231
|
+
},
|
|
232
|
+
}) as typeof Image;
|
|
233
|
+
HTMLImageElement.prototype.decode = vi.fn().mockReturnValue(new Promise(() => {}));
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const promise = loadImageResourceFromUrl('/images/slow.png', undefined, controller.signal);
|
|
238
|
+
controller.abort(new Error('cancelled'));
|
|
239
|
+
await expect(promise).rejects.toThrow('cancelled');
|
|
240
|
+
expect(capturedImg?.getAttribute('src')).toBe('');
|
|
241
|
+
} finally {
|
|
242
|
+
globalThis.Image = origImage;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('removes its abort listener after decode succeeds', async () => {
|
|
247
|
+
const controller = new AbortController();
|
|
248
|
+
const add = vi.spyOn(controller.signal, 'addEventListener');
|
|
249
|
+
const remove = vi.spyOn(controller.signal, 'removeEventListener');
|
|
250
|
+
|
|
251
|
+
await loadImageResourceFromUrl('/images/logo.png', undefined, controller.signal);
|
|
252
|
+
|
|
253
|
+
expect(add).toHaveBeenCalledOnce();
|
|
254
|
+
expect(remove).toHaveBeenCalledOnce();
|
|
255
|
+
expect(remove.mock.calls[0][1]).toBe(add.mock.calls[0][1]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('resolves to an Image whose source is an HTMLImageElement', async () => {
|
|
225
259
|
const resource = await loadImageResourceFromUrl('data:image/png;base64,abc');
|
|
226
260
|
expect(resource.source).toBeInstanceOf(HTMLImageElement);
|
|
227
261
|
});
|
|
228
262
|
|
|
229
|
-
it('
|
|
263
|
+
it.each(['anonymous', 'use-credentials'] as const)('sets crossOrigin to %s when provided', async (crossOrigin) => {
|
|
230
264
|
let capturedImg: HTMLImageElement | undefined;
|
|
231
265
|
const origImage = globalThis.Image;
|
|
232
266
|
globalThis.Image = new Proxy(origImage, {
|
|
@@ -237,8 +271,8 @@ describe('loadImageResourceFromUrl', () => {
|
|
|
237
271
|
},
|
|
238
272
|
}) as typeof Image;
|
|
239
273
|
|
|
240
|
-
await loadImageResourceFromUrl('https://cdn.other-domain.com/image.png',
|
|
241
|
-
expect(capturedImg?.crossOrigin).toBe(
|
|
274
|
+
await loadImageResourceFromUrl('https://cdn.other-domain.com/image.png', crossOrigin);
|
|
275
|
+
expect(capturedImg?.crossOrigin).toBe(crossOrigin);
|
|
242
276
|
|
|
243
277
|
globalThis.Image = origImage;
|
|
244
278
|
});
|