implemented CompressedBG image format.

This commit is contained in:
morkt 2015-08-31 01:09:27 +04:00
parent ff54563560
commit 0a8a6247a4
3 changed files with 290 additions and 15 deletions

View File

@ -23,11 +23,11 @@
// IN THE SOFTWARE.
//
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
namespace GameRes.Formats.BGI
{
@ -57,7 +57,7 @@ namespace GameRes.Formats.BGI
if (width <= 0 || height <= 0)
return null;
int bpp = input.ReadInt32();
if (24 != bpp && 32 != bpp)
if (24 != bpp && 32 != bpp && 8 != bpp)
return null;
if (0 != input.ReadInt64())
return null;
@ -72,21 +72,295 @@ namespace GameRes.Formats.BGI
public override ImageData Read (Stream stream, ImageMetaData info)
{
PixelFormat format;
if (24 == info.BPP)
format = PixelFormats.Bgr24;
else if (32 == info.BPP)
format = PixelFormats.Bgra32;
else
format = PixelFormats.Gray8;
int stride = (int)info.Width*((info.BPP+7)/8);
var pixels = new byte[stride*info.Height];
stream.Position = 0x10;
int read = stream.Read (pixels, 0, pixels.Length);
if (read != pixels.Length)
throw new InvalidFormatException();
PixelFormat format;
if (24 == info.BPP)
format = PixelFormats.Bgr24;
else
format = PixelFormats.Bgra32;
var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, 96, 96,
format, null, pixels, stride);
bitmap.Freeze();
return new ImageData (bitmap, info);
return ImageData.Create (info, format, null, pixels, stride);
}
}
internal class CbgMetaData : ImageMetaData
{
public int IntermediateLength;
public uint Key;
public int EncLength;
public byte CheckSum;
public byte CheckXor;
}
[Export(typeof(ImageFormat))]
public class CompressedBGFormat : ImageFormat
{
public override string Tag { get { return "CompressedBG"; } }
public override string Description { get { return "BGI/Ethornell compressed image format"; } }
public override uint Signature { get { return 0x706D6F43; } }
public CompressedBGFormat ()
{
Extensions = new string[] { "", "bgi" };
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("BgiFormat.Write not implemented");
}
public override ImageMetaData ReadMetaData (Stream stream)
{
var header = new byte[0x30];
if (header.Length != stream.Read (header, 0, header.Length))
return null;
if (!Binary.AsciiEqual (header, "CompressedBG___"))
return null;
return new CbgMetaData
{
Width = LittleEndian.ToUInt16 (header, 0x10),
Height = LittleEndian.ToUInt16 (header, 0x12),
BPP = LittleEndian.ToInt32 (header, 0x14),
IntermediateLength = LittleEndian.ToInt32 (header, 0x20),
Key = LittleEndian.ToUInt32 (header, 0x24),
EncLength = LittleEndian.ToInt32 (header, 0x28),
CheckSum = header[0x2C],
CheckXor = header[0x2D],
};
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
var meta = info as CbgMetaData;
if (null == meta)
throw new ArgumentException ("CompressedBGFormat.Read should be supplied with CbgMetaData", "info");
using (var reader = new CbgReader (stream, meta))
{
reader.Unpack();
return ImageData.Create (meta, reader.Format, null, reader.Data);
}
}
}
internal class CbgReader : BgiDecoderBase
{
byte[] m_output;
CbgMetaData m_info;
public byte[] Data { get { return m_output; } }
public PixelFormat Format { get; private set; }
public CbgReader (Stream input, CbgMetaData info) : base (input, true)
{
m_info = info;
int stride = (int)info.Width * info.BPP / 8;
m_output = new byte[stride * (int)info.Height];
m_key = m_info.Key;
m_magic = 0;
switch (m_info.BPP)
{
case 32: Format = PixelFormats.Bgra32; break;
case 24: Format = PixelFormats.Bgr24; break;
case 16: Format = PixelFormats.Bgr565; break;
case 8: Format = PixelFormats.Gray8; break;
default: throw new InvalidFormatException();
}
}
public void Unpack ()
{
Input.Position = 0x30;
var enc_buf = new byte[m_info.EncLength];
if (enc_buf.Length != Input.Read (enc_buf, 0, enc_buf.Length))
throw new EndOfStreamException();
byte sum = 0;
byte xor = 0;
int enc_pos = 0;
uint[] leaf_nodes_weight = new uint[0x100];
for (int i = 0; i < 0x100; ++i)
{
uint weight = 0;
byte code;
int code_length = 0;
do
{
if (enc_pos >= enc_buf.Length)
throw new InvalidFormatException ("Invalid compressed stream");
code = enc_buf[enc_pos++];
code -= UpdateKey();
sum += code;
xor ^= code;
weight |= ((uint)code & 0x7f) << code_length;
code_length += 7;
}
while (0 != (code & 0x80));
leaf_nodes_weight[i] = weight;
}
if (sum != m_info.CheckSum || xor != m_info.CheckXor)
throw new InvalidFormatException ("Compressed stream failed checksum check");
var nodes = new HuffmanNode[0x200];
int root_index = CreateHuffmanTree (nodes, leaf_nodes_weight);
byte[] packed = new byte[m_info.IntermediateLength];
HuffmanDecompress (nodes, root_index, packed);
UnpackZeros (packed);
ReverseAverageSampling();
}
class HuffmanNode
{
public bool Valid;
public bool IsParent;
public uint Weight;
public int ParentIndex;
public int LeftChildIndex;
public int RightChildIndex;
}
int CreateHuffmanTree (HuffmanNode[] nodes, uint[] leaf_nodes_weight)
{
uint root_node_weight = 0;
for (int i = 0; i < 0x100; ++i)
{
nodes[i] = new HuffmanNode
{
Valid = leaf_nodes_weight[i] != 0,
Weight = leaf_nodes_weight[i],
IsParent = false
};
root_node_weight += nodes[i].Weight;
}
int parent_node_index = 0x100;
int[] child_node_index = new int[2];
for (;;)
{
var parent_node = new HuffmanNode();
nodes[parent_node_index] = parent_node;
for (int i = 0; i < 2; i++)
{
uint min_weight = uint.MaxValue;
child_node_index[i] = -1;
for (int n = 0; n < parent_node_index; n++)
{
if (nodes[n].Valid)
{
if (nodes[n].Weight < min_weight)
{
min_weight = nodes[n].Weight;
child_node_index[i] = n;
}
}
}
nodes[child_node_index[i]].Valid = false;
nodes[child_node_index[i]].ParentIndex = parent_node_index;
}
parent_node.Valid = true;
parent_node.IsParent = true;
parent_node.LeftChildIndex = child_node_index[0];
parent_node.RightChildIndex = child_node_index[1];
parent_node.Weight = nodes[parent_node.LeftChildIndex].Weight
+ nodes[parent_node.RightChildIndex].Weight;
if (parent_node.Weight == root_node_weight)
break;
++parent_node_index;
}
return parent_node_index;
}
void HuffmanDecompress (HuffmanNode[] huffman_nodes, int root_node_index, byte[] output)
{
for (int dst = 0; dst < output.Length; dst++)
{
int node_index = root_node_index;
do
{
int bit = GetNextBit();
if (-1 == bit)
throw new EndOfStreamException();
if (0 == bit)
node_index = huffman_nodes[node_index].LeftChildIndex;
else
node_index = huffman_nodes[node_index].RightChildIndex;
}
while (huffman_nodes[node_index].IsParent);
output[dst] = (byte)node_index;
}
}
void UnpackZeros (byte[] input)
{
int dst = 0;
int dec_zero = 0;
int src = 0;
while (dst < m_output.Length)
{
int code_length = 0;
int count = 0;
byte code;
do
{
if (src >= input.Length)
return;
code = input[src++];
count |= (code & 0x7f) << code_length;
code_length += 7;
}
while (0 != (code & 0x80));
if (dst + count > m_output.Length)
break;
if (0 == dec_zero)
{
if (src + count > input.Length)
break;
Buffer.BlockCopy (input, src, m_output, dst, count);
src += count;
}
else
{
for (int i = 0; i < count; ++i)
m_output[dst+i] = 0;
}
dec_zero ^= 1;
dst += count;
}
}
void ReverseAverageSampling ()
{
int pixel_size = m_info.BPP / 8;
int stride = (int)m_info.Width * pixel_size;
for (int y = 0; y < m_info.Height; ++y)
{
int line = y * stride;
for (int x = 0; x < m_info.Width; ++x)
{
int pixel = line + x * pixel_size;
for (int p = 0; p < pixel_size; p++)
{
int avg = 0;
if (x > 0)
avg += m_output[pixel + p - pixel_size];
if (y > 0)
avg += m_output[pixel + p - stride];
if (x > 0 && y > 0)
avg /= 2;
if (0 != avg)
m_output[pixel + p] += (byte)avg;
}
}
}
}
}
}

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.1.9.419")]
[assembly: AssemblyFileVersion ("1.1.9.419")]
[assembly: AssemblyVersion ("1.1.9.421")]
[assembly: AssemblyFileVersion ("1.1.9.421")]

View File

@ -21,10 +21,11 @@ tr.odd td { background-color: #eee }
<tr><td>*.afs</td><td><tt>AFS</tt></td><td>No</td><td rowspan="2">PlayStation 2</td><td rowspan="2">Remember11</td></tr>
<tr><td>*.bip</td><td>-</td><td>No</td></tr>
<tr class="odd"><td>data.ami</td><td><tt>AMI</tt></td><td>Yes</td><td>-</td><td>Muv-Luv Amaterasu Translation data files</td></tr>
<tr><td>*.arc</td><td><tt>PackFile</tt></td><td>No</td><td>BGI/Ethornell</td><td>
<tr><td>*.arc</td><td><tt>PackFile</tt></td><td>No</td><td rowspan="2">BGI/Ethornell</td><td rowspan="2">
Chou Dengeki Stryker<br/>
H2O -Footprints in the Sand-<br/>
</td></tr>
<tr><td>-</td><td><tt>CompressedBG___</tt></td><td>No</td></tr>
<tr class="odd"><td>*</td><td>-<br/><tt>SM2MPX10</tt></td><td>No</td><td rowspan="4">DRS</td><td rowspan="4">
Anata no Osanazuma<br/>
Ecchi na Bunny-san wa Kirai?<br/>