Maki-chan Graphics

There is a Japanese specification for Maki-chan Graphics available at: http://www.jisyo.com/viewer/faq/mag_tech.htm

Furthermore, there is an open source C utility somewhere online called mag2png that may be helpful. Also, Irfanview and Grapholic may be able to natively view MAG images.

Back in the day before LZW compression became ubiquitous, less efficient image compression methods were used. Maki-chan Graphics became a popular image format in Japan, and was used for many classic games. Unfortunately, all specifications I could find were in Japanese.

As an aside on my work on SuperSakura, I pieced together what specifications I could. Although most games I'm working on use a refined compression algorithm, Tenshitachi no Gogo Collection 1 does have a number of images that use Maki-chan compression. Whatever you need this for, I hope it helps!


Each Maki-chan file starts with a meta-data string, which is terminated with the byte $1A. The metadata is mostly junk, except for a possible Maki-chan signature. The last four characters in the string may contain a version number; there are 1.04 and 1.01 revisions. Not sure what difference the revision makes.

The first $00 byte after $1A is the first byte of the header.

Offset Size  Description
------ ----  -----------
0        1   Start of header, always 00.
1        1   Machine-dependent code? Probably 00.
2        1   Machine-dependent flags? Always 00.
3        1   Screen mode; usually 04
4        2   Target X offset for image's top left corner, generally 0000
6        2   Target Y offset for image's top left corner, generally 0000
8        2   Image pixel width = this - (X offset) + 1
10       2   Image pixel height = this - (Y offset) + 1
12       4   Offset from start of header to "flag A"
16       4   Offset from start of header to "flag B"
20       4   Size of "flag B" section, in bytes
24       4   Offset from start of header to color index stream
28       4   Size of pixel color section, in bytes
32      48   Palette: 16 byte triplets, order GRB
------ ----  -----------

The list of screen modes is approximately: (translated from the MAG manual)
0 - PC-98 analog, 400 lines, 16 colors
1 - MSX SC7 analog, 200 lines, 16 colors
2 - VM 98 analog, 400 lines, 8 colors
3 - analog, 200 lines, 8 colors with 88-color palette
4 - digital, 400 lines, 16 colors
5 - digital, 200 lines, 16 colors
6 - old PC-98, digital, 400 lines, 8 colors
7 - digital, 200 lines, 8 colors with 88-color palette
129 - MSX SC8, analog, 200 lines, 256 colors

Evidently, in the above values bit 0 indicates a 200-line mode; bit 1 indicates an 8-color mode; bit 2 indicates a digital mode, whatever difference that makes; and the top bit indicates a 256-color mode.

The image is processed in standard left-to-right, top-down form. The compressed data is split into three streams, as delineated in the header: "Flag A", "Flag B" and a color index stream.

Flag A is a stream of single-bit boolean flags, read from highest to lowest bit in each byte. Flag B is an array of nibbles determining whether to fetch a new color or copy an earlier color. The color stream is an array of words, containing the new colors.

16-color images use 4 bits per pixel, and pixels are stuffed in word-aligned coordinates. Since each word is 16 bits, each word fits 4 pixels. Also, flag B is processed one byte at a time, for a total of 8 pixels per flag B byte. It is probably best to expand bit depth to 1 byte per pixel only after finishing decompression. I think the images by specification are forced to have a pixel width that is a multiple of 8.

I have not seen any 256-color images, but since they are also word-aligned, there would presumably be two pixels per word, and 4 pixels per flag B byte.

The decompression algorithm is pretty straightforward, and there is some pseudocode further below.

Basically, though, you will process the image one word at a time, and for each word you will need to read one flag A bit, and use it to get one action byte. If the flag A bit is not set, you must use the action byte from one row above; if the flag A bit is set, you must fetch the next flag B byte and XOR that with the action byte from one row above. At the top row you XOR with zero. This is probably easiest accomplished by having a running buffer that can contain one row's worth of action bytes.

For each nibble of the action byte, you need to output a word. If the action nibble is zero, fetch the next word from the color stream and output that. If the action nibble is not zero, the word must be copied from earlier in your output buffer. There are 15 relative locations that a word can be copied from. In the below lists are pairs of coordinates, that you subtract from the current word coordinate to get the source word.

 0: (0,0)       1: (1,0)        2: (2,0)        3: (4,0)
 4: (0,1)       5: (1,1)        6: (0,2)        7: (1,2)
 8: (2,2)       9: (0,4)       10: (1,4)       11: (2,4)
12: (0,8)      13: (1,8)       14: (2,8)       15: (0,16)

const deltax : array[0..15] of byte = (0,1,2,4,0,1,0,1,2,0,1,2,0,1,2,0);
      deltay : array[0..15] of byte = (0,0,0,0,1,1,2,2,2,4,4,4,8,8,8,16);

Basically, that's all. The below code is probably easier to understand than the explanation.


Decompression pseudocode for a 16-color image:

  Reserve (image_width * image_height) bytes of memory for output_buffer
  Reserve (image_width / 8) bytes of memory for flag_buffer
  Fill flag_buffer with zeroes

  For y = 0 to (image_height - 1)
    For x = 0 to (image_width / 8 - 1)

      If (next flag A bit) = TRUE
      Then flag_buffer[x] = flag_buffer[x] XOR (next flag B byte)
      action_byte = flag_buffer[x]

      i = top nibble of action_byte
      If i = 0 Then (j = next color word)
               Else (j = copy from output_buffer at delta[i])
      output_buffer[next word] = j

      i = bottom nibble of action_byte
      If i = 0 Then (j = next color word)
               Else (j = copy from output_buffer at delta[i])
      output_buffer[next word] = j

    Next x
  Next y