|
| 1 | +//! \file ImageGPC.cs |
| 2 | +//! \date 2023 Sep 22 |
| 3 | +//! \brief Adv98 engine image format (PC-98). |
| 4 | +// |
| 5 | +// Copyright (C) 2023 by morkt |
| 6 | +// |
| 7 | +// Permission is hereby granted, free of charge, to any person obtaining a copy |
| 8 | +// of this software and associated documentation files (the "Software"), to |
| 9 | +// deal in the Software without restriction, including without limitation the |
| 10 | +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| 11 | +// sell copies of the Software, and to permit persons to whom the Software is |
| 12 | +// furnished to do so, subject to the following conditions: |
| 13 | +// |
| 14 | +// The above copyright notice and this permission notice shall be included in |
| 15 | +// all copies or substantial portions of the Software. |
| 16 | +// |
| 17 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 20 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 21 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 22 | +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 23 | +// IN THE SOFTWARE. |
| 24 | +// |
| 25 | + |
| 26 | +using System.ComponentModel.Composition; |
| 27 | +using System.IO; |
| 28 | +using System.Windows.Media; |
| 29 | +using System.Windows.Media.Imaging; |
| 30 | + |
| 31 | +namespace GameRes.Formats.Adv98 |
| 32 | +{ |
| 33 | + internal class GpcMetaData : ImageMetaData |
| 34 | + { |
| 35 | + public long PaletteOffset; |
| 36 | + public long DataOffset; |
| 37 | + public int Interleaving; |
| 38 | + } |
| 39 | + |
| 40 | + [Export(typeof(ImageFormat))] |
| 41 | + public class GpcFormat : ImageFormat |
| 42 | + { |
| 43 | + public override string Tag => "GPC/PC98"; |
| 44 | + public override string Description => "Adv98 engine image format"; |
| 45 | + public override uint Signature => 0x38394350; // 'PC98' |
| 46 | + |
| 47 | + public override ImageMetaData ReadMetaData (IBinaryStream file) |
| 48 | + { |
| 49 | + var header = file.ReadHeader (0x20); |
| 50 | + if (!header.AsciiEqual (4, ")GPCFILE \0")) |
| 51 | + return null; |
| 52 | + uint info_pos = header.ToUInt32 (0x18); |
| 53 | + var info = new GpcMetaData |
| 54 | + { |
| 55 | + Interleaving = header.ToUInt16 (0x10), |
| 56 | + PaletteOffset = header.ToUInt32 (0x14), |
| 57 | + DataOffset = info_pos + 0x10, |
| 58 | + BPP = 4, |
| 59 | + }; |
| 60 | + file.Position = info_pos; |
| 61 | + info.Width = file.ReadUInt16(); |
| 62 | + info.Height = file.ReadUInt16(); |
| 63 | + file.Position = info_pos + 0xA; |
| 64 | + info.OffsetX = file.ReadInt16(); |
| 65 | + info.OffsetY = file.ReadInt16(); |
| 66 | + return info; |
| 67 | + } |
| 68 | + |
| 69 | + public override ImageData Read (IBinaryStream file, ImageMetaData info) |
| 70 | + { |
| 71 | + var reader = new GpcReader (file, (GpcMetaData)info); |
| 72 | + return reader.Unpack(); |
| 73 | + } |
| 74 | + |
| 75 | + public override void Write (Stream file, ImageData image) |
| 76 | + { |
| 77 | + throw new System.NotImplementedException ("GpcFormat.Write not implemented"); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + internal class GpcReader |
| 82 | + { |
| 83 | + IBinaryStream m_input; |
| 84 | + GpcMetaData m_info; |
| 85 | + int m_stride; |
| 86 | + |
| 87 | + public BitmapPalette Palette { get; private set; } |
| 88 | + |
| 89 | + public GpcReader (IBinaryStream input, GpcMetaData info) |
| 90 | + { |
| 91 | + m_input = input; |
| 92 | + m_info = info; |
| 93 | + } |
| 94 | + |
| 95 | + public ImageData Unpack () |
| 96 | + { |
| 97 | + m_input.Position = m_info.PaletteOffset; |
| 98 | + Palette = ReadPalette(); |
| 99 | + int plane_stride = (m_info.iWidth + 7) >> 3; |
| 100 | + int row_size = plane_stride * 4 + 1; |
| 101 | + var data = new byte[row_size * m_info.iHeight]; |
| 102 | + m_input.Position = m_info.DataOffset; |
| 103 | + UnpackData (data); |
| 104 | + RestoreData (data, row_size); |
| 105 | + m_stride = plane_stride * 4; |
| 106 | + var pixels = new byte[m_stride * m_info.iHeight]; |
| 107 | + ConvertTo8bpp (data, pixels, plane_stride); |
| 108 | + return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, pixels, m_stride); |
| 109 | + } |
| 110 | + |
| 111 | + void ConvertTo8bpp (byte[] input, byte[] output, int plane_stride) |
| 112 | + { |
| 113 | + int interleaving_step = m_stride * m_info.Interleaving; |
| 114 | + int src_row = 1; |
| 115 | + int dst_row = 0; |
| 116 | + int i = 0; |
| 117 | + for (int y = 0; y < m_info.iHeight; ++y) |
| 118 | + { |
| 119 | + if (dst_row >= output.Length) |
| 120 | + { |
| 121 | + dst_row = m_stride * ++i; |
| 122 | + } |
| 123 | + int p0 = src_row; |
| 124 | + int p1 = p0 + plane_stride; |
| 125 | + int p2 = p1 + plane_stride; |
| 126 | + int p3 = p2 + plane_stride; |
| 127 | + src_row = p3 + plane_stride + 1; |
| 128 | + int dst = dst_row; |
| 129 | + for (int x = plane_stride; x > 0; --x) |
| 130 | + { |
| 131 | + byte b0 = input[p0++]; |
| 132 | + byte b1 = input[p1++]; |
| 133 | + byte b2 = input[p2++]; |
| 134 | + byte b3 = input[p3++]; |
| 135 | + for (int j = 0; j < 8; j += 2) |
| 136 | + { |
| 137 | + byte px = (byte)((((b0 << j) & 0x80) >> 3) |
| 138 | + | (((b1 << j) & 0x80) >> 2) |
| 139 | + | (((b2 << j) & 0x80) >> 1) |
| 140 | + | (((b3 << j) & 0x80) )); |
| 141 | + px |= (byte)((((b0 << j) & 0x40) >> 6) |
| 142 | + | (((b1 << j) & 0x40) >> 5) |
| 143 | + | (((b2 << j) & 0x40) >> 4) |
| 144 | + | (((b3 << j) & 0x40) >> 3)); |
| 145 | + output[dst++] = px; |
| 146 | + } |
| 147 | + } |
| 148 | + dst_row += interleaving_step; |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + void UnpackData (byte[] output) |
| 153 | + { |
| 154 | + int dst = 0; |
| 155 | + int ctl = 0; |
| 156 | + int ctl_mask = 0; |
| 157 | + while (dst < output.Length) |
| 158 | + { |
| 159 | + if (0 == ctl_mask) |
| 160 | + { |
| 161 | + ctl = m_input.ReadByte(); |
| 162 | + if (-1 == ctl) |
| 163 | + break; |
| 164 | + ctl_mask = 0x80; |
| 165 | + } |
| 166 | + if ((ctl & ctl_mask) != 0) |
| 167 | + { |
| 168 | + int cmd = m_input.ReadByte(); |
| 169 | + for (int cmd_mask = 0x80; cmd_mask != 0; cmd_mask >>= 1) |
| 170 | + { |
| 171 | + if ((cmd & cmd_mask) != 0) |
| 172 | + output[dst++] = m_input.ReadUInt8(); |
| 173 | + else |
| 174 | + ++dst; |
| 175 | + } |
| 176 | + } |
| 177 | + else |
| 178 | + { |
| 179 | + dst += 8; |
| 180 | + } |
| 181 | + ctl_mask >>= 1; |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + void RestoreData (byte[] data, int stride) |
| 186 | + { |
| 187 | + int src = 0; |
| 188 | + for (int y = 0; y < m_info.iHeight; ++y) |
| 189 | + { |
| 190 | + int interleave = data[src]; |
| 191 | + if (interleave != 0) |
| 192 | + { |
| 193 | + byte lastValue = 0; |
| 194 | + for (int i = 0; i < interleave; ++i) |
| 195 | + { |
| 196 | + int pos = 1 + i; |
| 197 | + while (pos < stride) |
| 198 | + { |
| 199 | + data[src + pos] ^= lastValue; |
| 200 | + lastValue = data[src + pos]; |
| 201 | + pos += interleave; |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + } |
| 206 | + if (y > 0) |
| 207 | + { |
| 208 | + int prev = src - stride; |
| 209 | + int length = (stride - 1) & -4; |
| 210 | + for (int x = 1; x <= length; ++x) |
| 211 | + { |
| 212 | + data[src + x] ^= data[prev + x]; |
| 213 | + |
| 214 | + } |
| 215 | + } |
| 216 | + src += stride; |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + BitmapPalette ReadPalette () |
| 221 | + { |
| 222 | + int count = m_input.ReadUInt16(); |
| 223 | + int elem_size = m_input.ReadUInt16(); |
| 224 | + if (elem_size != 2) |
| 225 | + throw new InvalidFormatException (string.Format ("Invalid palette element size {0}", elem_size)); |
| 226 | + var colors = new Color[count]; |
| 227 | + for (int i = 0; i < count; ++i) |
| 228 | + { |
| 229 | + int v = m_input.ReadUInt16(); |
| 230 | + int r = (v >> 4) & 0xF; |
| 231 | + int g = (v >> 8) & 0xF; |
| 232 | + int b = (v ) & 0xF; |
| 233 | + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); |
| 234 | + } |
| 235 | +// colors[0].A = 0; // force transparency |
| 236 | + return new BitmapPalette (colors); |
| 237 | + } |
| 238 | + } |
| 239 | +} |
0 commit comments