@flighthq/image 0.3.0 → 0.3.1-next.1041.35b950c

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
+ export * from './imageBackend';
1
2
  export * from './imageResource';
2
3
  export * from './imageResourceFrom';
3
4
  export * from './imageResourceReference';
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
package/dist/contract.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './imageBackend';
1
2
  export * from './imageResource';
2
3
  export * from './imageResourceFrom';
3
4
  export * from './imageResourceReference';
@@ -1 +1 @@
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
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { ImageBackend } from '@flighthq/types/contract';
2
+ export declare function createWebImageBackend(): ImageBackend;
3
+ export declare function getImageBackend(): ImageBackend;
4
+ export declare function setImageBackend(backend: ImageBackend | null): void;
5
+ //# sourceMappingURL=imageBackend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageBackend.d.ts","sourceRoot":"","sources":["../src/imageBackend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAYpE,wBAAgB,qBAAqB,IAAI,YAAY,CA8BpD;AAGD,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAKD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAElE"}
@@ -0,0 +1,57 @@
1
+ import { createImageResourceFromImageElement } from './imageResourceFrom';
2
+ // Builds the default web backend over `Image` + `decode()`. Created lazily by `getImageBackend` — no
3
+ // DOM is touched at import time, so importing the package has no side effect.
4
+ //
5
+ // ★ THIS BODY IS THE PREVIOUS `loadImageResourceFromUrl` MOVED VERBATIM, INCLUDING ITS KNOWN ABORT
6
+ // RACE: aborting rejects the caller and clears `src`, but the underlying load may already be in
7
+ // flight and is not guaranteed to stop. That defect is preserved deliberately rather than repaired
8
+ // here — this change is the seam, and fixing behaviour while relocating it would make the seam
9
+ // unreviewable against the approval that authorised it.
10
+ export function createWebImageBackend() {
11
+ return {
12
+ async loadImageFromUrl(url, crossOrigin, signal) {
13
+ signal?.throwIfAborted();
14
+ const img = new Image();
15
+ if (crossOrigin !== undefined)
16
+ img.crossOrigin = crossOrigin;
17
+ img.src = url;
18
+ // Wire abort to cancel the pending decode and reject with the signal's reason. Always remove the
19
+ // listener when the race settles so a long-lived signal does not retain the image and closure.
20
+ if (signal !== undefined) {
21
+ let rejectAbort = () => { };
22
+ const abortPromise = new Promise((_, reject) => {
23
+ rejectAbort = reject;
24
+ });
25
+ const onAbort = () => {
26
+ img.src = '';
27
+ rejectAbort(signal.reason);
28
+ };
29
+ signal.addEventListener('abort', onAbort, { once: true });
30
+ try {
31
+ await Promise.race([img.decode(), abortPromise]);
32
+ }
33
+ finally {
34
+ signal.removeEventListener('abort', onAbort);
35
+ }
36
+ }
37
+ else {
38
+ await img.decode();
39
+ }
40
+ return createImageResourceFromImageElement(img);
41
+ },
42
+ };
43
+ }
44
+ // The active image backend, lazily defaulting to the web one. There is always a backend.
45
+ export function getImageBackend() {
46
+ if (_backend === null)
47
+ _backend = createWebImageBackend();
48
+ return _backend;
49
+ }
50
+ // Installs a native host image backend; pass null to fall back to the lazy web default. Resettable on
51
+ // purpose: a seam a caller cannot restore is a one-way door, and a test that swapped the backend could
52
+ // not put it back.
53
+ export function setImageBackend(backend) {
54
+ _backend = backend;
55
+ }
56
+ let _backend = null;
57
+ //# sourceMappingURL=imageBackend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageBackend.js","sourceRoot":"","sources":["../src/imageBackend.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mCAAmC,EAAE,MAAM,qBAAqB,CAAC;AAE1E,qGAAqG;AACrG,8EAA8E;AAC9E,EAAE;AACF,mGAAmG;AACnG,gGAAgG;AAChG,mGAAmG;AACnG,+FAA+F;AAC/F,wDAAwD;AACxD,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,KAAK,CAAC,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM;YAC7C,MAAM,EAAE,cAAc,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;YACxB,IAAI,WAAW,KAAK,SAAS;gBAAE,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC;YAC7D,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;YACd,iGAAiG;YACjG,+FAA+F;YAC/F,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,IAAI,WAAW,GAA+B,GAAG,EAAE,GAAE,CAAC,CAAC;gBACvD,MAAM,YAAY,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;oBACpD,WAAW,GAAG,MAAM,CAAC;gBACvB,CAAC,CAAC,CAAC;gBACH,MAAM,OAAO,GAAG,GAAS,EAAE;oBACzB,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC;oBACb,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC7B,CAAC,CAAC;gBACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC1D,IAAI,CAAC;oBACH,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;gBACnD,CAAC;wBAAS,CAAC;oBACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;YACrB,CAAC;YACD,OAAO,mCAAmC,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,eAAe;IAC7B,IAAI,QAAQ,KAAK,IAAI;QAAE,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IAC1D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,sGAAsG;AACtG,uGAAuG;AACvG,mBAAmB;AACnB,MAAM,UAAU,eAAe,CAAC,OAA4B;IAC1D,QAAQ,GAAG,OAAO,CAAC;AACrB,CAAC;AAED,IAAI,QAAQ,GAAwB,IAAI,CAAC"}
@@ -1 +1 @@
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,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
+ {"version":3,"file":"imageResourceFrom.d.ts","sourceRoot":"","sources":["../src/imageResourceFrom.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAS9D,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,CAEhB"}
@@ -1,6 +1,7 @@
1
1
  import { createEntity } from '@flighthq/entity/contract';
2
2
  import { detectImageMimeType } from '@flighthq/image-codec/contract';
3
3
  import { ImageTextureSourceKind } from '@flighthq/types/contract';
4
+ import { getImageBackend } from './imageBackend';
4
5
  // Transcodes a Bitmap's raw pixels into an element-backed Image, via a detached canvas.
5
6
  // The inverse of captureBitmapFromImageResource. Lives here rather than in @flighthq/bitmap because a
6
7
  // conversion belongs with the type it PRODUCES: you look for it under what you want to end up with.
@@ -10,10 +11,29 @@ export function createImageResourceFromBitmap(bitmap) {
10
11
  canvas.width = bitmap.width;
11
12
  canvas.height = bitmap.height;
12
13
  const domImageData = new globalThis.ImageData(bitmap.width, bitmap.height);
13
- domImageData.data.set(bitmap.data);
14
+ domImageData.data.set(bitmap.alphaType === 'premultiplied' ? unpremultiplyRgba8(bitmap.data) : bitmap.data);
14
15
  canvas.getContext('2d').putImageData(domImageData, 0, 0);
15
16
  return createImageResourceFromCanvas(canvas);
16
17
  }
18
+ // ImageData is straight-alpha by contract. Preserve a Bitmap's declared representation at its own seam,
19
+ // then normalize only the scratch browser copy used to materialize a CanvasImageSource for Canvas/DOM.
20
+ function unpremultiplyRgba8(source) {
21
+ const data = new Uint8ClampedArray(source);
22
+ for (let i = 0; i < data.length; i += 4) {
23
+ const alpha = data[i + 3];
24
+ if (alpha === 0) {
25
+ data[i] = 0;
26
+ data[i + 1] = 0;
27
+ data[i + 2] = 0;
28
+ continue;
29
+ }
30
+ const scale = 255 / alpha;
31
+ data[i] = Math.min(255, Math.round(data[i] * scale));
32
+ data[i + 1] = Math.min(255, Math.round(data[i + 1] * scale));
33
+ data[i + 2] = Math.min(255, Math.round(data[i + 2] * scale));
34
+ }
35
+ return data;
36
+ }
17
37
  export function createImageResourceFromCanvas(canvas) {
18
38
  return createEntity({
19
39
  alphaType: DECODED_ALPHA_TYPE,
@@ -78,34 +98,7 @@ export async function loadImageResourceFromBytes(bytes, mimeType, signal) {
78
98
  return loadImageResourceFromBlob(new Blob([buf], { type }), signal);
79
99
  }
80
100
  export async function loadImageResourceFromUrl(url, crossOrigin, signal) {
81
- signal?.throwIfAborted();
82
- const img = new Image();
83
- if (crossOrigin !== undefined)
84
- img.crossOrigin = crossOrigin;
85
- img.src = url;
86
- // Wire abort to cancel the pending decode and reject with the signal's reason. Always remove the
87
- // listener when the race settles so a long-lived signal does not retain the image and closure.
88
- if (signal !== undefined) {
89
- let rejectAbort = () => { };
90
- const abortPromise = new Promise((_, reject) => {
91
- rejectAbort = reject;
92
- });
93
- const onAbort = () => {
94
- img.src = '';
95
- rejectAbort(signal.reason);
96
- };
97
- signal.addEventListener('abort', onAbort, { once: true });
98
- try {
99
- await Promise.race([img.decode(), abortPromise]);
100
- }
101
- finally {
102
- signal.removeEventListener('abort', onAbort);
103
- }
104
- }
105
- else {
106
- await img.decode();
107
- }
108
- return createImageResourceFromImageElement(img);
101
+ return getImageBackend().loadImageFromUrl(url, crossOrigin, signal);
109
102
  }
110
103
  // The host-decode truth for every browser-backed source; see imageResource.ts.
111
104
  const DECODED_ALPHA_TYPE = 'straight';
@@ -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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"}
@@ -1,8 +1,10 @@
1
- import type { EmbeddedImageResourceReference, ExternalImageResourceReference, Image, ImageResourceFailure, ImageResourceFetch, ImageResourceReference, ImageResourceReferenceResolutionExplanation } from '@flighthq/types/contract';
2
- export declare function createEmbeddedImageResourceReference(bytes: Uint8Array, mimeType?: string | null): EmbeddedImageResourceReference;
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
3
  export declare function createExternalImageResourceReference(uri: string, basePath?: string | null): ExternalImageResourceReference;
4
4
  export declare function createImageResourceFailure(cause: unknown): ImageResourceFailure;
5
+ export declare function disableImageBitmapComposition(): void;
6
+ export declare function enableImageBitmapComposition(): void;
5
7
  export declare function explainImageResourceReferenceResolution(ref: Readonly<ImageResourceReference>): ImageResourceReferenceResolutionExplanation;
6
8
  export declare function resetFailedImageResourceReference(ref: ImageResourceReference): boolean;
7
- export declare function resolveImageResourceReference(ref: ImageResourceReference, fetch: ImageResourceFetch, signal: AbortSignal): Promise<Image | null>;
9
+ export declare function resolveImageResourceReference(ref: ImageResourceReference, fetch: ImageResourceFetch, signal: AbortSignal): Promise<TextureSource | null>;
8
10
  //# sourceMappingURL=imageResourceReference.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"imageResourceReference.d.ts","sourceRoot":"","sources":["../src/imageResourceReference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAC9B,8BAA8B,EAC9B,KAAK,EACL,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,2CAA2C,EAC5C,MAAM,0BAA0B,CAAC;AAYlC,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,UAAU,EACjB,QAAQ,GAAE,MAAM,GAAG,IAAW,GAC7B,8BAA8B,CAShC;AAED,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,KAAK,GAAG,IAAI,CAAC,CAwBvB"}
1
+ {"version":3,"file":"imageResourceReference.d.ts","sourceRoot":"","sources":["../src/imageResourceReference.ts"],"names":[],"mappings":"AAOA,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;AAgCD,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;AAwBD,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD;AAKD,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;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,CAkC/B"}
@@ -1,10 +1,12 @@
1
- import { ImageResourceFailureKind, ImageResourceReferenceKind, ResourceResolutionState, } from '@flighthq/types/contract';
2
- import { loadImageResourceFromBytes } from './imageResourceFrom';
1
+ import { createEntity } from '@flighthq/entity/contract';
2
+ import { decodeImage, decodeImagePremultiplied, explainImageDecodeFailure, getImageBitmapComposer, } from '@flighthq/image-codec/contract';
3
+ import { BitmapTextureSourceKind, ImageResourceFailureKind, ImageResourceReferenceKind, ResourceResolutionState, } from '@flighthq/types/contract';
3
4
  // `bytes` is retained as the view the container handed over, not a copy: a parser carves an image payload
4
5
  // out of its source and the reference borrows it, so a document that never resolves an image never pays for
5
6
  // its pixels. `textures` starts empty and each waiting Texture subscribes itself.
6
- export function createEmbeddedImageResourceReference(bytes, mimeType = null) {
7
+ export function createEmbeddedImageResourceReference(bytes, mimeType = null, alphaType = 'straight') {
7
8
  return {
9
+ alphaType,
8
10
  bytes,
9
11
  failure: null,
10
12
  kind: ImageResourceReferenceKind.Embedded,
@@ -13,6 +15,33 @@ export function createEmbeddedImageResourceReference(bytes, mimeType = null) {
13
15
  textures: [],
14
16
  };
15
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
+ if (ref.bitmapComposition !== undefined && _resolveImageBitmapComposition !== null) {
24
+ return _resolveImageBitmapComposition(ref, signal);
25
+ }
26
+ signal.throwIfAborted();
27
+ const decoded = await (ref.alphaType === 'premultiplied'
28
+ ? decodeImagePremultiplied(ref.bytes, ref.mimeType ?? undefined)
29
+ : decodeImage(ref.bytes, ref.mimeType ?? undefined));
30
+ signal.throwIfAborted();
31
+ if (decoded === null)
32
+ return null;
33
+ const bitmap = {
34
+ alphaType: ref.alphaType,
35
+ data: new Uint8ClampedArray(decoded.data),
36
+ format: 'rgba8unorm',
37
+ gamut: 'srgb',
38
+ height: decoded.height,
39
+ kind: BitmapTextureSourceKind,
40
+ version: 0,
41
+ width: decoded.width,
42
+ };
43
+ return createEntity(bitmap);
44
+ }
16
45
  export function createExternalImageResourceReference(uri, basePath = null) {
17
46
  return {
18
47
  basePath,
@@ -32,6 +61,28 @@ export function createImageResourceFailure(cause) {
32
61
  }
33
62
  return { kind: ImageResourceFailureKind.Error, message: String(cause), name: null };
34
63
  }
64
+ async function resolveImageBitmapComposition(ref, signal) {
65
+ signal.throwIfAborted();
66
+ const composition = ref.bitmapComposition;
67
+ const composer = getImageBitmapComposer(composition.kind);
68
+ if (composer === null)
69
+ return null;
70
+ // A composer always receives straight decoded pixels. It may also own a raw raster with no MIME
71
+ // decoder, in which case decoded is null and its plain payload is the complete input.
72
+ const decoded = await decodeImage(ref.bytes, ref.mimeType ?? undefined);
73
+ signal.throwIfAborted();
74
+ return composer(decoded, composition.payload);
75
+ }
76
+ let _resolveImageBitmapComposition = null;
77
+ export function disableImageBitmapComposition() {
78
+ _resolveImageBitmapComposition = null;
79
+ }
80
+ // Installs the optional decoded-pixel join without making an ordinary embedded-image consumer retain
81
+ // its registry lookup or straight-decode branch. A format package calls this beside its composer
82
+ // registrations; until then the nullable hook leaves the original hot path byte-for-byte tree-shakable.
83
+ export function enableImageBitmapComposition() {
84
+ _resolveImageBitmapComposition = resolveImageBitmapComposition;
85
+ }
35
86
  // Returns a detached plain-data explanation suitable for logs, tools, and serialization. It never throws
36
87
  // and exposes no resolver runtime or raw thrown value.
37
88
  export function explainImageResourceReferenceResolution(ref) {
@@ -51,7 +102,7 @@ export function resetFailedImageResourceReference(ref) {
51
102
  ref.state = ResourceResolutionState.Unresolved;
52
103
  return true;
53
104
  }
54
- // Advances one reference through its lifecycle and returns the decoded image, or null for an expected
105
+ // Advances one reference through its lifecycle and returns the decoded texture source, or null for an expected
55
106
  // failure. Embedded bytes decode through @flighthq/image-codec; an External uri goes through the caller's
56
107
  // fetch seam. An abort is a cancel rather than a failure, so the reference reverts to Unresolved and the
57
108
  // rejection propagates — a caller racing several loads against one signal sees one cancellation, not a
@@ -63,16 +114,25 @@ export async function resolveImageResourceReference(ref, fetch, signal) {
63
114
  ref.failure = null;
64
115
  ref.state = ResourceResolutionState.Loading;
65
116
  try {
66
- const image = ref.kind === ImageResourceReferenceKind.Embedded
67
- ? await loadImageResourceFromBytes(ref.bytes, ref.mimeType ?? undefined, signal)
117
+ const usesOrdinaryEmbeddedDecode = ref.kind === ImageResourceReferenceKind.Embedded &&
118
+ (ref.bitmapComposition === undefined || _resolveImageBitmapComposition === null);
119
+ const source = ref.kind === ImageResourceReferenceKind.Embedded
120
+ ? await decodeEmbeddedImageResourceReference(ref, signal)
68
121
  : await fetch(ref, signal);
69
- if (image === null) {
70
- ref.failure = { kind: ImageResourceFailureKind.Unavailable, message: 'Image resource unavailable', name: null };
122
+ if (source === null) {
123
+ const decodeFailure = usesOrdinaryEmbeddedDecode
124
+ ? explainImageDecodeFailure(ref.bytes, ref.mimeType ?? undefined)
125
+ : null;
126
+ ref.failure = {
127
+ kind: ImageResourceFailureKind.Unavailable,
128
+ message: decodeFailure?.reason ?? 'Image resource unavailable',
129
+ name: null,
130
+ };
71
131
  ref.state = ResourceResolutionState.Failed;
72
132
  return null;
73
133
  }
74
134
  ref.state = ResourceResolutionState.Resolved;
75
- return image;
135
+ return source;
76
136
  }
77
137
  catch (cause) {
78
138
  if (signal.aborted) {
@@ -1 +1 @@
1
- {"version":3,"file":"imageResourceReference.js","sourceRoot":"","sources":["../src/imageResourceReference.ts"],"names":[],"mappings":"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"}
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, createCompressedImage, createEmbeddedImageResourceReference, createExternalImageResourceReference, createImageResource, createImageResourceFailure, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, explainImageResourceReferenceResolution, isImageResourceEmpty, isImageUrlSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, resetFailedImageResourceReference, resolveImageResourceReference, } from './contract';
1
+ export { cloneImageResource, createCompressedImage, createEmbeddedImageResourceReference, createExternalImageResourceReference, createImageResource, createImageResourceFailure, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, disableImageBitmapComposition, enableImageBitmapComposition, 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,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"}
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,6BAA6B,EAC7B,4BAA4B,EAC5B,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, createCompressedImage, createEmbeddedImageResourceReference, createExternalImageResourceReference, createImageResource, createImageResourceFailure, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, explainImageResourceReferenceResolution, isImageResourceEmpty, isImageUrlSameOrigin, loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, resetFailedImageResourceReference, resolveImageResourceReference, } from './contract';
1
+ export { cloneImageResource, createCompressedImage, createEmbeddedImageResourceReference, createExternalImageResourceReference, createImageResource, createImageResourceFailure, createImageResourceFromBitmap, createImageResourceFromCanvas, createImageResourceFromImageBitmap, createImageResourceFromImageElement, disableImageBitmapComposition, enableImageBitmapComposition, 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,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"}
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,6BAA6B,EAC7B,4BAA4B,EAC5B,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,6 @@
1
1
  {
2
2
  "name": "@flighthq/image",
3
- "version": "0.3.0",
3
+ "version": "0.3.1-next.1041.35b950c",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,9 +38,9 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/entity": "0.3.0",
42
- "@flighthq/image-codec": "0.3.0",
43
- "@flighthq/types": "0.3.0"
41
+ "@flighthq/entity": "0.3.1-next.1041.35b950c",
42
+ "@flighthq/image-codec": "0.3.1-next.1041.35b950c",
43
+ "@flighthq/types": "0.3.1-next.1041.35b950c"
44
44
  },
45
45
  "devDependencies": {
46
46
  "typescript": "^5.3.0"
@@ -0,0 +1,74 @@
1
+ import type { Image, ImageBackend } from '@flighthq/types/contract';
2
+ import { afterEach, describe, expect, it } from 'vitest';
3
+
4
+ import { createWebImageBackend, getImageBackend, setImageBackend } from './imageBackend';
5
+ import { loadImageResourceFromUrl } from './imageResourceFrom';
6
+
7
+ // Every test that installs a backend must put it back, or a later test sees a stale one.
8
+ afterEach(() => setImageBackend(null));
9
+
10
+ function recordingBackend(): { backend: ImageBackend; calls: unknown[][] } {
11
+ const calls: unknown[][] = [];
12
+ return {
13
+ backend: {
14
+ loadImageFromUrl(url, crossOrigin, signal): Promise<Image> {
15
+ calls.push([url, crossOrigin, signal]);
16
+ return Promise.resolve({} as Image);
17
+ },
18
+ },
19
+ calls,
20
+ };
21
+ }
22
+
23
+ describe('createWebImageBackend', () => {
24
+ it('builds a backend without touching the DOM until it is called', () => {
25
+ // Constructing must not decode anything; the DOM work belongs inside `loadImageFromUrl`.
26
+ const backend = createWebImageBackend();
27
+ expect(typeof backend.loadImageFromUrl).toBe('function');
28
+ });
29
+
30
+ it('builds a fresh backend each call rather than a shared singleton', () => {
31
+ expect(createWebImageBackend()).not.toBe(createWebImageBackend());
32
+ });
33
+ });
34
+
35
+ describe('getImageBackend', () => {
36
+ it('defaults lazily to a web backend rather than being registered at import', () => {
37
+ expect(typeof getImageBackend().loadImageFromUrl).toBe('function');
38
+ });
39
+
40
+ it('caches the lazy default so repeated calls do not rebuild it', () => {
41
+ expect(getImageBackend()).toBe(getImageBackend());
42
+ });
43
+
44
+ it('returns the installed backend once one is set', () => {
45
+ const { backend } = recordingBackend();
46
+ setImageBackend(backend);
47
+ expect(getImageBackend()).toBe(backend);
48
+ });
49
+ });
50
+
51
+ describe('setImageBackend', () => {
52
+ it('restores a working web default when passed null, rather than leaving a one-way door', () => {
53
+ const { backend } = recordingBackend();
54
+ setImageBackend(backend);
55
+ setImageBackend(null);
56
+ const restored = getImageBackend();
57
+ // Not merely non-null: it must not be the backend that was uninstalled, and it must be usable.
58
+ expect(restored).not.toBe(backend);
59
+ expect(typeof restored.loadImageFromUrl).toBe('function');
60
+ });
61
+
62
+ it('routes loadImageResourceFromUrl through the active backend, arguments intact', async () => {
63
+ // The seam is only real if the loader actually dispatches through it — a backend nobody calls
64
+ // would satisfy every test above and change nothing.
65
+ const { backend, calls } = recordingBackend();
66
+ setImageBackend(backend);
67
+ const signal = AbortSignal.abort === undefined ? undefined : new AbortController().signal;
68
+ await loadImageResourceFromUrl('https://example.test/a.png', 'anonymous', signal);
69
+ expect(calls).toHaveLength(1);
70
+ expect(calls[0]![0]).toBe('https://example.test/a.png');
71
+ expect(calls[0]![1]).toBe('anonymous');
72
+ expect(calls[0]![2]).toBe(signal);
73
+ });
74
+ });
@@ -44,6 +44,26 @@ describe('createImageResourceFromBitmap', () => {
44
44
  expect(resource.height).toBe(4);
45
45
  expect(resource.source).not.toBeNull();
46
46
  });
47
+
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]);
66
+ });
47
67
  });
48
68
 
49
69
  describe('createImageResourceFromCanvas', () => {
@@ -1,23 +1,44 @@
1
- import type { Image } from '@flighthq/types/contract';
2
- import { ImageResourceFailureKind, ResourceResolutionState } from '@flighthq/types/contract';
1
+ import { createEntity } from '@flighthq/entity/contract';
2
+ import {
3
+ clearImageBitmapComposers,
4
+ clearImageDecoders,
5
+ registerImageBitmapComposer,
6
+ registerImageDecoder,
7
+ } from '@flighthq/image-codec/contract';
8
+ import type { Bitmap, Image, ImageDecoder } from '@flighthq/types/contract';
9
+ import {
10
+ BitmapTextureSourceKind,
11
+ EntityRuntimeKey,
12
+ ImageResourceFailureKind,
13
+ ResourceResolutionState,
14
+ } from '@flighthq/types/contract';
3
15
 
4
16
  import {
5
17
  createEmbeddedImageResourceReference,
6
18
  createExternalImageResourceReference,
7
19
  createImageResourceFailure,
20
+ disableImageBitmapComposition,
21
+ enableImageBitmapComposition,
8
22
  explainImageResourceReferenceResolution,
9
23
  resetFailedImageResourceReference,
10
24
  resolveImageResourceReference,
11
25
  } from './imageResourceReference';
12
26
 
13
- // Stub img.decode() so the embedded decode path resolves in jsdom.
27
+ let decoder: ReturnType<typeof vi.fn<ImageDecoder>>;
28
+
14
29
  beforeEach(() => {
15
- HTMLImageElement.prototype.decode = vi.fn().mockResolvedValue(undefined);
30
+ decoder = vi.fn<ImageDecoder>().mockResolvedValue({
31
+ data: new Uint8ClampedArray([0x11, 0x22, 0x33, 0x44]),
32
+ height: 1,
33
+ width: 1,
34
+ });
35
+ registerImageDecoder('image/png', decoder);
16
36
  });
17
37
 
18
38
  afterEach(() => {
19
- vi.restoreAllMocks();
20
- delete (HTMLImageElement.prototype as Partial<HTMLImageElement>).decode;
39
+ disableImageBitmapComposition();
40
+ clearImageBitmapComposers();
41
+ clearImageDecoders();
21
42
  });
22
43
 
23
44
  const unusedFetch = () => Promise.resolve(null);
@@ -28,6 +49,7 @@ describe('createEmbeddedImageResourceReference', () => {
28
49
  const ref = createEmbeddedImageResourceReference(bytes, 'image/png');
29
50
  expect(ref.bytes).toBe(bytes);
30
51
  expect(ref.mimeType).toBe('image/png');
52
+ expect(ref.alphaType).toBe('straight');
31
53
  });
32
54
 
33
55
  it('starts unresolved with no failure and no subscribers', () => {
@@ -67,6 +89,37 @@ describe('createImageResourceFailure', () => {
67
89
  });
68
90
  });
69
91
 
92
+ describe('disableImageBitmapComposition', () => {
93
+ it('restores the ordinary decoder path for a reference that carries composition data', async () => {
94
+ const composer = vi.fn().mockReturnValue(createTestBitmap('straight'));
95
+ registerImageBitmapComposer('acme/alpha-plane', composer);
96
+ enableImageBitmapComposition();
97
+ disableImageBitmapComposition();
98
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([1]), 'image/png');
99
+ ref.bitmapComposition = { kind: 'acme/alpha-plane', payload: new Uint8Array([7]) };
100
+
101
+ const source = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
102
+
103
+ expect(composer).not.toHaveBeenCalled();
104
+ expect(decoder).toHaveBeenCalledWith(ref.bytes);
105
+ expect(source?.kind).toBe(BitmapTextureSourceKind);
106
+ });
107
+ });
108
+
109
+ describe('enableImageBitmapComposition', () => {
110
+ it('installs the registered decoded-pixel composition route', async () => {
111
+ const bitmap = createTestBitmap('straight');
112
+ const composer = vi.fn().mockReturnValue(bitmap);
113
+ registerImageBitmapComposer('acme/alpha-plane', composer);
114
+ enableImageBitmapComposition();
115
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([1]), 'image/png');
116
+ ref.bitmapComposition = { kind: 'acme/alpha-plane', payload: new Uint8Array([7]) };
117
+
118
+ expect(await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal)).toBe(bitmap);
119
+ expect(composer).toHaveBeenCalledOnce();
120
+ });
121
+ });
122
+
70
123
  describe('explainImageResourceReferenceResolution', () => {
71
124
  it('detaches the failure so a caller cannot mutate the reference', () => {
72
125
  const ref = createEmbeddedImageResourceReference(new Uint8Array(1));
@@ -106,12 +159,85 @@ describe('resetFailedImageResourceReference', () => {
106
159
  describe('resolveImageResourceReference', () => {
107
160
  it('decodes embedded bytes and marks the reference resolved', async () => {
108
161
  const ref = createEmbeddedImageResourceReference(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), 'image/png');
109
- const image = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
110
- expect(image).not.toBeNull();
162
+ const source = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
163
+ expect(source).toMatchObject({
164
+ alphaType: 'straight',
165
+ data: new Uint8ClampedArray([0x11, 0x22, 0x33, 0x44]),
166
+ height: 1,
167
+ kind: BitmapTextureSourceKind,
168
+ width: 1,
169
+ });
170
+ expect(EntityRuntimeKey in source!).toBe(true);
171
+ expect(decoder).toHaveBeenCalledWith(ref.bytes);
111
172
  expect(ref.state).toBe(ResourceResolutionState.Resolved);
112
173
  expect(ref.failure).toBeNull();
113
174
  });
114
175
 
176
+ it('requests and declares premultiplied output when the plain reference says its source retains it', async () => {
177
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([1]), 'image/png', 'premultiplied');
178
+
179
+ const source = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
180
+
181
+ expect(decoder).toHaveBeenCalledWith(ref.bytes, { premultiplyAlpha: true });
182
+ expect(source?.alphaType).toBe('premultiplied');
183
+ });
184
+
185
+ it('hands straight decoded pixels and plain payload bytes to a registered Bitmap composer', async () => {
186
+ const payload = new Uint8Array([7, 8, 9]);
187
+ const bitmap = createTestBitmap('straight');
188
+ const composer = vi.fn().mockReturnValue(bitmap);
189
+ registerImageBitmapComposer('acme/alpha-plane', composer);
190
+ enableImageBitmapComposition();
191
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([1]), 'image/png', 'premultiplied');
192
+ ref.bitmapComposition = { kind: 'acme/alpha-plane', payload };
193
+
194
+ const source = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
195
+
196
+ expect(decoder).toHaveBeenCalledWith(ref.bytes);
197
+ expect(composer).toHaveBeenCalledWith(
198
+ { data: new Uint8ClampedArray([0x11, 0x22, 0x33, 0x44]), height: 1, width: 1 },
199
+ payload,
200
+ );
201
+ expect(source).toBe(bitmap);
202
+ });
203
+
204
+ it('lets a registered raw-pixel producer return a Bitmap when no MIME decoder recognizes the bytes', async () => {
205
+ const bitmap = createTestBitmap('opaque');
206
+ const composer = vi.fn().mockReturnValue(bitmap);
207
+ registerImageBitmapComposer('acme/raw-raster', composer);
208
+ enableImageBitmapComposition();
209
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([1]));
210
+ ref.bitmapComposition = { kind: 'acme/raw-raster', payload: ref.bytes };
211
+
212
+ const source = await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal);
213
+
214
+ expect(composer).toHaveBeenCalledWith(null, ref.bytes);
215
+ expect(source).toBe(bitmap);
216
+ });
217
+
218
+ it('reports an unavailable resource when its declared Bitmap composer is not registered', async () => {
219
+ enableImageBitmapComposition();
220
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([1]));
221
+ ref.bitmapComposition = { kind: 'acme/missing', payload: ref.bytes };
222
+
223
+ expect(await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal)).toBeNull();
224
+ expect(decoder).not.toHaveBeenCalled();
225
+ expect(ref.state).toBe(ResourceResolutionState.Failed);
226
+ expect(ref.failure?.kind).toBe(ImageResourceFailureKind.Unavailable);
227
+ });
228
+
229
+ it('retains the missing-decoder cause when embedded bytes cannot be decoded', async () => {
230
+ clearImageDecoders();
231
+ const ref = createEmbeddedImageResourceReference(new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
232
+
233
+ expect(await resolveImageResourceReference(ref, unusedFetch, new AbortController().signal)).toBeNull();
234
+ expect(ref.failure).toEqual({
235
+ kind: ImageResourceFailureKind.Unavailable,
236
+ message: 'decoder-not-registered',
237
+ name: null,
238
+ });
239
+ });
240
+
115
241
  it('routes an external reference through the fetch seam', async () => {
116
242
  const ref = createExternalImageResourceReference('atlas.png', '/assets');
117
243
  const fetched = { width: 2 } as Image;
@@ -158,3 +284,16 @@ describe('resolveImageResourceReference', () => {
158
284
  expect(ref.state).toBe(ResourceResolutionState.Resolved);
159
285
  });
160
286
  });
287
+
288
+ function createTestBitmap(alphaType: Bitmap['alphaType']): Bitmap {
289
+ return createEntity({
290
+ alphaType,
291
+ data: new Uint8ClampedArray([0x11, 0x22, 0x33, 0x44]),
292
+ format: 'rgba8unorm',
293
+ gamut: 'srgb',
294
+ height: 1,
295
+ kind: BitmapTextureSourceKind,
296
+ version: 0,
297
+ width: 1,
298
+ });
299
+ }