@energyausweis/image-avif
v0.3.6
Published
Browser image transform SDK (AVIF with WebP/JPEG fallback): worker pool by default, automatic main-thread fallback when Worker APIs are missing. Zero network — transform(Blob|File|ImageData). Proprietary — see LICENSE.
Readme
@energyausweis/image-avif
Zero-network browser image transform SDK: decode → resize → encode (AVIF preferred; verified WebP → JPEG fallback). Uses a worker pool whenever Worker + OffscreenCanvas + createImageBitmap exist; otherwise an automatic main-thread canvas fallback (same caps / quality / verified fallbacks). No opt-in main-thread mode.
transform takes an in-memory ImageInput (Blob | browser File | ImageData) — not a filesystem path. This package does not upload files or hold API keys.
Blob decode uses createImageBitmap(..., { imageOrientation: 'from-image' }) so phone JPEG EXIF Orientation is honored (parity with website <img> drawing).
License: proprietary GreenPurpose e.K. terms — see LICENSE. Non-commercial / evaluation use; commercial licensing via https://www.energyausweis.de/kontakt.
Which factory?
Both factories return the same ImageTransformer API (transform / preload / dispose). The only difference is default resize/quality/caps.
| Factory | When to use |
|---------|-------------|
| createEpcDocumentTransformer | EPC document photos (building / windows / walls / heating). Applies EPC_DOCUMENT_PRESET: long edge 1000, quality 50, 25 MB / 40 MP input caps. Prefer this for Energyausweis document slots. |
| createImageTransformer | Anything else, or when you need custom defaults (options.defaults) or per-call plan without the EPC preset. Same pipeline; you choose maxPixels / quality / caps. |
createEpcDocumentTransformer(opts) ≡ createImageTransformer({ ...opts, defaults: EPC_DOCUMENT_PRESET }). You can still pass a per-call plan to either: transform(input, { maxPixels: 800 }).
Install
pnpm add @energyausweis/image-avif
# or: npm install @energyausweis/image-avifPublic on npmjs.org. Pin a version in production apps.
Usage
EPC document photos
import {
createEpcDocumentTransformer,
type ImageInput,
} from '@energyausweis/image-avif'
const transformer = createEpcDocumentTransformer({
// optional: workerUrl / workerFactory for bundlers
maxWorkers: 'auto',
// optional: override HEIC rejection copy (code stays heic_unsupported)
messages: { heicUnsupported: 'Bitte JPG statt HEIC' },
})
await transformer.preload() // optional
// Picker helpers (empty file.type / Android octet-stream):
// pickImageFileFromFileList(input.files) · DEFAULT_FILE_INPUT_ACCEPT
// From a remote URL (you fetch — this package does no network):
const fromUrl: ImageInput = await fetch(imageUrl).then((r) => r.blob())
// From <input type="file"> — browser File is a Blob + name, not a disk path:
const fromPicker: ImageInput = inputElement.files![0]!
// From canvas:
const fromCanvas: ImageInput = ctx.getImageData(0, 0, w, h)
const { blob, width, height, fileName, mimeType, pipeline } =
await transformer.transform(fromUrl)
// pipeline is 'worker' or 'main' (automatic fallback only)Custom sizing
import { createImageTransformer } from '@energyausweis/image-avif'
const transformer = createImageTransformer({
defaults: { maxPixels: 1600, quality: 60 },
maxWorkers: 'auto',
})
const result = await transformer.transform(imageInput)
// or override one call:
await transformer.transform(imageInput, { maxPixels: 800, quality: 40 })Always use the returned mimeType / fileName / width / height when confirming an upload — output may be WebP or JPEG if AVIF WASM fails. Output fileName is a suggested download name derived from input metadata, not a path.
Typical partner document flow (outside this package): mint an opaque upload URL on your server (Bearer) → PUT the transformed blob with matching Content-Type → confirm with the transform metadata. See the Energyausweis API docs for the HTTP recipe.
Worker entry
import { createImageTransformer } from '@energyausweis/image-avif'
const transformer = createImageTransformer({
workerUrl: new URL('@energyausweis/image-avif/worker', import.meta.url),
})Or a bundler worker factory:
createImageTransformer({
workerFactory: () =>
new Worker(new URL('@energyausweis/image-avif/worker', import.meta.url), {
type: 'module',
}),
})Compat / performance
| Environment | Notes |
|-------------|--------|
| Chrome / Edge / Firefox | Worker pipeline (Worker + OffscreenCanvas + createImageBitmap) |
| Safari / iOS | Same when APIs exist; convertToBlob('image/webp') may silently yield PNG — we verify blob.type and fall through to JPEG |
| Missing worker APIs | Automatic main-thread DOM canvas fallback (same caps); result.pipeline === 'main' |
| Neither worker nor canvas | ImageAvifError unsupported_environment |
| HEIC/HEIF | Not supported — heic_unsupported (overridable message via messages.heicUnsupported) |
Main thread (when workers work): metadata validation + job queue only. Decode, resize, and encode run in a worker pool. Main-thread encode runs only as fallback when worker APIs are missing — there is no option to prefer it.
Caps (EPC preset defaults): 25 MB input, 40 MP before resize, long edge 1000px, quality 50.
CSP: allow worker-src for your origin (and the worker module URL) when using workers. Prefer module workers over Blob workers.
MANUAL smoke: phone JPEG with EXIF Orientation=6 should stay upright after transform (playground).
What this package does not do
- No filesystem path strings — only in-memory
ImageInput(Blob|File|ImageData) - No
fetch/ PUT / upload helpers (you mayfetcha URL yourself, thentransform(blob)) - No API Bearer handling
- No CDN /
<script>loader - No HEIC decode/convert
- No opt-in / preferred main-thread mode when workers are available
