@su-engineering/heic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +83 -0
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/SECURITY.md +70 -0
- package/dist/chunk-Y5FFG5J7.js +49 -0
- package/dist/chunk-Y5FFG5J7.js.map +1 -0
- package/dist/heic.global.js +2 -0
- package/dist/heic.global.js.map +1 -0
- package/dist/index.d.ts +445 -0
- package/dist/index.js +1648 -0
- package/dist/index.js.map +1 -0
- package/dist/types-Bv9KPnri.d.ts +112 -0
- package/dist/wasm.d.ts +38 -0
- package/dist/wasm.js +97 -0
- package/dist/wasm.js.map +1 -0
- package/docs/api.md +110 -0
- package/docs/compatibility.md +53 -0
- package/docs/releasing.md +42 -0
- package/package.json +95 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
Initial public release.
|
|
6
|
+
|
|
7
|
+
- Browser-first HEIC decoding with native, WebCodecs HEVC, and optional libheif WASM strategies.
|
|
8
|
+
- Single-image and tiled-grid planning with container rotation, mirroring, and clean-aperture cropping.
|
|
9
|
+
- Typed errors, source metadata, capability probes, and worker-compatible rendering.
|
|
10
|
+
- Reproducible build/test setup, public API documentation, and package-entry validation.
|
|
11
|
+
|
|
12
|
+
See [compatibility limitations](docs/compatibility.md) before deploying.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping improve HEIC decoding on the web. We welcome bug fixes, documentation improvements, and regression tests for files the parser or decoder mishandles.
|
|
4
|
+
|
|
5
|
+
## Local development
|
|
6
|
+
|
|
7
|
+
Use Node.js 22.12+ and pnpm 9.15.9 (declared in `package.json`). If your Node installation does not include Corepack, install pnpm 9.15.9 using your preferred package manager.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
git clone https://github.com/su-engineering/heic-web.git
|
|
11
|
+
cd heic-web
|
|
12
|
+
corepack enable
|
|
13
|
+
pnpm install --frozen-lockfile
|
|
14
|
+
pnpm typecheck
|
|
15
|
+
pnpm build
|
|
16
|
+
pnpm test:package
|
|
17
|
+
pnpm test:unit
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The package can be used before registry publication by building it and importing `dist/index.js`, or by installing a tarball created with `pnpm pack --pack-destination /tmp/heic-pack`. The WASM entry point is `dist/wasm.js`; its optional peer dependency must be available in the consuming project.
|
|
21
|
+
|
|
22
|
+
## Repository map
|
|
23
|
+
|
|
24
|
+
| Directory | Responsibility |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `src/parser/` | Bounds-checked ISOBMFF/HEIF parsing, item extraction, grid descriptors, and HEVC configuration. |
|
|
27
|
+
| `src/plan.ts` | Primary-image planning, dimensions, tile groups, transforms, and feature warnings. |
|
|
28
|
+
| `src/decoders/` | Native image decoding and WebCodecs HEVC decoding. |
|
|
29
|
+
| `src/render/` | Canvas compositing helpers and container transforms. |
|
|
30
|
+
| `wasm/` | Optional libheif adapter and its module declarations. |
|
|
31
|
+
| `test/unit/` | Parser tests and deterministic mutation fuzzing. |
|
|
32
|
+
| `test/browser/` | Public-API browser tests and worker harnesses. |
|
|
33
|
+
| `test/fixtures/` | Committed fixtures; private local corpus is ignored by Git. |
|
|
34
|
+
| `tools/` | Test server, package validation, fixture generation, and inspection tools. |
|
|
35
|
+
|
|
36
|
+
## Browser tests
|
|
37
|
+
|
|
38
|
+
Build and run `pnpm test:package` first; the harness imports `dist/` and the bundled consumer created by that check.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pnpm exec playwright install chromium firefox webkit
|
|
42
|
+
pnpm test:browser
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
On a supported Linux distribution, add `--with-deps` to install required system libraries. If a browser fails to launch on an unsupported distribution, run the suite in the same Ubuntu environment as CI rather than changing host libraries blindly.
|
|
46
|
+
|
|
47
|
+
The default projects are `chromium`, `firefox`, and `webkit`. To test a real Chromium installation with platform HEVC:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
HEIC_CHROME="/path/to/chrome" pnpm test:browser --project=chrome-hevc
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The environment variable adds that project to the matrix. Setting it does not create HEVC support; capability-dependent tests still skip if the installed browser cannot decode HEVC. Playwright WebKit is not a substitute for testing released Safari on an Apple device.
|
|
54
|
+
|
|
55
|
+
Run `pnpm test:all` after installing browsers for the combined checks. Record skipped tests as well as failures when reporting coverage.
|
|
56
|
+
|
|
57
|
+
## Reproducing a bug
|
|
58
|
+
|
|
59
|
+
Include the input's provenance, browser and OS versions, package version, strategy, and error name/message/context. For `HeicUnsupportedError`, include `attempts`. Explain the expected orientation, dimensions, or colors. A minimal redistributable fixture is preferable to a personal photograph.
|
|
60
|
+
|
|
61
|
+
For container inspection (Node.js 22.6+ with type stripping):
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pnpm dump path/to/photo.heic --boxes
|
|
65
|
+
pnpm validate-corpus path/to/corpus
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
See [fixture documentation](https://github.com/su-engineering/heic-web/blob/master/test/fixtures/README.md) for reference-image generation and manifest provenance. Fixture regeneration requires libheif command-line tools, ImageMagick, and ExifTool; normal builds and tests use committed fixtures and do not need those tools.
|
|
69
|
+
|
|
70
|
+
## Submitting a change
|
|
71
|
+
|
|
72
|
+
1. Create a focused branch from `master`.
|
|
73
|
+
2. Explain the observable problem and add a regression test for behavior changes.
|
|
74
|
+
3. Preserve typed parser errors and bounds/count checks. Avoid DOM dependencies in decode paths.
|
|
75
|
+
4. Run typecheck, build, package validation, and unit tests; run relevant browser tests for decoding changes.
|
|
76
|
+
5. Update documentation when API behavior or requirements change. For package changes, run `pnpm exec changeset` and add a concise release note.
|
|
77
|
+
6. Open a pull request explaining the change, validation, and any coverage gaps.
|
|
78
|
+
|
|
79
|
+
Do not commit `dist/`, browser reports, `node_modules/`, or the private photo corpus. Only contribute fixtures you have permission to redistribute, without sensitive metadata. Contributions are licensed under the repository's MIT license.
|
|
80
|
+
|
|
81
|
+
## Community expectations
|
|
82
|
+
|
|
83
|
+
Be respectful, explain disagreements with evidence, and focus reviews on the work. Do not harass contributors or disclose private information. Report conduct concerns privately to hello@su.engineering. Use the [security policy](SECURITY.md) for vulnerabilities.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SU Engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# @su-engineering/heic
|
|
2
|
+
|
|
3
|
+
**Decode HEIC photos in the browser with native decoding, WebCodecs, and an optional WebAssembly fallback.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/su-engineering/heic-web/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
HEIC uploads need not force every visitor to download a software codec. This TypeScript library parses the HEIF container, tries the browser's image decoder, then uses WebCodecs HEVC decoding when available. You choose whether to load the libheif fallback if both paths fail.
|
|
9
|
+
|
|
10
|
+
- **On-demand fallback:** the core entry point does not import libheif or fetch a codec.
|
|
11
|
+
- **Primary-image decoding:** single HEVC items and tiled grids, with container rotation, mirroring, and clean-aperture cropping.
|
|
12
|
+
- **Useful diagnostics:** selected strategy, source dimensions, color metadata, and warnings accompany each bitmap.
|
|
13
|
+
- **Worker support:** no DOM dependency; compositing uses `OffscreenCanvas`.
|
|
14
|
+
- **Typed API:** ESM, TypeScript declarations, and a standalone browser bundle.
|
|
15
|
+
|
|
16
|
+
The project is at **0.1.0**. Test it with representative files and target devices before production use. The repository is named `heic-web`; the npm package name is `@su-engineering/heic`.
|
|
17
|
+
|
|
18
|
+
[API reference](docs/api.md) · [Compatibility and limitations](docs/compatibility.md) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
When the package has been published to your registry:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install @su-engineering/heic
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For a checkout before publication, see [local development](CONTRIBUTING.md#local-development). Decoding runs in a browser or browser worker. Node.js can run the container parser, but this package does not provide a Node.js pixel decoder.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
This example assumes an `<input id="photo" type="file">` and `<canvas id="preview">` on the page. It uses only browser-provided decoders; unsupported environments receive a typed error.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { decodeHeic, isHeic, HeicUnsupportedError } from '@su-engineering/heic';
|
|
36
|
+
|
|
37
|
+
const input = document.querySelector<HTMLInputElement>('#photo')!;
|
|
38
|
+
const canvas = document.querySelector<HTMLCanvasElement>('#preview')!;
|
|
39
|
+
|
|
40
|
+
input.addEventListener('change', async () => {
|
|
41
|
+
const file = input.files?.[0];
|
|
42
|
+
if (!file || !(await isHeic(file)).isHeic) return;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const decoded = await decodeHeic(file, { maxDimension: 1024 });
|
|
46
|
+
try {
|
|
47
|
+
canvas.width = decoded.width;
|
|
48
|
+
canvas.height = decoded.height;
|
|
49
|
+
canvas.getContext('2d')!.drawImage(decoded.image, 0, 0);
|
|
50
|
+
console.log(decoded.strategy, decoded.warnings);
|
|
51
|
+
} finally {
|
|
52
|
+
decoded.image.close(); // Release pixels when you no longer need them.
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof HeicUnsupportedError) {
|
|
56
|
+
console.error('No decoder succeeded:', error.attempts);
|
|
57
|
+
} else {
|
|
58
|
+
console.error('Could not decode this photo:', error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`isHeic()` inspects file contents, rather than trusting the filename or MIME type. It is a routing hint, not a complete validation step. AVIF is not decoded by this package.
|
|
65
|
+
|
|
66
|
+
## Add the optional WASM fallback
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
npm install libheif-js
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { decodeHeic } from '@su-engineering/heic';
|
|
74
|
+
|
|
75
|
+
const decoded = await decodeHeic(file, {
|
|
76
|
+
wasmLoader: async () => {
|
|
77
|
+
const { wasmDecoder } = await import('@su-engineering/heic/wasm');
|
|
78
|
+
return wasmDecoder;
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
// Draw or transfer decoded.image, then close it when finished.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The separate entry point loads `libheif-js/wasm-bundle.js` on its first decode. A bundler supporting dynamic imports can keep the adapter and codec out of the initial chunk. Check your bundler's output: asset splitting and download sizes depend on your toolchain and the libheif version.
|
|
85
|
+
|
|
86
|
+
For self-hosted assets, custom builds, or direct browser imports, use [`createWasmAdapter`](docs/api.md#wasm-adapters). The core library is MIT licensed; optional libheif distributions have [their own licenses](docs/compatibility.md#third-party-code).
|
|
87
|
+
|
|
88
|
+
## Choose by capability
|
|
89
|
+
|
|
90
|
+
In `auto` mode the cascade is **native → WebCodecs → supplied fallback**. Without a fallback, a file that needs one fails with `HeicUnsupportedError`.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { probeSupport } from '@su-engineering/heic';
|
|
94
|
+
|
|
95
|
+
const support = await probeSupport();
|
|
96
|
+
console.log(support.native, support.webcodecs, support.recommended);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
HEVC support depends on the browser, OS, installed codecs, hardware, and file profile. A capability probe is advisory; decoding a particular file can still fail. Use HTTPS or localhost for WebCodecs. See the [compatibility guide](docs/compatibility.md) for requirements and test coverage.
|
|
100
|
+
|
|
101
|
+
## API at a glance
|
|
102
|
+
|
|
103
|
+
| Export | Purpose |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `decodeHeic(input, options?)` | Decode a `Blob`, `File`, `ArrayBuffer`, or `Uint8Array` to an `ImageBitmap` plus metadata. |
|
|
106
|
+
| `isHeic(input)` | Inspect up to the first 64 KiB for HEIC identification and coding hints. |
|
|
107
|
+
| `probeSupport()` | Probe native HEIC decoding and accepted WebCodecs HEVC configurations. |
|
|
108
|
+
| `parseHeif(buffer)` | Inspect container items, properties, references, and locations without decoding pixels. |
|
|
109
|
+
| `planDecode(buffer)` | Resolve the primary image, tile layout, transforms, and source metadata. |
|
|
110
|
+
| `registerDecoderAdapter(adapter)` | Register a fallback for subsequent calls in the current JavaScript realm. |
|
|
111
|
+
|
|
112
|
+
Common decode options are `strategy`, `maxDimension`, `colorSpace`, `signal`, and `wasmLoader`. The [API reference](docs/api.md) documents their behavior and the complete exported surface.
|
|
113
|
+
|
|
114
|
+
## Scope and limits
|
|
115
|
+
|
|
116
|
+
This library returns pixels for the primary HEVC image. It does not encode HEIC, supply an upload UI, preserve EXIF in the output, or decode AVIF, animation, or Live Photo video. Recognized alpha, depth, and HDR gain-map auxiliary items produce warnings; warning coverage is not exhaustive.
|
|
117
|
+
|
|
118
|
+
`maxDimension` reduces the returned bitmap size. **It does not cap peak decode memory:** decoding and compositing may still allocate the full-resolution image. Apply file-size limits, bound concurrent decodes, and use workers for large or untrusted uploads.
|
|
119
|
+
|
|
120
|
+
`display-p3` requests a compositing canvas color space; it is not a guarantee of color fidelity across strategies. See [color and HDR limitations](docs/compatibility.md#color-and-hdr).
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
git clone https://github.com/su-engineering/heic-web.git
|
|
126
|
+
cd heic-web
|
|
127
|
+
corepack enable
|
|
128
|
+
pnpm install --frozen-lockfile
|
|
129
|
+
pnpm typecheck
|
|
130
|
+
pnpm build
|
|
131
|
+
pnpm test:package
|
|
132
|
+
pnpm test:unit
|
|
133
|
+
pnpm exec playwright install --with-deps chromium firefox webkit
|
|
134
|
+
pnpm test:browser
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Use Node.js 22.12 or newer for development. The locked pnpm version is declared in `package.json`. Browser installation with `--with-deps` is intended for supported Linux distributions; on other platforms, see [test setup](CONTRIBUTING.md#browser-tests).
|
|
138
|
+
|
|
139
|
+
Headless Linux tests exercise the parser and fallback. They cannot establish platform HEVC coverage. Committed fixtures also do not cover the full range of Apple camera files. See [test fixtures](https://github.com/su-engineering/heic-web/blob/master/test/fixtures/README.md) and the [release checklist](docs/releasing.md).
|
|
140
|
+
|
|
141
|
+
## Help and contributions
|
|
142
|
+
|
|
143
|
+
Report reproducible bugs through [GitHub issues](https://github.com/su-engineering/heic-web/issues). Include browser/OS versions, the selected strategy or error context, and a redistributable sample when possible. Keep personal photographs and exploitable inputs out of public reports.
|
|
144
|
+
|
|
145
|
+
Small fixes, regression tests, and consented fixture contributions are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md). Report vulnerabilities privately using [SECURITY.md](SECURITY.md).
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
[MIT](LICENSE), copyright SU Engineering. See [third-party code](docs/compatibility.md#third-party-code) for optional codec licensing.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
This package parses untrusted, attacker-controlled binary input inside your
|
|
4
|
+
users' browsers. That is its whole job, so security reports are welcome and
|
|
5
|
+
taken seriously.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Email **hello@su.engineering** with `@su-engineering/heic` in the subject line.
|
|
10
|
+
|
|
11
|
+
Please include the file that triggers it if you can share one, or a script that
|
|
12
|
+
generates it. A reproducing input is worth more than a description.
|
|
13
|
+
|
|
14
|
+
Expect an acknowledgement within a few days. Please do not open a public issue
|
|
15
|
+
for anything that looks exploitable until a fix is out.
|
|
16
|
+
|
|
17
|
+
## Threat model
|
|
18
|
+
|
|
19
|
+
The input is hostile. The attacker controls every byte of the file. The attack
|
|
20
|
+
surface includes container parsing, decode planning, rendering, and the selected
|
|
21
|
+
browser or optional libheif codec. Upstream codecs are outside this repository,
|
|
22
|
+
but reports that help isolate a problem are welcome.
|
|
23
|
+
|
|
24
|
+
The parser and planner enforce these checks:
|
|
25
|
+
|
|
26
|
+
- **Binary reads are bounds-checked.** The `Reader` class validates its window
|
|
27
|
+
before accessing the buffer. Specialized payload and codec parsers also use
|
|
28
|
+
explicit bounds checks.
|
|
29
|
+
- **Box nesting is capped** at 32, and the number of sibling boxes at one level
|
|
30
|
+
at 65,536. These bound the box walker's recursion and sibling iteration.
|
|
31
|
+
- **No allocation on a declared size** without validating it against the real
|
|
32
|
+
buffer first. A malformed `extent_length` cannot trigger a 4 GB allocation; a
|
|
33
|
+
64-bit box size above `Number.MAX_SAFE_INTEGER` is rejected rather than
|
|
34
|
+
silently truncated.
|
|
35
|
+
- **Implausible dimensions are refused** before any canvas is created: 256
|
|
36
|
+
megapixels total, and at most 4,096 grid tiles.
|
|
37
|
+
- **A grid whose declared rows × columns disagrees with its `dimg` reference
|
|
38
|
+
list is rejected**, rather than decoded with a silently shifted mosaic.
|
|
39
|
+
- **File-supplied counts never drive an unbounded loop.** Every count that comes
|
|
40
|
+
out of a box has a ceiling.
|
|
41
|
+
- **Essential properties we do not understand cause a failure**, not a
|
|
42
|
+
confidently wrong image.
|
|
43
|
+
|
|
44
|
+
What it does not defend against:
|
|
45
|
+
|
|
46
|
+
- Bugs in the browser's own HEVC decoder or in libheif. Those are upstream.
|
|
47
|
+
- Resource exhaustion from a large valid image within the caps above.
|
|
48
|
+
`maxDimension` scales the final result and does not cap peak decode memory.
|
|
49
|
+
Enforce application input/dimension limits and bound concurrent decodes.
|
|
50
|
+
|
|
51
|
+
## This is a client-side decoder
|
|
52
|
+
|
|
53
|
+
Decoding a file here says nothing about whether it is safe to store, serve, or
|
|
54
|
+
trust. **Validate uploads on your server independently.** A file that decodes
|
|
55
|
+
cleanly in a browser can still be crafted to attack whatever handles it next.
|
|
56
|
+
|
|
57
|
+
## Testing
|
|
58
|
+
|
|
59
|
+
The parser is fuzzed against mutated real-world files on every run: byte flips,
|
|
60
|
+
truncation, hostile 32-bit size and count fields, zeroed runs, and spliced
|
|
61
|
+
regions. Every case must produce a typed `HeicError` rather than a crash, and
|
|
62
|
+
completed cases are checked against an elapsed-time budget. This synchronous
|
|
63
|
+
fuzzer does not interrupt an infinite loop; process-level CI timeouts remain
|
|
64
|
+
necessary.
|
|
65
|
+
|
|
66
|
+
## Supported versions
|
|
67
|
+
|
|
68
|
+
Security fixes target the latest released version. There is currently no
|
|
69
|
+
long-term support commitment for older releases. Use current browser and
|
|
70
|
+
optional codec versions, and update when a security fix is released.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var HeicError = class extends Error {
|
|
3
|
+
context;
|
|
4
|
+
constructor(message, context = {}, options) {
|
|
5
|
+
const detail = formatContext(context);
|
|
6
|
+
super(detail ? `${message} (${detail})` : message, options);
|
|
7
|
+
this.name = "HeicError";
|
|
8
|
+
this.context = context;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var HeicParseError = class extends HeicError {
|
|
12
|
+
constructor(message, context = {}, options) {
|
|
13
|
+
super(message, context, options);
|
|
14
|
+
this.name = "HeicParseError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var HeicUnsupportedError = class extends HeicError {
|
|
18
|
+
/** Strategies that were tried, and why each one was unavailable or failed. */
|
|
19
|
+
attempts;
|
|
20
|
+
constructor(message, attempts = [], context = {}, options) {
|
|
21
|
+
const summary = attempts.map((a) => `${a.strategy}: ${a.reason}`).join("; ");
|
|
22
|
+
super(summary ? `${message} [${summary}]` : message, context, options);
|
|
23
|
+
this.name = "HeicUnsupportedError";
|
|
24
|
+
this.attempts = attempts;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var HeicDecodeError = class extends HeicError {
|
|
28
|
+
constructor(message, context = {}, options) {
|
|
29
|
+
super(message, context, options);
|
|
30
|
+
this.name = "HeicDecodeError";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var HeicAbortError = class extends HeicError {
|
|
34
|
+
constructor(message = "Decode aborted", context = {}, options) {
|
|
35
|
+
super(message, context, options);
|
|
36
|
+
this.name = "HeicAbortError";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function formatContext(context) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
for (const [key, value] of Object.entries(context)) {
|
|
42
|
+
if (value !== void 0) parts.push(`${key}=${value}`);
|
|
43
|
+
}
|
|
44
|
+
return parts.join(" ");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { HeicAbortError, HeicDecodeError, HeicError, HeicParseError, HeicUnsupportedError };
|
|
48
|
+
//# sourceMappingURL=chunk-Y5FFG5J7.js.map
|
|
49
|
+
//# sourceMappingURL=chunk-Y5FFG5J7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"names":[],"mappings":";AAuBO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EAC1B,OAAA;AAAA,EAET,WAAA,CAAY,OAAA,EAAiB,OAAA,GAA4B,IAAI,OAAA,EAAwB;AACnF,IAAA,MAAM,MAAA,GAAS,cAAc,OAAO,CAAA;AACpC,IAAA,KAAA,CAAM,SAAS,CAAA,EAAG,OAAO,KAAK,MAAM,CAAA,CAAA,CAAA,GAAM,SAAS,OAAO,CAAA;AAC1D,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AACF;AAGO,IAAM,cAAA,GAAN,cAA6B,SAAA,CAAU;AAAA,EAC5C,WAAA,CAAY,OAAA,EAAiB,OAAA,GAA4B,IAAI,OAAA,EAAwB;AACnF,IAAA,KAAA,CAAM,OAAA,EAAS,SAAS,OAAO,CAAA;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AAAA,EACd;AACF;AAGO,IAAM,oBAAA,GAAN,cAAmC,SAAA,CAAU;AAAA;AAAA,EAEzC,QAAA;AAAA,EAET,WAAA,CACE,SACA,QAAA,GAAgE,IAChE,OAAA,GAA4B,IAC5B,OAAA,EACA;AACA,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,CAAC,MAAM,CAAA,EAAG,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK,CAAA,CAAE,MAAM,CAAA,CAAE,CAAA,CAAE,KAAK,IAAI,CAAA;AAC3E,IAAA,KAAA,CAAM,OAAA,GAAU,GAAG,OAAO,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA,CAAA,GAAM,OAAA,EAAS,SAAS,OAAO,CAAA;AACrE,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AACZ,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AACF;AAGO,IAAM,eAAA,GAAN,cAA8B,SAAA,CAAU;AAAA,EAC7C,WAAA,CAAY,OAAA,EAAiB,OAAA,GAA4B,IAAI,OAAA,EAAwB;AACnF,IAAA,KAAA,CAAM,OAAA,EAAS,SAAS,OAAO,CAAA;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EACd;AACF;AAGO,IAAM,cAAA,GAAN,cAA6B,SAAA,CAAU;AAAA,EAC5C,YAAY,OAAA,GAAU,gBAAA,EAAkB,OAAA,GAA4B,IAAI,OAAA,EAAwB;AAC9F,IAAA,KAAA,CAAM,OAAA,EAAS,SAAS,OAAO,CAAA;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,gBAAA;AAAA,EACd;AACF;AAEA,SAAS,cAAc,OAAA,EAAmC;AACxD,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AAClD,IAAA,IAAI,KAAA,KAAU,QAAW,KAAA,CAAM,IAAA,CAAK,GAAG,GAAG,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,CAAA;AAAA,EACvD;AACA,EAAA,OAAO,KAAA,CAAM,KAAK,GAAG,CAAA;AACvB","file":"chunk-Y5FFG5J7.js","sourcesContent":["/**\n * Typed error hierarchy. The parser consumes hostile input and the decode path\n * fails in browser-specific ways, so every throw carries enough context to file\n * a useful bug report without the reporter having to reproduce it.\n */\n\nexport interface HeicErrorContext {\n /** ftyp major brand, when we got far enough to read it. */\n brand?: string | undefined;\n /** item_type of the item being worked on ('grid', 'hvc1', ...). */\n itemType?: string | undefined;\n /** Item ID being worked on. */\n itemId?: number | undefined;\n /** Strategy that produced the failure. */\n strategy?: string | undefined;\n /** Codec string handed to VideoDecoder. */\n codec?: string | undefined;\n /** Byte offset in the source buffer, for parse failures. */\n offset?: number | undefined;\n /** Four-character box type being read, for parse failures. */\n box?: string | undefined;\n}\n\nexport class HeicError extends Error {\n readonly context: HeicErrorContext;\n\n constructor(message: string, context: HeicErrorContext = {}, options?: ErrorOptions) {\n const detail = formatContext(context);\n super(detail ? `${message} (${detail})` : message, options);\n this.name = 'HeicError';\n this.context = context;\n }\n}\n\n/** The file is malformed, truncated, or uses a container feature we refuse to guess at. */\nexport class HeicParseError extends HeicError {\n constructor(message: string, context: HeicErrorContext = {}, options?: ErrorOptions) {\n super(message, context, options);\n this.name = 'HeicParseError';\n }\n}\n\n/** The file is well-formed but this environment cannot decode it. */\nexport class HeicUnsupportedError extends HeicError {\n /** Strategies that were tried, and why each one was unavailable or failed. */\n readonly attempts: ReadonlyArray<{ strategy: string; reason: string }>;\n\n constructor(\n message: string,\n attempts: ReadonlyArray<{ strategy: string; reason: string }> = [],\n context: HeicErrorContext = {},\n options?: ErrorOptions,\n ) {\n const summary = attempts.map((a) => `${a.strategy}: ${a.reason}`).join('; ');\n super(summary ? `${message} [${summary}]` : message, context, options);\n this.name = 'HeicUnsupportedError';\n this.attempts = attempts;\n }\n}\n\n/** A decoder was available and accepted the config, but decoding failed. */\nexport class HeicDecodeError extends HeicError {\n constructor(message: string, context: HeicErrorContext = {}, options?: ErrorOptions) {\n super(message, context, options);\n this.name = 'HeicDecodeError';\n }\n}\n\n/** The caller's AbortSignal fired. */\nexport class HeicAbortError extends HeicError {\n constructor(message = 'Decode aborted', context: HeicErrorContext = {}, options?: ErrorOptions) {\n super(message, context, options);\n this.name = 'HeicAbortError';\n }\n}\n\nfunction formatContext(context: HeicErrorContext): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(context)) {\n if (value !== undefined) parts.push(`${key}=${value}`);\n }\n return parts.join(' ');\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var HeicDecoder=(()=>{var re=Object.defineProperty;var $e=Object.getOwnPropertyDescriptor;var Re=Object.getOwnPropertyNames;var Ue=Object.prototype.hasOwnProperty;var Me=(t,e)=>{for(var r in e)re(t,r,{get:e[r],enumerable:!0})},Ne=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Re(e))!Ue.call(t,o)&&o!==r&&re(t,o,{get:()=>e[o],enumerable:!(n=$e(e,o))||n.enumerable});return t};var ke=t=>Ne(re({},"__esModule",{value:!0}),t);var St={};Me(St,{HeicAbortError:()=>I,HeicDecodeError:()=>w,HeicError:()=>D,HeicParseError:()=>d,HeicUnsupportedError:()=>x,decodeHeic:()=>Pt,findProperty:()=>C,getRegisteredAdapter:()=>Bt,hvccToAnnexBPrologue:()=>j,hvccToCodecString:()=>U,isHeic:()=>Ct,lengthPrefixedToAnnexB:()=>q,parseGridPayload:()=>ne,parseHeif:()=>N,parseHvcC:()=>V,planDecode:()=>Z,probeSupport:()=>Te,propertiesForItem:()=>T,readGrid:()=>Y,readItemData:()=>k,registerDecoderAdapter:()=>Tt});function X(t){return new Blob([t],{type:"image/heic"})}var D=class extends Error{context;constructor(e,r={},n){let o=Fe(r);super(o?`${e} (${o})`:e,n),this.name="HeicError",this.context=r}},d=class extends D{constructor(e,r={},n){super(e,r,n),this.name="HeicParseError"}},x=class extends D{attempts;constructor(e,r=[],n={},o){let i=r.map(s=>`${s.strategy}: ${s.reason}`).join("; ");super(i?`${e} [${i}]`:e,n,o),this.name="HeicUnsupportedError",this.attempts=r}},w=class extends D{constructor(e,r={},n){super(e,r,n),this.name="HeicDecodeError"}},I=class extends D{constructor(e="Decode aborted",r={},n){super(e,r,n),this.name="HeicAbortError"}};function Fe(t){let e=[];for(let[r,n]of Object.entries(t))n!==void 0&&e.push(`${r}=${n}`);return e.join(" ")}function S(t,e){if(typeof OffscreenCanvas>"u")throw new w("OffscreenCanvas is not available; this environment cannot composite",{});return new OffscreenCanvas(t,e)}function A(t){if(t?.aborted)throw new I}var pe=!1;async function fe(t,e,r){if(typeof createImageBitmap>"u")return{status:"unsupported",reason:"createImageBitmap is not available"};if(pe)return{status:"unsupported",reason:"this browser has no HEIC image decoder"};A(r);let n;try{n=await createImageBitmap(t)}catch{return pe=!0,{status:"unsupported",reason:"createImageBitmap rejected the file"}}if(A(r),!Le(n,e)){let o=`${n.width}x${n.height}`;return n.close(),{status:"wrong-image",reason:`returned ${o}, expected ${e.displayWidth}x${e.displayHeight}`}}return{status:"ok",bitmap:n}}function Le(t,e){let{displayWidth:r,displayHeight:n}=e,o=t.width===r&&t.height===n,i=t.width===n&&t.height===r;return o||i}async function ue(){if(typeof createImageBitmap>"u")return!1;try{let t=_e(ze),e=await createImageBitmap(X(t)),r=e.width===Ge&&e.height===We;return e.close(),r}catch{return!1}}function _e(t){let e=atob(t),r=new Uint8Array(e.length);for(let n=0;n<e.length;n++)r[n]=e.charCodeAt(n);return r}var Ge=2,We=2,ze="AAAAHGZ0eXBoZWljAAAAAG1pZjFoZWljbWlhZgAAAXxtZXRhAAAAAAAAACFoZGxyAAAAAAAAAABwaWN0AAAAAAAAAAAAAAAAAAAAACJpbG9jAAAAAERAAAEAAQAAAAABoAABAAAAAAAAADcAAAAjaWluZgAAAAAAAQAAABVpbmZlAgAAAAABAABodmMxAAAAAA5waXRtAAAAAAABAAAA/GlwcnAAAADcaXBjbwAAAHVodmNDAQNwAAAAAAAAAAAAHvAA/P34+AAADwNgAAEAGEABDAH//wNwAAADAJAAAAMAAAMAHroCQGEAAQApQgEBA3AAAAMAkAAAAwAAAwAeoCCBBZbqrprm4CGgwIAAAAyAAAADAIRiAAEABkQBwXPBiQAAABNjb2xybmNseAABAA0ABoAAAAAUaXNwZQAAAAAAAABAAAAAQAAAAChjbGFwAAAAAgAAAAEAAAACAAAAAf///8IAAAAC////wgAAAAIAAAAQcGl4aQAAAAADCAgIAAAAGGlwbWEAAAAAAAAAAQABBYECAwWEAAAAP21kYXQAAAAzKAGvBjIWhzSJIPC/cov//8tX9l+i9qzWyeuEfoBjx+S3kJGe9F97GFLlPHQg9JxTuc2A";var O=class t{bytes;view;base;pos=0;constructor(e,r=0,n){let o=e instanceof Uint8Array?e:new Uint8Array(e),i=o.byteOffset+r,s=n??o.byteLength-r;if(r<0||s<0||r+s>o.byteLength)throw new d("Reader window is outside the source buffer",{offset:r});this.bytes=new Uint8Array(o.buffer,i,s),this.view=new DataView(o.buffer,i,s),this.base=i}get length(){return this.bytes.byteLength}get offset(){return this.pos}get absoluteOffset(){return this.base+this.pos}get remaining(){return this.length-this.pos}get eof(){return this.pos>=this.length}seek(e){this.require(0,e),this.pos=e}skip(e){this.require(e),this.pos+=e}require(e,r=this.pos){if(!Number.isFinite(e)||e<0||!Number.isFinite(r)||r<0)throw new d("Malformed read request",{offset:this.base+this.pos});if(r+e>this.length)throw new d(`Read of ${e} bytes at ${r} exceeds the ${this.length}-byte window`,{offset:this.base+r})}u8(){return this.require(1),this.view.getUint8(this.pos++)}u16(){this.require(2);let e=this.view.getUint16(this.pos);return this.pos+=2,e}u24(){this.require(3);let e=this.view.getUint8(this.pos)<<16|this.view.getUint8(this.pos+1)<<8|this.view.getUint8(this.pos+2);return this.pos+=3,e>>>0}u32(){this.require(4);let e=this.view.getUint32(this.pos);return this.pos+=4,e>>>0}u64(){this.require(8);let e=this.view.getBigUint64(this.pos);if(this.pos+=8,e>BigInt(Number.MAX_SAFE_INTEGER))throw new d("64-bit value exceeds the safe integer range",{offset:this.base+this.pos-8});return Number(e)}uint(e){switch(e){case 0:return 0;case 1:return this.u8();case 2:return this.u16();case 4:return this.u32();case 8:return this.u64();default:throw new d(`Unsupported integer width: ${e} bytes`,{offset:this.base+this.pos})}}fourCC(){this.require(4);let e="";for(let r=0;r<4;r++){let n=this.view.getUint8(this.pos+r);e+=n>=32&&n<=126?String.fromCharCode(n):`\\x${n.toString(16).padStart(2,"0")}`}return this.pos+=4,e}cString(){let e=this.pos;for(;this.pos<this.length&&this.bytes[this.pos]!==0;)this.pos++;let r=this.bytes.subarray(e,this.pos);return this.pos<this.length&&this.pos++,new TextDecoder().decode(r)}view_(e){this.require(e);let r=this.bytes.subarray(this.pos,this.pos+e);return this.pos+=e,r}copy(e){return new Uint8Array(this.view_(e))}sub(e){this.require(e);let r=new t(this.bytes,this.pos,e);return this.pos+=e,r}peekRest(){return new t(this.bytes,this.pos,this.remaining)}};function v(t){let e=t.u8(),r=t.u24();return{version:e,flags:r}}var Xe=32,Ve=33,je=34,qe=32,Qe=256;function V(t){let e=t.peekRest().bytes,r=t.u8();if(r!==1)throw new d(`Unsupported HEVCDecoderConfigurationRecord version ${r}`,{box:"hvcC"});let n=t.u8(),o=n>>6&3,i=n>>5&1,s=n&31,a=t.u32(),c=t.copy(6),p=t.u8(),u=t.u16()&4095,m=t.u8()&3,f=t.u8()&3,l=t.u8()&7,g=t.u8()&7,y=t.u16(),h=t.u8(),B=h>>6&3,P=h>>3&7,H=h>>2&1,$=h&3,E=t.u8();if(E>qe)throw new d(`hvcC declares ${E} NAL arrays`,{box:"hvcC"});let W=[];for(let R=0;R<E;R++){let z=t.u8(),L=(z>>7&1)===1,_=z&63,te=t.u16();if(te>Qe)throw new d(`hvcC array declares ${te} NAL units`,{box:"hvcC"});let ae=[];for(let ce=0;ce<te;ce++){let Oe=t.u16();ae.push(t.view_(Oe))}W.push({arrayCompleteness:L,nalUnitType:_,nalus:ae})}return{configurationVersion:r,generalProfileSpace:o,generalTierFlag:i,generalProfileIdc:s,generalProfileCompatibilityFlags:a,generalConstraintIndicatorFlags:c,generalLevelIdc:p,minSpatialSegmentationIdc:u,parallelismType:m,chromaFormat:f,bitDepthLumaMinus8:l,bitDepthChromaMinus8:g,avgFrameRate:y,constantFrameRate:B,numTemporalLayers:P,temporalIdNested:H,lengthSizeMinusOne:$,arrays:W,raw:e}}var Ye=["","A","B","C"];function U(t,e="hvc1"){let n=`${Ye[t.generalProfileSpace]??""}${t.generalProfileIdc}`,o=Ze(t.generalProfileCompatibilityFlags).toString(16),s=`${t.generalTierFlag===1?"H":"L"}${t.generalLevelIdc}`,a=[...t.generalConstraintIndicatorFlags];for(;a.length>0&&a[a.length-1]===0;)a.pop();let c=a.map(p=>p.toString(16).padStart(2,"0").toUpperCase());return[e,n,o,s,...c].join(".")}function Ze(t){let e=t>>>0;return e=(e&1431655765)<<1|e>>>1&1431655765,e=(e&858993459)<<2|e>>>2&858993459,e=(e&252645135)<<4|e>>>4&252645135,e=(e&16711935)<<8|e>>>8&16711935,e=e>>>16|e<<16,e>>>0}function de(t){return t.bitDepthLumaMinus8+8}function j(t){let r=[Xe,Ve,je].flatMap(s=>t.arrays.filter(a=>a.nalUnitType===s)).flatMap(s=>s.nalus),n=0;for(let s of r)n+=4+s.byteLength;let o=new Uint8Array(n),i=0;for(let s of r)o.set([0,0,0,1],i),i+=4,o.set(s,i),i+=s.byteLength;return o}function q(t,e){if(e<1||e>4)throw new d(`Invalid NAL length size ${e}`,{box:"hvcC"});let r=new Uint8Array(t.byteLength+Je(t,e)*(4-e)),n=0,o=0;for(;n+e<=t.byteLength;){let i=0;for(let s=0;s<e;s++)i=i<<8|t[n+s];if(n+=e,i<0||n+i>t.byteLength)throw new d("NAL unit length runs past the end of the item payload",{offset:n});r.set([0,0,0,1],o),o+=4,r.set(t.subarray(n,n+i),o),o+=i,n+=i}return r.subarray(0,o)}function Je(t,e){let r=0,n=0;for(;r+e<=t.byteLength;){let o=0;for(let i=0;i<e;i++)o=o<<8|t[r+i];if(r+=e+o,o<0||r>t.byteLength)break;n++}return n}var me=32,le=65536;function*G(t,e={}){let{depth:r=0,lenient:n=!1}=typeof e=="number"?{depth:e,lenient:!1}:e;if(r>me)throw new d(`Box nesting deeper than ${me}`,{offset:t.absoluteOffset});let o=0;for(;t.remaining>=8;){if(++o>le)throw new d(`More than ${le} sibling boxes at one level`,{offset:t.absoluteOffset});let i=t.absoluteOffset,s=t.offset,a=t.u32(),c=t.fourCC(),p=8;if(a===1?(a=t.u64(),p=16):a===0&&(a=t.length-s),a<p){if(n)return;throw new d(`Box size ${a} is smaller than its ${p}-byte header`,{offset:i,box:c})}if(s+a>t.length){if(n)return;throw new d(`Box extends ${s+a-t.length} bytes past its container`,{offset:i,box:c})}let u=a-p,m=t.sub(u);yield{type:c,offset:i,size:a,headerSize:p,body:m},t.seek(s+a)}}function M(t,e={}){return[...G(t,e)]}function b(t,e){return t.find(r=>r.type===e)}function he(t,e){return t.filter(r=>r.type===e)}var Q=65536,Ke=4096,ge=4096,et=256,tt=8192;function rt(t){let e=t.body,{version:r,flags:n}=v(e),o=(n&1)===1;if(r>=2){let p=r===2?e.u16():e.u32(),u=e.u16(),m=e.fourCC(),f=e.cString(),l={itemId:p,protectionIndex:u,itemType:m,itemName:f,hidden:o};return m==="mime"&&(l.contentType=e.cString()),l}let i=e.u16(),s=e.u16(),a=e.cString(),c=e.cString();return{itemId:i,protectionIndex:s,itemType:"",itemName:a,contentType:c,hidden:o}}function nt(t){let e=t.body,{version:r}=v(e),n=r===0?e.u16():e.u32();if(n>Q)throw new d(`iinf declares ${n} items`,{box:"iinf"});let o=new Map,i=0;for(let s of G(e.peekRest())){if(s.type!=="infe")continue;if(++i>Q)break;let a=rt(s);o.set(a.itemId,a)}return o}function ot(t){let e=t.body,{version:r}=v(e),n=e.u8(),o=n>>4&15,i=n&15,s=e.u8(),a=s>>4&15,c=r===1||r===2?s&15:0,p=r<2?e.u16():e.u32();if(p>Q)throw new d(`iloc declares ${p} items`,{box:"iloc"});let u=new Map;for(let m=0;m<p;m++){let f=r<2?e.u16():e.u32(),l=0;(r===1||r===2)&&(l=e.u16()&15),e.u16();let g=e.uint(a),y=e.u16();if(y>Ke)throw new d(`Item ${f} declares ${y} extents`,{box:"iloc",itemId:f});let h=[];for(let B=0;B<y;B++){(r===1||r===2)&&c>0&&e.uint(c);let P=e.uint(o),H=e.uint(i);h.push({offset:P,length:H})}u.set(f,{itemId:f,constructionMethod:l,baseOffset:g,extents:h})}return u}function it(t){let e=t.body;switch(t.type){case"ispe":return v(e),{type:"ispe",width:e.u32(),height:e.u32()};case"hvcC":return{type:"hvcC",hvcc:V(e)};case"irot":return{type:"irot",angle:(e.u8()&3)*90};case"imir":return{type:"imir",axis:e.u8()&1};case"colr":{let r=e.fourCC();if(r==="nclx"){let n=e.u16(),o=e.u16(),i=e.u16(),s=(e.u8()&128)!==0;return{type:"colr",colorType:"nclx",primaries:n,transfer:o,matrix:i,fullRange:s}}return r==="rICC"||r==="prof"?{type:"colr",colorType:"icc",profile:e.copy(e.remaining)}:{type:"unknown",boxType:`colr:${r}`}}case"pixi":{v(e);let r=e.u8(),n=[];for(let o=0;o<r;o++)n.push(e.u8());return{type:"pixi",bitsPerChannel:n}}case"clap":return{type:"clap",widthN:e.u32(),widthD:e.u32(),heightN:e.u32(),heightD:e.u32(),horizOffN:e.u32()|0,horizOffD:e.u32(),vertOffN:e.u32()|0,vertOffD:e.u32()};case"auxC":return v(e),{type:"auxC",auxType:e.cString()};default:return{type:"unknown",boxType:t.type}}}function st(t,e){let r=t.body,{version:n,flags:o}=v(r),i=(o&1)===1,s=r.u32();if(s>Q)throw new d(`ipma declares ${s} entries`,{box:"ipma"});for(let a=0;a<s;a++){let c=n===0?r.u16():r.u32(),p=r.u8();if(p>et)throw new d(`Item ${c} declares ${p} properties`,{box:"ipma",itemId:c});let u=[];for(let f=0;f<p;f++)if(i){let l=r.u16();u.push({essential:(l&32768)!==0,index:l&32767})}else{let l=r.u8();u.push({essential:(l&128)!==0,index:l&127})}let m=e.get(c);m?m.push(...u):e.set(c,u)}}function at(t){let e=M(t.body),r=b(e,"ipco"),n=[];if(r)for(let i of G(r.body)){if(n.length>=ge)throw new d(`ipco holds more than ${ge} properties`,{box:"ipco"});n.push(it(i))}let o=new Map;for(let i of he(e,"ipma"))st(i,o);return{properties:n,associations:o}}function ct(t){let e=t.body,{version:r}=v(e),n=new Map;for(let o of G(e.peekRest())){let i=o.body,s=r===0?i.u16():i.u32(),a=i.u16();if(a>tt)throw new d(`Item ${s} declares ${a} references`,{box:o.type,itemId:s});let c=[];for(let u=0;u<a;u++)c.push(r===0?i.u16():i.u32());let p=n.get(o.type);p||n.set(o.type,p=new Map),p.set(s,c)}return n}function N(t,e={}){let r=t instanceof Uint8Array?t:new Uint8Array(t),n=new O(r),o=M(n,{lenient:e.truncated===!0}),i=b(o,"ftyp");if(!i)throw new d("No 'ftyp' box: this is not an ISOBMFF file",{offset:0});let s=i.body.fourCC(),a=i.body.u32(),c=[];for(;i.body.remaining>=4;)c.push(i.body.fourCC());let p=b(o,"meta");if(!p)throw new d("No 'meta' box: not a HEIF image file",{brand:s});v(p.body);let u=M(p.body,1),m=b(u,"hdlr"),f="";if(m&&(v(m.body),m.body.u32(),f=m.body.fourCC()),f&&f!=="pict")throw new d(`meta handler is '${f}', expected 'pict'`,{brand:s});let l=0,g=b(u,"pitm");if(g){let{version:L}=v(g.body);l=L===0?g.body.u16():g.body.u32()}let y=b(u,"iinf"),h=y?nt(y):new Map,B=b(u,"iloc"),P=B?ot(B):new Map,H=b(u,"iprp"),$=H?at(H):{properties:[],associations:new Map},E=b(u,"iref"),W=E?ct(E):new Map,R=b(u,"idat"),z=R?R.body.copy(R.body.remaining):void 0;if(l===0){for(let[L,_]of h)if(_.itemType==="hvc1"||_.itemType==="hev1"||_.itemType==="grid"){l=L;break}}return{majorBrand:s,minorVersion:a,compatibleBrands:c,primaryItemId:l,handlerType:f,items:h,locations:P,itemProperties:$,references:W,itemData:z,source:r}}function T(t,e){let r=t.itemProperties.associations.get(e)??[],n=[];for(let o of r){if(o.index===0)continue;let i=t.itemProperties.properties[o.index-1];if(!i)throw new d(`Item ${e} references property ${o.index}, but ipco holds ${t.itemProperties.properties.length}`,{itemId:e,box:"ipma"});if(o.essential&&i.type==="unknown")throw new d(`Item ${e} requires unsupported essential property '${i.boxType}'`,{itemId:e,box:i.boxType});n.push(i)}return n}function C(t,e){return t.find(r=>r.type===e)}function k(t,e){let r=t.locations.get(e);if(!r)throw new d(`No iloc entry for item ${e}`,{itemId:e,box:"iloc"});let n=t.items.get(e),o={itemId:e,itemType:n?.itemType,box:"iloc"},i;switch(r.constructionMethod){case 0:i=t.source;break;case 1:if(!t.itemData)throw new d(`Item ${e} points into 'idat', but the file has no idat box`,o);i=t.itemData;break;case 2:throw new d(`Item ${e} uses construction_method 2 (item offset), which is not supported`,o);default:throw new d(`Item ${e} uses unknown construction_method ${r.constructionMethod}`,o)}let s=0;for(let p of r.extents){let u=r.baseOffset+p.offset,m=p.length===0?i.byteLength-u:p.length;if(u<0||m<0||u+m>i.byteLength)throw new d(`Item ${e} extent [${u}, ${u+m}) is outside its ${i.byteLength}-byte container`,o);s+=m}if(r.extents.length===1){let p=r.extents[0],u=r.baseOffset+p.offset;return i.subarray(u,u+s)}let a=new Uint8Array(s),c=0;for(let p of r.extents){let u=r.baseOffset+p.offset,m=p.length===0?i.byteLength-u:p.length;a.set(i.subarray(u,u+m),c),c+=m}return a}var ye=4096;function ne(t){let e=new O(t),r=e.u8();if(r!==0)throw new d(`Unsupported grid version ${r}`,{itemType:"grid"});let o=(e.u8()&1)===1,i=e.u8()+1,s=e.u8()+1,a=o?e.u32():e.u16(),c=o?e.u32():e.u16();return{rows:i,columns:s,outputWidth:a,outputHeight:c}}function Y(t,e,r=[]){let n=k(t,e),{rows:o,columns:i,outputWidth:s,outputHeight:a}=ne(n),c=t.references.get("dimg")?.get(e)??[];if(c.length===0)throw new d(`Grid item ${e} has no 'dimg' tile references`,{itemId:e,itemType:"grid"});let p=o*i;if(p!==c.length)throw new d(`Grid item ${e} declares ${o}x${i} = ${p} tiles but 'dimg' lists ${c.length}`,{itemId:e,itemType:"grid"});if(p>ye)throw new d(`Grid item ${e} declares ${p} tiles (max ${ye})`,{itemId:e,itemType:"grid"});let u=s,m=a,f=C(T(t,e),"ispe");return f&&(f.width!==s||f.height!==a)&&(r.push({code:"grid-dimension-mismatch",message:`Grid payload declares ${s}x${a} but ispe declares ${f.width}x${f.height}; using ispe`}),u=f.width,m=f.height),{rows:o,columns:i,outputWidth:u,outputHeight:m,tileItemIds:c}}var Ae=256e6;function Z(t){let e=N(t),r=[],n=e.primaryItemId,o=e.items.get(n);if(!o)throw new d(`Primary item ${n} is not described by iinf`,{brand:e.majorBrand,itemId:n});let i=o.itemType==="grid";if(!i&&o.itemType!=="hvc1"&&o.itemType!=="hev1")throw new x(`Primary item type '${o.itemType}' is not a supported image item`,[],{brand:e.majorBrand,itemType:o.itemType,itemId:n});let s=T(e,n),a=[],c,p;if(i){let h=Y(e,n,r);c=h.outputWidth,p=h.outputHeight;let B=T(e,h.tileItemIds[0]),P=C(B,"ispe");if(!P)throw new d(`Grid tile ${h.tileItemIds[0]} has no ispe`,{itemId:h.tileItemIds[0]});for(let[H,$]of h.tileItemIds.entries()){let E=C(T(e,$),"ispe")??P;a.push({itemId:$,x:H%h.columns*P.width,y:Math.floor(H/h.columns)*P.height,width:E.width,height:E.height})}}else{let h=C(s,"ispe");if(!h)throw new d(`Primary item ${n} has no ispe`,{itemId:n});c=h.width,p=h.height,a.push({itemId:n,x:0,y:0,width:h.width,height:h.height})}if(c<=0||p<=0)throw new d(`Implausible image dimensions ${c}x${p}`,{itemId:n});if(c*p>Ae)throw new x(`Image is ${c}x${p}, above the ${Ae}-pixel limit`,[],{itemId:n});let u=pt(e,a,r),m=ft(s,c,p),{displayWidth:f,displayHeight:l}=dt(c,p,m),y=C(s,"pixi")?.bitsPerChannel[0]??de(u[0].hvcc);return lt(e,n,r),{file:e,primaryItemId:n,isGrid:i,codedWidth:c,codedHeight:p,displayWidth:f,displayHeight:l,tiles:a,tileGroups:u,transforms:m,bitDepth:y,sourceColor:mt(s,T(e,a[0].itemId)),warnings:r}}function pt(t,e,r){let n=new Map;for(let[i,s]of e.entries()){let c=(t.itemProperties.associations.get(s.itemId)??[]).find(u=>t.itemProperties.properties[u.index-1]?.type==="hvcC");if(!c)throw new d(`Item ${s.itemId} has no hvcC property`,{itemId:s.itemId});let p=n.get(c.index);if(!p){let u=t.itemProperties.properties[c.index-1];if(u?.type!=="hvcC")throw new d(`Property ${c.index} is not an hvcC`,{itemId:s.itemId});p={configIndex:c.index,hvcc:u.hvcc,codec:U(u.hvcc),tileIndices:[]},n.set(c.index,p)}p.tileIndices.push(i)}let o=[...n.values()];if(o.length===0)throw new d("No decoder configuration found for any tile",{});return o.length>1&&r.push({code:"mixed-tile-configs",message:`Tiles use ${o.length} different decoder configurations; decoding in ${o.length} groups`}),o}function ft(t,e,r){let n=[],o=e,i=r;for(let s of t)switch(s.type){case"clap":{let a=ut(s,o,i);a&&(n.push(a),o=a.width,i=a.height);break}case"irot":s.angle!==0&&(n.push({kind:"rotate",angle:s.angle}),(s.angle===90||s.angle===270)&&([o,i]=[i,o]));break;case"imir":n.push({kind:"mirror",axis:s.axis});break;default:break}return n}function ut(t,e,r){if(t.widthD===0||t.heightD===0||t.horizOffD===0||t.vertOffD===0)return;let n=Math.round(t.widthN/t.widthD),o=Math.round(t.heightN/t.heightD),i=t.horizOffN/t.horizOffD,s=t.vertOffN/t.vertOffD,a=Math.round((e-n)/2+i),c=Math.round((r-o)/2+s);if(!(n<=0||o<=0)&&!(n===e&&o===r&&a===0&&c===0)&&!(a<0||c<0||a+n>e||c+o>r))return{kind:"crop",width:n,height:o,offsetX:a,offsetY:c}}function dt(t,e,r){let n=t,o=e;for(let i of r)i.kind==="crop"?(n=i.width,o=i.height):i.kind==="rotate"&&(i.angle===90||i.angle===270)&&([n,o]=[o,n]);return{displayWidth:n,displayHeight:o}}function mt(t,e){let r=C(t,"colr")??C(e,"colr");return r?r.colorType==="nclx"?{type:"nclx",primaries:r.primaries,transfer:r.transfer,matrix:r.matrix,fullRange:r.fullRange}:{type:"icc",profile:r.profile}:null}function lt(t,e,r){let n=new Set,o=s=>{n.has(s.code)||(n.add(s.code),r.push(s))},i=t.references.get("auxl");if(i)for(let[s,a]of i){if(!a.includes(e))continue;let c=C(T(t,s),"auxC")?.auxType??"";/alpha/i.test(c)?o({code:"alpha-ignored",message:`Alpha aux image ${s} ignored`}):/depth|disparity/i.test(c)?o({code:"depth-ignored",message:`Depth aux image ${s} ignored`}):/hdrgainmap|gainmap/i.test(c)&&o({code:"gain-map-ignored",message:`HDR gain map ${s} ignored; the image decodes as SDR`})}for(let s of t.items.values())if(s.itemType==="tmap"){o({code:"gain-map-ignored",message:`Tone-map item ${s.itemId} ignored; the image decodes as SDR`});break}}function be(t,e){return k(t.file,e.itemId)}function F(){return typeof VideoDecoder<"u"&&typeof EncodedVideoChunk<"u"}async function ht(t,e,r){let n=t.hvcc.lengthSizeMinusOne+1,o=[],i={codec:t.codec,description:new Uint8Array(t.hvcc.raw),codedWidth:e,codedHeight:r,optimizeForLatency:!0};try{let a=await VideoDecoder.isConfigSupported(i);if(a.supported)return{mode:"hvc1",config:a.config??i,lengthSize:n};o.push({strategy:"hvc1",reason:"isConfigSupported returned false"})}catch(a){o.push({strategy:"hvc1",reason:String(a)})}let s={codec:U(t.hvcc,"hev1"),codedWidth:e,codedHeight:r,optimizeForLatency:!0};try{let a=await VideoDecoder.isConfigSupported(s);if(a.supported)return{mode:"hev1",config:a.config??s,prologue:j(t.hvcc),lengthSize:n};o.push({strategy:"hev1",reason:"isConfigSupported returned false"})}catch(a){o.push({strategy:"hev1",reason:String(a)})}throw new x("No HEVC decoder configuration was accepted",o,{strategy:"webcodecs",codec:t.codec})}function gt(t,e){if(t.mode==="hvc1")return e;let r=q(e,t.lengthSize),n=t.prologue,o=new Uint8Array(n.byteLength+r.byteLength);return o.set(n,0),o.set(r,n.byteLength),o}async function xe(t,e,r){if(!F())throw new x("WebCodecs VideoDecoder is not available in this environment",[{strategy:"webcodecs",reason:"VideoDecoder is undefined"}],{strategy:"webcodecs"});A(r);let n=S(t.codedWidth,t.codedHeight),o=n.getContext("2d",{colorSpace:e,alpha:!1,willReadFrequently:!1});if(!o)throw new w("Could not get a 2d context for compositing",{strategy:"webcodecs"});for(let i of t.tileGroups)A(r),await yt(t,i,o,r);return n}async function yt(t,e,r,n){let o=t.tiles[e.tileIndices[0]],i=await ht(e,o.width,o.height);A(n);let s=0,a=0,c,p=new Promise((f,l)=>{c=l}),u=new VideoDecoder({output:f=>{try{let l=t.tiles[e.tileIndices[s++]];l&&(r.drawImage(f,l.x,l.y,l.width,l.height),a++)}finally{f.close()}},error:f=>{c?.(new w(`VideoDecoder failed: ${f.message}`,{strategy:"webcodecs",codec:i.config.codec}))}}),m=()=>c?.(new I);n?.addEventListener("abort",m,{once:!0});try{try{u.configure(i.config);for(let f of e.tileIndices){let l=t.tiles[f],g=gt(i,be(t,l));u.decode(new EncodedVideoChunk({type:"key",timestamp:f,duration:0,data:g}))}}catch(f){throw f instanceof I||f instanceof w?f:new w(`VideoDecoder rejected the stream: ${f instanceof Error?f.message:String(f)}`,{strategy:"webcodecs",codec:i.config.codec},{cause:f})}if(await Promise.race([u.flush(),p]),a!==e.tileIndices.length)throw new w(`Decoder emitted ${a} frames for ${e.tileIndices.length} tiles`,{strategy:"webcodecs",codec:i.config.codec})}finally{n?.removeEventListener("abort",m);try{u.close()}catch{}}}var At=["hvc1.3.e.L93.B0","hvc1.1.6.L93.B0","hvc1.2.4.L120.B0"];async function we(){if(!F())return[];let t=[];for(let e of At)try{(await VideoDecoder.isConfigSupported({codec:e,codedWidth:1920,codedHeight:1080})).supported&&t.push(e)}catch{}return t}var bt=new Set(["heic","heix","hevc","hevx","heim","heis","hevm","hevs","mif1","msf1"]),xt=new Set(["heic","heix","hevc","hevx","heim","heis","hevm","hevs"]),Ie=65536;function ve(t){let e=t instanceof Uint8Array?t:new Uint8Array(t),r,n;try{let c=M(new O(e),{lenient:!0}),p=b(c,"ftyp");if(!p)return{isHeic:!1};for(n=p.body.fourCC(),r=new Set([n]),p.body.u32();p.body.remaining>=4;)r.add(p.body.fourCC())}catch{return{isHeic:!1}}if(![...r].some(c=>bt.has(c)))return{isHeic:!1,brand:n};let o;try{o=N(e,{truncated:!0})}catch{o=void 0}let i=o?.items.get(o.primaryItemId)?.itemType,s=o?Ce(o,i):"unknown";if(s==="av1")return{isHeic:!1,brand:n,primaryItemType:i,coding:s};if(s==="hevc")return{isHeic:!0,brand:n,primaryItemType:i,coding:s};let a={isHeic:[...r].some(c=>xt.has(c)),brand:n,coding:"unknown"};return i!==void 0&&(a.primaryItemType=i),a}function Ce(t,e,r=0){if(e==="hvc1"||e==="hev1")return"hevc";if(e==="av01")return"av1";if(r<4&&(e==="grid"||e==="iovl"||e==="iden")){let n=t.references.get("dimg")?.get(t.primaryItemId)?.[0];if(n!==void 0)return Ce(t,t.items.get(n)?.itemType,r+1)}return"unknown"}function J(t,e,r){let n=t;for(let o of e)switch(o.kind){case"crop":n=oe(n,wt(n,o,r),t);break;case"rotate":n=oe(n,It(n,o.angle,r),t);break;case"mirror":n=oe(n,vt(n,o.axis,r),t);break}return{canvas:n,applied:K(e)}}function K(t){let e={rotation:0,mirrored:"none",cropped:!1};for(let r of t)switch(r.kind){case"crop":e.cropped=!0;break;case"rotate":e.rotation=(e.rotation+r.angle)%360;break;case"mirror":{let n=Pe(r.axis);e.mirrored==="none"?e.mirrored=n:e.mirrored===n?e.mirrored="none":(e.mirrored="none",e.rotation=(e.rotation+180)%360);break}}return e}function Pe(t){return t===0?"vertical":"horizontal"}function wt(t,e,r){let n=S(e.width,e.height);return ie(n,r).drawImage(t,e.offsetX,e.offsetY,e.width,e.height,0,0,e.width,e.height),n}function It(t,e,r){let n=e===90||e===270,o=S(n?t.height:t.width,n?t.width:t.height),i=ie(o,r);return i.translate(o.width/2,o.height/2),i.rotate(-e*Math.PI/180),i.drawImage(t,-t.width/2,-t.height/2),o}function vt(t,e,r){let n=S(t.width,t.height),o=ie(n,r);return Pe(e)==="horizontal"?(o.translate(t.width,0),o.scale(-1,1)):(o.translate(0,t.height),o.scale(1,-1)),o.drawImage(t,0,0),n}function ie(t,e){let r=t.getContext("2d",{colorSpace:e,alpha:!1});if(!r)throw new Error("Could not get a 2d context");return r}function oe(t,e,r){return t!==r&&(t.width=0,t.height=0),e}async function Te(){let[t,e]=await Promise.all([ue(),we()]),r=F()&&e.length>0;return{native:t,webcodecs:r,hevcCodecStrings:e,recommended:t?"native":r?"webcodecs":"wasm"}}async function Ct(t){let e=await Et(t,Ie),r=ve(e),n={isHeic:r.isHeic};return r.brand!==void 0&&(n.brand=r.brand),r.primaryItemType!==void 0&&(n.primaryItemType=r.primaryItemType),r.coding!==void 0&&(n.coding=r.coding),n}async function Pt(t,e={}){let{strategy:r="auto",colorSpace:n="srgb",maxDimension:o,signal:i,wasmLoader:s}=e;A(i);let a=await De(t);A(i);let c=Z(a);A(i);let p=[],u=m=>r==="auto"||r===m;if(u("native")){let m=t instanceof Blob?t:X(a),f=await fe(m,c,i);if(f.status==="ok"){let l=await He(f.bitmap,o,i);return se(c,l,"native",K(c.transforms))}p.push({strategy:"native",reason:f.reason})}if(u("webcodecs"))if(!F())p.push({strategy:"webcodecs",reason:"VideoDecoder is not available"});else try{let m=await xe(c,n,i),{canvas:f,applied:l}=J(m,c.transforms,n),g=await Be(f,o,i);return se(c,g,"webcodecs",l)}catch(m){if(m instanceof I||r==="webcodecs")throw m;p.push({strategy:"webcodecs",reason:Ee(m)})}if(u("wasm")){let m=await Ht(s);if(!m)p.push({strategy:"wasm",reason:"no adapter: pass options.wasmLoader or call registerDecoderAdapter()"});else try{let f=await m.decode({data:a,colorSpace:n,signal:i}),l=K(c.transforms),g;if(f.image instanceof ImageBitmap){let y=m.appliesTransforms?f.image:await Dt(f.image,c,n);g=await He(y,o,i)}else{let y=m.appliesTransforms?f.image:J(f.image,c.transforms,n).canvas;g=await Be(y,o,i)}return se(c,g,"wasm",l)}catch(f){if(f instanceof I||r==="wasm")throw f;p.push({strategy:"wasm",reason:Ee(f)})}}throw new x("Could not decode this HEIC",p,{brand:c.file.majorBrand,itemType:c.file.items.get(c.primaryItemId)?.itemType,itemId:c.primaryItemId})}var ee;function Tt(t){ee=t}function Bt(){return ee}async function Ht(t){if(ee)return ee;if(t)return t()}async function De(t){return t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(await t.arrayBuffer())}async function Et(t,e){return t instanceof Blob?new Uint8Array(await t.slice(0,e).arrayBuffer()):(await De(t)).subarray(0,e)}function Se(t,e,r){if(!r||r<=0)return 1;let n=Math.max(t,e);return n<=r?1:r/n}async function Be(t,e,r){A(r);let n=Se(t.width,t.height,e);if(n===1)return t.transferToImageBitmap();let o=Math.max(1,Math.round(t.width*n)),i=Math.max(1,Math.round(t.height*n));try{return await createImageBitmap(t,{resizeWidth:o,resizeHeight:i,resizeQuality:"high"})}finally{t.width=0,t.height=0}}async function He(t,e,r){A(r);let n=Se(t.width,t.height,e);if(n===1)return t;let o=await createImageBitmap(t,{resizeWidth:Math.max(1,Math.round(t.width*n)),resizeHeight:Math.max(1,Math.round(t.height*n)),resizeQuality:"high"});return t.close(),o}async function Dt(t,e,r){if(e.transforms.length===0)return t;let n=S(t.width,t.height),o=n.getContext("2d",{colorSpace:r,alpha:!1});if(!o)return t;o.drawImage(t,0,0),t.close();let{canvas:i}=J(n,e.transforms,r);return i.transferToImageBitmap()}function se(t,e,r,n){return{image:e,width:e.width,height:e.height,sourceWidth:t.displayWidth,sourceHeight:t.displayHeight,strategy:r,bitDepth:t.bitDepth,isGrid:t.isGrid,tileCount:t.tiles.length,sourceColor:t.sourceColor,transformsApplied:n,warnings:t.warnings}}function Ee(t){return t instanceof Error?t.message:String(t)}return ke(St);})();
|
|
2
|
+
//# sourceMappingURL=heic.global.js.map
|