Files
bfl/docs/specification.typ
Slendi 4dd65bdc66 oop
Signed-off-by: Slendi <slendi@socopon.com>
2025-09-08 17:52:24 +03:00

127 lines
3.4 KiB
Typst
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#import "@preview/codly:1.3.0": *
#import "@preview/codly-languages:0.1.1": *
#show: codly-init.with()
#codly(
languages: codly-languages,
number-format: none,
zebra-fill: none,
display-name: false,
display-icon: false
)
#set page(
paper: "a4",
flipped: true,
margin: 1cm,
)
#set text(size: 8pt)
#align(center)[
#text(20pt)[*BitFLip (BFL) Image Format*] \
#text(12pt)[
Specification *v1.0*,
#datetime.today().display("[year]-[month]-[day]"),
Slendi <#link("mailto:slendi@socopon.com")>
]
#line(length: 100%, stroke: 0.5pt + gray)
#v(8pt)
]
#set heading(numbering: "1.1.")
#columns(2)[
= Overview
BFL is a compact, monochrome bitmap image format with optional 1-bit
transparency. Each image has two bitplanes that are encoded:
1. *Image bitplane* (required): 1 = white pixel, 0 = black pixel
2. *Alpha bitplane* (optional): 1 = transparent pixel, 0 = opaque pixel
Each bitplane is independently encoded using a run-length encoding or stored raw
bit-packed. Each resulting byte stream can then be optionally LZSS-compressed.
All multi-byte integers are little-endian.
= File Structure
```
struct Header {
char magic[3]; // Magic bytes "BFL"
u16 width; // Width of the image in pixels
u16 height; // Height of the image in pixels
u8 flags; // Various flags
u32 img_len; // Image stream length
u32 al_len; // Alpha stream length, 0 if none
u8 img[]; // Image stream bytes
u8 al[]; // Alpha stream bytes
}
```
== Dimensions
- Width and height are 16-bit unsigned integers.
- Pixel order is row-major, top-to-bottom, left-to-right.
- Total pixel count `N = Width * Height`. All bitplane encoders/decoders operate
on exactly `N` bits.
== Flags
- `0x01` *FLAG_HAS_ALPHA* - Alpha bitplane is present.
- `0x02` *FLAG_IMG_RAW* - Image stream is raw bit-packed.
- `0x04` *FLAG_TRA_RAW* - Alpha stream is raw bit-packed.
- `0x08` *FLAG_IMG_NOLZ* - Image stream is *not* LZSS-compressed.
- `0x10` *FLAG_TRA_NOLZ* - Alpha stream is *not* LZSS-compressed.
It is up to the encoder to determine which combination of those flags results in
a smaller file.
All remaining bits are reserved and should be set to 0.
= Bit-packing RAW streams
When a stream is marked `RAW` in flags, bits are packed LSB-first within each
byte.
- Bit for pixel index `i` is stored at byte `i / 8`, bit position `i % 8`.
- Unused high bits of the final byte (if `N` is not a multiple of 8) *must* be
zero when encoding and *must* be ignored when decoding.
#colbreak()
= Coinflip RLE
Unless a stream is marked `RAW`, each bitplane is encoded using "coinflip"
run-length coding:
```
Byte 0: Initial state (0 = start with 0-runs, 1 = start with 1-runs)
Byte 1..k: Repeated groups of:
Count (u8, number of pixels to emit of the current state)
[Optional 0] (u8, do not toggle state if present)
```
= LZSS
After coinflip RLE or RAW bit-packing, each resulting byte stream may be
compressed independently using LZSS with the following parameters:
- *Window size*: 4096
- *Lookahead*: 18
- *Minimum match*: 3
== Block format
Streams are encoded as a sequence of 8item groups preceded by a flag byte:
- For each bit b (0..7) in the flag (LSB first):
- If `(flag >> b) & 1 == 1`: Literal - copy next byte to output.
- Else: Match - read two bytes b0, b1 and emit:
- `length = (b0 >> 4) + 3` (range 3..18)
- `offset = ((b0 & 0x0F) << 8) | b1` (range 1..4095)
- Copy length bytes from `out_size - offset`.
]