GARbro-mirror/Legacy/Pearl/ImagePL4.cs

383 lines
14 KiB
C#
Raw Normal View History

//! \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;
}
}
}