https://github.com/ghosthack/imageio-native
Java ImageIO readers that delegate to platform-native image decoding APIs via Project Panama (FFM API, Java 22+). HEIC, AVIF, WEBP, JPEG 2000, camera RAW, and more through standard ImageIO.read().
https://github.com/ghosthack/imageio-native
avif heic image-processing imageio java native panama webp
Last synced: 2 months ago
JSON representation
Java ImageIO readers that delegate to platform-native image decoding APIs via Project Panama (FFM API, Java 22+). HEIC, AVIF, WEBP, JPEG 2000, camera RAW, and more through standard ImageIO.read().
- Host: GitHub
- URL: https://github.com/ghosthack/imageio-native
- Owner: ghosthack
- License: mit
- Created: 2026-03-10T08:05:14.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-03-10T15:21:19.000Z (3 months ago)
- Last Synced: 2026-03-10T15:54:19.760Z (3 months ago)
- Topics: avif, heic, image-processing, imageio, java, native, panama, webp
- Language: Java
- Size: 85.9 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# imageio-native
[](https://github.com/ghosthack/imageio-native/actions/workflows/ci.yml) [](https://javadoc.io/doc/io.github.ghosthack/imageio-native) [](https://central.sonatype.com/artifact/io.github.ghosthack/imageio-native)
Java ImageIO readers that delegate to **platform-native image decoding APIs** via [Project Panama](https://openjdk.org/jeps/454) (Foreign Function & Memory API, Java 22+).
Drop the JAR on your classpath and `ImageIO.read()` gains support for **HEIC, AVIF, WEBP, JPEG 2000, JPEG XL, camera RAW, PSD, EXR**, and more. No JNI, no native builds, no manual SPI wiring.
Decode only. Still images only (video files yield a single poster frame). All modules are pure Java — they compile on any OS and auto-detect the platform at runtime.
## Quick start
Add the dependency and the JVM flag:
```xml
io.github.ghosthack
imageio-native
1.0.2
```
```
--enable-native-access=ALL-UNNAMED
```
Then use standard ImageIO:
```java
BufferedImage img = ImageIO.read(new File("photo.heic"));
BufferedImage img = ImageIO.read(new File("photo.avif"));
BufferedImage img = ImageIO.read(new File("photo.webp"));
```
All standard lookup methods work: `getImageReadersByFormatName`, `getImageReadersByMIMEType`, `getImageReadersBySuffix`.
The `imageio-native` aggregator pulls in both platform modules and auto-selects at runtime. You can also depend on `imageio-native-apple` or `imageio-native-windows` directly.
Gradle
```kotlin
implementation("io.github.ghosthack:imageio-native:1.0.2")
```
## Format configuration
This implementation has modules for two native APIs:
| Module | Platform | Native API | Formats |
|--------|----------|------------|---------|
| `imageio-native-apple` | macOS | CGImageSource (Apple ImageIO framework) | 60+ |
| `imageio-native-windows` | Windows 10+ | Windows Imaging Component (WIC) | 30+ |
Controlled by the system property `imageio.native.formats`:
| Value | Behaviour |
|-------|-----------|
| `supplemental` (default) | Only formats Java can't decode natively. JPEG/PNG/GIF/BMP/TIFF are left to Java's built-in readers. |
| `all` | Every format the platform can decode, including JPEG/PNG/GIF/BMP/TIFF. |
| `none` | Disabled entirely. |
| comma-separated list | Explicit whitelist, e.g. `heic,avif,webp,jp2`. |
### Supported formats (supplemental defaults)
**Both platforms:** HEIC, HEIF, AVIF, WebP, DNG, CR2, CR3, NEF, ARW, ICO, CUR, DDS, and many more camera RAW formats
**Apple-only:** JPEG 2000, JPEG XL, PSD, OpenEXR, Radiance HDR, DICOM, ICNS, TGA, SGI, PBM/PGM/PPM, PICT, MPO, KTX, KTX2, ASTC, PVR, ATX
**Windows-only:** JPEG-XR (JXR/WDP/HDP)
### Windows codec requirements
| Format | Requirement |
|--------|-------------|
| HEIC/HEIF | [HEVC Video Extensions](https://apps.microsoft.com/detail/9nmzlz57r3t7) from Microsoft Store |
| AVIF | [AV1 Video Extensions](https://apps.microsoft.com/detail/9mvzqvxjbq9v) from Microsoft Store |
| WebP | Built-in (Windows 10 1809+) |
| JPEG-XR | Built-in |
To check whether the required codecs are already installed:
```powershell
Get-AppxPackage -Name *hevc* # HEVC (for HEIC/HEIF)
Get-AppxPackage -Name *av1* # AV1 (for AVIF)
```
> **Minimum image dimensions:** The HEVC and AV1 codec extensions cannot decode very small images. HEIC/HEIF requires at least 8×8 pixels and AVIF requires at least 8×8 pixels. Smaller images will fail with `E_INVALIDARG` during pixel decoding even though header parsing and format detection succeed. This is a limitation of the Windows codec extensions, not of WIC or this library.
## Runtime detection
To check at runtime whether imageio-native is on the classpath (e.g. when it's an optional dependency), probe a class from the `imageio-native-common` module — it's a transitive dependency of every platform module, so it's always present regardless of which artifact was included:
```java
boolean available = false;
try {
Class.forName("io.github.ghosthack.imageio.common.FormatRegistry");
available = true;
} catch (ClassNotFoundException ignored) { }
```
To detect a specific platform module, check its SPI class:
```java
// macOS (imageio-native-apple)
Class.forName("io.github.ghosthack.imageio.apple.AppleImageReaderSpi");
// Windows (imageio-native-windows)
Class.forName("io.github.ghosthack.imageio.windows.WicImageReaderSpi");
```
## Optional backends
The `imageio-native-vips` module is an optional backend that delegates to [libvips](https://www.libvips.org/) for image decoding. It is **not** included in the `imageio-native` aggregator -- add it explicitly to opt in.
```xml
io.github.ghosthack
imageio-native-vips
1.0.2
```
Requires libvips installed on the system:
```sh
# macOS (MacPorts)
sudo port install vips
# macOS (Homebrew)
brew install vips
# Debian/Ubuntu
sudo apt install libvips-dev
```
The vips backend adds cross-platform support (macOS, Linux, Windows) for HEIC, AVIF, WebP, JPEG 2000, PDF, SVG, EXR, FITS, Netpbm, HDR, and more -- depending on the libvips build configuration. It respects the `imageio.native.formats` property (supplemental mode by default).
The SPI declares a fixed set of common formats. Formats not in the list but supported by the installed libvips can still be decoded via the direct `VipsNative` API -- they just won't be auto-discovered by `ImageIO.read()`.
### ImageMagick
The `imageio-native-magick` module is an optional backend that delegates to [ImageMagick 7](https://imagemagick.org/) (MagickWand C API). Supports 200+ formats including EPS, PSD, XCF, DPX, TGA, PCX, XBM/XPM, and more.
```xml
io.github.ghosthack
imageio-native-magick
1.0.2
```
Requires ImageMagick 7 installed on the system. Both Q16 and Q16HDRI builds are supported.
```sh
# macOS (MacPorts)
sudo port install ImageMagick7
# macOS (Homebrew)
brew install imagemagick
# Debian/Ubuntu
sudo apt install libmagickwand-7-dev
```
### Backend priority
When multiple backends are on the classpath (e.g. platform-native + vips), the consumer controls which backend handles each format via system properties:
```
# Global ordering (left = highest priority). Default: native,vips,magick,ffmpeg
-Dimageio.native.backend.priority=native,vips,magick,ffmpeg
# Per-format override
-Dimageio.native.backend.priority.jpeg=vips,native
-Dimageio.native.backend.priority.tiff=vips
```
With no properties set, the default ordering is: platform-native first, then vips, then magick, then ffmpeg. Existing users see no change when adding optional backends -- they only activate for formats the higher-priority backends can't handle.
## Video poster frames
The optional `imageio-native-video` module extracts a **single still image** from a video file -- the same way the image modules decode a still image from a HEIC or WebP file. The output is always a `BufferedImage`; no video playback, no audio, no frame sequences.
This means `ImageIO.read(new File("clip.mp4"))` works exactly like `ImageIO.read(new File("photo.heic"))` -- same API, same result type.
```xml
io.github.ghosthack
imageio-native-video
1.0.2
```
Through the standard ImageIO SPI:
```java
// Poster frame via ImageIO -- identical to reading any image
BufferedImage poster = ImageIO.read(new File("clip.mp4"));
```
Or through the direct API for more control:
```java
// Thumbnail (poster frame at or near t=0)
BufferedImage thumb = VideoFrameExtractor.extractThumbnail(Path.of("clip.mp4"));
// Frame at a specific time
BufferedImage frame = VideoFrameExtractor.extractFrame(
Path.of("clip.mp4"), Duration.ofSeconds(30));
// Video metadata (dimensions, duration, codec, frame rate)
VideoInfo info = VideoFrameExtractor.getInfo(Path.of("clip.mp4"));
```
| Module | Platform | Native API | Containers |
|--------|----------|------------|------------|
| `imageio-native-video-apple` | macOS | AVFoundation (AVAssetImageGenerator) | MP4, MOV, M4V, 3GP |
| `imageio-native-video-windows` | Windows 10+ | Media Foundation (IMFSourceReader) | *In progress* |
| `imageio-native-video-ffmpeg` | Any (optional) | FFmpeg libavformat/libavcodec | All FFmpeg-supported containers |
The Windows video backend is not yet complete -- `isAvailable()` returns `false` until the implementation is finished. See `TODO-windows.md` for details.
### FFmpeg video backend
The `imageio-native-video-ffmpeg` module is an optional cross-platform video backend. It works on any OS where FFmpeg libraries are installed, including **Linux** (the only video backend available on Linux).
```xml
io.github.ghosthack
imageio-native-video-ffmpeg
1.0.2
```
```sh
# macOS (MacPorts)
sudo port install ffmpeg
# macOS (Homebrew)
brew install ffmpeg
# Debian/Ubuntu
sudo apt install libavformat-dev libavcodec-dev libswscale-dev libavutil-dev
```
Struct offsets are version-specific. Currently supports FFmpeg 4.x (libavcodec major 58). The backend detects the FFmpeg version at runtime via `avcodec_version()` and disables itself if the version is not recognized. Additional version support can be added by providing offset tables.
## Architecture
```
ImageIO.read(file)
│
▼
ImageReaderSpi one universal SPI per platform
│ canDecodeInput():
│ 1. skip Java-native formats in "supplemental" mode
│ 2. probe via native API (CGImageSource / WIC)
▼
ImageReader lazy decode + cache
│
▼
┌─────────────────────┬──────────────────────────────┐
│ macOS │ Windows │
│ AppleNative │ WicNative │
│ Panama downcalls │ Panama COM vtable dispatch │
│ CoreGraphics + │ ole32 + windowscodecs │
│ ImageIO.framework │ IWICImagingFactory → │
│ CGImageSource → │ IWICStream → Decoder → │
│ CGBitmapContext → │ FormatConverter → │
│ pixel copy │ CopyPixels │
└─────────────────────┴──────────────────────────────┘
│
▼
BufferedImage (TYPE_INT_ARGB_PRE)
```
Both platforms output BGRA premultiplied pixels that map directly to `TYPE_INT_ARGB_PRE` when read as little-endian ints — zero pixel conversion overhead.
One universal SPI per platform means `canDecodeInput` delegates to the native API to probe headers, so any format the OS adds in a future update works automatically. Both modules compile on all OSes; native loading is guarded by OS checks.
## EXIF orientation
Images from phones and cameras often carry an EXIF orientation tag (values 1-8) that describes how the sensor image should be rotated or flipped for correct display. Both backends apply this transform automatically during decode so the returned `BufferedImage` is always display-ready.
| EXIF value | Transform |
|------------|-----------|
| 1 | None (identity) |
| 2 | Flip horizontal |
| 3 | Rotate 180 |
| 4 | Flip vertical |
| 5 | Rotate 90 + flip horizontal |
| 6 | Rotate 90 |
| 7 | Rotate 270 + flip horizontal |
| 8 | Rotate 270 |
**macOS (CGImageSource):** Uses `CGImageSourceCreateThumbnailAtIndex` with `kCGImageSourceCreateThumbnailWithTransform = true` and the thumbnail size set to the full image dimensions. CoreGraphics applies the EXIF transform internally during hardware-accelerated decode -- same decode path, same performance, correct orientation.
**Windows (WIC):** Reads the EXIF orientation tag via `IWICMetadataQueryReader`, then inserts an `IWICBitmapFlipRotator` between the frame decoder and the format converter. The flip-rotator is a zero-copy coordinate remap -- it transforms pixel coordinates during `CopyPixels` rather than allocating a second buffer. For orientation 1 (no rotation), the flip-rotator is skipped entirely.
Both `getSize()` and `decode()` are orientation-aware: dimensions are swapped for orientations 5-8 (90/270 rotations), so width and height always reflect the display-oriented image.
**Performance impact:** Negligible. Both platforms apply the transform as part of the existing decode pipeline with no extra buffer allocations or pixel copies. The macOS path creates a small options dictionary per decode call; the Windows flip-rotator is a zero-copy coordinate remap. Orientation 1 (the common case for non-phone images) skips the transform entirely.
## Project structure
```
├── pom.xml parent POM (reactor)
├── imageio-native-common/ shared format registry & detection
├── imageio-native-apple/ macOS image module
├── imageio-native-windows/ Windows image module
├── imageio-native/ cross-platform image aggregator
├── imageio-native-video-common/ shared video SPI & format detection
├── imageio-native-video-apple/ macOS video module (AVFoundation)
├── imageio-native-video-windows/ Windows video module (Media Foundation)
├── imageio-native-video/ cross-platform video aggregator
├── imageio-native-vips/ optional libvips backend
├── imageio-native-magick/ optional ImageMagick 7 backend
├── imageio-native-video-ffmpeg/ optional FFmpeg video backend
├── scripts/ test fixture generators
└── example-consumer/ standalone demo (not in reactor)
```
## Building
Requires Java 22+ and Maven 3.9+.
```sh
mvn clean test # compile + test
mvn install -DskipTests # install to local repo
mvn -f example-consumer/pom.xml test # example-consumer
swift scripts/generate-heic-avif-cgimage.swift # HEIC + AVIF (macOS CGImage)
./scripts/generate-png-webp-chrome.sh # PNG + WebP (Chrome headless)
python scripts/generate-all-pillow.py # all formats (pip: pillow + plugins)
swift scripts/generate-video-fixtures.swift # video fixtures (macOS AVFoundation)
./scripts/generate-video-bframes.sh # B-frame video fixture (ffmpeg)
```
## Releasing
1. Set the release version in `pom.xml` (remove `-SNAPSHOT`)
2. Commit, push, and merge via PR
3. CI detects the version change, creates a GitHub release, and deploys to Maven Central
4. Publish the deployment at https://central.sonatype.com/publishing/deployments
## License
[MIT](LICENSE)