@flighthq/image 0.3.0-next.906.07cea63 → 0.3.1-next.209.43ef1bc

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.
@@ -1,3 +1,4 @@
1
1
  export * from './imageResource';
2
2
  export * from './imageResourceFrom';
3
+ export * from './imageResourceReference';
3
4
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
package/dist/contract.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './imageResource';
2
2
  export * from './imageResourceFrom';
3
+ export * from './imageResourceReference';
3
4
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
@@ -1,13 +1,7 @@
1
- import type { ImageResource, ImageResourceCompressed } from '@flighthq/types/contract';
2
- export declare function cloneImageResource(resource: Readonly<ImageResource>): ImageResource;
3
- export declare function createCompressedImageResource(compressed: Readonly<ImageResourceCompressed>): ImageResource;
4
- export declare function createImageResource(image?: CanvasImageSource): ImageResource;
5
- export declare function disposeImageResource(resource: ImageResource): void;
6
- export declare function getImageResourceByteSize(resource: Readonly<ImageResource>): number;
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,aAAa,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAMvF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,aAAa,CAWnF;AAMD,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,QAAQ,CAAC,uBAAuB,CAAC,GAAG,aAAa,CAW1G;AAED,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,iBAAiB,GAAG,aAAa,CAa5E;AAMD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAKlE;AAMD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,CAElF;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,OAAO,CAE/E;AAQD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,OAAO,CAEjF;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,OAAO,CAEjF;AAKD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAErE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,OAAO,CAE/E;AAID,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAIvG"}
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,CAUnE;AAID,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GAAG,eAAe,CAUhG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CAYnE;AAGD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,KAAK,GAAG,IAAI,CAG7D;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAEvE"}
@@ -1,104 +1,58 @@
1
1
  import { createEntity } from '@flighthq/entity/contract';
2
- // Allocates a new resource identity over the SAME underlying pixels. The element, the `data` array, and
3
- // the `compressed` payload are shared by reference, not duplicated clone gives you an independent
4
- // version counter and entity identity over the same pixels, e.g. to upload one image into two render
5
- // states with separate invalidation. Use a Surface copy when you need the pixels themselves duplicated.
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
7
  alphaType: resource.alphaType,
9
- compressed: resource.compressed,
10
- data: resource.data,
11
- format: resource.format,
8
+ gamut: resource.gamut,
12
9
  height: resource.height,
10
+ kind: resource.kind,
13
11
  source: resource.source,
14
12
  version: resource.version,
15
13
  width: resource.width,
16
14
  });
17
15
  }
