Skip to main content

lunar_image/
lib.rs

1//! Lunar Image Format (LIF / `.li`) — a fast, lossless, zstd-compressed
2//! internal image format for the Lunar engine.
3//!
4//! designed for fast decode and direct GPU upload. not a general-purpose
5//! image format — source assets are PNG/WebP/BMP, compiled bundles use `.li`.
6//!
7//! # quick start
8//!
9//! ```
10//! use lunar_image::{encode, decode};
11//!
12//! let pixels: Vec<u8> = (0..16 * 16 * 4).map(|i| i as u8).collect();
13//! let bytes = encode(16, 16, &pixels).unwrap();
14//! let image = decode(&bytes).unwrap();
15//! assert_eq!(image.width, 16);
16//! assert_eq!(image.pixels, pixels);
17//! ```
18//!
19//! # binary format
20//!
21//! ## file layout
22//!
23//! ```text
24//! +------------------+
25//! |     header       |  16 bytes fixed
26//! +------------------+
27//! |    chunk 0       |  pixel data (required, zstd compressed)
28//! +------------------+
29//! |    chunk 1       |  metadata (optional)
30//! +------------------+
31//! |    chunk 2       |  icc profile (optional)
32//! +------------------+
33//! ```
34//!
35//! ## header (16 bytes, all multi-byte fields little-endian)
36//!
37//! ```text
38//! offset  size  field    type    description
39//! 0       4     magic    [u8;4]  b"LIF\0" = 0x4C 0x49 0x46 0x00
40//! 4       2     version  u16     format version (currently 1)
41//! 6       2     flags    u16     bit flags (see below)
42//! 8       4     width    u32     image width in pixels
43//! 12      4     height   u32     image height in pixels
44//! ```
45//!
46//! **flags** (u16 LE):
47//! ```text
48//! bit 0  has_alpha      image contains alpha channel
49//! bit 1  has_metadata   metadata chunk present
50//! bit 2  has_icc        icc profile chunk present
51//! bit 3  premultiplied  alpha is premultiplied
52//! bit 4  planar         pixel data stored as channel planes (RRRR…GGGG…BBBB…AAAA…)
53//! bit 5  filtered       each plane row is delta-filtered before zstd (implies planar)
54//! 6-15   reserved       must be zero
55//! ```
56//!
57//! ## chunk layout (each chunk has a 16-byte header followed by compressed data)
58//!
59//! ```text
60//! offset  size  field              type    description
61//! 0       1     chunk type         u8      0x00=pixel data, 0x01=metadata, 0x02=icc
62//! 1       3     reserved           [u8;3]  zero padding
63//! 4       4     uncompressed size  u32     size after decompression
64//! 8       4     compressed size    u32     size of compressed data in bytes
65//! 12      4     zstd dict id       u32     dictionary id (0 = no dictionary)
66//! 16      N     compressed data    [u8;N]  zstd frame (N = compressed size)
67//! ```
68//!
69//! pixel data chunk: uncompressed size is exactly `width * height * 4`, row-major
70//! RGBA, no stride padding. decoded directly into a `Vec<u8>` ready for GPU upload.
71//!
72//! ## delta filtering
73//!
74//! when the `filtered` flag (bit 5) is set, each row of each channel plane is
75//! replaced before zstd with the difference from a per-row predictor (PNG-style:
76//! None/Sub/Up/Average/Paeth). the filter type is stored as one byte prefixed to
77//! each row, so the filtered chunk is `4 * height` bytes larger before compression.
78//!
79//! filtering turns smooth gradients into long runs of near-zero bytes, stacking
80//! on the per-channel coherence from planar layout. measured gains: smooth gradients
81//! compress ~90% smaller, photographic content ~35% smaller than planar-only.
82//! flat/sparse sprites can come out slightly larger, so the encoder compresses both
83//! ways and keeps the smaller — the flag is only set when filtering wins, making it
84//! a guaranteed non-regression. decode cost is one extra linear pass, paid only on
85//! files that used it.
86//!
87//! ## memory model
88//!
89//! decode peak: `compressed_file_size + width * height * 4` (roughly 2× raw pixels).
90//! the zstd buffer is freed immediately after decompression; the pixel vec is returned.
91
92mod decode;
93mod encode;
94mod error;
95mod filter;
96mod format;
97mod simd;
98
99pub use decode::{Image, decode};
100pub use encode::{EncodeOptions, encode, encode_with_opts};
101pub use error::{DecodeError, EncodeError};
102pub use simd::{
103	deinterleave_rgba, premultiply_alpha, reinterleave_rgba, rgba_to_bgra, srgb_to_linear,
104};