mirror of
https://github.com/Nexoniarz/YAPF.git
synced 2026-11-03 04:54:26 +00:00
262 lines
8.3 KiB
Markdown
262 lines
8.3 KiB
Markdown
# YAPF — Yet Another Picture Format
|
||
|
||
**Fast. Small. Easy.**
|
||
|
||
YAPF is a lossless image codec built around a single principle: decoding should
|
||
be as close to a memory copy as possible. Instead of running a general-purpose
|
||
compressor like DEFLATE over pixel data, YAPF encodes images with five cheap
|
||
operations — a pixel cache lookup, two flavours of delta encoding, run-length
|
||
encoding, and verbatim pixel storage — producing files that are smaller than
|
||
uncompressed formats while decoding nearly as fast as `memcpy`.
|
||
|
||
---
|
||
|
||
## Why YAPF?
|
||
|
||
Standard lossless formats like PNG compress well, but decompressing DEFLATE
|
||
streams is CPU-intensive. On large images (4K and above) or in
|
||
latency-sensitive applications (game engines, real-time pipelines, embedded
|
||
systems) this is a meaningful bottleneck.
|
||
|
||
YAPF trades a modest compression ratio advantage for dramatically lower decode
|
||
complexity. There are no Huffman tables to build, no sliding windows to
|
||
maintain, and no bit-level unpacking. The decoder is a tight byte-dispatch
|
||
loop that any modern CPU handles at memory bandwidth.
|
||
|
||
### Performance
|
||
|
||
Benchmark: loading a 5K professional photograph (5120 × 2880 px).
|
||
|
||
| Format | Load time |
|
||
| :----- | ----------: |
|
||
| **YAPF** | **0.111 s** |
|
||
| PNG | 0.194 s |
|
||
|
||
YAPF decodes approximately **1.7× faster** than PNG on the same hardware
|
||
without SIMD or threading.
|
||
|
||
### When YAPF excels
|
||
|
||
- **Pixel art, sprites, UI textures** — large flat regions of identical colour
|
||
compress to single RUN bytes; palette-like images benefit heavily from the
|
||
cache.
|
||
- **Screenshots and diagrams** — solid backgrounds and repeated gradients hit
|
||
DIFF/LUMA and the cache constantly.
|
||
- **Game asset pipelines** — load textures at startup without paying a
|
||
decompression tax every frame.
|
||
- **Embedded / resource-constrained targets** — the entire codec fits in ~200
|
||
lines of portable C with zero external dependencies.
|
||
|
||
---
|
||
|
||
## Integration
|
||
|
||
The core library is two files. Drop them into your project and compile:
|
||
|
||
```
|
||
yapf.h — public API and format documentation
|
||
yapf.c — encoder / decoder implementation
|
||
```
|
||
|
||
```c
|
||
#include "yapf.h"
|
||
|
||
int main(void) {
|
||
/* Load */
|
||
yapf_image_t *img = yapf_load("photo.yapf");
|
||
if (!img) { /* file not found or not a valid YAPF stream */ }
|
||
|
||
printf("%u x %u, %u channel(s)\n",
|
||
img->width, img->height, img->channels);
|
||
|
||
/* Save */
|
||
int rc = yapf_save("output.yapf", img);
|
||
if (rc != YAPF_OK) { /* write error */ }
|
||
|
||
yapf_free(img);
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
Compile with any C99-capable compiler — no flags required for the baseline:
|
||
|
||
```sh
|
||
gcc -O2 -std=c99 main.c yapf.c -o myapp
|
||
```
|
||
|
||
---
|
||
|
||
## API Reference
|
||
|
||
### Types
|
||
|
||
```c
|
||
typedef struct {
|
||
uint32_t width; /* pixels per row */
|
||
uint32_t height; /* rows */
|
||
uint8_t channels; /* 1 gray / 2 gray+alpha / 3 RGB / 4 RGBA */
|
||
uint8_t *pixels; /* raw pixel buffer, malloc-owned */
|
||
} yapf_image_t;
|
||
```
|
||
|
||
`pixels` is a tightly packed, row-major byte array of `width × height × channels`
|
||
bytes. The layout per pixel matches the `channels` value:
|
||
|
||
| `channels` | Layout per pixel |
|
||
| :--------- | :--------------- |
|
||
| 1 | `Y` |
|
||
| 2 | `Y A` |
|
||
| 3 | `R G B` |
|
||
| 4 | `R G B A` |
|
||
|
||
### Functions
|
||
|
||
```c
|
||
yapf_image_t *yapf_load(const char *filename);
|
||
```
|
||
Decodes a YAPF file from disk. Returns a heap-allocated `yapf_image_t` on
|
||
success, `NULL` on any failure (file not found, invalid magic, unsupported
|
||
version, out of memory). Free with `yapf_free()`.
|
||
|
||
```c
|
||
int yapf_save(const char *filename, const yapf_image_t *img);
|
||
```
|
||
Encodes an image to disk. Returns `YAPF_OK` (0) on success or a negative
|
||
`YAPF_ERR_*` code on failure.
|
||
|
||
```c
|
||
void yapf_free(yapf_image_t *img);
|
||
```
|
||
Releases memory returned by `yapf_load()`. Passing `NULL` is safe.
|
||
|
||
### Return codes
|
||
|
||
| Constant | Value | Meaning |
|
||
| :---------------- | ----: | :----------------------------------- |
|
||
| `YAPF_OK` | 0 | Success |
|
||
| `YAPF_ERR_INVALID`| –1 | NULL or logically invalid argument |
|
||
| `YAPF_ERR_IO` | –2 | File could not be opened or written |
|
||
|
||
### Channel constants
|
||
|
||
```c
|
||
YAPF_CHANNELS_GRAY 1
|
||
YAPF_CHANNELS_GRAY_ALPHA 2
|
||
YAPF_CHANNELS_RGB 3
|
||
YAPF_CHANNELS_RGBA 4
|
||
```
|
||
|
||
---
|
||
|
||
## Building the libraries
|
||
|
||
The build scripts uses the [LLVM](https://releases.llvm.org/) compiler
|
||
|
||
```sh
|
||
# Unix (Linux / macOS)
|
||
chmod +x build.sh
|
||
./build.sh
|
||
|
||
# Windows
|
||
build.bat
|
||
```
|
||
|
||
Output goes to `dist/` with one subdirectory per platform:
|
||
|
||
```
|
||
dist/
|
||
├── windows-x64/ yapf.dll yapf.lib
|
||
├── windows-arm64/ yapf.dll yapf.lib
|
||
├── macos-x64/ libyapf.dylib
|
||
├── macos-arm64/ libyapf.dylib
|
||
├── linux-x64/ libyapf.so libyapf.a
|
||
└── linux-arm64/ libyapf.so libyapf.a
|
||
```
|
||
|
||
### Optional build flags
|
||
|
||
| Flag | Effect |
|
||
| :---------------- | :-------------------------------------------------- |
|
||
| `-DYAPF_USE_SIMD` | Reserved for future SIMD acceleration paths |
|
||
|
||
The interactive build script will prompt for these options.
|
||
|
||
---
|
||
|
||
## Technical Specification
|
||
|
||
### File header — 14 bytes
|
||
|
||
| Offset | Size | Type | Description |
|
||
| :----- | ---: | :--------- | :-------------------------------------- |
|
||
| 0x00 | 4 | `char[4]` | Magic: `"YAPF"` |
|
||
| 0x04 | 1 | `uint8_t` | Format version (currently `1`) |
|
||
| 0x05 | 4 | `uint32_t` | Width in pixels, little-endian |
|
||
| 0x09 | 4 | `uint32_t` | Height in pixels, little-endian |
|
||
| 0x0D | 1 | `uint8_t` | Channel count: 1 / 2 / 3 / 4 |
|
||
|
||
All multi-byte integers are always stored little-endian, regardless of host
|
||
architecture.
|
||
|
||
### Byte-stream opcodes
|
||
|
||
The payload begins immediately after the header and is decoded left-to-right.
|
||
A 64-slot pixel cache (zero-initialised) and a running "previous pixel" state
|
||
(`r=0, g=0, b=0, a=255`) are maintained throughout.
|
||
|
||
| Byte range | Opcode | Size | Description |
|
||
| :---------- | :----- | ------: | :-------------------------------------------- |
|
||
| 0x00–0x3F | INDEX | 1 byte | Restore pixel from cache slot `byte & 0x3F` |
|
||
| 0x40–0x7F | DIFF | 1 byte | Per-channel deltas: dr = bits[5:4]–2, dg = bits[3:2]–2, db = bits[1:0]–2. Range –2..+1. Alpha unchanged. |
|
||
| 0x80–0xBF | LUMA | 2 bytes | Green delta dg = (byte & 0x3F)–32. Second byte: dr = ((b1>>4)&0xF)–8+dg, db = (b1&0xF)–8+dg. Alpha unchanged. |
|
||
| 0xC0–0xFD | RUN | 1 byte | Repeat previous pixel (byte&0x3F)+1 times (1–62). Cache not updated. |
|
||
| 0xFE | RGB | 4 bytes | Next 3 bytes = R, G, B. Alpha unchanged. |
|
||
| 0xFF | RGBA | 5 bytes | Next 4 bytes = R, G, B, A. |
|
||
|
||
After every non-RUN opcode, the cache slot `HASH(r,g,b,a)` is updated and the
|
||
pixel becomes the new previous pixel.
|
||
|
||
```
|
||
HASH(r,g,b,a) = (r×3 + g×5 + b×7 + a×11) mod 64
|
||
```
|
||
|
||
The stream ends when exactly `width × height` pixels have been decoded; no
|
||
end-of-stream marker is required.
|
||
|
||
### Pixel buffer layout
|
||
|
||
Within the decoded buffer, each pixel occupies `channels` consecutive bytes in
|
||
the order shown in the API table above. Grayscale images store a single
|
||
luminance byte per pixel; the encoder internally promotes gray values to
|
||
`r = g = b = Y` for uniform opcode selection.
|
||
|
||
---
|
||
|
||
## GIMP Plugin
|
||
|
||
A plugin for GIMP is located in `extensions/gimp/`. See the
|
||
[build instructions](extensions/gimp/howtocompile.txt) for details.
|
||
|
||
Supported colour modes: Gray, Gray+Alpha, RGB, RGBA.
|
||
File extension: `.yapf`. MIME type: `image/x-yapf`.
|
||
|
||
---
|
||
|
||
## Platforms
|
||
|
||
| Platform | Architectures | Library format |
|
||
| :------------- | :------------ | :------------------ |
|
||
| Windows | x64, ARM64 | `.dll` + `.lib` |
|
||
| macOS | ARM64 | `.dylib` |
|
||
| Linux (glibc) | x64, ARM64 | `.so` |
|
||
| Linux (musl) | x64, ARM64 | `.a` (static) |
|
||
|
||
The core library (`yapf.c` / `yapf.h`) uses only the C99 standard library
|
||
(`<stdio.h>`, `<stdlib.h>`, `<string.h>`, `<stdint.h>`) and compiles cleanly
|
||
on any conforming C99 or C++ toolchain with no configuration required.
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
Apache License 2.0 — see [LICENSE](LICENSE).
|