.mcai files are an experimental 64-bit file format I am using to explore various image data compression ideas.
The general concept is that an image is broken up into regular-sized blocks, then multiple strategies are tested against each block, with the strategy that results in the greatest compression being the one chosen for actually storing the block in the .mcai file. The more strategies are added, the more likely one of them will provide significantly better compression than the others.
iToolBox will always be able to load .mcai files from older versions, but as the format gains various compression modes, newer file saves will result in smaller files that cannot be loaded by older versions of the software. So backwards compatability is guaranteed, again, older files will always be loadable by newer versions of this software, but not the other way around. You may prefer to use the .ato format instead, as it is just as capable.
// Header flags as bitmasks // All undefined bits will be zero // ------------------------------- // flags1: #define MCAI_littleEndian 0x01 // if set, all values are stored in file as little endian #define MCAI_thumbnail 0x02 // if set, there is a THUM chunk containing a jpeg #define MCAI_monochrome 0x04 // if set, the image consists of an MCHA chunk #define MCAI_mappedR 0x08 // if set, red channel is indexed to a corresponding remap chunk #define MCAI_mappedG 0x10 // if set, green channel is indexed to a corresponding remap chunk #define MCAI_mappedB 0x20 // if set, blue channel is indexed to a corresponding remap chunk #define MCAI_mappedA 0x40 // if set, alpha channel is indexed to a corresponding remap chunk #define MCAI_mappedM 0x80 // if set, monochrome channel is indexed to a corresponding remap chunk // flags2 - presently unused, will be 0x00 // flags3 - presently unused, will be 0x00 // flags4 - presently unused, will be 0x00 // flags5 - presently unused, will be 0x00 // flags6 - presently unused, will be 0x00 // flags7 - presently unused, will be 0x00 // flags8 - presently unused, will be 0x00 struct header { }; struct mcaiHeader // Offset --- Description { // ====================== char chunkID[8]; // 0 -------- The ASCII characters "MCAIiacm" quint32 size; // 8 -------- sizeof(struct mcaiHeader) - (8 + sizeof(quint32)) quint32 magic; // 12 -------- Always 0x90862081 (observe endian flag in flags1) quint32 xw; // 16 -------- Image x width (observe endian flag in flags1) quint32 yw; // 20 -------- Image y width (observe endian flag in flags1) quint32 cx; // 24 -------- Cell x size (always 8) (observe endian flag in flags1) quint32 cy; // 28 -------- Cell y size (always 8) (observe endian flag in flags1) quint32 version; // 32 -------- 1...1 (observe endian flag in flags1) // When reading the checksum below, observe endian flag in flags1 quint32 checksum; // 36 -------- Sum of all bytes in header as unsigned values EXCEPT flags1; quint8 flags1; // 40 -------- Flags; see above defines quint8 flags2; // 41 -------- Flags; see above defines quint8 flags3; // 42 -------- Flags; see above defines quint8 flags4; // 43 -------- Flags; see above defines quint8 flags5; // 44 -------- Flags; see above defines quint8 flags6; // 45 -------- Flags; see above defines quint8 flags7; // 46 -------- Flags; see above defines quint8 flags8; // 47 -------- Flags; see above defines };
THUMmuht 32-bit size (observe endian flag in flags1) JPEG content
Optional; if present, contains a .jpeg image of the file content scaled to a reasonable thumbnail size. Readers that handle the "THUM" chunk must be prepared to further scale the thumbnail image to their specific desired preview size.
The canonical thumbnail size written in the reference implementation in iToolBox is 256 pixels wide, with the vertical size scaled proportionally. Writers are well advised to keep .jpeg quality relatively low (or in other words, keep the .jpeg compression high); the thumbnail image is only a preview and so does not require high fidelity.
Also in the canonical iToolBox implementation, when source images are smaller than the thumbnail size on both axis', they will be pixel-duplicated to the larger thumbnail size so as to preserve sharp pixel detail, while all other source images will be (somewhat) smoothly scaled down so as to preserve regional detail. This is suggested, but not required, behavior for an .mcai writer.
All of the following chunks contain UTF-8 text; integer and real numbers defined here are also text fields which must be converted by the file reader into the reader's internal numeric formats.
Optional; if present, provides image author details.
AUTHhtua 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image author details.
ANNOonna 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image copyright details.
COPYypoc 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image creation time details in the format "YYYYmmDD:HHmmSS UTC".
CTIMmitc 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image modification time details in the format "YYYYmmDD:HHmmSS UTC".
MTIMmitm 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image short-form name details.
NAMEeman 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides details on the software used to create or edit the image.
SOFTtfos 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides geographical latitude in meters as a real number.
LATIital 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides geographical longitude in meters as a real number.
LONGgnol 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides geographical altitude in meters as a real number.
ALTIitla 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides camera shutter speed in seconds as a real number.
SHUTtuhs 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides camera f/aperture as a real number.
APERrepa 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides camera focal length in millimeters as a real number.
FLENnelf 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides camera ISO value as a real number.
ISOVvosi 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image X centering as an integer pixel location. If the image is placed over another image, this is the horizontal location on the underlying image where the center of this image should align.
XCENnecx 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image Y centering as an integer pixel location. If the image is placed over another image, this is the vertical location on the underlying image where the center of this image should align.
YCENnecy 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image horizontal DPI as an integer.
XDPIipdx 32-bit size (observe endian flag in flags1) UTF-8 content
Optional; if present, provides image vertical DPI as an integer.
YDPIipdy 32-bit size (observe endian flag in flags1) UTF-8 content
There are five possible channel chunks which contain 16-bit image data...
...any of which may be immediately preceded by an associated map chunk...
MAPX chunks allow a channel that contains less than 257 or 17 distinct values to be reduced, repectively, to 8-bit or 4-bit monochome palette, or map, entries. An .mcai encoder first determines how many values are present in a channel, and then generates the appropriate map, then a mapped channel result immediately following.
A MAPX chunk is structured as follows, where X stands in for R (red), G (green), B (blue), A (alpha), or M (monochrome):
MAPXxpam 32-bit size (observe endian flag in flags1) 16-bit count of map entities, valid values of 1-256 (observe endian flag in flags1) [count] mapping values
When a MAPX chunk is present, if count is less than 17, then the lower four-bits of each decoded channel value select a mapping value to replace that channel value. So for instance, if a decompresed channel value is 0x7777, then masking that to four bits results in 0x0007, which is then used to select the 8th value provided in the map. The entire channel must be remapped in this fashion after decompression. These map values are based at zero; in other words, a value of 0x0000 selects the first map value.
When a MAPX chunk is present, if count is greater than 16, then the lower eight-bits of each decoded channel value select a mapping value to replace that channel value. So for instance, if a decompresed channel value is 0x8181, then masking that to eight bits results in 0x0081, which is then used to select the 130th value provided in the map channel. The entire channel must be remapped in this fashion after decompression. These map values are based at zero; in other words, a value of 0x0000 selects the first map value.
A XCHA chunk is structured as follows, where X stands in for R (red), G (green), B (blue), A (alpha), or M (monochrome):
XCHAxahc 32-bit size (observe endian flag in flags1) A series of compression blocks that will decompress to size cx times cy as defined in the header
The RCHA, GCHA, BCHA and ACHA chunks are respectively decompressed to the red, green, blue and alpha channels of the image. A value of 0x000 is minimum (zero) channel intensity; a value of 0xFFFF is maximum (full) channel intensity. In the case of an ACHA chunk, a value of 0x0000 results in a fully transparent pixel, while a value of 0xFFFF results in a fully opaque pixel.
The MCHA chunk, if present, is decompressed to all three of the red, green and blue channels, ultimately resulting in identical channel values for all three color channels.
Each .mcai image file must contain either (at least) one MCHA chunk, or (at least one of) all three of the CHA, CHA and CHA chunks.
If the ACHA chunk is not present, then the all alpha channel values default to 0xFFFF, which means the associated pixels are all fully opaque.
Each compression block will decompress to a perfectly square group of channel values exactly cx times cy in size. When a decompressed block overlaps an image right or bottom edge, the additional values are to be ignored; so in reconstructing a channel, the block is overlapped at cx and cy intervals over the actual channel, and only those values in the block that actually overlap the channel data are used. Once a block has been decompressed, if the associated MAPx chunk was encountered first, then the block is remapped as described above before being transferred to the target channel(s.)
Each compression block begins with a single code byte that indicates the type of compression the block is encoded with:
Code10 | Mode |
---|---|
1 | MCAI_nocomp |
5 | MCAI_newsingle |
7 | MCAI_newsingle8 |
8 | MCAI_nocomp8 |
10 | MCAI_nocomp4 |
11 | MCAI_4dm2 |
17 | MCAI_monolithic |
18 | MCAI_4mapped16 |
19 | MCAI_8mapped16 |
20 | MCAI_2mapped16 |
21 | MCAI_1mapped16 |
22 | MCAI_zlib |
The MCAI_nocomp code is followed by cx times cy 16 bit values. Observe the endian flag in the header in resolving these values. Data order is column, row.
The MCAI_newsingle code is followed by exactly one 16 bit value. This value is copied to each position in the cx times cy block. Observe the endian flag in the header in resolving this value.
The MCAI_newsingle8 code is followed by exactly one 8 bit value. This value is duplicated to the upper 8 bits of a 16-bit value (in other words, 0x5C would become 0x5C5C), and then is copied to each position in the cx times cy block.
The MCAI_nocomp8 code is followed by cx times cy 8 bit values. These values are each duplicated to the upper 8 bits of a 16-bit value (in other words, 0x5C would become 0x5C5C), and then the resulting values are placed at each relevant position in the cx times cy block. Data order is column, row.
The MCAI_nocomp4 code is followed by cx times cy 4 bit values, arranged as 2 to a byte, with the left most pixel in the left nybble of each byte. These values are each duplicated to each nybble of the upper 12 bits of a 16-bit value (in other words, 0x5 would become 0x5555), and then the resulting values are placed at each relevant position in the cx times cy block. Data order is column, row.
The MCAI_4dm2 code is followed by a single byte that contains a value in the range 0x00 to 0x0f. This is the 4-bit value of the [0,0]th block. The rest of the MCAI_monolithic block consists of four 2-bit values per byte, with the highest two bits being the leftmost value, and the lowest two bits the rightmost value. Each of these two-bit values is a signed value indicating the delta from the previous value as follows...
00 | No change |
01 | Subtract 1 from previous value |
10 | Subtract -2 from previous value |
11 | Subtract -1 from previous value |
...that will result in the final value after being copied to the upper three nybbles in the 16-bit channel value. Data order is spiral: The deltas are taken starting at 0,0, proceeding clockwise from the outside to the inside up to and including the last pixel in the block. This is shown graphically in the example below.
Here is an example 4x4 block, along with the associated data, scan order, and decode process:
Block Data and Subsequent Decode Process | |||||
---|---|---|---|---|---|
MCAI_4dm2 code |
value at [0,0] |
0x3f | 0xD5 | 0xD4 | 0x00 |
bit pairs: | 00 11 11 11 | 11 01 01 01 | 11 01 01 00 | 00 00 00 00 | |
delta values: | 0 -1 -1 -1 | -1 1 1 1 | -1 1 1 0 | 0 0 0 0 | |
4 bit results: | 6 7 8 9 | A 9 8 7 | 8 7 6 6 | 6 6 6 6 | |
channel values: | 6666 7777 8888 9999 | AAAA 9999 8888 7777 | 8888 7777 6666 6666 | 6666 6666 6666 6666 |
The MCAI_monolithic code is followed by exactly one 16 bit value (Observe the endian flag in the header in resolving this value.) This value is copied to every pixel in the entire channel. This is a special case: Only one block is used to describe the entire channel. Therefore, a reader must check the first block to see if the first block is an MCAI_monolithic block, and if it is, perform the MCAI_monolithic decompression and then skip the normal block-by block sequencing, as the channel decoding is already complete.
The MCAI_1mapped16 code is followed by a single byte that contains a count in the range 0x01 to 0x02. This value indicates how many 16-bit mapping values are to follow. After [count] of 16-bit mapping values comes one byte for every set of eight block values, left-side in the high bit, right in the low bit; the eight bits in each byte contain an index value from 0 to 1, which tell you which of the previously fetched 16-bit mapping values is to be used as the value for that position in the block.
The MCAI_2mapped16 code is followed by a single byte that contains a count in the range 0x03 to 0x04. This value indicates how many 16-bit mapping values are to follow. After [count] of 16-bit mapping values comes one byte for every set of four of block values, left-side in the high two bits, right in the lowest two bits; the four bit-pairs in each byte contain an index value from 0x00 to 0x03, which tell you which of the previously fetched 16-bit mapping values is to be used as the value for that position in the block.
The MCAI_4mapped16 code is followed by a single byte that contains a count in the range 0x05 to 0x10. This value indicates how many 16-bit mapping values are to follow. After [count] of 16-bit mapping values comes one byte for every pair of block values, left-side in the high nybble, right in the low; the two nybbles in each byte contain an index value from 0x00 to 0x0f, which tell you which of the previously fetched 16-bit mapping values is to be used as the value for that position in the block.
The MCAI_8mapped16 code is followed by a single byte that contains a count in the range 0x11 to 0xff. This value indicates how many 16-bit mapping values are to follow. After [count] of 16-bit mapping values comes one byte per block value; these bytes contain an index value from 0x00 to 0xff, which tell you which of the previously fetched 16-bit mapping values is to be used as the value for that position in the block.
The MCAI_zlib code is followed by a two-byte, 16-bit value that contains a count in the range 0x11 to 0xff that indicates the size of the compressed data in bytes. The value may be little endian, or big-endian, as indicated by the MCAI_littleEndian bit being set in the header's flags1 field, or not. Following this count is [count] of byte data as compressed by Qt's qCompress() function; it may be directly uncompressed with Qt's qUncompress() function. This compression is essentially the same as zlib's DEFLATE mechanism, except as the following note indicates.
Please consider supporting my iToolBox development efforts with a small PayPal donation. |