18
- // Wraps a parsed block-compressed (KTX2/DDS/Basis) payload as an ImageResource. `width`/`height` mirror
19
- // the container's base-mip dimensions so the same 2D draw path sizes the quad; `source`/`data` stay null
20
- // (a GPU backend uploads the `compressed` payload instead, and a Canvas/DOM backend ignores it). The
21
- // caller owns the payload bytes the container's level ranges index into.
22
- export function createCompressedImageResource(compressed) {
16
+ // Wraps a parsed block-compressed payload as its own GPU-only source. The caller owns the payload
17
+ // bytes indexed by the container's level ranges.
18
+ export function createCompressedImage(compressed) {
23
19
  return createEntity({
24
- alphaType: 'straight',
20
+ alphaType: DECODED_ALPHA_TYPE,
25
21
  compressed,
26
- data: null,
27
- format: 'rgba8unorm',
22
+ gamut: DECODED_GAMUT,
28
23
  height: compressed.container.height,
29
- source: null,
24
+ kind: CompressedImageTextureSourceKind,
30
25
  version: 0,
31
26
  width: compressed.container.width,
32
27
  });
33
28
  }
34
29
  export function createImageResource(image) {
35
30
  const resource = createEntity({
36
- alphaType: 'straight',
37
- compressed: null,
38
- data: null,
39
- format: 'rgba8unorm',
31
+ alphaType: DECODED_ALPHA_TYPE,
32
+ gamut: DECODED_GAMUT,
40
33
  height: 0,
41
- source: image ?? null,
34
+ kind: ImageTextureSourceKind,
35
+ source: image,
42
36
  version: 0,
43
37
  width: 0,
44
38
  });
45
- if (resource.source !== null)
46
- updateImageResourceSize(resource);
39
+ updateImageResourceSize(resource);
47
40
  return resource;
48
41
  }
49
- // Releases the element and raw-data references so they become eligible for GC, and marks the resource
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.
42
+ // Marks changed pixels behind the same borrowed host handle. The handle itself remains immutable.
84
43
  export function invalidateImageResource(resource) {
44
+ updateImageResourceSize(resource);
85
45
  resource.version = (resource.version + 1) >>> 0;
86
46
  }
87
47
  export function isImageResourceEmpty(resource) {
88
48
  return resource.width <= 0 || resource.height <= 0;
89
49
  }
90
- // Swaps the element representation, re-reads its dimensions, and marks the resource changed. Pass
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
50
+ // Reads pixel dimensions from the current host element. Video sources carry their size on
99
51
  // videoWidth/videoHeight; every other CanvasImageSource exposes width/height directly.
100
52
  function updateImageResourceSize(resource) {
101
53
  const element = resource.source;
54
+ if (element === null)
55
+ return;
102
56
  if (element instanceof HTMLVideoElement) {
103
57
  resource.width = element.videoWidth;
104
58
  resource.height = element.videoHeight;
@@ -109,4 +63,11 @@ function updateImageResourceSize(resource) {
109
63
  resource.height = sized.height;
110
64
  }
111
65
  }
66
+ // What a HOST decode yields, and the only honest default for a source whose pixels we never touch.
67
+ // Every browser image/canvas/ImageBitmap decode is straight-alpha sRGB, so declaring it is a statement
68
+ // of fact rather than an assumption — which is the point: a producer that knows otherwise (a native
69
+ // iOS/Android decode commonly premultiplies) now has somewhere to say so, and the uploaders' existing
70
+ // `alphaType !== 'premultiplied'` guard starts protecting Image the way it already protects Bitmap.
71
+ const DECODED_ALPHA_TYPE = 'straight';
72
+ const DECODED_GAMUT = 'srgb';
112
73
  //# sourceMappingURL=imageResource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"imageResource.js","sourceRoot":"","sources":["../src/imageResource.ts"],"names":[],"mappings":"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"}
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,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,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,SAAS,EAAE,kBAAkB;QAC7B,UAAU;QACV,KAAK,EAAE,aAAa;QACpB,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,SAAS,EAAE,kBAAkB;QAC7B,KAAK,EAAE,aAAa;QACpB,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;AAED,mGAAmG;AACnG,uGAAuG;AACvG,oGAAoG;AACpG,sGAAsG;AACtG,oGAAoG;AACpG,MAAM,kBAAkB,GAAG,UAAU,CAAC;AACtC,MAAM,aAAa,GAAG,MAAM,CAAC"}
@@ -1,11 +1,11 @@
1
- import type { ImageResource } from '@flighthq/types/contract';
2
- export declare function createCanvasFromImageResource(image: Readonly<ImageResource>): HTMLCanvasElement | null;
3
- export declare function createImageResourceFromCanvas(canvas: HTMLCanvasElement): ImageResource;
4
- export declare function createImageResourceFromImageBitmap(bitmap: ImageBitmap): ImageResource;
5
- export declare function createImageResourceFromImageElement(img: HTMLImageElement): ImageResource;
6
- export declare function isImageResourceSameOrigin(url: string): boolean;
7
- export declare function loadImageResourceFromBase64(base64: string, mimeType: string, signal?: AbortSignal): Promise<ImageResource>;
8
- export declare function loadImageResourceFromBlob(blob: Blob, signal?: AbortSignal): Promise<ImageResource>;
9
- export declare function loadImageResourceFromBytes(bytes: Uint8Array, mimeType?: string, signal?: AbortSignal): Promise<ImageResource>;
10
- export declare function loadImageResourceFromUrl(url: string, crossOrigin?: string, signal?: AbortSignal): Promise<ImageResource>;
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,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAQ9D,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,iBAAiB,GAAG,IAAI,CAStG;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,iBAAiB,GAAG,aAAa,CAWtF;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAWrF;AAED,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,gBAAgB,GAAG,aAAa,CAWxF;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO9D;AAED,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAExB;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAOxG;AAED,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAOxB;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAexB"}
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;AAsBD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,iBAAiB,GAAG,KAAK,CAU9E;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAU7E;AAED,wBAAgB,mCAAmC,CAAC,GAAG,EAAE,gBAAgB,GAAG,KAAK,CAUhF;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,44 @@
1
1
  import { createEntity } from '@flighthq/entity/contract';
2
2
  import { detectImageMimeType } from '@flighthq/image-codec/contract';
3
- // Materializes a data-backed ImageResource's raw pixels into a detached, drawable HTMLCanvasElement
4
- // via putImageData. The inverse of createImageResourceFromCanvas: it turns the portable `data`
5
- // representation of a generated Surface into the host element that Canvas/DOM `drawImage` needs.
6
- // Returns null for an element-only resource (no `data` to transcode). Straight-alpha in, straight-alpha
7
- // out the drawn canvas matches the source's `alphaType`. Callers that draw data-only resources cache
8
- // the result keyed on `version`; this primitive allocates a fresh canvas on every call.
9
- export function createCanvasFromImageResource(image) {
10
- if (image.data === null)
11
- return null;
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 = image.width;
14
- canvas.height = image.height;
15
- const imageData = new globalThis.ImageData(image.width, image.height);
16
- imageData.data.set(image.data);
17
- canvas.getContext('2d').putImageData(imageData, 0, 0);
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.alphaType === 'premultiplied' ? unpremultiplyRgba8(bitmap.data) : bitmap.data);
14
+ canvas.getContext('2d').putImageData(domImageData, 0, 0);
15
+ return createImageResourceFromCanvas(canvas);
16
+ }
17
+ // ImageData is straight-alpha by contract. Preserve a Bitmap's declared representation at its own seam,
18
+ // then normalize only the scratch browser copy used to materialize a CanvasImageSource for Canvas/DOM.
19
+ function unpremultiplyRgba8(source) {
20
+ const data = new Uint8ClampedArray(source);
21
+ for (let i = 0; i < data.length; i += 4) {
22
+ const alpha = data[i + 3];
23
+ if (alpha === 0) {
24
+ data[i] = 0;
25
+ data[i + 1] = 0;
26
+ data[i + 2] = 0;
27
+ continue;
28
+ }
29
+ const scale = 255 / alpha;
30
+ data[i] = Math.min(255, Math.round(data[i] * scale));
31
+ data[i + 1] = Math.min(255, Math.round(data[i + 1] * scale));
32
+ data[i + 2] = Math.min(255, Math.round(data[i + 2] * scale));
33
+ }
34
+ return data;
19
35
  }
20
36
  export function createImageResourceFromCanvas(canvas) {
21
37
  return createEntity({
22
- alphaType: 'straight',
23
- compressed: null,
24
- data: null,
25
- format: 'rgba8unorm',
38
+ alphaType: DECODED_ALPHA_TYPE,
39
+ gamut: DECODED_GAMUT,
26
40
  height: canvas.height,
41
+ kind: ImageTextureSourceKind,
27
42
  source: canvas,
28
43
  version: 0,
29
44
  width: canvas.width,
@@ -31,11 +46,10 @@ export function createImageResourceFromCanvas(canvas) {
31
46
  }
32
47
  export function createImageResourceFromImageBitmap(bitmap) {
33
48
  return createEntity({
34
- alphaType: 'straight',
35
- compressed: null,
36
- data: null,
37
- format: 'rgba8unorm',
49
+ alphaType: DECODED_ALPHA_TYPE,
50
+ gamut: DECODED_GAMUT,
38
51
  height: bitmap.height,
52
+ kind: ImageTextureSourceKind,
39
53
  source: bitmap,
40
54
  version: 0,
41
55
  width: bitmap.width,
@@ -43,17 +57,16 @@ export function createImageResourceFromImageBitmap(bitmap) {
43
57
  }
44
58
  export function createImageResourceFromImageElement(img) {
45
59
  return createEntity({
46
- alphaType: 'straight',
47
- compressed: null,
48
- data: null,
49
- format: 'rgba8unorm',
60
+ alphaType: DECODED_ALPHA_TYPE,
61
+ gamut: DECODED_GAMUT,
50
62
  height: img.height,
63
+ kind: ImageTextureSourceKind,
51
64
  source: img,
52
65
  version: 0,
53
66
  width: img.width,
54
67
  });
55
68
  }
56
- export function isImageResourceSameOrigin(url) {
69
+ export function isImageUrlSameOrigin(url) {
57
70
  if (url.startsWith('data:') || url.startsWith('blob:'))
58
71
  return true;
59
72
  try {
@@ -89,16 +102,31 @@ export async function loadImageResourceFromUrl(url, crossOrigin, signal) {
89
102
  if (crossOrigin !== undefined)
90
103
  img.crossOrigin = crossOrigin;
91
104
  img.src = url;
92
- // Wire abort to cancel the pending decode by rejecting the promise.
105
+ // Wire abort to cancel the pending decode and reject with the signal's reason. Always remove the
106
+ // listener when the race settles so a long-lived signal does not retain the image and closure.
93
107
  if (signal !== undefined) {
94
- await Promise.race([
95
- img.decode(),
96
- new Promise((_, reject) => signal.addEventListener('abort', () => reject(signal.reason), { once: true })),
97
- ]);
108
+ let rejectAbort = () => { };
109
+ const abortPromise = new Promise((_, reject) => {
110
+ rejectAbort = reject;
111
+ });
112
+ const onAbort = () => {
113
+ img.src = '';
114
+ rejectAbort(signal.reason);
115
+ };
116
+ signal.addEventListener('abort', onAbort, { once: true });
117
+ try {
118
+ await Promise.race([img.decode(), abortPromise]);
119
+ }
120
+ finally {
121
+ signal.removeEventListener('abort', onAbort);
122
+ }
98
123
  }
99
124
  else {
100
125
  await img.decode();
101
126
  }
102
127
  return createImageResourceFromImageElement(img);
103
128
  }
129
+ // The host-decode truth for every browser-backed source; see imageResource.ts.
130
+ const DECODED_ALPHA_TYPE = 'straight';
131
+ const DECODED_GAMUT = 'srgb';
104
132
  //# sourceMappingURL=imageResourceFrom.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"imageResourceFrom.js","sourceRoot":"","sources":["../src/imageResourceFrom.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"imageResourceFrom.js","sourceRoot":"","sources":["../src/imageResourceFrom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { AlphaType, EmbeddedImageResourceReference, ExternalImageResourceReference, ImageResourceFailure, ImageResourceFetch, ImageResourceReference, ImageResourceReferenceResolutionExplanation, TextureSource } from '@flighthq/types/contract';
2
+ export declare function createEmbeddedImageResourceReference(bytes: Uint8Array, mimeType?: string | null, alphaType?: AlphaType): EmbeddedImageResourceReference;
3
+ export declare function createExternalImageResourceReference(uri: string, basePath?: string | null): ExternalImageResourceReference;
4
+ export declare function createImageResourceFailure(cause: unknown): ImageResourceFailure;
5
+ export declare function explainImageResourceReferenceResolution(ref: Readonly<ImageResourceReference>): ImageResourceReferenceResolutionExplanation;
6
+ export declare function resetFailedImageResourceReference(ref: ImageResourceReference): boolean;
7
+ export declare function resolveImageResourceReference(ref: ImageResourceReference, fetch: ImageResourceFetch, signal: AbortSignal): Promise<TextureSource | null>;
8
+ //# sourceMappingURL=imageResourceReference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageResourceReference.d.ts","sourceRoot":"","sources":["../src/imageResourceReference.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EAET,8BAA8B,EAE9B,8BAA8B,EAC9B,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,2CAA2C,EAC3C,aAAa,EACd,MAAM,0BAA0B,CAAC;AAWlC,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,UAAU,EACjB,QAAQ,GAAE,MAAM,GAAG,IAAW,EAC9B,SAAS,GAAE,SAAsB,GAChC,8BAA8B,CAUhC;AA6BD,wBAAgB,oCAAoC,CAClD,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,GAAG,IAAW,GAC7B,8BAA8B,CAUhC;AAID,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAK/E;AAID,wBAAgB,uCAAuC,CACrD,GAAG,EAAE,QAAQ,CAAC,sBAAsB,CAAC,GACpC,2CAA2C,CAO7C;AAID,wBAAgB,iCAAiC,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAKtF;AAUD,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,sBAAsB,EAC3B,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAwB/B"}
@@ -0,0 +1,113 @@
1
+ import { createEntity } from '@flighthq/entity/contract';
2
+ import { decodeImage, decodeImagePremultiplied } from '@flighthq/image-codec/contract';
3
+ import { BitmapTextureSourceKind, ImageResourceFailureKind, ImageResourceReferenceKind, ResourceResolutionState, } from '@flighthq/types/contract';
4
+ // `bytes` is retained as the view the container handed over, not a copy: a parser carves an image payload
5
+ // out of its source and the reference borrows it, so a document that never resolves an image never pays for
6
+ // its pixels. `textures` starts empty and each waiting Texture subscribes itself.
7
+ export function createEmbeddedImageResourceReference(bytes, mimeType = null, alphaType = 'straight') {
8
+ return {
9
+ alphaType,
10
+ bytes,
11
+ failure: null,
12
+ kind: ImageResourceReferenceKind.Embedded,
13
+ mimeType,
14
+ state: ResourceResolutionState.Unresolved,
15
+ textures: [],
16
+ };
17
+ }
18
+ // The one encoded-byte producer for image resource references. A registered decoder owns format
19
+ // knowledge; this async resource layer turns its RGBA result into the live Bitmap entity renderers
20
+ // dispatch by kind. The reference's alpha request is carried onto the source verbatim, so bytes that
21
+ // are already premultiplied never get multiplied a second time at renderer upload.
22
+ async function decodeEmbeddedImageResourceReference(ref, signal) {
23
+ signal.throwIfAborted();
24
+ const decoded = await (ref.alphaType === 'premultiplied'
25
+ ? decodeImagePremultiplied(ref.bytes, ref.mimeType ?? undefined)
26
+ : decodeImage(ref.bytes, ref.mimeType ?? undefined));
27
+ signal.throwIfAborted();
28
+ if (decoded === null)
29
+ return null;
30
+ const bitmap = {
31
+ alphaType: ref.alphaType,
32
+ data: new Uint8ClampedArray(decoded.data),
33
+ format: 'rgba8unorm',
34
+ gamut: 'srgb',
35
+ height: decoded.height,
36
+ kind: BitmapTextureSourceKind,
37
+ version: 0,
38
+ width: decoded.width,
39
+ };
40
+ return createEntity(bitmap);
41
+ }
42
+ export function createExternalImageResourceReference(uri, basePath = null) {
43
+ return {
44
+ basePath,
45
+ failure: null,
46
+ kind: ImageResourceReferenceKind.External,
47
+ mimeType: null,
48
+ state: ResourceResolutionState.Unresolved,
49
+ textures: [],
50
+ uri,
51
+ };
52
+ }
53
+ // Reduces a thrown value to the serialization-safe categories a reference retains. Raw Error objects and
54
+ // arbitrary thrown values stay inside the resolving operation; diagnostics get category, name, and message.
55
+ export function createImageResourceFailure(cause) {
56
+ if (cause instanceof Error) {
57
+ return { kind: ImageResourceFailureKind.Error, message: cause.message, name: cause.name };
58
+ }
59
+ return { kind: ImageResourceFailureKind.Error, message: String(cause), name: null };
60
+ }
61
+ // Returns a detached plain-data explanation suitable for logs, tools, and serialization. It never throws
62
+ // and exposes no resolver runtime or raw thrown value.
63
+ export function explainImageResourceReferenceResolution(ref) {
64
+ return {
65
+ failure: ref.failure === null ? null : { ...ref.failure },
66
+ kind: ref.kind,
67
+ retryable: ref.state === ResourceResolutionState.Failed,
68
+ state: ref.state,
69
+ };
70
+ }
71
+ // Returns a failed reference to the requestable state. Loading/resolved/unresolved references are unchanged
72
+ // so this atom cannot invalidate live work or a successfully bound resource accidentally.
73
+ export function resetFailedImageResourceReference(ref) {
74
+ if (ref.state !== ResourceResolutionState.Failed)
75
+ return false;
76
+ ref.failure = null;
77
+ ref.state = ResourceResolutionState.Unresolved;
78
+ return true;
79
+ }
80
+ // Advances one reference through its lifecycle and returns the decoded texture source, or null for an expected
81
+ // failure. Embedded bytes decode through @flighthq/image-codec; an External uri goes through the caller's
82
+ // fetch seam. An abort is a cancel rather than a failure, so the reference reverts to Unresolved and the
83
+ // rejection propagates — a caller racing several loads against one signal sees one cancellation, not a
84
+ // document full of spurious Failed references.
85
+ //
86
+ // This is the whole lifecycle for a document that resolves its images once. A caller needing concurrency
87
+ // limits, priority, or retry drives those around this atom rather than inside it.
88
+ export async function resolveImageResourceReference(ref, fetch, signal) {
89
+ ref.failure = null;
90
+ ref.state = ResourceResolutionState.Loading;
91
+ try {
92
+ const source = ref.kind === ImageResourceReferenceKind.Embedded
93
+ ? await decodeEmbeddedImageResourceReference(ref, signal)
94
+ : await fetch(ref, signal);
95
+ if (source === null) {
96
+ ref.failure = { kind: ImageResourceFailureKind.Unavailable, message: 'Image resource unavailable', name: null };
97
+ ref.state = ResourceResolutionState.Failed;
98
+ return null;
99
+ }
100
+ ref.state = ResourceResolutionState.Resolved;
101
+ return source;
102
+ }
103
+ catch (cause) {
104
+ if (signal.aborted) {
105
+ ref.state = ResourceResolutionState.Unresolved;
106
+ throw cause;
107
+ }
108
+ ref.failure = createImageResourceFailure(cause);
109
+ ref.state = ResourceResolutionState.Failed;
110
+ return null;
111
+ }
112
+ }
113
+ //# sourceMappingURL=imageResourceReference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageResourceReference.js","sourceRoot":"","sources":["../src/imageResourceReference.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { cloneImageResource, createCanvasFromImageResource, createCompressedImageResource, createImageResource, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, disposeImageResource, getImageResourceByteSize, hasImageResourceData, hasImageResourcePixels, hasImageResourceSource, isImageResourceEmpty, isImageResourceSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, setImageResourceSource, } from './contract';
1
+ export { cloneImageResource, createCompressedImage, createEmbeddedImageResourceReference, createExternalImageResourceReference, createImageResource, createImageResourceFailure, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, explainImageResourceReferenceResolution, isImageResourceEmpty, isImageUrlSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, resetFailedImageResourceReference, resolveImageResourceReference, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,6BAA6B,EAC7B,mBAAmB,EACnB,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,oCAAoC,EACpC,oCAAoC,EACpC,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,uCAAuC,EACvC,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,iCAAiC,EACjC,6BAA6B,GAC9B,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { cloneImageResource, createCanvasFromImageResource, createCompressedImageResource, createImageResource, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, disposeImageResource, getImageResourceByteSize, hasImageResourceData, hasImageResourcePixels, hasImageResourceSource, isImageResourceEmpty, isImageResourceSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, setImageResourceSource, } from './contract';
1
+ export { cloneImageResource, createCompressedImage, createEmbeddedImageResourceReference, createExternalImageResourceReference, createImageResource, createImageResourceFailure, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, explainImageResourceReferenceResolution, isImageResourceEmpty, isImageUrlSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, resetFailedImageResourceReference, resolveImageResourceReference, } from './contract';
2
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,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,6BAA6B,EAC7B,mBAAmB,EACnB,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,oCAAoC,EACpC,oCAAoC,EACpC,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,uCAAuC,EACvC,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,iCAAiC,EACjC,6BAA6B,GAC9B,MAAM,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "@flighthq/image",
3
- "version": "0.3.0-next.906.07cea63",
3
+ "version": "0.3.1-next.209.43ef1bc",
4
+ "author": "Joshua Granick and other contributors",
5
+ "license": "MIT",
4
6
  "repository": {
5
7
  "type": "git",
6
8
  "url": "https://github.com/flighthq/flight.git",
@@ -36,9 +38,9 @@
36
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
37
39
  },
38
40
  "dependencies": {
39
- "@flighthq/entity": "0.3.0-next.906.07cea63",
40
- "@flighthq/image-codec": "0.3.0-next.906.07cea63",
41
- "@flighthq/types": "0.3.0-next.906.07cea63"
41
+ "@flighthq/entity": "0.3.1-next.209.43ef1bc",
42
+ "@flighthq/image-codec": "0.3.1-next.209.43ef1bc",
43
+ "@flighthq/types": "0.3.1-next.209.43ef1bc"
42
44
  },
43
45
  "devDependencies": {
44
46
  "typescript": "^5.3.0"
@@ -1,234 +1,86 @@
1
- import type { ImageResourceCompressed } from '@flighthq/types/contract';
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
- createCompressedImageResource,
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
- // A minimal single-mip bc3 4x4 compressed payload stand-in for the resource wrapper tests.
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
- faces: 1,
20
+ levels: [{ byteLength: 16, byteOffset: 0, height: 4, width: 4 }],
21
+ mipLevels: 1,
28
22
  supercompression: 'None',
29
- levels: [{ byteOffset: 0, byteLength: 16, width: 4, height: 4 }],
23
+ width: 4,
30
24
  },
31
25
  payload: new Uint8Array(16),
32
26
  };
33
27
  }
34
28
 
35
29
  describe('cloneImageResource', () => {
36
- it('copies fields, shares the element, and gives an independent identity', () => {
37
- const element = { width: 4, height: 5 } as HTMLImageElement;
38
- const resource = createImageResource(element);
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(element);
45
- expect(copy.width).toStrictEqual(4);
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).toStrictEqual(3);
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('createCompressedImageResource', () => {
74
- it('wraps the container, mirrors its base dimensions, and leaves source/data null', () => {
45
+ describe('createCompressedImage', () => {
46
+ it('wraps the compressed payload as a distinct source', () => {
75
47
  const compressed = makeCompressed();
76
- const resource = createCompressedImageResource(compressed);
77
- expect(resource.compressed).toBe(compressed);
78
- expect(resource.width).toStrictEqual(4);
79
- expect(resource.height).toStrictEqual(4);
80
- expect(resource.source).toBeNull();
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('returns an object', () => {
88
- const resource = createImageResource();
89
- expect(resource).not.toBeNull();
90
- });
91
-
92
- it('sets source, width and height if you pass in a source element', () => {
93
- const element = { width: 100, height: 100 } as HTMLImageElement;
94
- const resource = createImageResource(element);
95
- expect(resource.source).toStrictEqual(element);
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('increments the version', () => {
191
- const resource = createImageResource();
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('is true with no dimensions and false once sized', () => {
208
- expect(isImageResourceEmpty(createImageResource())).toStrictEqual(true);
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
- createCanvasFromImageResource,
7
+ createImageResourceFromBitmap,
4
8
  createImageResourceFromCanvas,
5
9
  createImageResourceFromImageBitmap,
6
10
  createImageResourceFromImageElement,
7
- isImageResourceSameOrigin,
11
+ isImageUrlSameOrigin,
8
12
  loadImageResourceFromBase64,
9
13
  loadImageResourceFromBlob,
10
14
  loadImageResourceFromBytes,
@@ -21,29 +25,44 @@ afterEach(() => {
21
25
  delete (HTMLImageElement.prototype as Partial<HTMLImageElement>).decode;
22
26
  });
23
27
 
24
- describe('createCanvasFromImageResource', () => {
25
- function makeDataResource(width: number, height: number) {
26
- const resource = createImageResource();
27
- resource.width = width;
28
- resource.height = height;
29
- resource.data = new Uint8ClampedArray(width * height * 4).fill(255);
30
- return resource;
31
- }
32
-
33
- it('materializes a canvas sized to the resource', () => {
34
- const canvas = createCanvasFromImageResource(makeDataResource(6, 5));
35
- expect(canvas).toBeInstanceOf(HTMLCanvasElement);
36
- expect(canvas!.width).toBe(6);
37
- expect(canvas!.height).toBe(5);
38
- });
39
-
40
- it('returns a new canvas each call', () => {
41
- const resource = makeDataResource(4, 4);
42
- expect(createCanvasFromImageResource(resource)).not.toBe(createCanvasFromImageResource(resource));
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
+ gamut: '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();
43
46
  });
44
47
 
45
- it('returns null for an element-only resource with no data', () => {
46
- expect(createCanvasFromImageResource(createImageResource(document.createElement('img')))).toBeNull();
48
+ it('normalizes premultiplied Bitmap pixels for the straight-alpha ImageData bridge', () => {
49
+ const bitmap: Bitmap = createEntity({
50
+ alphaType: 'premultiplied',
51
+ gamut: 'srgb',
52
+ data: new Uint8ClampedArray([0x40, 0x20, 0x10, 0x80]),
53
+ format: 'rgba8unorm',
54
+ height: 1,
55
+ kind: BitmapTextureSourceKind,
56
+ version: 0,
57
+ width: 1,
58
+ });
59
+ const putImageData = vi.spyOn(CanvasRenderingContext2D.prototype, 'putImageData');
60
+
61
+ createImageResourceFromBitmap(bitmap);
62
+
63
+ const imageData = putImageData.mock.calls[0][0];
64
+ expect([...imageData.data]).toEqual([0x80, 0x40, 0x20, 0x80]);
65
+ expect([...bitmap.data]).toEqual([0x40, 0x20, 0x10, 0x80]);
47
66
  });
48
67
  });
49
68
 
@@ -115,26 +134,26 @@ describe('createImageResourceFromImageElement', () => {
115
134
  });
116
135
  });
117
136
 
118
- describe('isImageResourceSameOrigin', () => {
137
+ describe('isImageUrlSameOrigin', () => {
119
138
  it('returns true for data: URLs', () => {
120
- expect(isImageResourceSameOrigin('data:image/png;base64,abc')).toBe(true);
139
+ expect(isImageUrlSameOrigin('data:image/png;base64,abc')).toBe(true);
121
140
  });
122
141
 
123
142
  it('returns true for blob: URLs', () => {
124
- expect(isImageResourceSameOrigin('blob:http://localhost/some-id')).toBe(true);
143
+ expect(isImageUrlSameOrigin('blob:http://localhost/some-id')).toBe(true);
125
144
  });
126
145
 
127
146
  it('returns true for relative URLs (same origin)', () => {
128
- expect(isImageResourceSameOrigin('/images/logo.png')).toBe(true);
147
+ expect(isImageUrlSameOrigin('/images/logo.png')).toBe(true);
129
148
  });
130
149
 
131
150
  it('returns false for a different-origin absolute URL', () => {
132
- expect(isImageResourceSameOrigin('https://cdn.other-domain.com/image.png')).toBe(false);
151
+ expect(isImageUrlSameOrigin('https://cdn.other-domain.com/image.png')).toBe(false);
133
152
  });
134
153
  });
135
154
 
136
155
  describe('loadImageResourceFromBase64', () => {
137
- it('resolves to an ImageResource', async () => {
156
+ it('resolves to an Image', async () => {
138
157
  const resource = await loadImageResourceFromBase64('abc123', 'image/png');
139
158
  expect(resource).not.toBeNull();
140
159
  expect(resource.source).toBeInstanceOf(HTMLImageElement);
@@ -147,7 +166,7 @@ describe('loadImageResourceFromBase64', () => {
147
166
  });
148
167
 
149
168
  describe('loadImageResourceFromBlob', () => {
150
- it('resolves to an ImageResource', async () => {
169
+ it('resolves to an Image', async () => {
151
170
  const blob = new Blob([], { type: 'image/png' });
152
171
  const resource = await loadImageResourceFromBlob(blob);
153
172
  expect(resource).not.toBeNull();
@@ -221,12 +240,47 @@ describe('loadImageResourceFromUrl', () => {
221
240
  );
222
241
  });
223
242
 
224
- it('resolves to an ImageResource whose source is an HTMLImageElement', async () => {
243
+ it('clears the image source when aborted during decode', async () => {
244
+ let capturedImg: HTMLImageElement | undefined;
245
+ const origImage = globalThis.Image;
246
+ globalThis.Image = new Proxy(origImage, {
247
+ construct(Target, args) {
248
+ const img = new Target(...(args as []));
249
+ capturedImg = img;
250
+ return img;
251
+ },
252
+ }) as typeof Image;
253
+ HTMLImageElement.prototype.decode = vi.fn().mockReturnValue(new Promise(() => {}));
254
+ const controller = new AbortController();
255
+
256
+ try {
257
+ const promise = loadImageResourceFromUrl('/images/slow.png', undefined, controller.signal);
258
+ controller.abort(new Error('cancelled'));
259
+ await expect(promise).rejects.toThrow('cancelled');
260
+ expect(capturedImg?.getAttribute('src')).toBe('');
261
+ } finally {
262
+ globalThis.Image = origImage;
263
+ }
264
+ });
265
+
266
+ it('removes its abort listener after decode succeeds', async () => {
267
+ const controller = new AbortController();
268
+ const add = vi.spyOn(controller.signal, 'addEventListener');
269
+ const remove = vi.spyOn(controller.signal, 'removeEventListener');
270
+
271
+ await loadImageResourceFromUrl('/images/logo.png', undefined, controller.signal);
272
+
273
+ expect(add).toHaveBeenCalledOnce();
274
+ expect(remove).toHaveBeenCalledOnce();
275
+ expect(remove.mock.calls[0][1]).toBe(add.mock.calls[0][1]);
276
+ });
277
+
278
+ it('resolves to an Image whose source is an HTMLImageElement', async () => {
225
279
  const resource = await loadImageResourceFromUrl('data:image/png;base64,abc');
226
280
  expect(resource.source).toBeInstanceOf(HTMLImageElement);
227
281
  });
228
282
 
229
- it('sets crossOrigin when the crossOrigin parameter is provided', async () => {
283
+ it.each(['anonymous', 'use-credentials'] as const)('sets crossOrigin to %s when provided', async (crossOrigin) => {
230
284
  let capturedImg: HTMLImageElement | undefined;
231
285
  const origImage = globalThis.Image;
232
286
  globalThis.Image = new Proxy(origImage, {
@@ -237,8 +291,8 @@ describe('loadImageResourceFromUrl', () => {
237
291
  },
238
292
  }) as typeof Image;
239
293
 
240
- await loadImageResourceFromUrl('https://cdn.other-domain.com/image.png', 'anonymous');
241
- expect(capturedImg?.crossOrigin).toBe('anonymous');
294
+ await loadImageResourceFromUrl('https://cdn.other-domain.com/image.png', crossOrigin);
295
+ expect(capturedImg?.crossOrigin).toBe(crossOrigin);
242
296
 
243
297
  globalThis.Image = origImage;
244
298
  });
@@ -0,0 +1,189 @@
1
+ import { clearImageDecoders, registerImageDecoder } from '@flighthq/image-codec/contract';
2
+ import type { Image, ImageDecoder } from '@flighthq/types/contract';
3
+ import {
4
+ BitmapTextureSourceKind,
5
+ EntityRuntimeKey,
6
+ ImageResourceFailureKind,
7
+ ResourceResolutionState,
8
+ } from '@flighthq/types/contract';
9
+
10
+ import {
11
+ createEmbeddedImageResourceReference,
12
+ createExternalImageResourceReference,
13
+ createImageResourceFailure,
14
+ explainImageResourceReferenceResolution,
15
+ resetFailedImageResourceReference,
16
+ resolveImageResourceReference,
17
+ } from './imageResourceReference';
18
+
19
+ let decoder: ReturnType<typeof vi.fn<ImageDecoder>>;
20
+
21
+ beforeEach(() => {
22
+ decoder = vi.fn<ImageDecoder>().mockResolvedValue({
23
+ data: new Uint8ClampedArray([0x11, 0x22, 0x33, 0x44]),
24
+ height: 1,
25
+ width: 1,
26
+ });
27
+ registerImageDecoder('image/png', decoder);
28
+ });
29
+
30
+ afterEach(() => {
31
+ clearImageDecoders();
32
+ });
33
+
34
+ const unusedFetch = () => Promise.resolve(null);
35
+
36
+ describe('createEmbeddedImageResourceReference', () => {
37
+ it('borrows the byte view rather than copying it', () => {
38
+ const bytes = new Uint8Array([1, 2, 3, 4]);
39
+ const ref = createEmbeddedImageResourceReference(bytes, 'image/png');
40
+ expect(ref.bytes).toBe(bytes);
41
+ expect(ref.mimeType).toBe('image/png');
42
+ expect(ref.alphaType).toBe('straight');
43
+ });
44
+
45
+ it('starts unresolved with no failure and no subscribers', () => {
46
+ const ref = createEmbeddedImageResourceReference(new Uint8Array(1));
47
+ expect(ref.state).toBe(ResourceResolutionState.Unresolved);
48
+ expect(ref.failure).toBeNull();
49
+ expect(ref.mimeType).toBeNull();
50
+ expect(ref.textures).toEqual([]);
51
+ });
52
+ });
53
+
54
+ describe('createExternalImageResourceReference', () => {
55
+ it('retains the uri and base path', () => {
56
+ const ref = createExternalImageResourceReference('atlas.png', '/assets');
57
+ expect(ref.uri).toBe('atlas.png');
58
+ expect(ref.basePath).toBe('/assets');
59
+ expect(ref.state).toBe(ResourceResolutionState.Unresolved);
60
+ });
61
+
62
+ it('defaults the base path to null', () => {
63
+ expect(createExternalImageResourceReference('/atlas.png').basePath).toBeNull();
64
+ });
65
+ });
66
+
67
+ describe('createImageResourceFailure', () => {
68
+ it('keeps an Error name and message', () => {
69
+ const failure = createImageResourceFailure(new TypeError('bad magic'));
70
+ expect(failure).toEqual({ kind: ImageResourceFailureKind.Error, message: 'bad magic', name: 'TypeError' });
71
+ });
72
+
73
+ it('stringifies a non-Error cause with no name', () => {
74
+ expect(createImageResourceFailure('exploded')).toEqual({
75
+ kind: ImageResourceFailureKind.Error,
76
+ message: 'exploded',
77
+ name: null,
78
+ });
79
+ });
80
+ });
81
+
82
+ describe('explainImageResourceReferenceResolution', () => {
83
+ it('detaches the failure so a caller cannot mutate the reference', () => {
84
+ const ref = createEmbeddedImageResourceReference(new Uint8Array(1));
85
+ ref.failure = { kind: ImageResourceFailureKind.Error, message: 'nope', name: null };
86
+ ref.state = ResourceResolutionState.Failed;
87
+ const explanation = explainImageResourceReferenceResolution(ref);
88
+ expect(explanation.failure).not.toBe(ref.failure);
89
+ expect(explanation.failure).toEqual(ref.failure);
90
+ });
91
+
92
+ it('reports only a failed reference as retryable', () => {
93
+ const ref = createExternalImageResourceReference('a.png');
94
+ expect(explainImageResourceReferenceResolution(ref).retryable).toBe(false);
95
+ ref.state = ResourceResolutionState.Failed;
96
+ expect(explainImageResourceReferenceResolution(ref).retryable).toBe(true);
97
+ });
98
+ });
99
+
100
+ describe('resetFailedImageResourceReference', () => {
101
+ it('clears a failed reference back to unresolved', () => {
102
+ const ref = createExternalImageResourceReference('a.png');
103
+ ref.failure = { kind: ImageResourceFailureKind.Unavailable, message: 'missing', name: null };
104
+ ref.state = ResourceResolutionState.Failed;
105
+ expect(resetFailedImageResourceReference(ref)).toBe(true);
106
+ expect(ref.state).toBe(ResourceResolutionState.Unresolved);
107
+ expect(ref.failure).toBeNull();
108
+ });
109
+
110
+ it('leaves a resolved reference untouched', () => {
111
+ const ref = createExternalImageResourceReference('a.png');
112
+ ref.state = ResourceResolutionState.Resolved;
113
+ expect(resetFailedImageResourceReference(ref)).toBe(false);
114
+ expect(ref.state).toBe(ResourceResolutionState.Resolved);
115
+ });
116
+ });
117
+
118
+ describe('resolveImageResourceReference', () => {
119
+ it('decodes embedded bytes and marks the reference resolved', async () => {
120
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), 'image/png');
121
+ const source = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
122
+ expect(source).toMatchObject({
123
+ alphaType: 'straight',
124
+ data: new Uint8ClampedArray([0x11, 0x22, 0x33, 0x44]),
125
+ height: 1,
126
+ kind: BitmapTextureSourceKind,
127
+ width: 1,
128
+ });
129
+ expect(EntityRuntimeKey in source!).toBe(true);
130
+ expect(decoder).toHaveBeenCalledWith(ref.bytes);
131
+ expect(ref.state).toBe(ResourceResolutionState.Resolved);
132
+ expect(ref.failure).toBeNull();
133
+ });
134
+
135
+ it('requests and declares premultiplied output when the plain reference says its source retains it', async () => {
136
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([1]), 'image/png', 'premultiplied');
137
+
138
+ const source = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
139
+
140
+ expect(decoder).toHaveBeenCalledWith(ref.bytes, { premultiplyAlpha: true });
141
+ expect(source?.alphaType).toBe('premultiplied');
142
+ });
143
+
144
+ it('routes an external reference through the fetch seam', async () => {
145
+ const ref = createExternalImageResourceReference('atlas.png', '/assets');
146
+ const fetched = { width: 2 } as Image;
147
+ const fetch = vi.fn().mockResolvedValue(fetched);
148
+ expect(await resolveImageResourceReference(ref, fetch, new AbortController().signal)).toBe(fetched);
149
+ expect(fetch).toHaveBeenCalledOnce();
150
+ expect(ref.state).toBe(ResourceResolutionState.Resolved);
151
+ });
152
+
153
+ it('records an unavailable failure when the fetch seam returns null', async () => {
154
+ const ref = createExternalImageResourceReference('missing.png');
155
+ expect(await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal)).toBeNull();
156
+ expect(ref.state).toBe(ResourceResolutionState.Failed);
157
+ expect(ref.failure?.kind).toBe(ImageResourceFailureKind.Unavailable);
158
+ });
159
+
160
+ it('records a thrown cause as a failure rather than rethrowing', async () => {
161
+ const ref = createExternalImageResourceReference('boom.png');
162
+ const fetch = vi.fn().mockRejectedValue(new Error('network down'));
163
+ expect(await resolveImageResourceReference(ref, fetch, new AbortController().signal)).toBeNull();
164
+ expect(ref.state).toBe(ResourceResolutionState.Failed);
165
+ expect(ref.failure).toEqual({ kind: ImageResourceFailureKind.Error, message: 'network down', name: 'Error' });
166
+ });
167
+
168
+ it('treats an abort as a cancel: reverts to unresolved and rethrows', async () => {
169
+ const ref = createExternalImageResourceReference('slow.png');
170
+ const controller = new AbortController();
171
+ const fetch = vi.fn().mockImplementation(() => {
172
+ controller.abort();
173
+ return Promise.reject(new Error('aborted'));
174
+ });
175
+ await expect(resolveImageResourceReference(ref, fetch, controller.signal)).rejects.toThrow('aborted');
176
+ expect(ref.state).toBe(ResourceResolutionState.Unresolved);
177
+ expect(ref.failure).toBeNull();
178
+ });
179
+
180
+ it('clears a prior failure when the reference is retried', async () => {
181
+ const ref = createExternalImageResourceReference('flaky.png');
182
+ ref.failure = { kind: ImageResourceFailureKind.Error, message: 'old', name: null };
183
+ ref.state = ResourceResolutionState.Failed;
184
+ const fetch = vi.fn().mockResolvedValue({ width: 1 } as Image);
185
+ await resolveImageResourceReference(ref, fetch, new AbortController().signal);
186
+ expect(ref.failure).toBeNull();
187
+ expect(ref.state).toBe(ResourceResolutionState.Resolved);
188
+ });
189
+ });