mirror of
https://github.com/crskycode/GARbro.git
synced 2024-11-30 17:05:37 +08:00
383 lines
14 KiB
C#
383 lines
14 KiB
C#
|
//! \file ImagePL4.cs
|
||
|
//! \date 2023 Sep 23
|
||
|
//! \brief Pearl Soft image format.
|
||
|
//
|
||
|
// Copyright (C) 2023 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 GameRes.Utility;
|
||
|
using System;
|
||
|
using System.ComponentModel.Composition;
|
||
|
using System.IO;
|
||
|
using System.Windows.Media;
|
||
|
using System.Windows.Media.Imaging;
|
||
|
|
||
|
// [980424][Pearl Soft] Watashi
|
||
|
|
||
|
namespace GameRes.Formats.Pearl
|
||
|
{
|
||
|
internal class Pl4MetaData : ImageMetaData
|
||
|
{
|
||
|
public ushort CompressionMethod;
|
||
|
}
|
||
|
|
||
|
[Export(typeof(ImageFormat))]
|
||
|
public class Pl4Format : ImageFormat
|
||
|
{
|
||
|
public override string Tag => "PL4";
|
||
|
public override string Description => "Pearl Soft image format";
|
||
|
public override uint Signature => 0x20344C50; // 'PL4 '
|
||
|
|
||
|
public override ImageMetaData ReadMetaData (IBinaryStream file)
|
||
|
{
|
||
|
var header = file.ReadHeader (0x10);
|
||
|
int version = header.ToUInt16 (4);
|
||
|
if (version != 1)
|
||
|
return null;
|
||
|
var info = new Pl4MetaData {
|
||
|
Width = header.ToUInt16 (0xC) * 8u,
|
||
|
Height = header.ToUInt16 (0xE),
|
||
|
CompressionMethod = header.ToUInt16 (6),
|
||
|
BPP = 8,
|
||
|
};
|
||
|
if (info.CompressionMethod > 1)
|
||
|
return null;
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
public override ImageData Read (IBinaryStream file, ImageMetaData info)
|
||
|
{
|
||
|
var reader = new Pl4Reader (file, (Pl4MetaData)info);
|
||
|
return reader.Unpack();
|
||
|
}
|
||
|
|
||
|
public override void Write (Stream file, ImageData image)
|
||
|
{
|
||
|
throw new System.NotImplementedException ("Pl4Format.Write not implemented");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class Pl4Reader
|
||
|
{
|
||
|
IBinaryStream m_input;
|
||
|
Pl4MetaData m_info;
|
||
|
int m_stride;
|
||
|
|
||
|
public Pl4Reader (IBinaryStream input, Pl4MetaData info)
|
||
|
{
|
||
|
m_input = input;
|
||
|
m_info = info;
|
||
|
m_stride = m_info.iWidth;
|
||
|
}
|
||
|
|
||
|
public ImageData Unpack ()
|
||
|
{
|
||
|
m_input.Position = 0x10;
|
||
|
var palette = ReadPalette (16);
|
||
|
var pixels = new byte[m_stride * m_info.iHeight];
|
||
|
m_input.Position = 0x40;
|
||
|
if (m_info.CompressionMethod == 0)
|
||
|
UnpackV0 (pixels);
|
||
|
else if (m_info.CompressionMethod == 1)
|
||
|
UnpackV1 (pixels);
|
||
|
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, m_stride);
|
||
|
}
|
||
|
|
||
|
BitmapPalette ReadPalette (int colors)
|
||
|
{
|
||
|
var color_data = m_input.ReadBytes (colors * 3);
|
||
|
var color_map = new Color[colors];
|
||
|
int src = 0;
|
||
|
for (int i = 0; i < colors; ++i)
|
||
|
{
|
||
|
color_map[i] = Color.FromRgb ((byte)(color_data[src ] * 0x11),
|
||
|
(byte)(color_data[src+1] * 0x11),
|
||
|
(byte)(color_data[src+2] * 0x11));
|
||
|
src += 3;
|
||
|
}
|
||
|
return new BitmapPalette (color_map);
|
||
|
}
|
||
|
|
||
|
void UnpackV0 (byte[] output)
|
||
|
{
|
||
|
int height = m_info.iHeight;
|
||
|
int x = m_info.iWidth / 4;
|
||
|
int output_size = height * m_stride;
|
||
|
int row = 0;
|
||
|
int dst = 0;
|
||
|
int ctl, word;
|
||
|
while ((ctl = m_input.ReadByte()) != -1)
|
||
|
{
|
||
|
byte next = m_input.ReadUInt8();
|
||
|
if (0x98 == ctl)
|
||
|
{
|
||
|
ctl = m_input.ReadUInt8();
|
||
|
if (0 == next)
|
||
|
{
|
||
|
next = m_input.ReadUInt8();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
word = ctl << 8 | next;
|
||
|
int count = ((word >> 1) & 0x1F) + 2;
|
||
|
int src_y;
|
||
|
int src_x = Math.DivRem ((word >> 6) + 1, height, out src_y);
|
||
|
int src = dst - src_y * m_stride - 4 * src_x;
|
||
|
src_y = row - src_y;
|
||
|
if (src_y < 0)
|
||
|
{
|
||
|
src_y += height;
|
||
|
src += output_size - 4;
|
||
|
}
|
||
|
while (count --> 0)
|
||
|
{
|
||
|
output[dst ] = output[src ];
|
||
|
output[dst+1] = output[src+1];
|
||
|
output[dst+2] = output[src+2];
|
||
|
output[dst+3] = output[src+3];
|
||
|
dst += m_stride;
|
||
|
if (++row >= height)
|
||
|
{
|
||
|
row = 0;
|
||
|
dst -= output_size - 4;
|
||
|
if (--x <= 0)
|
||
|
return;
|
||
|
}
|
||
|
src += m_stride;
|
||
|
if (++src_y >= height)
|
||
|
{
|
||
|
src_y = 0;
|
||
|
src -= output_size - 4;
|
||
|
}
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
word = next << 8 | ctl;
|
||
|
int px = 0;
|
||
|
if ((word & 0x1000) != 0) px = 0x01000000;
|
||
|
if ((word & 0x2000) != 0) px |= 0x00010000;
|
||
|
if ((word & 0x4000) != 0) px |= 0x00000100;
|
||
|
if ((word & 0x8000) != 0) px |= 0x00000001;
|
||
|
if ((word & 0x0100) != 0) px |= 0x02000000;
|
||
|
if ((word & 0x0200) != 0) px |= 0x00020000;
|
||
|
if ((word & 0x0400) != 0) px |= 0x00000200;
|
||
|
if ((word & 0x0800) != 0) px |= 0x00000002;
|
||
|
if ((word & 0x0010) != 0) px |= 0x04000000;
|
||
|
if ((word & 0x0020) != 0) px |= 0x00040000;
|
||
|
if ((word & 0x0040) != 0) px |= 0x00000400;
|
||
|
if ((word & 0x0080) != 0) px |= 0x00000004;
|
||
|
if ((word & 0x0001) != 0) px |= 0x08000000;
|
||
|
if ((word & 0x0002) != 0) px |= 0x00080000;
|
||
|
if ((word & 0x0004) != 0) px |= 0x00000800;
|
||
|
if ((word & 0x0008) != 0) px |= 0x00000008;
|
||
|
LittleEndian.Pack (px, output, dst);
|
||
|
dst += m_stride;
|
||
|
if (++row >= height)
|
||
|
{
|
||
|
row = 0;
|
||
|
dst -= output_size - 4;
|
||
|
if (--x <= 0)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
byte[] m_pixelBuffer;
|
||
|
MsbBitStream m_bits;
|
||
|
|
||
|
void UnpackV1 (byte[] output)
|
||
|
{
|
||
|
m_pixelBuffer = InitLineBuffer();
|
||
|
int height = m_info.iHeight;
|
||
|
int dst = 0;
|
||
|
int output_size = m_stride * height;
|
||
|
int x = m_info.iWidth / 8;
|
||
|
int y = 0;
|
||
|
using (m_bits = new MsbBitStream (m_input.AsStream, true))
|
||
|
{
|
||
|
int p1 = 0, p2 = 0, p3 = 0, p4 = 0;
|
||
|
int ctl_bit;
|
||
|
while ((ctl_bit = m_bits.GetNextBit()) != -1)
|
||
|
{
|
||
|
if (ctl_bit != 0)
|
||
|
{
|
||
|
int src = dst;
|
||
|
int src_y = y;
|
||
|
switch (m_bits.GetBits (2))
|
||
|
{
|
||
|
case 0:
|
||
|
src_y = y - 2;
|
||
|
src = dst - 2 * m_stride;
|
||
|
break;
|
||
|
case 1:
|
||
|
src_y = y - 1;
|
||
|
src = dst - m_stride;
|
||
|
break;
|
||
|
case 2:
|
||
|
src_y = y - 4;
|
||
|
src = dst - 4 * m_stride;
|
||
|
break;
|
||
|
case 3:
|
||
|
src = dst - 8;
|
||
|
break;
|
||
|
}
|
||
|
if (src_y < 0)
|
||
|
{
|
||
|
src_y += height;
|
||
|
src += output_size - 8;
|
||
|
}
|
||
|
int count_length = 0;
|
||
|
while (m_bits.GetNextBit() == 0)
|
||
|
++count_length;
|
||
|
int count = 1;
|
||
|
if (count_length != 0)
|
||
|
{
|
||
|
count = m_bits.GetBits (count_length) | 1 << count_length;
|
||
|
}
|
||
|
while (count --> 0)
|
||
|
{
|
||
|
Buffer.BlockCopy (output, src, output, dst, 8);
|
||
|
dst += m_stride;
|
||
|
if (++y >= height)
|
||
|
{
|
||
|
y = 0;
|
||
|
dst -= output_size - 8;
|
||
|
if (--x <= 0)
|
||
|
return;
|
||
|
}
|
||
|
src += m_stride;
|
||
|
if (++src_y >= height)
|
||
|
{
|
||
|
src_y = 0;
|
||
|
src -= output_size - 8;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int px1 = 0;
|
||
|
int px2 = 0;
|
||
|
p1 = UpdatePixel (p1);
|
||
|
p2 = UpdatePixel (p2);
|
||
|
p3 = UpdatePixel (p3);
|
||
|
p4 = UpdatePixel (p4);
|
||
|
if ((p1 & 0x80) != 0) px1 = 0x00000001;
|
||
|
if ((p1 & 0x40) != 0) px1 |= 0x00000100;
|
||
|
if ((p1 & 0x20) != 0) px1 |= 0x00010000;
|
||
|
if ((p1 & 0x10) != 0) px1 |= 0x01000000;
|
||
|
if ((p1 & 0x08) != 0) px2 = 0x00000001;
|
||
|
if ((p1 & 0x04) != 0) px2 |= 0x00000100;
|
||
|
if ((p1 & 0x02) != 0) px2 |= 0x00010000;
|
||
|
if ((p1 & 0x01) != 0) px2 |= 0x01000000;
|
||
|
if ((p2 & 0x80) != 0) px1 |= 0x00000002;
|
||
|
if ((p2 & 0x40) != 0) px1 |= 0x00000200;
|
||
|
if ((p2 & 0x20) != 0) px1 |= 0x00020000;
|
||
|
if ((p2 & 0x10) != 0) px1 |= 0x02000000;
|
||
|
if ((p2 & 0x08) != 0) px2 |= 0x00000002;
|
||
|
if ((p2 & 0x04) != 0) px2 |= 0x00000200;
|
||
|
if ((p2 & 0x02) != 0) px2 |= 0x00020000;
|
||
|
if ((p2 & 0x01) != 0) px2 |= 0x02000000;
|
||
|
if ((p3 & 0x80) != 0) px1 |= 0x00000004;
|
||
|
if ((p3 & 0x40) != 0) px1 |= 0x00000400;
|
||
|
if ((p3 & 0x20) != 0) px1 |= 0x00040000;
|
||
|
if ((p3 & 0x10) != 0) px1 |= 0x04000000;
|
||
|
if ((p3 & 0x08) != 0) px2 |= 0x00000004;
|
||
|
if ((p3 & 0x04) != 0) px2 |= 0x00000400;
|
||
|
if ((p3 & 0x02) != 0) px2 |= 0x00040000;
|
||
|
if ((p3 & 0x01) != 0) px2 |= 0x04000000;
|
||
|
if ((p4 & 0x80) != 0) px1 |= 0x00000008;
|
||
|
if ((p4 & 0x40) != 0) px1 |= 0x00000800;
|
||
|
if ((p4 & 0x20) != 0) px1 |= 0x00080000;
|
||
|
if ((p4 & 0x10) != 0) px1 |= 0x08000000;
|
||
|
if ((p4 & 0x08) != 0) px2 |= 0x00000008;
|
||
|
if ((p4 & 0x04) != 0) px2 |= 0x00000800;
|
||
|
if ((p4 & 0x02) != 0) px2 |= 0x00080000;
|
||
|
if ((p4 & 0x01) != 0) px2 |= 0x08000000;
|
||
|
LittleEndian.Pack (px1, output, dst);
|
||
|
LittleEndian.Pack (px2, output, dst+4);
|
||
|
dst += m_stride;
|
||
|
if (++y >= height)
|
||
|
{
|
||
|
y = 0;
|
||
|
dst -= output_size - 8;
|
||
|
if (--x <= 0)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int UpdatePixel (int pixel)
|
||
|
{
|
||
|
byte nibble = GetNextPixel (pixel);
|
||
|
return GetNextPixel (nibble) | nibble << 4;
|
||
|
}
|
||
|
|
||
|
byte GetNextPixel (int pixel)
|
||
|
{
|
||
|
int bits = GetPixelBits();
|
||
|
int prior = (pixel & 0xF) << 4;
|
||
|
byte next = m_pixelBuffer[prior+bits];
|
||
|
int pos = prior + bits;
|
||
|
if (bits == 0)
|
||
|
return next;
|
||
|
while (bits --> 0)
|
||
|
{
|
||
|
m_pixelBuffer[pos] = m_pixelBuffer[pos - 1];
|
||
|
--pos;
|
||
|
}
|
||
|
return m_pixelBuffer[prior] = next;
|
||
|
}
|
||
|
|
||
|
int GetPixelBits ()
|
||
|
{
|
||
|
if (m_bits.GetNextBit() != 0)
|
||
|
{
|
||
|
return m_bits.GetBits (1);
|
||
|
}
|
||
|
else if (m_bits.GetNextBit() != 0)
|
||
|
{
|
||
|
return m_bits.GetBits (1) + 2;
|
||
|
}
|
||
|
else if (m_bits.GetNextBit() != 0)
|
||
|
{
|
||
|
return m_bits.GetBits (2) + 4;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return m_bits.GetBits (3) + 8;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static byte[] InitLineBuffer ()
|
||
|
{
|
||
|
var buffer = new byte[256];
|
||
|
for (int i = 0; i < 256; ++i)
|
||
|
{
|
||
|
buffer[i] = (byte)((i + (i >> 4)) & 0xF);
|
||
|
}
|
||
|
return buffer;
|
||
|
}
|
||
|
}
|
||
|
}
|