Maki-chan Graphics

JapaneseEnglish
MAKI bibleMAKI bible
MAG bible
MAG errataMAG errata

Various Japanese specifications and native X68000 software are found here. The table on the right has copies of the specifications with rough translations.

Japanese history of the MAG format at a doujin dictionary: http://www.paradisearmy.com/doujin/pasok4u.htm

You can find a ton of images archived as the MSX Pixel Art Preservation Collection, over here.

The open-source Recoil project specialises in handling many old image formats such as these. They have a handy browser-based viewer here.

Another browser-based MAG viewer is here. Just drag and drop a picture file in the browser window! It'll only open MAG files, not MAKI files.

There is an open source C utility somewhere online called mag2png that may be helpful. Irfanview and Grapholic and Susie may be able to natively view MKI and MAG files.

My own code for opening MAKI and MAG files is on Gitlab as part of SuperSakura. You can convert MKI, MAG, and MAX files to PNG with "sakutool". There are win32 binaries here.


Background

Back in the day before LZW compression became ubiquitous, less efficient image compression methods were used. Woody Rinn's MAKI format (or, more familiarly, Maki-chan graphics) was an early storage format in Japan. He developed the refined MAG format soon after, which became the dominant sharable image type until the World Wide Web brought GIFs into popular consciousness. Rinn-san is actually a capable illustrator as well as programmer, and has a few commercially published mangas. He developed the Maguro Paint System (MPS) (later rebranded to Multi Paint), which became the most popular PC-98 graphic editor. Someone kindly made a disk image set of Multi Paint available.

Unfortunately, all Maki-chan specifications I could find were in Japanese. So, as an aside on my work on SuperSakura, I tried to put together a decent English specification. Although most games I was trying to support use the more advanced Pi format, there are a handful of MAG images in Tenshitachi no Gogo Collection 1.

Afterward, the excellent Fábio Schmidlin got in touch to help improve this specification with a better understanding of the file header and the realisation that there's more than one version of the format! Thank you very much. ^_^


Specification

Each Maki-chan file starts with a signature: MAKI01A, MAKI01B, or MAKI02, padded to 8 bytes with spaces. This indicates the format version; MAKI01 is the MAKI format with two variants, and MAKI02 is the MAG format.

MAKI header

(MSB-first, since MAKI originated on the Sharp X68000)

OffsetSizeDescription
08Signature "MAKI01A " or "MAKI01B "
84 Computer model that the image was saved on, e.g. PC98, PC88, ESEQ, X68K, MSX2
1220 User name etc. metadata string, encoded in Shift-JIS; usually terminates with byte 0x1A, but not always
322Size of "flag B" section, in bytes
342Size of "pixel color data A" section, in bytes
362Size of "pixel color data B" section, in bytes
382Extension flag, see below
402Top left corner X offset, only 00 00 allowed
422Top left corner Y offset, only 00 00 allowed
442Image pixel width
462Image pixel height
4848Palette: 16 byte triplets, order GRB

