>
technical stuff
>
the file formats
>
images
images in .veado
files are written from bottom to top, left to right.
AIMG
chunkprovides information on an image, static or animated.
length | description |
---|---|
4 | image width, unsigned integer, non-zero |
4 | image height, unsigned integer, non-zero |
1+ | frame count = n , variable-length integer |
0+ | loop count, variable-length integer, only present if n > 1 |
then for each frame in n
:
length | description |
---|---|
4 | id for the ABMP chunk containing frame texture |
4 | offset in X for the frame texture, unsigned integer |
4 | offset in Y for the frame texture, unsigned integer |
8 | frame duration, double-precision float |
ABMP
chunkprovides texture data.
length | description |
---|---|
4 | texture width, unsigned integer, non-zero |
4 | texture height, unsigned integer, non-zero |
4 | texture format, FourCC |
rem. | texture data |
RAW.
formattextures stored as raw are saved as RGBA, one byte per channel, 0 to 255, from bottom to top, then left to right.
VDD.
formatthe VeadoDelta texture format is a sub-byte RLE encoding, meant to work well with multithreading (one thread per channel both for read and write). has small sizes for illustrations and vector images, not so much but still a lot better than raw for photos.
it stores everything in 4-byte unsigned integers (uint), and just like the raw format, each value ranges from 0 to 255, encoding from bottom to top, then left to right.
the first 4 uints in the buffer represent the length (in uints, so multiply that by 4 to get the length in bytes) of each channel, and then after those 4, each channel is encoded separately. alpha is encoded first, then R, G, and B.
if the length of a channel is at least 0xFFFFFF00
, the length is considered to be zero, and the least-significant byte is used as the constant value of that channel throughout the entire image. for example, a length of 0xFFFFFF80
means this channel doesn’t have an uint buffer, and the channel value is 0x80
(aka 128) for all pixels.
when encoding a channel other than alpha, values where the corresponding pixel has an alpha of value zero are skipped.
to encode a channel, it first takes the value sequence and applies the difference between the current value and the previous one. for the first value, if we’re encoding the alpha channel, the delta value is the difference between the current value and 255; otherwise, the delta value is the same as the current value.
here’s an example:
original | 6 | 7 | 8 | 7 | 11 | 14 | 14 | 14 | 14 | 14 |
delta | 6 | 1 | 1 | -1 | 4 | 3 | 0 | 0 | 0 | 0 |
the delta values are stored as signed bytes, meaning that they range from -128 to 127 and take advantage of byte overflows/underflows:
original | 0 | 255 | 0 | 240 |
delta | 0 | -1 | 1 | -16 |
the delta values are then encoded as prefix codes of variable bit length:
abs value | sequences |
---|---|
x = 0 |
1 0 |
x = 1 ~ 2 |
1 1 x - 1 (1 bit) sign |
x = 3 ~ 4 |
0 1 0 x - 3 (1 bit) sign |
x = 5 ~ 8 |
0 1 1 x - 5 (2 bits) sign |
x = 9 ~ 16 |
0 0 1 0 x - 9 (3 bits) sign |
x = 17 ~ 32 |
0 0 1 1 x - 17 (4 bits) sign |
x = 33 ~ 64 |
0 0 0 1 0 x - 33 (5 bits) sign |
x = 65 ~ 128 |
0 0 0 1 1 x - 65 (6 bits) sign |
the sign
segment is 1
for positive, and0
for negative.
when 0 is repeated at least 7 times, instead of encoding it as multiple 1
0
, it is instead encoded like this, where n
is the number of zeroes, with a maximum value of 262:
sequences |
---|
0 0 0 0 n - 7 (8 bits) |
let’s encode some values, as an example:
value | sequences |
---|---|
1 | 1 1 0 1 |
-2 | 1 1 1 0 |
3 | 0 1 0 0 1 |
4 | 0 1 0 1 1 |
-6 | 0 1 1 01 0 |
-72 | 0 0 0 1 1 000111 0 |
15 | 0 0 1 0 110 1 |
then each sequence is encoded into the uint buffer of the channel, where we can fit 32 bits, from the least to the most significant bits. any remaining bits in a sequence leak into the next uint, again starting on the least signficant bits.
let’s take our previous example and encode it as an uint buffer:
buffer | binary |
---|---|
#1 | 111 1 1 0 0 0 0 01 1 1 0 1 1 0 1 0 1 0 0 1 0 0 1 1 1 1 0 1 1 |
#2 | 1 110 0 1 0 0 0 000 |
note that even though the sequence ordering will look reversed (due to adding each sequence to the least significant bits first), the order of bits inside each sequence is kept intact.
this channel would therefore be encoded as two uints: 0xF83B527B
and 0xD40
!
to parse each value in a channel, a similar but reverse process would be done: every time you need to read a delta value from the uint buffer, you’d read one bit from the current uint until it matches a prefix, then read a number of a certain bit length, and then read the sign bit if applicable.
the following pseudo-code explains the process of parsing each delta value, and how many times said value should be repeated:
delta = 0
repeat = 1
if readBits(1) == 1:
if readBits(1) == 1:
delta = readBits(1) + 1
elif readBits(1) == 1:
if readBits(1) == 0:
delta = readBits(1) + 3
else:
delta = readBits(2) + 5
elif readBits(1) == 1:
if readBits(1) == 0:
delta = readBits(3) + 9
else:
delta = readBits(4) + 17
elif readBits(1) == 1:
if readBits(1) == 0:
delta = readBits(5) + 33
else:
delta = readBits(6) + 65
else:
repeat = readBits(8) + 7
if delta != 0:
if readBits(1) == 0:
delta = -delta
in this example, readBits(n)
is a function that consumes n
bits from the uint buffer, starting from the least significant bits, and reading from the next uint if the remaining bits in the current uint aren’t enough.
this would yield the delta value sequence – from it, you can convert it into the absolute value sequence by accumulating it:
delta_sequence = [...] # assuming this is populated with delta values
current_value = 0 # or 255, if we're parsing the alpha channel
final_sequence = []
for delta in delta_sequence:
current_value = current_value + delta
current_value = modulo(current_value, 256) # ensure we're within byte range
final_sequence.push(current_value)
when reading each channel, you may start by parsing the alpha channel before the other ones. this way, you know which pixels have an alpha value of zero, and thus you can parse the other channels directly into pixels by skipping the zero alpha ones.