diff --git a/ArcFormats/Macintosh/ImagePICT.cs b/ArcFormats/Macintosh/ImagePICT.cs new file mode 100644 index 00000000..d332afa1 --- /dev/null +++ b/ArcFormats/Macintosh/ImagePICT.cs @@ -0,0 +1,436 @@ +//! \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; + } + } + } + } + } +}