//! \file ImageG.cs //! \date 2023 Oct 15 //! \brief System-98 engine image format (PC-98). // // Copyright (C) 2023 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // using GameRes.Utility; using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; // [951216][Four-Nine] Lilith namespace GameRes.Formats.System98 { [Export(typeof(ImageFormat))] public class GFormat : ImageFormat { public override string Tag => "G/SYSTEM98"; public override string Description => "System-98 engine image format"; public override uint Signature => 0; public GFormat () { Extensions = new[] { "g", "" }; } public override ImageMetaData ReadMetaData (IBinaryStream file) { if (file.Length < 61) return null; var header = file.ReadHeader (0xA); ushort width = Binary.BigEndian (header.ToUInt16 (6)); ushort height = Binary.BigEndian (header.ToUInt16 (8)); if (0 == width || 0 == height || (width & 7) != 0 || width > 640 || height > 400) return null; return new ImageMetaData { Width = width, Height = height, BPP = 4, }; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { file.Position = 0xA; var palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb); var reader = new GraBaseReader (file, info); reader.UnpackBits(); return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride); } public override void Write (Stream file, ImageData image) { throw new System.NotImplementedException ("GFormat.Write not implemented"); } } /// <summary> /// This compression format is used in several PC-98 game engines. /// </summary> internal class GraBaseReader { protected IBinaryStream m_input; protected ImageMetaData m_info; protected int m_output_stride; protected byte[] m_pixels; protected int m_dst; public byte[] Pixels => m_pixels; public int Stride => m_output_stride; public GraBaseReader (IBinaryStream file, ImageMetaData info) { m_input = file; m_info = info; m_output_stride = m_info.iWidth >> 1; m_pixels = new byte[m_output_stride * m_info.iHeight]; } protected ushort[] m_buffer; public void UnpackBits () { try { UnpackBitsInternal(); } catch (EndOfStreamException) { FlushBuffer(); } } void UnpackBitsInternal () { int width = m_info.iWidth; int wTimes2 = width << 1; int wTimes4 = width << 2; int buffer_size = wTimes4 + wTimes2; m_buffer = new ushort[buffer_size >> 1]; m_dst = 0; InitFrame(); InitBitReader(); ushort p = ReadPair (0); for (int i = 0; i < width; ++i) m_buffer[i] = p; int dst = wTimes2; int prev_src = 0; while (m_dst < m_pixels.Length) { bool same_line = false; int src = -width; if (GetNextBit() != 0) { if (GetNextBit() == 0) src <<= 1; else if (GetNextBit() == 0) src += 1; else src -= 1; } else if (GetNextBit() == 0) { src = -4; p = m_buffer[dst/2-1]; if ((p & 0xFF) == (p >> 8)) same_line = src != prev_src; } if (src != prev_src) { prev_src = src; if (!same_line) src += dst; else src = dst - 2; if (GetNextBit() != 0) { int bitlength = 0; do { ++bitlength; } while (GetNextBit() != 0); int count = 1; while (bitlength --> 0) count = count << 1 | GetNextBit(); int remaining = (buffer_size - dst) >> 1; while (count > remaining) { count -= remaining; MovePixels (m_buffer, src, dst, remaining); src += remaining << 1; if (FlushBuffer()) return; dst = wTimes2; src -= wTimes4; remaining = wTimes4 >> 1; } MovePixels (m_buffer, src, dst, count); dst += count << 1; if (dst == buffer_size) { if (FlushBuffer()) return; dst = wTimes2; } } else { MovePixels (m_buffer, src, dst, 1); dst += 2; if (dst == buffer_size) { if (FlushBuffer()) return; dst = wTimes2; } } } else { p = m_buffer[dst/2-1]; do { byte prev = (byte)(p >> 8); p = ReadPair (prev); m_buffer[dst >> 1] = p; dst += 2; if (dst == buffer_size) { if (FlushBuffer()) return; dst = wTimes2; } } while (GetNextBit() != 0); prev_src = 0; } } } bool FlushBuffer () { MovePixels (m_buffer, m_info.iWidth * 4, 0, m_info.iWidth); int src = m_info.iWidth; int count = Math.Min (m_info.iWidth << 1, m_pixels.Length - m_dst); while (count --> 0) { ushort p = m_buffer[src++]; m_pixels[m_dst++] = (byte)((p & 0xF0) | p >> 12); } return m_dst == m_pixels.Length; } protected ushort ReadPair (int pos) { byte al = ReadPixel (pos); byte ah = ReadPixel (al); return (ushort)(al | ah << 8); } protected byte ReadPixel (int pos) { byte px = 0; if (GetNextBit() == 0) { int count = 1; if (GetNextBit() != 0) { if (GetNextBit() != 0) { count = count << 1 | GetNextBit(); } count = count << 1 | GetNextBit(); } count = count << 1 | GetNextBit(); pos += count; px = m_frame[pos--]; while (count --> 0) { m_frame[pos+1] = m_frame[pos]; --pos; } m_frame[pos+1] = px; } else if (GetNextBit() == 0) { px = m_frame[pos]; } else { px = m_frame[pos+1]; m_frame[pos+1] = m_frame[pos]; m_frame[pos] = px; } return px; } byte[] m_frame; protected void InitFrame () { m_frame = new byte[0x100]; int p = 0; byte a = 0; for (int j = 0; j < 0x10; ++j) { for (int i = 0; i < 0x10; ++i) { m_frame[p++] = a; a -= 0x10; } a += 0x10; } } protected void MovePixels (ushort[] pixels, int src, int dst, int count) { count <<= 1; if (dst > src) { while (count > 0) { int preceding = Math.Min (dst - src, count); Buffer.BlockCopy (pixels, src, pixels, dst, preceding); dst += preceding; count -= preceding; } } else { Buffer.BlockCopy (pixels, src, pixels, dst, count); } } int m_bits; int m_bit_count; protected void InitBitReader () { m_bit_count = 1; } protected byte GetNextBit () { if (--m_bit_count <= 0) { m_bits = m_input.ReadUInt8(); m_bit_count = 8; } int bit = (m_bits >> 7) & 1; m_bits <<= 1; return (byte)bit; } } }