mirror of
https://github.com/Nexoniarz/YAPF.git
synced 2026-11-02 20:44:27 +00:00
Add files via upload
This commit is contained in:
parent
0a2b234169
commit
b9b8c93a22
3 changed files with 1060 additions and 0 deletions
262
README.md
Normal file
262
README.md
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
# 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).
|
||||
588
yapf.c
Normal file
588
yapf.c
Normal file
|
|
@ -0,0 +1,588 @@
|
|||
/*
|
||||
* yapf.c — Yet Another Picture Format
|
||||
* Encoder and decoder implementation.
|
||||
*
|
||||
* Copyright 2026 Nexoniarz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "yapf.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── Constants ───────────────────────────────────────────────────────── */
|
||||
|
||||
#define YAPF__MAGIC "YAPF"
|
||||
#define YAPF__VERSION 2
|
||||
#define YAPF__HDR_LEN 20
|
||||
#define YAPF__MIP_ENTRY 8
|
||||
#define YAPF__MAX_TILE_PX (YAPF_TILE_SIZE * YAPF_TILE_SIZE) /* 1024 */
|
||||
|
||||
/* ── Tile pixel type ─────────────────────────────────────────────────── */
|
||||
/*
|
||||
* After YCoCg-R, channels 1 (Co) and 2 (Cg) span [-255, 255] and need
|
||||
* 9 bits, so we use int16_t for all channels internally within a tile.
|
||||
* Grayscale and alpha channels stay in [0, 255].
|
||||
*/
|
||||
typedef struct { int16_t v[4]; } yapf__tpx;
|
||||
|
||||
/* ── I/O helpers ─────────────────────────────────────────────────────── */
|
||||
|
||||
static uint32_t yapf__r32le(const uint8_t *p) {
|
||||
return (uint32_t)p[0]
|
||||
| ((uint32_t)p[1] << 8)
|
||||
| ((uint32_t)p[2] << 16)
|
||||
| ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static void yapf__w32le(uint8_t *p, uint32_t v) {
|
||||
p[0] = (uint8_t)(v);
|
||||
p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16);
|
||||
p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
/* ── Bit-writer ──────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf;
|
||||
size_t cap;
|
||||
size_t len;
|
||||
uint8_t acc; /* accumulator */
|
||||
int bits; /* bits filled in acc (0-7) */
|
||||
} yapf__bw;
|
||||
|
||||
static int yapf__bw_init(yapf__bw *w, size_t cap) {
|
||||
w->buf = (uint8_t *)malloc(cap ? cap : 64);
|
||||
if (!w->buf) return -1;
|
||||
w->cap = cap ? cap : 64;
|
||||
w->len = 0;
|
||||
w->acc = 0;
|
||||
w->bits = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int yapf__bw_grow(yapf__bw *w, size_t need) {
|
||||
if (w->len + need <= w->cap) return 0;
|
||||
size_t nc = w->cap * 2 + need;
|
||||
uint8_t *nb = (uint8_t *)realloc(w->buf, nc);
|
||||
if (!nb) return -1;
|
||||
w->buf = nb;
|
||||
w->cap = nc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write n bits from v, MSB first; n in [1, 32]. */
|
||||
static int yapf__bw_put(yapf__bw *w, uint32_t v, int n) {
|
||||
if (yapf__bw_grow(w, (size_t)(n + 7) / 8 + 2) != 0) return -1;
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
w->acc = (uint8_t)((w->acc << 1) | ((v >> i) & 1u));
|
||||
if (++w->bits == 8) {
|
||||
w->buf[w->len++] = w->acc;
|
||||
w->acc = 0;
|
||||
w->bits = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Flush partial byte (zero-padded) and reset to byte boundary. */
|
||||
static void yapf__bw_flush(yapf__bw *w) {
|
||||
if (w->bits > 0) {
|
||||
w->buf[w->len++] = (uint8_t)(w->acc << (8 - w->bits));
|
||||
w->acc = 0;
|
||||
w->bits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Bit-reader ──────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *buf;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
uint8_t acc;
|
||||
int bits;
|
||||
} yapf__br;
|
||||
|
||||
static void yapf__br_init(yapf__br *r, const uint8_t *buf, size_t len) {
|
||||
r->buf = buf;
|
||||
r->len = len;
|
||||
r->pos = 0;
|
||||
r->acc = 0;
|
||||
r->bits = 0;
|
||||
}
|
||||
|
||||
/* Read n bits MSB-first into *out. Returns 0 on success, -1 on underflow. */
|
||||
static int yapf__br_get(yapf__br *r, int n, uint32_t *out) {
|
||||
uint32_t v = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (r->bits == 0) {
|
||||
if (r->pos >= r->len) return -1;
|
||||
r->acc = r->buf[r->pos++];
|
||||
r->bits = 8;
|
||||
}
|
||||
v = (v << 1) | ((r->acc >> 7) & 1u);
|
||||
r->acc = (uint8_t)(r->acc << 1);
|
||||
r->bits--;
|
||||
}
|
||||
*out = v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Discard remaining bits in current byte (tile boundary alignment). */
|
||||
static void yapf__br_align(yapf__br *r) { r->acc = 0; r->bits = 0; }
|
||||
|
||||
/* ── YCoCg-R transform ───────────────────────────────────────────────── */
|
||||
/*
|
||||
* Lossless, integer, fully reversible.
|
||||
* Y ∈ [0,255], Co ∈ [-255,255], Cg ∈ [-255,255].
|
||||
* Alpha passes through unchanged.
|
||||
*/
|
||||
static void yapf__forward(yapf__tpx *dst, const uint8_t *src, int ch) {
|
||||
if (ch >= 3) {
|
||||
int r = src[0], g = src[1], b = src[2];
|
||||
int co = r - b;
|
||||
int t = b + (co >> 1);
|
||||
int cg = g - t;
|
||||
int y = t + (cg >> 1);
|
||||
dst->v[0] = (int16_t)y;
|
||||
dst->v[1] = (int16_t)co;
|
||||
dst->v[2] = (int16_t)cg;
|
||||
if (ch == 4) dst->v[3] = (int16_t)src[3];
|
||||
} else {
|
||||
for (int c = 0; c < ch; c++) dst->v[c] = (int16_t)src[c];
|
||||
}
|
||||
}
|
||||
|
||||
static void yapf__inverse(uint8_t *dst, const yapf__tpx *src, int ch) {
|
||||
if (ch >= 3) {
|
||||
int y = src->v[0];
|
||||
int co = src->v[1];
|
||||
int cg = src->v[2];
|
||||
int t = y - (cg >> 1);
|
||||
int g = cg + t;
|
||||
int b = t - (co >> 1);
|
||||
int r = b + co;
|
||||
dst[0] = (uint8_t)(r < 0 ? 0 : r > 255 ? 255 : r);
|
||||
dst[1] = (uint8_t)(g < 0 ? 0 : g > 255 ? 255 : g);
|
||||
dst[2] = (uint8_t)(b < 0 ? 0 : b > 255 ? 255 : b);
|
||||
if (ch == 4) dst[3] = (uint8_t)src->v[3];
|
||||
} else {
|
||||
for (int c = 0; c < ch; c++) dst[c] = (uint8_t)src->v[c];
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Predictor ───────────────────────────────────────────────────────── */
|
||||
/* left + above - diag, unclipped (works for signed int16 channels). */
|
||||
static int32_t yapf__predict(int16_t L, int16_t A, int16_t D) {
|
||||
return (int32_t)L + (int32_t)A - (int32_t)D;
|
||||
}
|
||||
|
||||
/* ── Signed bit width ────────────────────────────────────────────────── */
|
||||
/* Minimum two's-complement bits to represent v. Returns 0 if v == 0. */
|
||||
static int yapf__sbits(int v) {
|
||||
if (v == 0) return 0;
|
||||
if (v < 0) v = ~v; /* largest positive needing same width */
|
||||
int b = 1; /* sign bit */
|
||||
while (v > 0) { v >>= 1; b++; }
|
||||
return b;
|
||||
}
|
||||
|
||||
/* First-pixel bit widths: Co (ch1) and Cg (ch2) need 9 bits each because
|
||||
* YCoCg-R maps them to [-255, 255]; all other channels stay at 8 bits. */
|
||||
static int yapf__first_bits(int c, int ch) {
|
||||
return (ch >= 3 && (c == 1 || c == 2)) ? 9 : 8;
|
||||
}
|
||||
|
||||
/* ── Tile encode ─────────────────────────────────────────────────────── */
|
||||
|
||||
static int yapf__encode_tile(
|
||||
yapf__bw *w,
|
||||
const uint8_t *pixels, uint32_t img_w,
|
||||
uint32_t tx, uint32_t ty, uint32_t tw, uint32_t th,
|
||||
int ch)
|
||||
{
|
||||
yapf__tpx raw[YAPF__MAX_TILE_PX];
|
||||
int max_abs[4] = {0, 0, 0, 0};
|
||||
int bd[4];
|
||||
size_t total = (size_t)tw * th;
|
||||
|
||||
/* Gather + transform */
|
||||
for (uint32_t y = 0; y < th; y++) {
|
||||
for (uint32_t x = 0; x < tw; x++) {
|
||||
const uint8_t *src =
|
||||
pixels + ((size_t)(ty + y) * img_w + (tx + x)) * (size_t)ch;
|
||||
yapf__forward(&raw[y * tw + x], src, ch);
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute residuals and track max |residual| per channel.
|
||||
* Pixel (0,0) is stored raw; all others use 2-D prediction. */
|
||||
for (size_t i = 1; i < total; i++) {
|
||||
uint32_t x = (uint32_t)(i % tw);
|
||||
uint32_t y = (uint32_t)(i / tw);
|
||||
|
||||
for (int c = 0; c < ch; c++) {
|
||||
int16_t L = (x > 0) ? raw[y * tw + (x-1)].v[c] : 0;
|
||||
int16_t A = (y > 0) ? raw[(y-1)*tw + x].v[c] : 0;
|
||||
int16_t D = (x > 0 && y > 0) ? raw[(y-1)*tw + (x-1)].v[c] : 0;
|
||||
int32_t pred = yapf__predict(L, A, D);
|
||||
int res = (int)raw[i].v[c] - (int)pred;
|
||||
int ab = res < 0 ? -res : res;
|
||||
if (ab > max_abs[c]) max_abs[c] = ab;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write per-channel residual bit depths */
|
||||
for (int c = 0; c < ch; c++) {
|
||||
bd[c] = yapf__sbits(max_abs[c]);
|
||||
if (yapf__bw_put(w, (uint32_t)bd[c], 8) != 0) return -1;
|
||||
}
|
||||
|
||||
/* Write first pixel with channel-appropriate width */
|
||||
for (int c = 0; c < ch; c++) {
|
||||
int nbits = yapf__first_bits(c, ch);
|
||||
uint32_t bits = (uint32_t)((int32_t)raw[0].v[c] & ((1 << nbits) - 1));
|
||||
if (yapf__bw_put(w, bits, nbits) != 0) return -1;
|
||||
}
|
||||
|
||||
/* Write packed residuals for pixels 1 .. total-1 */
|
||||
for (size_t i = 1; i < total; i++) {
|
||||
uint32_t x = (uint32_t)(i % tw);
|
||||
uint32_t y = (uint32_t)(i / tw);
|
||||
|
||||
for (int c = 0; c < ch; c++) {
|
||||
if (bd[c] == 0) continue;
|
||||
int16_t L = (x > 0) ? raw[y * tw + (x-1)].v[c] : 0;
|
||||
int16_t A = (y > 0) ? raw[(y-1)*tw + x].v[c] : 0;
|
||||
int16_t D = (x > 0 && y > 0) ? raw[(y-1)*tw + (x-1)].v[c] : 0;
|
||||
int32_t pred = yapf__predict(L, A, D);
|
||||
int res = (int)raw[i].v[c] - (int)pred;
|
||||
uint32_t bits = (uint32_t)(res & ((1 << bd[c]) - 1));
|
||||
if (yapf__bw_put(w, bits, bd[c]) != 0) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
yapf__bw_flush(w);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Tile decode ─────────────────────────────────────────────────────── */
|
||||
|
||||
static int yapf__decode_tile(
|
||||
yapf__br *r,
|
||||
uint8_t *pixels, uint32_t img_w,
|
||||
uint32_t tx, uint32_t ty, uint32_t tw, uint32_t th,
|
||||
int ch)
|
||||
{
|
||||
yapf__tpx raw[YAPF__MAX_TILE_PX];
|
||||
int bd[4];
|
||||
uint32_t bits;
|
||||
size_t total = (size_t)tw * th;
|
||||
|
||||
/* Read residual bit depths */
|
||||
for (int c = 0; c < ch; c++) {
|
||||
if (yapf__br_get(r, 8, &bits) != 0) return -1;
|
||||
bd[c] = (int)bits;
|
||||
}
|
||||
|
||||
/* Read first pixel.
|
||||
* 8-bit channels (Y, A, grayscale) are unsigned [0,255] — no sign-extend.
|
||||
* 9-bit channels (Co, Cg) are signed [-255,255] — two's complement. */
|
||||
for (int c = 0; c < ch; c++) {
|
||||
int nbits = yapf__first_bits(c, ch);
|
||||
if (yapf__br_get(r, nbits, &bits) != 0) return -1;
|
||||
int v;
|
||||
if (nbits == 9) {
|
||||
v = (int)(bits & 0xFFu);
|
||||
if (bits & 0x100u) v -= 256;
|
||||
} else {
|
||||
v = (int)bits; /* unsigned: 0–255 */
|
||||
}
|
||||
raw[0].v[c] = (int16_t)v;
|
||||
}
|
||||
|
||||
/* Decode pixels 1 .. total-1 */
|
||||
for (size_t i = 1; i < total; i++) {
|
||||
uint32_t x = (uint32_t)(i % tw);
|
||||
uint32_t y = (uint32_t)(i / tw);
|
||||
|
||||
for (int c = 0; c < ch; c++) {
|
||||
int16_t L = (x > 0) ? raw[y * tw + (x-1)].v[c] : 0;
|
||||
int16_t A = (y > 0) ? raw[(y-1)*tw + x].v[c] : 0;
|
||||
int16_t D = (x > 0 && y > 0) ? raw[(y-1)*tw + (x-1)].v[c] : 0;
|
||||
int32_t pred = yapf__predict(L, A, D);
|
||||
int res = 0;
|
||||
|
||||
if (bd[c] > 0) {
|
||||
if (yapf__br_get(r, bd[c], &bits) != 0) return -1;
|
||||
int sb = 1 << (bd[c] - 1);
|
||||
res = (int)(bits & (uint32_t)(sb - 1));
|
||||
if (bits & (uint32_t)sb) res -= sb;
|
||||
}
|
||||
raw[i].v[c] = (int16_t)((int)pred + res);
|
||||
}
|
||||
}
|
||||
|
||||
yapf__br_align(r);
|
||||
|
||||
/* Inverse transform and write to output buffer */
|
||||
for (uint32_t y = 0; y < th; y++) {
|
||||
for (uint32_t x = 0; x < tw; x++) {
|
||||
uint8_t *dst =
|
||||
pixels + ((size_t)(ty + y) * img_w + (tx + x)) * (size_t)ch;
|
||||
yapf__inverse(dst, &raw[y * tw + x], ch);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Level encode / decode ───────────────────────────────────────────── */
|
||||
|
||||
static int yapf__encode_level(
|
||||
yapf__bw *w, const uint8_t *pixels, uint32_t mw, uint32_t mh, int ch)
|
||||
{
|
||||
uint32_t tx_count = (mw + YAPF_TILE_SIZE - 1) / YAPF_TILE_SIZE;
|
||||
uint32_t ty_count = (mh + YAPF_TILE_SIZE - 1) / YAPF_TILE_SIZE;
|
||||
|
||||
for (uint32_t ty = 0; ty < ty_count; ty++) {
|
||||
for (uint32_t tx = 0; tx < tx_count; tx++) {
|
||||
uint32_t tw = (tx + 1) * YAPF_TILE_SIZE <= mw
|
||||
? YAPF_TILE_SIZE : mw - tx * YAPF_TILE_SIZE;
|
||||
uint32_t th = (ty + 1) * YAPF_TILE_SIZE <= mh
|
||||
? YAPF_TILE_SIZE : mh - ty * YAPF_TILE_SIZE;
|
||||
if (yapf__encode_tile(w, pixels, mw,
|
||||
tx * YAPF_TILE_SIZE,
|
||||
ty * YAPF_TILE_SIZE,
|
||||
tw, th, ch) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int yapf__decode_level(
|
||||
const uint8_t *data, size_t data_len,
|
||||
uint8_t *pixels, uint32_t mw, uint32_t mh, int ch)
|
||||
{
|
||||
yapf__br r;
|
||||
yapf__br_init(&r, data, data_len);
|
||||
|
||||
uint32_t tx_count = (mw + YAPF_TILE_SIZE - 1) / YAPF_TILE_SIZE;
|
||||
uint32_t ty_count = (mh + YAPF_TILE_SIZE - 1) / YAPF_TILE_SIZE;
|
||||
|
||||
for (uint32_t ty = 0; ty < ty_count; ty++) {
|
||||
for (uint32_t tx = 0; tx < tx_count; tx++) {
|
||||
uint32_t tw = (tx + 1) * YAPF_TILE_SIZE <= mw
|
||||
? YAPF_TILE_SIZE : mw - tx * YAPF_TILE_SIZE;
|
||||
uint32_t th = (ty + 1) * YAPF_TILE_SIZE <= mh
|
||||
? YAPF_TILE_SIZE : mh - ty * YAPF_TILE_SIZE;
|
||||
if (yapf__decode_tile(&r, pixels, mw,
|
||||
tx * YAPF_TILE_SIZE,
|
||||
ty * YAPF_TILE_SIZE,
|
||||
tw, th, ch) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* yapf_load
|
||||
* ======================================================================== */
|
||||
|
||||
yapf_image_t *yapf_load(const char *filename) {
|
||||
if (!filename) return NULL;
|
||||
|
||||
FILE *f = fopen(filename, "rb");
|
||||
if (!f) return NULL;
|
||||
|
||||
uint8_t hdr[YAPF__HDR_LEN];
|
||||
if (fread(hdr, 1, YAPF__HDR_LEN, f) != YAPF__HDR_LEN) goto fail_f;
|
||||
if (memcmp(hdr, YAPF__MAGIC, 4) != 0 || hdr[4] != YAPF__VERSION) goto fail_f;
|
||||
|
||||
uint8_t channels = hdr[5];
|
||||
uint8_t gpu_fmt = hdr[6];
|
||||
uint8_t mip_levels = hdr[7];
|
||||
uint8_t flags = hdr[8];
|
||||
uint32_t base_w = yapf__r32le(hdr + 12);
|
||||
uint32_t base_h = yapf__r32le(hdr + 16);
|
||||
|
||||
if (channels < 1 || channels > 4) goto fail_f;
|
||||
if (mip_levels < 1 || mip_levels > YAPF_MAX_MIPS) goto fail_f;
|
||||
if (base_w == 0 || base_w > YAPF_MAX_DIM) goto fail_f;
|
||||
if (base_h == 0 || base_h > YAPF_MAX_DIM) goto fail_f;
|
||||
|
||||
/* Mip index */
|
||||
size_t idx_bytes = (size_t)mip_levels * YAPF__MIP_ENTRY;
|
||||
uint8_t *mip_idx = (uint8_t *)malloc(idx_bytes);
|
||||
if (!mip_idx) goto fail_f;
|
||||
if (fread(mip_idx, 1, idx_bytes, f) != idx_bytes) { free(mip_idx); goto fail_f; }
|
||||
|
||||
yapf_image_t *img = (yapf_image_t *)calloc(1, sizeof(yapf_image_t));
|
||||
if (!img) { free(mip_idx); goto fail_f; }
|
||||
|
||||
img->width = base_w;
|
||||
img->height = base_h;
|
||||
img->channels = channels;
|
||||
img->gpu_format = gpu_fmt;
|
||||
img->flags = flags;
|
||||
img->mip_levels = mip_levels;
|
||||
|
||||
img->mips = (uint8_t **)calloc(mip_levels, sizeof(uint8_t *));
|
||||
if (!img->mips) { free(mip_idx); goto fail_img; }
|
||||
|
||||
for (uint8_t m = 0; m < mip_levels; m++) {
|
||||
uint32_t offset = yapf__r32le(mip_idx + m * YAPF__MIP_ENTRY);
|
||||
uint32_t size = yapf__r32le(mip_idx + m * YAPF__MIP_ENTRY + 4);
|
||||
uint32_t mw = base_w >> m; if (mw < 1) mw = 1;
|
||||
uint32_t mh = base_h >> m; if (mh < 1) mh = 1;
|
||||
|
||||
uint8_t *data = (uint8_t *)malloc(size);
|
||||
if (!data) { free(mip_idx); goto fail_mips; }
|
||||
|
||||
if (fseek(f, (long)offset, SEEK_SET) != 0 ||
|
||||
fread(data, 1, size, f) != size) {
|
||||
free(data); free(mip_idx); goto fail_mips;
|
||||
}
|
||||
|
||||
size_t px_bytes = (size_t)mw * mh * channels;
|
||||
img->mips[m] = (uint8_t *)malloc(px_bytes);
|
||||
if (!img->mips[m]) { free(data); free(mip_idx); goto fail_mips; }
|
||||
|
||||
if (yapf__decode_level(data, size, img->mips[m], mw, mh, channels) != 0) {
|
||||
free(data); free(mip_idx); goto fail_mips;
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
||||
free(mip_idx);
|
||||
img->pixels = img->mips[0];
|
||||
fclose(f);
|
||||
return img;
|
||||
|
||||
fail_mips:
|
||||
for (uint8_t m = 0; m < mip_levels; m++) free(img->mips[m]);
|
||||
free(img->mips);
|
||||
fail_img:
|
||||
free(img);
|
||||
fail_f:
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* yapf_save
|
||||
* ======================================================================== */
|
||||
|
||||
int yapf_save(const char *filename, const yapf_image_t *img) {
|
||||
if (!filename || !img || !img->pixels) return YAPF_ERR_INVALID;
|
||||
if (img->width == 0 || img->width > YAPF_MAX_DIM) return YAPF_ERR_INVALID;
|
||||
if (img->height == 0 || img->height > YAPF_MAX_DIM) return YAPF_ERR_INVALID;
|
||||
if (img->channels < 1 || img->channels > 4) return YAPF_ERR_INVALID;
|
||||
if (img->mip_levels < 1 || img->mip_levels > YAPF_MAX_MIPS) return YAPF_ERR_INVALID;
|
||||
|
||||
int ch = (int)img->channels;
|
||||
uint8_t mip_levels = img->mip_levels;
|
||||
|
||||
/* Encode each mip level */
|
||||
yapf__bw bws[YAPF_MAX_MIPS];
|
||||
memset(bws, 0, sizeof(bws));
|
||||
|
||||
for (uint8_t m = 0; m < mip_levels; m++) {
|
||||
uint32_t mw = img->width >> m; if (mw < 1) mw = 1;
|
||||
uint32_t mh = img->height >> m; if (mh < 1) mh = 1;
|
||||
|
||||
const uint8_t *src = (m == 0)
|
||||
? img->pixels
|
||||
: (img->mips ? img->mips[m] : NULL);
|
||||
if (!src) {
|
||||
for (uint8_t j = 0; j < m; j++) free(bws[j].buf);
|
||||
return YAPF_ERR_INVALID;
|
||||
}
|
||||
|
||||
size_t est = (size_t)mw * mh * (size_t)ch + 256;
|
||||
if (yapf__bw_init(&bws[m], est) != 0) {
|
||||
for (uint8_t j = 0; j < m; j++) free(bws[j].buf);
|
||||
return YAPF_ERR_OOM;
|
||||
}
|
||||
if (yapf__encode_level(&bws[m], src, mw, mh, ch) != 0) {
|
||||
for (uint8_t j = 0; j <= m; j++) free(bws[j].buf);
|
||||
return YAPF_ERR_OOM;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute mip index offsets */
|
||||
uint32_t offsets[YAPF_MAX_MIPS];
|
||||
uint32_t cur = YAPF__HDR_LEN + (uint32_t)mip_levels * YAPF__MIP_ENTRY;
|
||||
for (uint8_t m = 0; m < mip_levels; m++) {
|
||||
offsets[m] = cur;
|
||||
cur += (uint32_t)bws[m].len;
|
||||
}
|
||||
|
||||
FILE *f = fopen(filename, "wb");
|
||||
if (!f) {
|
||||
for (uint8_t m = 0; m < mip_levels; m++) free(bws[m].buf);
|
||||
return YAPF_ERR_IO;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
uint8_t hdr[YAPF__HDR_LEN];
|
||||
memset(hdr, 0, sizeof(hdr));
|
||||
memcpy(hdr, YAPF__MAGIC, 4);
|
||||
hdr[4] = YAPF__VERSION;
|
||||
hdr[5] = img->channels;
|
||||
hdr[6] = img->gpu_format;
|
||||
hdr[7] = mip_levels;
|
||||
hdr[8] = img->flags;
|
||||
yapf__w32le(hdr + 12, img->width);
|
||||
yapf__w32le(hdr + 16, img->height);
|
||||
fwrite(hdr, 1, YAPF__HDR_LEN, f);
|
||||
|
||||
/* Mip index */
|
||||
for (uint8_t m = 0; m < mip_levels; m++) {
|
||||
uint8_t entry[YAPF__MIP_ENTRY];
|
||||
yapf__w32le(entry + 0, offsets[m]);
|
||||
yapf__w32le(entry + 4, (uint32_t)bws[m].len);
|
||||
fwrite(entry, 1, YAPF__MIP_ENTRY, f);
|
||||
}
|
||||
|
||||
/* Payload */
|
||||
int err = YAPF_OK;
|
||||
for (uint8_t m = 0; m < mip_levels; m++) {
|
||||
if (fwrite(bws[m].buf, 1, bws[m].len, f) != bws[m].len)
|
||||
err = YAPF_ERR_IO;
|
||||
free(bws[m].buf);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* yapf_free
|
||||
* ======================================================================== */
|
||||
|
||||
void yapf_free(yapf_image_t *img) {
|
||||
if (!img) return;
|
||||
if (img->mips) {
|
||||
for (uint8_t m = 0; m < img->mip_levels; m++)
|
||||
free(img->mips[m]);
|
||||
free(img->mips);
|
||||
}
|
||||
free(img);
|
||||
}
|
||||
210
yapf.h
Normal file
210
yapf.h
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* yapf.h — Yet Another Picture Format
|
||||
* Public API and format specification.
|
||||
*
|
||||
* Copyright 2026 Nexoniarz
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* YAPF — lossless, GPU-ready image format.
|
||||
*
|
||||
* Space savings come from two cheap mathematical steps — no entropy coder,
|
||||
* no lookup tables, no opcode dispatch. Decode is straight arithmetic:
|
||||
*
|
||||
* 1. YCoCg-R color transform (RGB↔Y/Co/Cg, fully reversible integers)
|
||||
* 2. 2-D neighbor prediction + per-tile adaptive bit-packing
|
||||
*
|
||||
* The file carries GPU metadata (format hint, mip levels, flags) so a
|
||||
* loader can upload the decoded buffer straight to a texture without any
|
||||
* additional transcoding step.
|
||||
*
|
||||
* =========================================================================
|
||||
* FILE FORMAT (version 2)
|
||||
* =========================================================================
|
||||
*
|
||||
* All multi-byte integers are little-endian.
|
||||
*
|
||||
* ┌─────────────────────────────────────────────────────────────────────┐
|
||||
* │ HEADER — 20 bytes │
|
||||
* ├────────┬──────┬────────────────────────────────────────────────────┤
|
||||
* │ Offset │ Size │ Field │
|
||||
* ├────────┼──────┼────────────────────────────────────────────────────┤
|
||||
* │ 0x00 │ 4 │ Magic: "YAPF" │
|
||||
* │ 0x04 │ 1 │ Version: 2 │
|
||||
* │ 0x05 │ 1 │ Channels: 1 gray / 2 gray+A / 3 RGB / 4 RGBA │
|
||||
* │ 0x06 │ 1 │ GPU format hint (YAPF_GPU_* constant) │
|
||||
* │ 0x07 │ 1 │ Mip levels (1 = base only, max 16) │
|
||||
* │ 0x08 │ 1 │ Flags (see YAPF_FLAG_* bitmask) │
|
||||
* │ 0x09 │ 3 │ Reserved (write 0, ignore on read) │
|
||||
* │ 0x0C │ 4 │ Base width (uint32, pixels) │
|
||||
* │ 0x10 │ 4 │ Base height (uint32, pixels) │
|
||||
* └────────┴──────┴────────────────────────────────────────────────────┘
|
||||
*
|
||||
* ┌─────────────────────────────────────────────────────────────────────┐
|
||||
* │ MIP INDEX — 8 bytes × mip_levels, immediately after header │
|
||||
* ├────────┬──────┬────────────────────────────────────────────────────┤
|
||||
* │ +0 │ 4 │ Byte offset of mip data from start of file (uint32) │
|
||||
* │ +4 │ 4 │ Byte size of mip data (uint32) │
|
||||
* └────────┴──────┴────────────────────────────────────────────────────┘
|
||||
*
|
||||
* ┌─────────────────────────────────────────────────────────────────────┐
|
||||
* │ PIXEL DATA — per mip level, referenced by the mip index │
|
||||
* ├─────────────────────────────────────────────────────────────────────┤
|
||||
* │ Pixels are divided into 32×32 tiles, stored in row-major order. │
|
||||
* │ The last column / row of tiles may be smaller (no padding). │
|
||||
* │ │
|
||||
* │ Encoding pipeline (encoder): │
|
||||
* │ a) RGB→YCoCg-R (skipped for gray/gray-alpha channels) │
|
||||
* │ b) 2-D prediction residuals (see below) │
|
||||
* │ c) Determine per-channel bit depth from max |residual| in tile │
|
||||
* │ d) Write tile header + packed residuals │
|
||||
* │ │
|
||||
* │ Tile layout: │
|
||||
* │ [bit_depth per channel] 1 byte each, channels bytes total │
|
||||
* │ [first pixel, raw] 1 byte per channel │
|
||||
* │ [packed residuals] bit_depth[c] bits per sample, │
|
||||
* │ stored MSB-first, zero-padded to the │
|
||||
* │ next byte boundary at the end of tile │
|
||||
* │ │
|
||||
* │ Prediction (lossless 2-D): │
|
||||
* │ pixel (0,0) in tile → stored raw, no residual │
|
||||
* │ pixel (x,0) → predict = left │
|
||||
* │ pixel (0,y) → predict = above │
|
||||
* │ pixel (x,y) → predict = left + above − diag │
|
||||
* │ residual = (actual − predict) wrapped mod 256, stored signed │
|
||||
* │ │
|
||||
* │ Bit packing: │
|
||||
* │ bits_needed = bit_depth[c] (0 means all residuals are 0) │
|
||||
* │ Each residual is stored as a (bits_needed)-bit two's-complement │
|
||||
* │ signed integer, MSB first, packed across byte boundaries. │
|
||||
* │ bit_depth 1 → only the sign bit (value is −1 or 0) │
|
||||
* │ bit_depth 0 → no bits written; all samples in channel = 0 │
|
||||
* │ │
|
||||
* │ YCoCg-R forward transform (applied before prediction): │
|
||||
* │ Co = R − B │
|
||||
* │ t = B + (Co >> 1) │
|
||||
* │ Cg = G − t │
|
||||
* │ Y = t + (Cg >> 1) │
|
||||
* │ Stored channel order: Y, Co, Cg (replaces R, G, B) │
|
||||
* │ Alpha is left unchanged. │
|
||||
* │ │
|
||||
* │ YCoCg-R inverse transform (applied after prediction): │
|
||||
* │ t = Y − (Cg >> 1) │
|
||||
* │ G = Cg + t │
|
||||
* │ B = t − (Co >> 1) │
|
||||
* │ R = B + Co │
|
||||
* └─────────────────────────────────────────────────────────────────────┘
|
||||
*
|
||||
* GPU FORMAT HINTS (YAPF_GPU_* values for the gpu_format field)
|
||||
* ──────────────────────────────────────────────────────────────
|
||||
* 0x00 RGBA8 GL_RGBA8 / VK_FORMAT_R8G8B8A8_UNORM
|
||||
* 0x01 RGB8 GL_RGB8 / VK_FORMAT_R8G8B8_UNORM
|
||||
* 0x02 RG8 GL_RG8 / VK_FORMAT_R8G8_UNORM
|
||||
* 0x03 R8 GL_R8 / VK_FORMAT_R8_UNORM
|
||||
* 0x04 SRGB8_A8 GL_SRGB8_ALPHA8
|
||||
* 0x05 SRGB8 GL_SRGB8
|
||||
*
|
||||
* FLAGS (YAPF_FLAG_* bitmask for the flags field)
|
||||
* ─────────────────────────────────────────────────
|
||||
* bit 0 PREMULT_ALPHA alpha is premultiplied into RGB
|
||||
* bit 1 SRGB pixels are in sRGB gamma (informational only)
|
||||
*/
|
||||
|
||||
#ifndef YAPF_H
|
||||
#define YAPF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* ── GPU format hints ────────────────────────────────────────────────── */
|
||||
|
||||
#define YAPF_GPU_RGBA8 0x00
|
||||
#define YAPF_GPU_RGB8 0x01
|
||||
#define YAPF_GPU_RG8 0x02
|
||||
#define YAPF_GPU_R8 0x03
|
||||
#define YAPF_GPU_SRGB8_A8 0x04
|
||||
#define YAPF_GPU_SRGB8 0x05
|
||||
|
||||
/* ── Flags ───────────────────────────────────────────────────────────── */
|
||||
|
||||
#define YAPF_FLAG_PREMULT_ALPHA 0x01
|
||||
#define YAPF_FLAG_SRGB 0x02
|
||||
|
||||
/* ── Channel constants ───────────────────────────────────────────────── */
|
||||
|
||||
#define YAPF_CHANNELS_GRAY 1
|
||||
#define YAPF_CHANNELS_GRAY_ALPHA 2
|
||||
#define YAPF_CHANNELS_RGB 3
|
||||
#define YAPF_CHANNELS_RGBA 4
|
||||
|
||||
/* ── Return codes ────────────────────────────────────────────────────── */
|
||||
|
||||
#define YAPF_OK 0
|
||||
#define YAPF_ERR_INVALID -1
|
||||
#define YAPF_ERR_IO -2
|
||||
#define YAPF_ERR_OOM -3
|
||||
#define YAPF_ERR_CORRUPT -4
|
||||
|
||||
/* ── Limits ──────────────────────────────────────────────────────────── */
|
||||
|
||||
#define YAPF_MAX_DIM 65535u
|
||||
#define YAPF_MAX_MIPS 16u
|
||||
#define YAPF_TILE_SIZE 32u
|
||||
|
||||
/* ── Image descriptor ────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint8_t channels; /* 1–4 */
|
||||
uint8_t gpu_format; /* YAPF_GPU_* */
|
||||
uint8_t flags; /* YAPF_FLAG_* bitmask */
|
||||
uint8_t mip_levels; /* number of mip levels stored (1 = base only) */
|
||||
uint8_t *pixels; /* base-level pixels, malloc-owned */
|
||||
uint8_t **mips; /* mips[0]=base alias, mips[1..mip_levels-1] owned */
|
||||
} yapf_image_t;
|
||||
|
||||
/* ── Public API ──────────────────────────────────────────────────────── */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* yapf_load — decode a .yapf file.
|
||||
* Returns NULL on any failure (bad magic, truncated data, OOM, I/O error).
|
||||
* The returned image must be released with yapf_free().
|
||||
*/
|
||||
yapf_image_t *yapf_load(const char *filename);
|
||||
|
||||
/*
|
||||
* yapf_save — encode an image to disk.
|
||||
* img->mip_levels controls how many levels are written; set to 1 to write
|
||||
* only the base level. img->mips may be NULL when mip_levels == 1.
|
||||
* Returns YAPF_OK or a negative YAPF_ERR_* code.
|
||||
*/
|
||||
int yapf_save(const char *filename, const yapf_image_t *img);
|
||||
|
||||
/*
|
||||
* yapf_free — release all memory owned by an image returned by yapf_load().
|
||||
* NULL-safe.
|
||||
*/
|
||||
void yapf_free(yapf_image_t *img);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* YAPF_H */
|
||||
Loading…
Reference in a new issue