MAG画像ローダ
唐突にPC98時代によく使われていたMAG画像形式ファイルを読み込むプログラムをC#に移植してみました。2,3個の画像でしか試してないのでバグがあるかも。
// 謝辞 // MITH(T.Saito)氏のWAB−S用マルチグラフィックローダ WMLのソースを参考にさせていただきました。 using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Text; /// <summary> /// MAG形式画像ローダ。 /// </summary> public static class MagFile { private static readonly sbyte[] FlagX = {0, -1, -2, -4, 0, -1, 0, -1, -2, 0, -1, -2, 0, -1, -2, 0}; private static readonly sbyte[] FlagY = {0, 0, 0, 0, -1, -1, -2, -2, -2, -4, -4, -4, -8, -8, -8, 0}; [StructLayout(LayoutKind.Sequential, Pack = 1)] private unsafe struct MagFileHeader { private fixed byte mgHeader [8]; private fixed byte mgMachine [4]; private fixed byte mgUser [8]; } [StructLayout(LayoutKind.Sequential, Pack = 1)] private struct MagHeader { private readonly byte mgHeader; private readonly byte mgMachine; private readonly byte mgSystem; public readonly byte mgScreenmode; public readonly ushort mgStartX; public readonly ushort mgStartY; public readonly ushort mgEndX; public readonly ushort mgEndY; public readonly uint mgFlagA_offset; public readonly uint mgFlagB_offset; public readonly uint mgFlagBSize; public readonly uint mgPixelD_offst; public readonly uint mgPixelDSize; } private static unsafe void ReadMagFileHeader(Stream stream, out MagFileHeader magFileHeader) { fixed (void* p = &magFileHeader) { var buf = new byte[sizeof (MagFileHeader)]; stream.Read(buf, 0, buf.Length); Marshal.Copy(buf, 0, (IntPtr) p, buf.Length); } } private static unsafe void ReadMagHeader(Stream stream, out MagHeader magHeader) { fixed (void* p = &magHeader) { var buf = new byte[sizeof (MagHeader)]; stream.Read(buf, 0, buf.Length); Marshal.Copy(buf, 0, (IntPtr) p, buf.Length); } } private static string ReadComment(Stream stream) { var bytes = new List<byte>(); var data = stream.ReadByte(); while (data != 0x1a) { bytes.Add((byte) data); data = stream.ReadByte(); } return Encoding.GetEncoding(932).GetString(bytes.ToArray()); } private static PixelFormat GetPixelFormat(MagHeader magHeader) { return (magHeader.mgScreenmode & 0x80) == 0x80 ? PixelFormat.Format8bppIndexed : PixelFormat.Format4bppIndexed; } private static Bitmap CreateBitmap(MagHeader magHeader) { return new Bitmap(magHeader.mgEndX + 1, magHeader.mgEndY + 1, GetPixelFormat(magHeader)); } private static IEnumerable<Color> ReadPalette(Stream stream, MagHeader magHeader) { var n = (magHeader.mgScreenmode & 0x80) == 0x80 ? 256 : 16; var palette = new byte[n*3]; stream.Read(palette, 0, palette.Length); for (var i = 0; i < n; ++i) { var g = palette[i*3 + 0]; var r = palette[i*3 + 1]; var b = palette[i*3 + 2]; yield return Color.FromArgb(0xff, r, g, b); } } private static void SetPalette(Bitmap bitmap, IEnumerable<Color> colors) { var i = 0; var colorPalette = bitmap.Palette; foreach (var color in colors) colorPalette.Entries[i++] = color; bitmap.Palette = colorPalette; } private static int GetStartX(MagHeader magHeader) { var n = (magHeader.mgScreenmode & 0x80) == 0x80 ? 256 : 16; int startx; if (n == 256) startx = magHeader.mgStartX/4*4; else startx = magHeader.mgStartX/8*8; return startx; } private static int GetPixelSizeX(MagHeader magHeader) { var n = (magHeader.mgScreenmode & 0x80) == 0x80 ? 256 : 16; int xsize; int startx; if (n == 256) { startx = magHeader.mgStartX/4*4; xsize = ((magHeader.mgEndX + 4)/4*4 - startx); } else { startx = magHeader.mgStartX/8*8; xsize = ((magHeader.mgEndX + 8)/8*8 - startx + 1)/2; } return xsize; } private static int GetFlagASize(MagHeader magHeader) { int xsize = GetPixelSizeX(magHeader); var x = xsize/4; var ysize = magHeader.mgEndY - magHeader.mgStartY + 1; return (x*ysize + 7)/8; } private static void ReadFlagA(Stream stream, MagHeader magHeader, long startPos, byte[] flagA) { stream.Seek(startPos + magHeader.mgFlagA_offset, SeekOrigin.Begin); stream.Read(flagA, 0, flagA.Length); } private static void ReadFlagB(Stream stream, ref uint left, ref uint xpoint, long startPos, byte[] flagB) { stream.Seek(startPos + xpoint, SeekOrigin.Begin); if (left > 0x8000) { stream.Read(flagB, 0, 0x8000); xpoint += 0x8000; left -= 0x8000; } else { stream.Read(flagB, 0, 0x8000); left = 0; } } /// <summary> /// ピクセル単位の読み込み。 /// <remarks>MAGフォーマットでは2byte単位をピクセルと呼ぶ。1ピクセルには16色の場合4ドット、256色なら2ドットがパックされている。</remarks> /// </summary> /// <param name="stream"></param> /// <param name="left"></param> /// <param name="xpoint"></param> /// <param name="startPos"></param> /// <param name="pixel"></param> private static void ReadMagPixel(Stream stream, ref uint left, ref uint xpoint, long startPos, ushort[] pixel) { stream.Seek(startPos + xpoint, SeekOrigin.Begin); var tmp = new byte[0x8000]; if (left > 0x8000) { stream.Read(tmp, 0, 0x8000); xpoint += 0x8000; left -= 0x8000; } else { stream.Read(tmp, 0, 0x8000); left = 0; } unsafe { fixed (void* p = &pixel[0]) { Marshal.Copy(tmp, 0, (IntPtr) p, tmp.Length); } } } /// <summary> /// MAG画像のロード。 /// </summary> /// <param name="stream">MAG画像のストリーム。</param> /// <returns>ビットマップ。</returns> public static Bitmap Load(Stream stream) { MagFileHeader magFileHeader; ReadMagFileHeader(stream, out magFileHeader); var comment = ReadComment(stream); // コメントを読み飛ばした位置がMAGヘッダの開始位置。 var startPos = stream.Position; // MAGヘッダ読み込み。 MagHeader magHeader; ReadMagHeader(stream, out magHeader); // 出力用ビットマップの作成。 var bitmap = CreateBitmap(magHeader); // とりあえずタグにコメントを入れる。 bitmap.Tag = comment; // パレットの設定。 var colors = ReadPalette(stream, magHeader); SetPalette(bitmap, colors); // フラグAを読み込む。 var flagA = new byte[GetFlagASize(magHeader)]; ReadFlagA(stream, magHeader, startPos, flagA); // フラグBを読み込む。 var flagB = new byte[0x8000]; var leftFlagB = magHeader.mgFlagBSize; var offsetFlagB = magHeader.mgFlagB_offset; ReadFlagB(stream, ref leftFlagB, ref offsetFlagB, startPos, flagB); // ピクセルを読み込む。 var pixel = new ushort[0x8000/sizeof (ushort)]; var leftPixel = magHeader.mgPixelDSize; var offsetPixel = magHeader.mgPixelD_offst; ReadMagPixel(stream, ref leftPixel, ref offsetPixel, startPos, pixel); // フラグA,Bからピクセルデータを復元。 unsafe { var pixelSizeX = GetPixelSizeX(magHeader); var flagBmap = new byte[pixelSizeX]; var line = new ushort[pixelSizeX*8]; fixed (byte* p1 = &flagA[0]) fixed (ushort* p2 = &line[0]) fixed (byte* p3 = &flagBmap[0]) { var startx = GetStartX(magHeader); var pFlagA = p1; var pline = p2; var p = 0; var w = 0; var ibit = 0x80; var curFlagA = *pFlagA++; var j = 15; for (int y = magHeader.mgStartY; y <= magHeader.mgEndY; ++y) { var flag = new int[16]; ++j; for (var i = 0; i < flag.Length; ++i) flag[i] = FlagX[i]*2 + ((FlagY[i] + j) & 0xf)*pixelSizeX; if (j == 16) j = 0; var pFlagBmap = p3; var ftmp = flag[0]; var cline = pline + (ftmp/2); for (var x = 0; x < pixelSizeX;) { if ((curFlagA & ibit) != 0) { if (p >= 0x8000) { ReadFlagB(stream, ref leftFlagB, ref offsetFlagB, startPos, flagB); p = 0; } *pFlagBmap ^= flagB[p++]; } ibit >>= 1; if (ibit == 0) { ibit = 0x80; curFlagA = *pFlagA++; } var fr = *pFlagBmap; pFlagBmap++; if ((fr >> 4) == 0) { if (w >= 0x8000) { ReadMagPixel(stream, ref leftPixel, ref offsetPixel, startPos, pixel); w = 0; } line[(ftmp + x)/2] = pixel[w/2]; w += 2; } else { line[(ftmp + x)/2] = line[(flag[fr >> 4] + x)/2]; } x += 2; if ((fr & 0x0f) == 0) { if (w >= 0x8000) { ReadMagPixel(stream, ref leftPixel, ref offsetPixel, startPos, pixel); w = 0; } line[(ftmp + x)/2] = pixel[w/2]; w += 2; } else { line[(ftmp + x)/2] = line[(flag[fr & 0xf] + x)/2]; } x += 2; } // 1ライン分をビットマップに書き出す。 var b = new byte[pixelSizeX]; for (var i = 0; i < pixelSizeX/2; i++) { var c = cline[i]; b[i*2] = (byte) (c & 0xff); b[i*2 + 1] = (byte) ((c >> 8) & 0xff); } var dst = bitmap.LockBits(new Rectangle(magHeader.mgStartX, y, bitmap.Width, 1), ImageLockMode.WriteOnly, bitmap.PixelFormat); Marshal.Copy(b, magHeader.mgStartX - startx, dst.Scan0, bitmap.PixelFormat == PixelFormat.Format8bppIndexed ? bitmap.Width : bitmap.Width/2); bitmap.UnlockBits(dst); } } } return bitmap; } /// <summary> /// MAG画像のロード。 /// </summary> /// <param name="path">MAG画像ファイル名。</param> /// <returns>ビットマップ。</returns> public static Bitmap Load(string path) { using (var fl = new FileStream(path, FileMode.Open)) { return Load(fl); } } } /* 使い方 * * var bmp = MagFile.Load(magfile); * */