From 45e086583c6a416220640804e655a41c83670c01 Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 12 Dec 2017 11:34:24 +0400 Subject: [PATCH] implemented GSA images. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/Bishop/ImageGSA.cs | 319 ++++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 ArcFormats/Bishop/ImageGSA.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 78b9dbb5..1056bc66 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -100,6 +100,7 @@ + diff --git a/ArcFormats/Bishop/ImageGSA.cs b/ArcFormats/Bishop/ImageGSA.cs new file mode 100644 index 00000000..a99efd9c --- /dev/null +++ b/ArcFormats/Bishop/ImageGSA.cs @@ -0,0 +1,319 @@ +//! \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; + } + } + } +}