FIOQ is a lossless image format designed for fast encoding and decoding of RGBA image data used by Game Maker Studio. It uses a combination of techniques inspired by the QOI (Quite OK Image) format, including an index of previously seen colors, run-length encoding, and differential color encoding.
An FIOQ file starts with a 12-byte header:
| Bytes | Description | Type |
|---|---|---|
| 0-3 | Magic Bytes | char[4] |
| 4-5 | Image Width | u16 |
| 6-7 | Image Height | u16 |
| 8-11 | Payload Size | u32 |
The Payload Size indicates the number of bytes that follow the header, containing the encoded pixel data (operation chunks).
Images are encoded row by row, left to right, top to bottom. The decoder and encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. Pixels are encoded as:
- A run of the current color
- An index into an array of previously seen pixels
- A difference to the current color value in r,g,b
- Full r,g,b or r,g,b,a values
After a pixel color is determined by an operation (except for run operations), that color is stored in the color_array at an index calculated by:
index_position = (r ^ g ^ b ^ a) % 64
The operation payload is a sequence of "chunks." Each chunk starts with a 1-byte operation code. The top two bits or the top four bits of the op-byte determine the operation type.
-
Op-Byte Pattern: 00xxxxxx
-
Total Bytes: 1
op_code= 2-bit tag b00index= 6-bit index into the color index array: 0..63
-
Action:
- The
current_coloris set to the color stored atcolor_array[index]. - The
current_coloris output as the next pixel.
- The
- Op-Byte Pattern:
010xxxxx(binary) - Total Bytes: 1
- Payload Breakdown:
op_code_tag= 3-bit tagb010.value= 5-bit value (op_byte & 0x1F).run_length=value + 1. (This results in arun_lengthfrom 1 to 32 pixels.)
- Action:
- The
current_color(the color of the previous pixel) is outputrun_lengthtimes.
- The
-
Op-Byte Pattern:
011yxxxx(binary)- This corresponds to
(op_byte & 0xE0) == 0x60. Theybit (bit 4) is part of the011prefix. The lower 4 bitsxxxxare used for the run length calculation.
- This corresponds to
-
Total Bytes: 2
-
Payload Breakdown:
Byte Part Description 0 Op-Byte Tag b011(bits 7-5). Lower 4 bits (op_byte & 0x0F) arerun_length_high_bits.1 Data run_length_low_byte(8 bits).run_length = (run_length_high_bits << 8) + run_length_low_byte + 33.
-
Action:
- The
current_color(the color of the previous pixel) is outputrun_lengthtimes. - The
run_lengthranges from(0 << 8) + 0 + 33 = 33to(15 << 8) + 255 + 33 = 3840 + 255 + 33 = 4128pixels.
- The
-
Op-Byte Pattern:
10xxyyzz(binary)- This corresponds to
(op_byte & 0xC0) == 0x80.
- This corresponds to
-
Total Bytes: 1
-
Payload Breakdown:
Bits Description 7-6 Tag b105-4 dr_2bit(2-bit signed difference for Red, bias -2)3-2 dg_2bit(2-bit signed difference for Green, bias -2)1-0 db_2bit(2-bit signed difference for Blue, bias -2)- The signed differences
dr, dg, dbrange from -2 to 1. - (
00-> 0,01-> 1,10-> -2,11-> -1).
- The signed differences
-
Action:
new_r = (current_color.r + dr_2bit) % 256new_g = (current_color.g + dg_2bit) % 256new_b = (current_color.b + db_2bit) % 256new_a = current_color.a(alpha remains unchanged).current_coloris updated to{new_r, new_g, new_b, new_a}.- The new
current_coloris output as the next pixel. - The new
current_coloris stored incolor_array. (Using the hash function) - The difference to the current channel values are using a wraparound operation, so 1 - 2 will result in 255, while 255 + 1 will result in 0.
These operations use 2 bytes. The first byte determines the operation type and the red channel difference. The second byte encodes green and blue differences.
-
Op-Byte Patterns:
FIOQ_OP_ADD_DIFF:1100rrrr(binary) ((op_byte & 0xF0) == 0xC0)FIOQ_OP_SUB_DIFF:1101rrrr(binary) ((op_byte & 0xF0) == 0xD0)
-
Total Bytes: 2
-
Payload Breakdown:
Byte Bits Description 0 7-4 Tag b1100(ADD_DIFF) orb1101(SUB_DIFF)0 3-0 val_r(4 bits used fordrcalculation)1 7-4 val_g(4-bit signed difference for Green, bias -8)1 3-0 val_b(4-bit signed difference for Blue, bias -8) -
Action:
dgis derived fromval_g(a 4-bit signed value, range -8 to 7).dbis derived fromval_b(a 4-bit signed value, range -8 to 7).- For
FIOQ_OP_ADD_DIFF(1100rrrr):dr = val_r(unsigned, range 0 to 15).
- For
FIOQ_OP_SUB_DIFF(1101rrrr):dr = val_r - 16(range -16 to -1, assumingval_ris treated as 0-15 before subtraction).
new_r = (current_color.r + dr) % 256new_g = (current_color.g + dg) % 256new_b = (current_color.b + db) % 256new_a = current_color.a(alpha remains unchanged).current_coloris updated to{new_r, new_g, new_b, new_a}.- The new
current_coloris output as the next pixel. - The new
current_coloris stored incolor_array. (Using the hash function)
- Op-Byte Pattern:
1110xxxx(binary)- This corresponds to
(op_byte & 0xF0) == 0xE0.
- This corresponds to
- Total Bytes: 3
- Payload Breakdown:
The 3 bytes (op-byte, data-byte1, data-byte2) form a 20-bit data payload after the 4-bit tagb1110. These 20 bits are split into four 5-bit signed differences for dr, dg, db, da.- Op-Byte:
1110 dR4 dR3 dR2 dR1 - Data-Byte 1:
dR0 dG4 dG3 dG2 dG1 dG0 dB4 dB3 - Data-Byte 2:
dB2 dB1 dB0 dA4 dA3 dA2 dA1 dA0
Where: dR4 dR3 dR2 dR1 dR0form a 5-bit signeddr(bias -16, range -16 to 15).dG4 dG3 dG2 dG1 dG0form a 5-bit signeddg(bias -16, range -16 to 15).dB4 dB3 dB2 dB1 dB0form a 5-bit signeddb(bias -16, range -16 to 15).dA4 dA3 dA2 dA1 dA0form a 5-bit signedda(bias -16, range -16 to 15).
- Op-Byte:
- Action:
new_r = (current_color.r + dr) % 256new_g = (current_color.g + dg) % 256new_b = (current_color.b + db) % 256new_a = (current_color.a + da) % 256current_coloris updated to{new_r, new_g, new_b, new_a}.- The new
current_coloris output as the next pixel. - The new
current_coloris stored incolor_array. (Using the hash function)
- Op-Byte Pattern:
1111rgba(binary)- This corresponds to
(op_byte & 0xF0) == 0xF0.
- This corresponds to
- Total Bytes: 1 to 5
- 1 byte for the op-code.
- +1 byte for each channel flag set in the lower 4 bits of the op-code.
- Payload Breakdown:
- Op-Byte:
- Bits 7-4: Tag
b1111. - Bit 3 (
R_flag): If set (1), the next byte in the stream is the new Red value. - Bit 2 (
G_flag): If set (1), the next byte (after R if R_flag was set) is the new Green value. - Bit 1 (
B_flag): If set (1), the next byte (after G if G_flag was set) is the new Blue value. - Bit 0 (
A_flag): If set (1), the next byte (after B if B_flag was set) is the new Alpha value.
- Bits 7-4: Tag
- Op-Byte:
- Action:
- If
R_flagis set, read 1 byte and setcurrent_color.rto this value. - If
G_flagis set, read 1 byte and setcurrent_color.gto this value. - If
B_flagis set, read 1 byte and setcurrent_color.bto this value. - If
A_flagis set, read 1 byte and setcurrent_color.ato this value. - (Channels not flagged remain unchanged from the
current_colorbefore this operation). - The new
current_coloris output as the next pixel. - The new
current_coloris stored incolor_array. (Using the hash function)
- If