mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-12 22:36:58 +08:00
437 lines
16 KiB
C#
437 lines
16 KiB
C#
|
//! \file ImagePICT.cs
|
||
|
//! \date 2023 Aug 24
|
||
|
//! \brief Macintosh picture 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;
|
||
|
|
||
|
namespace GameRes.Formats.Apple
|
||
|
{
|
||
|
internal class PictMetaData : ImageMetaData
|
||
|
{
|
||
|
public uint DataOffset;
|
||
|
}
|
||
|
|
||
|
[Export(typeof(ImageFormat))]
|
||
|
public class PictFormat : ImageFormat
|
||
|
{
|
||
|
public override string Tag { get => "PICT/MAC"; }
|
||
|
public override string Description { get => "Apple Macintosh image format"; }
|
||
|
public override uint Signature { get => 0; }
|
||
|
|
||
|
public PictFormat ()
|
||
|
{
|
||
|
Signatures = new[] { 0u, 0x54434950u };
|
||
|
Extensions = new[] { "pct", "pict", "pic" };
|
||
|
}
|
||
|
|
||
|
public override ImageMetaData ReadMetaData (IBinaryStream file)
|
||
|
{
|
||
|
int header_pos = 0x200;
|
||
|
if (file.Signature == 0x54434950) // 'PICT'
|
||
|
header_pos = 4;
|
||
|
if (file.Length < header_pos + 0x10)
|
||
|
return null;
|
||
|
file.Position = header_pos + 2;
|
||
|
short top = file.ReadI16BE();
|
||
|
short left = file.ReadI16BE();
|
||
|
short bottom = file.ReadI16BE();
|
||
|
short right = file.ReadI16BE();
|
||
|
if (file.ReadU16BE() != 0x11)
|
||
|
return null;
|
||
|
int version = file.ReadU16BE();
|
||
|
if (version != 0x2FF)
|
||
|
return null;
|
||
|
return new PictMetaData {
|
||
|
Width = (uint)(right - left),
|
||
|
Height = (uint)(bottom - top),
|
||
|
OffsetX = left,
|
||
|
OffsetY = top,
|
||
|
BPP = 32,
|
||
|
DataOffset = (uint)file.Position,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public override ImageData Read (IBinaryStream file, ImageMetaData info)
|
||
|
{
|
||
|
var decoder = new PictReader (file, (PictMetaData)info);
|
||
|
var pixels = decoder.Unpack();
|
||
|
return ImageData.Create (info, decoder.Format, decoder.Palette, pixels);
|
||
|
}
|
||
|
|
||
|
public override void Write (Stream file, ImageData image)
|
||
|
{
|
||
|
throw new System.NotImplementedException ("PictFormat.Write not implemented");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static class BinaryStreamExtension
|
||
|
{
|
||
|
static public short ReadI16BE (this IBinaryStream file)
|
||
|
{
|
||
|
return Binary.BigEndian (file.ReadInt16());
|
||
|
}
|
||
|
|
||
|
static public ushort ReadU16BE (this IBinaryStream file)
|
||
|
{
|
||
|
return Binary.BigEndian (file.ReadUInt16());
|
||
|
}
|
||
|
|
||
|
static public int ReadI32BE (this IBinaryStream file)
|
||
|
{
|
||
|
return Binary.BigEndian (file.ReadInt32());
|
||
|
}
|
||
|
|
||
|
static public uint ReadU32BE (this IBinaryStream file)
|
||
|
{
|
||
|
return Binary.BigEndian (file.ReadUInt32());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class Pixmap
|
||
|
{
|
||
|
public short Version;
|
||
|
public short PackType;
|
||
|
public int PackSize;
|
||
|
public int HorizRes;
|
||
|
public int VertRes;
|
||
|
public short PixelType;
|
||
|
public short BPP;
|
||
|
public short CompCount;
|
||
|
public short CompSize;
|
||
|
public int PlaneBytes;
|
||
|
public int Table;
|
||
|
|
||
|
public void Deserialize (IBinaryStream input)
|
||
|
{
|
||
|
Version = input.ReadI16BE();
|
||
|
PackType = input.ReadI16BE();
|
||
|
PackSize = input.ReadI32BE();
|
||
|
HorizRes = input.ReadI32BE() >> 16; // read 2 bytes and skip next 2
|
||
|
VertRes = input.ReadI32BE() >> 16;
|
||
|
PixelType = input.ReadI16BE();
|
||
|
BPP = input.ReadI16BE();
|
||
|
CompCount = input.ReadI16BE();
|
||
|
CompSize = input.ReadI16BE();
|
||
|
PlaneBytes = input.ReadI32BE();
|
||
|
Table = input.ReadI32BE();
|
||
|
input.Seek (4, SeekOrigin.Current);
|
||
|
if (BPP <= 0 || BPP > 32 || CompCount <= 0 || CompCount > 4 || CompSize <= 0)
|
||
|
throw new InvalidFormatException();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class PictReader
|
||
|
{
|
||
|
IBinaryStream m_input;
|
||
|
PictMetaData m_info;
|
||
|
|
||
|
public PixelFormat Format { get; private set; }
|
||
|
public BitmapPalette Palette { get; private set; }
|
||
|
|
||
|
public PictReader (IBinaryStream input, PictMetaData info)
|
||
|
{
|
||
|
m_input = input;
|
||
|
m_info = info;
|
||
|
}
|
||
|
|
||
|
bool HasAlpha = false;
|
||
|
byte[] m_buffer;
|
||
|
|
||
|
public byte[] Unpack ()
|
||
|
{
|
||
|
Color[] colormap = null;
|
||
|
Pixmap pixmap = null;
|
||
|
m_input.Position = m_info.DataOffset;
|
||
|
|
||
|
while (m_input.PeekByte() != -1)
|
||
|
{
|
||
|
if ((m_input.Position & 1) != 0)
|
||
|
Skip (1);
|
||
|
int code = m_input.ReadU16BE();
|
||
|
if (0x00FF == code || 0xFFFF == code) // EOF
|
||
|
break;
|
||
|
switch (code)
|
||
|
{
|
||
|
case 0x0000: // NOP
|
||
|
continue;
|
||
|
|
||
|
case 0x0001: // Clip
|
||
|
{
|
||
|
int length = m_input.ReadU16BE();
|
||
|
if (length < 2)
|
||
|
throw new InvalidFormatException();
|
||
|
Skip (length-2);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 0x0090:
|
||
|
case 0x0091:
|
||
|
case 0x0098:
|
||
|
case 0x0099:
|
||
|
case 0x009A:
|
||
|
case 0x009B: // BitsRect
|
||
|
{
|
||
|
int stride = 0;
|
||
|
if (code != 0x9A && code != 0x9B)
|
||
|
stride = m_input.ReadU16BE();
|
||
|
else
|
||
|
Skip (6);
|
||
|
// FIXME we just read the first bitmap and override an existing frame
|
||
|
// TODO place bitmap into frame according to its RECT
|
||
|
m_info.OffsetY = m_input.ReadI16BE();
|
||
|
m_info.OffsetX = m_input.ReadI16BE();
|
||
|
m_info.Height = (uint)(m_input.ReadI16BE() - m_info.OffsetY);
|
||
|
m_info.Width = (uint)(m_input.ReadI16BE() - m_info.OffsetX);
|
||
|
if (0x9A == code || 0x9B == code || (stride & 0x8000) != 0)
|
||
|
{
|
||
|
pixmap = new Pixmap();
|
||
|
pixmap.Deserialize (m_input);
|
||
|
HasAlpha = pixmap.CompCount == 4;
|
||
|
}
|
||
|
if (code != 0x9A && code != 0x9B)
|
||
|
{
|
||
|
int colors = 2;
|
||
|
int flags = 0;
|
||
|
if ((stride & 0x8000) != 0)
|
||
|
{
|
||
|
Skip (4);
|
||
|
flags = m_input.ReadU16BE();
|
||
|
colors = m_input.ReadU16BE() + 1;
|
||
|
}
|
||
|
if (null == colormap)
|
||
|
colormap = new Color[colors];
|
||
|
if ((stride & 0x8000) != 0)
|
||
|
{
|
||
|
for (int i = 0; i < colors; i++)
|
||
|
{
|
||
|
int c = m_input.ReadU16BE() % colors;
|
||
|
if ((flags & 0x8000) != 0)
|
||
|
c = i;
|
||
|
int r = m_input.ReadU16BE() / 0x101;
|
||
|
int g = m_input.ReadU16BE() / 0x101;
|
||
|
int b = m_input.ReadU16BE() / 0x101;
|
||
|
colormap[c] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var White = Color.FromRgb (0xFF, 0xFF, 0xFF);
|
||
|
for (int i = 0; i < colors; i++)
|
||
|
{
|
||
|
colormap[i] = Color.Subtract (White, colormap[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Skip (8+8+2);
|
||
|
// -> Skip (8); // source RECT
|
||
|
// Skip (8); // destination RECT
|
||
|
// Skip (2); // transfer mode
|
||
|
if (code == 0x91 || code == 0x99 || code == 0x9b)
|
||
|
{
|
||
|
int length = m_input.ReadU16BE();
|
||
|
if (length > 2)
|
||
|
Skip (length - 2);
|
||
|
}
|
||
|
if (code != 0x9A && code != 0x9B && (stride & 0x8000) == 0)
|
||
|
DecodeRleBitmap (stride, 1);
|
||
|
else
|
||
|
DecodeRleBitmap (stride, pixmap.BPP);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 0x00A1: // LongComment
|
||
|
{
|
||
|
m_input.ReadU16BE(); // comment type
|
||
|
int length = m_input.ReadU16BE();
|
||
|
Skip (length);
|
||
|
break;
|
||
|
}
|
||
|
case 0x0C00: // Header
|
||
|
Skip (0x18);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new NotSupportedException (string.Format ("Unknown code 0x{0:X4} in PICT stream.", code));
|
||
|
}
|
||
|
}
|
||
|
if (colormap != null)
|
||
|
Palette = new BitmapPalette (colormap);
|
||
|
if (null == m_buffer)
|
||
|
throw new InvalidFormatException();
|
||
|
SetFormat (pixmap);
|
||
|
return RepackPixels (pixmap);
|
||
|
}
|
||
|
|
||
|
byte[] RepackPixels (Pixmap pixmap)
|
||
|
{
|
||
|
int bpp = m_info.BPP;
|
||
|
if (bpp <= 16)
|
||
|
return m_buffer;
|
||
|
int bytes_per_pixel = bpp / 8;
|
||
|
int stride = m_info.iWidth * bytes_per_pixel;
|
||
|
var pixels = new byte[stride * m_info.iHeight];
|
||
|
int src = 0;
|
||
|
for (int y = 0; y < m_info.iHeight; ++y)
|
||
|
{
|
||
|
int dst = y * stride;
|
||
|
for (int x = 0; x < m_info.iWidth; ++x)
|
||
|
{
|
||
|
if (HasAlpha)
|
||
|
{
|
||
|
pixels[dst+3] = m_buffer[src];
|
||
|
pixels[dst+2] = m_buffer[src+m_info.iWidth];
|
||
|
pixels[dst+1] = m_buffer[src+m_info.iWidth*2];
|
||
|
pixels[dst] = m_buffer[src+m_info.iWidth*3];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pixels[dst+2] = m_buffer[src];
|
||
|
pixels[dst+1] = m_buffer[src+m_info.iWidth];
|
||
|
pixels[dst] = m_buffer[src+m_info.iWidth*2];
|
||
|
}
|
||
|
++src;
|
||
|
dst += bytes_per_pixel;
|
||
|
}
|
||
|
src += (pixmap.CompCount - 1) * m_info.iWidth;
|
||
|
}
|
||
|
return pixels;
|
||
|
}
|
||
|
|
||
|
void SetFormat (Pixmap pixmap)
|
||
|
{
|
||
|
int bpp = null == pixmap ? 8 : pixmap.BPP;
|
||
|
if (32 == bpp)
|
||
|
{
|
||
|
if (4 == pixmap.CompCount)
|
||
|
Format = PixelFormats.Bgra32;
|
||
|
else
|
||
|
Format = PixelFormats.Bgr32;
|
||
|
}
|
||
|
else if (24 == bpp)
|
||
|
Format = PixelFormats.Bgr24;
|
||
|
else if (16 == bpp)
|
||
|
Format = PixelFormats.Bgr555;
|
||
|
else if (8 == bpp)
|
||
|
{
|
||
|
if (Palette != null)
|
||
|
Format = PixelFormats.Indexed8;
|
||
|
else
|
||
|
Format = PixelFormats.Gray8;
|
||
|
}
|
||
|
else
|
||
|
throw new NotSupportedException (string.Format ("Not supported PICT bitdepth -- {0}bpp", bpp));
|
||
|
m_info.BPP = bpp;
|
||
|
}
|
||
|
|
||
|
void Skip (int amount)
|
||
|
{
|
||
|
m_input.Seek (amount, SeekOrigin.Current);
|
||
|
}
|
||
|
|
||
|
byte[] m_unpack_buffer = new byte[0x800];
|
||
|
byte[] m_scanline;
|
||
|
|
||
|
void DecodeRleBitmap (int stride, int bpp)
|
||
|
{
|
||
|
if (bpp < 8)
|
||
|
throw new NotSupportedException();
|
||
|
if (bpp <= 8)
|
||
|
stride &= 0x7fff;
|
||
|
int width = m_info.iWidth;
|
||
|
int bytes_per_pixel = 1;
|
||
|
if (16 == bpp)
|
||
|
{
|
||
|
bytes_per_pixel = 2;
|
||
|
width *= 2;
|
||
|
}
|
||
|
else if (32 == bpp)
|
||
|
width *= HasAlpha ? 4 : 3;
|
||
|
if (stride == 0)
|
||
|
stride = width;
|
||
|
int stride_32bpp = m_info.iWidth * 4;
|
||
|
|
||
|
int total_bytes = stride_32bpp * m_info.iHeight;
|
||
|
if (null == m_buffer || m_buffer.Length < total_bytes)
|
||
|
m_buffer = new byte[total_bytes];
|
||
|
int scanline_length = stride_32bpp * 2;
|
||
|
if (null == m_scanline || m_scanline.Length < scanline_length)
|
||
|
{
|
||
|
m_scanline = new byte[scanline_length];
|
||
|
}
|
||
|
if (stride < 8)
|
||
|
{
|
||
|
int dst = 0;
|
||
|
int row_size = width * (bpp / 8);
|
||
|
for (int y = 0; y < m_info.iHeight; ++y)
|
||
|
{
|
||
|
m_input.Read (m_buffer, dst, stride);
|
||
|
dst += row_size;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
for (int y = 0; y < m_info.iHeight; ++y)
|
||
|
{
|
||
|
int dst = y * width;
|
||
|
if (stride > 200)
|
||
|
scanline_length = m_input.ReadU16BE();
|
||
|
else
|
||
|
scanline_length = m_input.ReadUInt8();
|
||
|
if (scanline_length >= m_scanline.Length || scanline_length == 0)
|
||
|
throw new InvalidFormatException();
|
||
|
m_input.Read (m_scanline, 0, scanline_length);
|
||
|
for (int j = 0; j < scanline_length; )
|
||
|
{
|
||
|
if ((m_scanline[j] & 0x80) == 0)
|
||
|
{
|
||
|
int pixel_count = m_scanline[j] + 1;
|
||
|
int count = pixel_count * bytes_per_pixel;
|
||
|
int src = j + 1;
|
||
|
if ((dst + count) <= total_bytes)
|
||
|
Buffer.BlockCopy (m_scanline, src, m_buffer, dst, count);
|
||
|
dst += count;
|
||
|
j += count + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int count = (m_scanline[j] ^ 0xFF) + 2;
|
||
|
int src = j + 1;
|
||
|
while (count --> 0)
|
||
|
{
|
||
|
if ((dst + bytes_per_pixel) <= total_bytes)
|
||
|
Buffer.BlockCopy (m_scanline, src, m_buffer, dst, bytes_per_pixel);
|
||
|
dst += bytes_per_pixel;
|
||
|
}
|
||
|
j += bytes_per_pixel + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|