(Note: The user name string may contain some MSX-specific screen mode values. Please see the errata linked at the top of the document for details. However, I haven't found any MAKI images actually using this.)

The extension flag, as defined, only uses the lowest two bits:

Bit 0 is the 200-row flag, or aspect ratio flag. If this bit is not set, use a normal pixel aspect ratio. If set, all pixels are twice as tall as they are wide; after decompressing normally, stretch the image to double height to unsquash it.

Bit 1 is the digital 8-color flag, used to indicate a legacy screenmode with only 3 bits per pixel. I have no example images using this, so I can't verify how exactly it works.

The image width and height can probably be ignored. The MAKI algorithm requires that all images are full-screen 640x400. Larger images are not allowed as defined, and smaller images are in practice always padded to full-screen with solid borders. For this reason, the top left corner offset is not really used either. (If there are any MAKI images that actually are not standard fullscreen ones, I've never found them.)

(Note: The image position and size in the header were specified with the intent of adding support for them later, but it seems Rinn-san moved on to MAG instead and never went back to add more features to MAKI.)

When reading the palette, note that only the top 4 bits are significant. By the specification, the bottom nibble must be set to 0 if the top is 0; otherwise, it must be set to 0xF. So values 0x10..0xFF must get a bitwise OR 0x0F, and values 0x00..0x0F must get a bitwise AND 0xF0. (I think for PC98 images specifically, where the palette was limited by hardware to 4 bits per channel, it would be more accurate to copy the top nibble into the bottom, but the visual difference is negligible.)

MAKI data layout
OffsetSizeDescription
096Header and palette
961000Flag A section
1096...Flag B section
......Pixel color sections A+B
End of file

The header and palette are immediately followed by a fixed "flag A" section which is always 1000 bytes long, then a variable-size "flag B" section, and a variable-size block of pixel color data.

The pixel color data is technically split in two sections due to the 64k segment size on 16-bit systems, but the second section immediately follows the first, so you can treat it as a single block.

Note: the flag A section's size is not included in the header since it is fixed, and the other section sizes given there may actually be incorrect. This is not a problem; during the first decompression phase, only the flag A and flag B sections are being read. Pixel color data is applied during the second phase, and can be assumed to start immediately after the last flag B byte you processed regardless of the nominal flag B section size.

MAKI decompression

Click the image to download the example MKI file, to test your decompressor.

First, a temporary buffer is needed for an image mask marking pixels that don't repeat vertically. Because all pictures are assumed to be full-screen, the buffer is always 320x400 single-bit values, where each bit applies to a pair of 4bpp pixels. (Feel free to make it an array or table of 128000 booleans, bytes, or native integers, whatever's easiest.)

The mask buffer is written into in 4x4 chunks. So, the first write goes in indexes [0,0] to [3,3], the second write in [4,0] to [7,3], and so on.

Start reading the flag A section one bit at a time, reading order from 0x80 to 0x01 in each byte.

If the flag A bit is 0, set the next 4x4 mask buffer chunk to all 0.

If the flag A bit is not 0, read 2 bytes from the flag B section and use them for the next 4x4 mask buffer chunk.

Example:

flag A (binary)
010
flag B (hexadecimal)
678F
first output 4x4
(a == 0)
second output 4x4
(a == 1)
third output 4x4
(a == 0)
0000 011 00000
0000 01110000
00001 0 0 00000
000011110000

If you want to render the mask buffer for whatever reason, it'll look something like this 1-bit-per-pixel outline, on the blue right side. If you see something like the distorted red left side, you're drawing the bits backwards.


Next, set up a 128000-byte output buffer. It will take 320x400 bytes, where each byte contains two 4-bit pixels.

Start reading the mask buffer one value at a time, from the top left, in normal scanline raster order (not as 4x4 chunks anymore).

If the mask value is 0, output a 0 byte (that is, two pixels of color 0).

If the mask value is not 0, fetch and output the next pixel color byte (that is, two pixels where at least one is not 0).

Example:

mask buffer
011 0 0000
0111 0000
1 0 0 0 0000
1111 0000
pixel data (hexadecimal)
38F166E5
output
(temp == 0)
output
(temp == 1)
output
(temp == 1)
output
(temp == 0)
0038F100

If you try to display the current output buffer using the image's proper palette, it'll look something like this colorfully outlined image. (Each byte has two palette indexes; the lower nibble is the left pixel in the pair.)


Finally, apply a bitwise XOR filter to take care of vertical pattern repetition. This step differs between the two MAKI variants:

The output buffer now contains the final 4bpp bitmap.

More example images


MAG header

(LSB-first, native x86 and PC-98 form)

OffsetSizeDescription
08Signature "MAKI02  " with two spaces at the end
84 Computer model or image editor that the image was saved on, e.g. PC98, MSX+, MPS, XPST
12.. User name etc. metadata string, encoded in Shift-JIS; Variable-length, terminates with byte 0x1A, and the first 00 after 0x1A marks the start of the real header
01 Start of header, always 00
11Computer model code
21Model-dependent flags
31Screen mode
42X coordinate for image's left edge
62Y coordinate for image's top edge
82X coordinate for image's right edge
102Y coordinate for image's bottom edge
124Offset from start of header to "flag A" stream
164Offset from start of header to "flag B" stream
204Size of "flag B" stream, in bytes
244Offset from start of header to "color index" stream
284Size of "color index" stream, in bytes
32..Palette: up to 256 byte triplets, order GRB

The model code, model-dependent flags, and screen mode mostly relate to hardware differences in the various computers used back then. For example, the PC-88 and earliest PC-98 models only had screen modes with up to 8 simultaneous colors. The later MSX2+ could display thousands of colors but only by using a mildly terrifying color encoding scheme. However, the basic decompression steps are always the same regardless of hardware, and images can be much larger than the screen mode resolution.

Model codeDescription
0x00PC-98, X68000, many others
0x03MSX, MSX2, MSX2+; requires special handling
0x1CX1tb ?..
0x6298-SA
0x68X68K or XPST, Chironon's Paint System Tool ported to X68000 by Kenna
0x70MPS images for slightly newer PC-98 models
0x88PC-88
0x99MAC
0xFFMPS images

The model code slightly affects how the image palette is interpreted. If the model code is 0x03, the model-dependent flag byte overrides the screen mode byte. Otherwise the flag byte can be ignored.

Model codeFlagColorsPixel ratio
0x030x00161:1MSX2 screen mode 7, 512x212
0x030x04161:2MSX2 screen mode 7, 512x212
0x030x142561:1MSX2 screen mode 8, 256x212
0x030x2412499+1:1MSX2+ screen mode 10, 256x212
0x030x3412499+1:1MSX2+ screen mode 11, 256x212
0x030x44192681:1MSX2+ screen mode 12, 256x212
0x030x54161:1MSX2 screen mode 5, 256x212
Screen modeVertical
resolution
ColorsPixel ratio
0400+ rows161:1PC-98
1200 rows161:2
2400 rows81:1VM98
3200 rows81:2late PC-88
4400 rows161:1
5200 rows161:2
6400 rows81:1early PC-98
7200 rows81:2PC-88
128400+ rows2561:1late PC-98
129212 rows256hardware palette?
132212 rows256
133212 rows256hardware palette?

The screen mode's top bit indicates a 256-color mode, or 8 bits per pixel. When the top bit is set, the bottom bit may indicate that the image uses a special hardware palette, but so far all my test images have embedded normal palette data anyway.

When the top bit is not set, the bottom bit indicates a 1:2 pixel aspect ratio, where all pixels are twice as tall as they are wide. Such images need to be stretched to double height as the final step after decompressing, or they'll look squashed.

You can ignore mode bit 1, which indicates an 8-color mode. The palette is still present like on any other image.

You can ignore mode bit 2, which indicates a digital mode. Some of the older PC models had a fixed 8-color palette, termed "digital" because the RGB components could only be fully on or off.

The edge coordinates are used to position non-fullscreen images, such as for facial expression sprites. The coordinates are inclusive; for most images the values will be (0,0) to (639,399), indicating a 640x400 pixel image.

The compressed image is split into three streams, as delineated in the header: "flag A", "flag B", and a "color index" stream. You can ignore the stream sizes listed in the header. Flag A runs up to the start of the flag B stream; flag B runs up to the start of the color stream; the color stream runs to the end of the file.

Finally, the palette is at the end of the header and can be up to 256 GRB triplets long. For best compatibility, initialise a 256-color palette structure, then keep reading GRB triplets up to the first byte of the flag A stream.

Although the palette color components are always one byte each, they have a different number of significant bits depending on the computer model. Use these guidelines to determine the number of significant bits:

Every palette component byte should have its most significant bits copied to the rest of the byte. Examples:

Input byteSignificant bitsInput binary, maskedFinal binaryFinal byte
0x553010 xxx xx010 010 010x49
0xBF41011 xxxx1011 10110xBB
0x67501100 xxx01100 0110x63
MAG decompression

Click the image to download the example MAG file, to test your decompressor.

Before anything else, the left and right image edges must be padded to the next nearest multiple of 4 bytes. (The top and bottom are never padded.) If the header's screen mode byte has its top bit set (the 256-color flag), bits per pixel is 8, else bits per pixel is 4. All fractions round down.

Example: 16-color image with coordinates (283,643) to (432,789). This is an image of exactly 150x147 pixels. With padding, the edge byte coordinates become (140,643) to (220,789) – an image size of 80x147 bytes, or 160x147 pixels. After decompression is finished, you can crop 3 pixel columns from the left, and 7 pixel columns from the right, to get the exact image size.

To decompress, two buffers are needed: an output buffer, and an action buffer. The buffer sizes are:

These are the purposes of the streams and buffers:


The decompression algorithm is pretty straightforward. Initialise the action buffer to all zeroes, then start processing:

Repeat these steps until the output buffer is full, or one of the input flag streams runs out. If the "color index" stream runs out, try using zeroes for any extra color indexes until the output buffer is full.

When the action nibble is not 0, you need to copy a 16-bit value from one of 15 relative locations in the output buffer. The official specification uses a diagram similar to this to illustrate:

Y\X -4 -3 -2 -1 0
-1615
-8141312
-411109
-2876
-154
03210

As you can see, this makes use of the repetition in ordered dithering patterns. 0 is the current location in the Output buffer. 1 indicates you should copy the previous 16-bit value; 2 indicates the value two steps behind the current position; 3, four steps back; 4, the value one row above the current position; 5, one row above and one step back. And so on.

Example: 320x400 byte image, current output offset 0x8A0.


Now you have a full output buffer, but we're not quite done yet.

If this image used an advanced MSX2+ screen mode, the YJK colors must be decoded at this point. See below.

If the output buffer is still 4 bits per pixel, it is now safe to expand it to a more natural 8 bits per pixel, or unpack the palette indexing to a truecolor image, as you prefer.

Any padding added to the left and right edges can be removed.

Finally, the image should be stretched to display as double height, only if:


More example images

Finally, an irregularly-sized example image: 353x289 pixels, with its top left corner at pixel offset 223,53.


MSX images and YJK conversion

You can download the example MAX file here to test your decompressor.

.MAX files are internally identical to MAG, and many MSX images use the .MAG extension. Images specifically intended for the MSX will have the model code set to 0x03.

MSX computers supported medium-resolution 16-color modes, as well as some more exotic modes using YJK encoding. Most images use the plain 16-color modes, and don't need additional steps.

This is where the model-dependent flag byte comes in. An extra step after decompression is needed only if the flag byte is 0x24, 0x34, or 0x44, indicating one of the YJK modes.

First, unpack the image normally, as described above. Without YJK decoding, the bitmap should look like a colorful mess with distinct vertical lines, as in the below image.

The colors must be converted to RGB. If the image had 4 bits per pixel, the conversion halves its pixel width.

In YJK, pixels are treated in groups of 4. Each pixel has its own Y value, but the J and K components are shared by all 4 pixels. Y values are unsigned 5-bit, while J and K are signed 6-bit. This allows packing four nearly high-color pixels in only four bytes.

After conversion, the output RGB values are also unsigned 5-bit, so they'll need to be scaled to a more standard 8-bit depth (multiply by 255 and divide by 31).

Here's how to convert:

Example: Model flag 0x44, YJK bytes 87 8F 8B 81, or in binary:
10000 111
10001 111
10001 011
10000 001

Y0Y1Y2Y3KJ
10000100011000110000111 111001 011
16171716-111
Output 5-bit pixels
RGB
271514.75
281616
281616
271514.75

Example: Model flag 0x24, YJK bytes 6A 68 29 31, or in binary:
01101 010
01101 000
00101 001
00110 001

Y0Y1Y2Y3KJ
01101011010010100110000 010001 001
13135629
Output 5-bit pixels
RGB
Palette index 6
Palette index 6
Palette index 2
1582.5

If converted correctly, the end result is a tiny but beautifully-colored image like this one.

More example images