diff --git a/ArcFormats/Succubus/ImageGH.cs b/ArcFormats/Succubus/ImageGH.cs index 2e964ab3..b2bd68c7 100644 --- a/ArcFormats/Succubus/ImageGH.cs +++ b/ArcFormats/Succubus/ImageGH.cs @@ -2,7 +2,7 @@ //! \date 2017 Dec 26 //! \brief Succubus image format. // -// Copyright (C) 2017 by morkt +// Copyright (C) 2017-2018 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 @@ -23,16 +23,23 @@ // IN THE SOFTWARE. // +using System; using System.ComponentModel.Composition; using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [000310][Succubus] Utagoe 2 ~Dance Mix~ namespace GameRes.Formats.Succubus { internal class GhpMetaData : ImageMetaData { + public int Version; public int Colors; public uint PaletteOffset; public uint DataOffset; + public int ChunkCount; } [Export(typeof(ImageFormat))] @@ -40,26 +47,42 @@ namespace GameRes.Formats.Succubus { public override string Tag { get { return "GH"; } } public override string Description { get { return "Succubus image format"; } } - public override uint Signature { get { return 0x33504847; } } // 'GHP3' + public override uint Signature { get { return 0x33504847; } } + + public GhFormat () + { + Signatures = new uint[] { 0x33504847, 0x32504847 }; // 'GHP3', 'GHP2' + } public override ImageMetaData ReadMetaData (IBinaryStream file) { var header = file.ReadHeader (0x28); - return new GhpMetaData { + int version = header[3] - '0'; + var info = new GhpMetaData { Width = header.ToUInt16 (0xC), Height = header.ToUInt16 (0xE), BPP = 8, Colors = header.ToUInt16 (0x10), - PaletteOffset = header.ToUInt32 (0x18), - DataOffset = header.ToUInt32 (0x24), + Version = version, }; + if (2 == version) + { + info.PaletteOffset = header.ToUInt32 (0x14); + info.ChunkCount = header.ToInt32 (0x18); + info.DataOffset = header.ToUInt32 (0x1C); + } + else + { + info.PaletteOffset = header.ToUInt32 (0x18); + info.DataOffset = header.ToUInt32 (0x24); + } + return info; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var reader = new Ghp3Reader (file, (GhpMetaData)info); - reader.Unpack(); - return ImageData.Create (info, reader.Format, reader.Palette, reader.Data, reader.Stride); + var reader = new GhpReader (file, (GhpMetaData)info); + return reader.Unpack(); } public override void Write (Stream file, ImageData image) @@ -68,38 +91,189 @@ namespace GameRes.Formats.Succubus } } - internal class Ghp3Reader + internal class GhpReader { IBinaryStream m_input; GhpMetaData m_info; byte[] m_output; + int m_stride; + int m_depth; - public PixelFormat Format { get { return PixelFormats.Indexed8; } } - public BitmapPalette Palette { get; private set; } - public int Stride { get { return m_stride; } } - public byte[] Data { get { return m_output; } } - - public Ghp3Reader (IBinaryStream input, GhpMetaData info) + public GhpReader (IBinaryStream input, GhpMetaData info) { m_input = input; m_info = info; m_stride = ((int)info.Width + 3) & ~3; + m_depth = GetColorDepth (m_info.Colors); + m_output = new byte[m_stride * (int)m_info.Height]; } - public void Unpack () + public ImageData Unpack () { m_input.Position = m_info.PaletteOffset; - Palette = ImageFormat.ReadPalette (m_input.AsStream, m_info.Colors, PaletteFormat.Bgr); + var palette = ImageFormat.ReadPalette (m_input.AsStream, m_info.Colors, PaletteFormat.Rgb); m_input.Position = m_info.DataOffset; + if (2 == m_info.Version) + Unpack2(); + else + Unpack3(); + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, m_output, m_stride); + } + + void Unpack2 () + { + int width = (int)m_info.Width; + var repeat_table = new uint[(width * (int)m_info.Height + 31) / 32]; + int count = 0; + int skip = 5; + int x = 0, y = 0; + int next_x = 0, next_y = 0; + ReadPos(); + int pix = ReadBits (m_depth); + for (int i = 0; i < m_info.ChunkCount; ) + { + if (count <= 0) + { + int ctl = ReadBits (2); + if (ctl > 2) + count = ReadCount() - 1; + else + skip = ReadBits (1) + 2 * ctl; + } + else + { + --count; + } + m_output[m_stride * y + x] = (byte)pix; + int rpos = width * y + x; + repeat_table[rpos >> 5] |= 1u << (rpos & 0x1F); + if (skip >= 5) + { + int pos = ReadPos() + next_x; + next_y += Math.DivRem (pos, width, out next_x); + pix = ReadBits (m_depth); + y = next_y; + x = next_x; + ++i; + } + else + { + ++y; + x += skip - 2; + } + } + pix = 0; + int src = 0; + uint bitmap = 0; + for (y = 0; y < (int)m_info.Height; ++y) + for (x = 0; x < width; ++x) + { + if ((src & 0x1F) == 0) + bitmap = repeat_table[src >> 5]; + if ((bitmap & 1) != 0) + pix = m_output[m_stride * y + x]; + else + m_output[m_stride * y + x] = (byte)pix; + bitmap >>= 1; + ++src; + } + } + + void Unpack3 () + { int image_size = (m_stride * (int)m_info.Height + 0x1F) & ~0x1F; int table_size = image_size >> 3; var rows_table = new int[m_info.Height]; - for (uint i = 0; i < info.Height; ++i) + for (uint i = 0; i < m_info.Height; ++i) { - int line_pos = i * m_stride; + int line_pos = (int)i * m_stride; uint y = m_info.Height - i; rows_table[y - 1] = line_pos; } + throw new NotImplementedException(); } + + internal static int GetColorDepth (int colors) + { + int depth = 0; + for (int bit = 1; bit < colors && depth < 24; bit <<= 1) + { + ++depth; + } + return depth; + } + + int ReadPos () + { + int pos = ReadBitCount(); + if (pos > 0) + return BitTable[2 * pos + 3] + ReadBits (BitTable[2 * pos + 2]) + 1; + else + return ReadBits (2) + 1; + } + + int ReadCount () + { + int count = 0; + int x = ReadBitCount(); + if (x > 0) + count = BitTable[2 * x + 1] + ReadBits (BitTable[2 * x]) + 1; + return count + 2; + } + + uint m_bits = 0; + int m_cached_bits = 0; + + int ReadBits (int count) + { + uint bits = 0; + if (count > m_cached_bits) + { + if (m_cached_bits > 0) + { + count -= m_cached_bits; + bits = m_bits >> (32 - m_cached_bits) << count; + } + FillBitsCache(); + } + bits |= m_bits >> (32 - count); + m_bits <<= count; + m_cached_bits -= count; + return (int)bits; + } + + int ReadBitCount () + { + uint count = 0; + for (;;) + { + if (0 == m_cached_bits) + FillBitsCache(); + uint bit = m_bits >> 31; + --m_cached_bits; + m_bits <<= 1; + if (0 == bit) + break; + ++count; + } + return (int)count; + } + + void FillBitsCache () + { + m_bits = 0; + for (int shift = 0; shift < 32; shift += 8) + { + int b = m_input.ReadByte(); + if (-1 == b) + break; + m_bits |= (uint)b << shift; + } + m_cached_bits = 32; + } + + static readonly int[] BitTable = { + 0, 0, 2, 0, 4, 4, 6, 0x14, 8, 0x54, 0x0C, 0x154, 0x10, 0x1154, 0x12, 0x11154, + }; } }