//! \file ImageGSA.cs //! \date 2017 Dec 08 //! \brief Bishop image format. // // Copyright (C) 2017 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 System; using System.ComponentModel.Composition; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; namespace GameRes.Formats.Bishop { internal class GsaMetaData : ImageMetaData { public int Type; } [Export(typeof(ImageFormat))] public class GsaFormat : ImageFormat { public override string Tag { get { return "GSA"; } } public override string Description { get { return "Bishop image format"; } } public override uint Signature { get { return 0x4D428E8C; } } public GsaFormat () { var ext_list = Enumerable.Range (1, 12).Select (x => string.Format ("g{0:D2}", x)); Extensions = Extensions.Concat (ext_list).ToArray(); } public override ImageMetaData ReadMetaData (IBinaryStream file) { file.Position = 0xC0; int type = file.ReadInt32(); uint w = file.ReadUInt32(); uint h = file.ReadUInt32(); int x = file.ReadInt32(); int y = file.ReadInt32(); return new GsaMetaData { Width = w, Height = h, OffsetX = x, OffsetY = y, BPP = 32, Type = type, }; } static readonly Regex PartFileNameRe = new Regex (@"\.G\d\d$", RegexOptions.IgnoreCase); public override ImageData Read (IBinaryStream file, ImageMetaData info) { using (var reader = new GsaReader (file, (GsaMetaData)info)) { var pixels = reader.Unpack(); if (PartFileNameRe.IsMatch (file.Name)) { var base_name = Path.ChangeExtension (file.Name, "GSA"); if (VFS.FileExists (base_name)) { try { var image = TryBlendImage (base_name, reader, info); if (image != null) return image; } catch { /* ignore failed blending attempt */ } } } return ImageData.CreateFlipped (info, reader.Format, null, pixels, reader.Stride); } } ImageData TryBlendImage (string base_name, GsaReader overlay, ImageMetaData overlay_info) { int ovl_x = overlay_info.OffsetX; int ovl_y = overlay_info.OffsetY; int ovl_width = (int)overlay_info.Width; int ovl_height = (int)overlay_info.Height; if (ovl_x < 0) { ovl_width += ovl_x; ovl_x = 0; } if (ovl_y < 0) { ovl_height += ovl_y; ovl_y = 0; } using (var input = VFS.OpenBinaryStream (base_name)) { var base_info = ReadMetaData (input) as GsaMetaData; if (null == base_info) return null; int base_width = (int)base_info.Width; int base_height = (int)base_info.Height; if (checked(ovl_x + ovl_width) > base_width) ovl_width = base_width - ovl_x; if (checked(ovl_y + ovl_height) > base_height) ovl_height = base_height - ovl_y; if (ovl_height <= 0 || ovl_width <= 0) return null; input.Position = 0; var reader = new GsaReader (input, base_info); var base_pixels = reader.Unpack(); int src_pixel_size = overlay.PixelSize; int dst_pixel_size = reader.PixelSize; int dst = ovl_y * reader.Stride + ovl_x * dst_pixel_size; int src = 0; for (int y = 0; y < ovl_height; ++y) { int src_pixel = src; int dst_pixel = dst; for (int x = 0; x < ovl_width; ++x) { int src_alpha = overlay.Data[src_pixel+3]; if (src_alpha > 0) { if (0xFF == src_alpha) { Buffer.BlockCopy (overlay.Data, src_pixel, base_pixels, dst_pixel, dst_pixel_size); } else // assume destination has no alpha channel { base_pixels[dst_pixel+0] = (byte)((overlay.Data[src_pixel+0] * src_alpha + base_pixels[dst_pixel+0] * (0xFF - src_alpha)) / 0xFF); base_pixels[dst_pixel+1] = (byte)((overlay.Data[src_pixel+1] * src_alpha + base_pixels[dst_pixel+1] * (0xFF - src_alpha)) / 0xFF); base_pixels[dst_pixel+2] = (byte)((overlay.Data[src_pixel+2] * src_alpha + base_pixels[dst_pixel+2] * (0xFF - src_alpha)) / 0xFF); } } src_pixel += src_pixel_size; dst_pixel += dst_pixel_size; } src += overlay.Stride; dst += reader.Stride; } return ImageData.CreateFlipped (base_info, reader.Format, null, base_pixels, reader.Stride); } } public override void Write (Stream file, ImageData image) { throw new System.NotImplementedException ("GsaFormat.Write not implemented"); } } internal sealed class GsaReader : IDisposable { LsbBitStream m_input; readonly int m_type; readonly int m_width; readonly int m_height; int m_stride; int m_bpp; // bytes per pixel byte[] m_output; public PixelFormat Format { get; private set; } public int Stride { get { return m_stride; } } public byte[] Data { get { return m_output; } } public int PixelSize { get { return m_bpp; } } public GsaReader (IBinaryStream file, GsaMetaData info) { m_input = new LsbBitStream (file.AsStream, true); m_type = info.Type; m_width = (int)info.Width; m_height = (int)info.Height; } public byte[] Unpack () { m_input.Input.Position = 0xD8; if (3 == m_type || 0x83 == m_type) UnpackV3(); else UnpackV4(); return m_output; } void UnpackV3 () { Format = PixelFormats.Bgr24; m_bpp = 3; m_stride = (m_width * 3 + 3) & ~3; m_output = new byte[m_stride * m_height]; UnpackRgb(); } void UnpackV4 () { Format = PixelFormats.Bgra32; m_bpp = 4; m_stride = m_width * 4; m_output = new byte[m_stride * m_height]; ReadPlane (3); UnpackRgb(); } void UnpackRgb () { for (int plane = 0; plane < 3; ++plane) { ReadPlane (plane); } if (0 != (m_type & 0x80)) { int dst_row = 0; for (int y = 0; y < m_height; ++y) { int dst = dst_row; dst_row += m_stride; for (int x = 0; x < m_width; ++x) { m_output[dst ] <<= 3; m_output[dst+1] <<= 2; m_output[dst+2] <<= 2; dst += m_bpp; } } } } void ReadPlane (int dst_row) { for (int y = 0; y < m_height; y += 2) { int dst = dst_row; dst_row += 2 * m_stride; for (int x = 0; x < m_width; x += 2) { switch (m_input.GetBits (3)) { case 0: CopyBits0 (dst); break; case 1: ReadBits1 (dst, 1, 0); break; case 2: ReadBits1 (dst, 2, 0xFF); break; case 3: ReadBits1 (dst, 3, 0xFD); break; case 4: ReadBits4 (dst, 4, 0xF9); break; case 5: ReadBits5 (dst, 6); break; case 6: ReadBits5 (dst, 7); break; case 7: ReadBits5 (dst, 8); break; case -1: throw new EndOfStreamException(); } dst += m_bpp * 2; } } } void CopyBits0 (int dst) { m_output[dst] = m_output[dst-m_bpp*2]; m_output[dst+m_bpp] = m_output[dst-m_bpp]; m_output[dst+m_stride] = m_output[dst+m_stride-m_bpp*2]; m_output[dst+m_stride+m_bpp] = m_output[dst+m_stride-m_bpp]; } void ReadBits1 (int dst, int count, byte n) { m_output[dst] = (byte)(n + m_input.GetBits (count) + m_output[dst-m_bpp*2]); m_output[dst+m_bpp] = (byte)(n + m_input.GetBits (count) + m_output[dst-m_bpp]); m_output[dst+m_stride] = (byte)(n + m_input.GetBits (count) + m_output[dst+m_stride-m_bpp*2]); m_output[dst+m_stride+m_bpp] = (byte)(n + m_input.GetBits (count) + m_output[dst+m_stride-m_bpp]); } void ReadBits4 (int dst, int count, byte n) { int prev = dst - 2 * m_stride; m_output[dst] = (byte)(n + m_input.GetBits (count) + m_output[prev]); m_output[dst+m_bpp] = (byte)(n + m_input.GetBits (count) + m_output[prev+m_bpp]); m_output[dst+m_stride] = (byte)(n + m_input.GetBits (count) + m_output[dst-m_stride]); m_output[dst+m_stride+m_bpp] = (byte)(n + m_input.GetBits (count) + m_output[dst-m_stride+m_bpp]); } void ReadBits5 (int dst, int count) { m_output[dst] = (byte)m_input.GetBits (count); m_output[dst+m_bpp] = (byte)m_input.GetBits (count); m_output[dst+m_stride] = (byte)m_input.GetBits (count); m_output[dst+m_stride+m_bpp] = (byte)m_input.GetBits (count); } bool m_disposed = false; public void Dispose () { if (!m_disposed) { m_input.Dispose(); m_disposed = true; } } } }