mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-30 23:04:12 +08:00
323 lines
12 KiB
C#
323 lines
12 KiB
C#
//! \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))
|
|
.Concat (Enumerable.Range (1, 9).Select (x => string.Format ("gs{0}", 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[01S]\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;
|
|
int reserved_lines = (m_height + 1) & ~1;
|
|
m_output = new byte[m_stride * reserved_lines];
|
|
UnpackRgb();
|
|
}
|
|
|
|
void UnpackV4 ()
|
|
{
|
|
Format = PixelFormats.Bgra32;
|
|
m_bpp = 4;
|
|
m_stride = m_width * 4;
|
|
int reserved_lines = (m_height + 1) & ~1;
|
|
m_output = new byte[m_stride * reserved_lines];
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|