Add specification

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-09-08 17:27:42 +03:00
parent e7158fd9a2
commit ea8d23b105
3 changed files with 127 additions and 4 deletions

BIN
docs/specification.pdf Normal file

Binary file not shown.

126
docs/specification.typ Normal file
View File

@@ -0,0 +1,126 @@
#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`.
]

View File

@@ -33,6 +33,7 @@
doxygen doxygen
gtest gtest
cppcheck cppcheck
typst
] ]
++ buildInputs ++ buildInputs
++ nativeBuildInputs ++ nativeBuildInputs
@@ -42,10 +43,6 @@
valgrind-light valgrind-light
] ]
); );
env = { };
shellHook = '''';
}; };
} }
); );