From 54fbad8ff1a88e7098418d4f1f8aeb512d06d975 Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 30 Dec 2017 00:33:28 +0400 Subject: [PATCH] implemented 'GaleX' images. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/LiveMaker/ImageGAL.cs | 67 ++++++++----- ArcFormats/LiveMaker/ImageGALX.cs | 155 ++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 22 deletions(-) create mode 100644 ArcFormats/LiveMaker/ImageGALX.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index b7170156..306c368d 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -117,6 +117,7 @@ + diff --git a/ArcFormats/LiveMaker/ImageGAL.cs b/ArcFormats/LiveMaker/ImageGAL.cs index 5f7ddd11..628fa324 100644 --- a/ArcFormats/LiveMaker/ImageGAL.cs +++ b/ArcFormats/LiveMaker/ImageGAL.cs @@ -153,7 +153,7 @@ namespace GameRes.Formats.LiveMaker return new GUI.WidgetGAL(); } - uint QueryKey () + internal uint QueryKey () { if (!KnownKeys.Any()) return 0; @@ -169,13 +169,13 @@ namespace GameRes.Formats.LiveMaker } } - internal sealed class GalReader : IDisposable + internal class GalReader : IDisposable { - IBinaryStream m_input; - GalMetaData m_info; - byte[] m_output; - List m_frames; - uint m_key; + protected IBinaryStream m_input; + protected GalMetaData m_info; + protected byte[] m_output; + protected List m_frames; + protected uint m_key; public byte[] Data { get { return m_output; } } public PixelFormat Format { get; private set; } @@ -206,6 +206,14 @@ namespace GameRes.Formats.LiveMaker { Layers = new List (layer_count); } + + public void SetStride () + { + Stride = (Width * BPP + 7) / 8; + AlphaStride = (Width + 3) & ~3; + if (BPP >= 8) + Stride = (Stride + 3) & ~3; + } } internal class Layer @@ -237,28 +245,23 @@ namespace GameRes.Formats.LiveMaker throw new InvalidFormatException(); if (frame.BPP <= 8) frame.Palette = ImageFormat.ReadColorMap (m_input.AsStream, 1 << frame.BPP); - frame.Stride = (frame.Width * frame.BPP + 7) / 8; - frame.AlphaStride = (frame.Width + 3) & ~3; - if (frame.BPP >= 8) - frame.Stride = (frame.Stride + 3) & ~3; + frame.SetStride(); m_frames.Add (frame); for (int i = 0; i < layer_count; ++i) { - m_input.ReadInt32(); - m_input.ReadInt32(); + m_input.ReadInt32(); // left + m_input.ReadInt32(); // top m_input.ReadByte(); // visibility - m_input.ReadInt32(); // -1 - m_input.ReadInt32(); // 0xFF - m_input.ReadByte(); + m_input.ReadInt32(); // (-1) TransColor + m_input.ReadInt32(); // (0xFF) alpha + m_input.ReadByte(); // AlphaOn name_length = m_input.ReadUInt32(); m_input.Seek (name_length, SeekOrigin.Current); if (m_info.Version >= 107) - m_input.ReadByte(); + m_input.ReadByte(); // lock var layer = new Layer(); int layer_size = m_input.ReadInt32(); - long layer_end = m_input.Position + layer_size; layer.Pixels = UnpackLayer (frame, layer_size); - m_input.Position = layer_end; int alpha_size = m_input.ReadInt32(); if (alpha_size != 0) { @@ -269,9 +272,12 @@ namespace GameRes.Formats.LiveMaker Flatten (0); } - byte[] UnpackLayer (Frame frame, int length, bool is_alpha = false) + protected byte[] UnpackLayer (Frame frame, int length, bool is_alpha = false) { - using (var packed = new StreamRegion (m_input.AsStream, m_input.Position, length, true)) + var layer_start = m_input.Position; + var layer_end = layer_start + length; + var packed = new StreamRegion (m_input.AsStream, layer_start, length, true); + try { if (0 == m_info.Compression || 2 == m_info.Compression && is_alpha) return ReadZlib (frame, packed, is_alpha); @@ -279,6 +285,11 @@ namespace GameRes.Formats.LiveMaker return ReadJpeg (frame, packed); return ReadBlocks (frame, packed, is_alpha); } + finally + { + packed.Dispose(); + m_input.Position = layer_end; + } } byte[] ReadBlocks (Frame frame, Stream packed, bool is_alpha) @@ -383,7 +394,7 @@ namespace GameRes.Formats.LiveMaker return pixels; } - void Flatten (int frame_num) + protected void Flatten (int frame_num) { // XXX only first layer is considered. @@ -550,8 +561,20 @@ namespace GameRes.Formats.LiveMaker } #region IDisposable Members + bool m_disposed = false; + public void Dispose () { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!m_disposed) + { + m_disposed = true; + } } #endregion } diff --git a/ArcFormats/LiveMaker/ImageGALX.cs b/ArcFormats/LiveMaker/ImageGALX.cs new file mode 100644 index 00000000..57f1e9ae --- /dev/null +++ b/ArcFormats/LiveMaker/ImageGALX.cs @@ -0,0 +1,155 @@ +//! \file ImageGALX.cs +//! \date 2017 Dec 29 +//! \brief LiveMaker GaleX image format. +// +// Copyright (C) 2017 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 System; +using System.ComponentModel.Composition; +using System.IO; +using System.Text.RegularExpressions; +using System.Xml; +using GameRes.Compression; +using GameRes.Utility; + +namespace GameRes.Formats.LiveMaker +{ + internal class GalXMetaData : GalMetaData + { + public XmlNode FrameXml; + } + + [Export(typeof(ImageFormat))] + public class GalXFormat : ImageFormat + { + public override string Tag { get { return "GAL/X200"; } } + public override string Description { get { return "LiveMaker image format"; } } + public override uint Signature { get { return 0x656C6147; } } // 'Gale' + public override bool CanWrite { get { return false; } } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (12); + if (!header.AsciiEqual ("GaleX200")) + return null; + int header_size = LittleEndian.ToInt32 (header, 8); + using (var zheader = new StreamRegion (file.AsStream, 12, header_size, true)) + using (var xheader = new ZLibStream (zheader, CompressionMode.Decompress)) + { + var xml = ReadXml (xheader); + var frames = xml.DocumentElement.SelectSingleNode ("/Frames"); + var attr = frames.Attributes; + return new GalXMetaData + { + Width = UInt32.Parse (attr["Width"].Value), + Height = UInt32.Parse (attr["Height"].Value), + BPP = Int32.Parse (attr["Bpp"].Value), + Version = Int32.Parse (attr["Version"].Value), + FrameCount = Int32.Parse (attr["Count"].Value), + Shuffled = attr["Randomized"].Value != "0", + Compression = Int32.Parse (attr["CompType"].Value), + Mask = UInt32.Parse (attr["BGColor"].Value), + BlockWidth = Int32.Parse (attr["BlockWidth"].Value), + BlockHeight = Int32.Parse (attr["BlockHeight"].Value), + DataOffset = header_size + 12, + FrameXml = frames, + }; + } + } + + static readonly Regex FrameRe = new Regex (@"]+>"); + + internal XmlDocument ReadXml (Stream input) + { + // GaleXml contains duplicate attributes which causes LoadXml to fail. + // this is a silly hack to prevent such failure. + using (var reader = new StreamReader (input)) + { + var text = reader.ReadToEnd(); + text = FrameRe.Replace (text, ""); + var xml = new XmlDocument(); + xml.LoadXml (text); + return xml; + } + } + + public override ImageData Read (IBinaryStream stream, ImageMetaData info) + { + var meta = (GalXMetaData)info; + if (meta.Shuffled) + throw new NotImplementedException ("Encrypted GaleX images not implemented."); + using (var reader = new GalXReader (stream, meta, 0)) + { + reader.Unpack(); + return ImageData.Create (info, reader.Format, reader.Palette, reader.Data, reader.Stride); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GalXFormat.Write not implemented"); + } + } + + internal class GalXReader : GalReader + { + readonly XmlNode FrameXml; + + public GalXReader (IBinaryStream input, GalXMetaData info, uint key) : base (input, info, key) + { + FrameXml = info.FrameXml; + } + + new public void Unpack () + { + m_input.Position = m_info.DataOffset; + var layers = FrameXml.SelectSingleNode ("Frame/Layers"); + var attr = layers.Attributes; + int layer_count = Int32.Parse (attr["Count"].Value); + var frame = new Frame (layer_count); + frame.Width = Int32.Parse (attr["Width"].Value); + frame.Height = Int32.Parse (attr["Height"].Value); + frame.BPP = Int32.Parse (attr["Bpp"].Value); + frame.SetStride(); + if (frame.BPP <= 8) + frame.Palette = ImageFormat.ReadColorMap (m_input.AsStream, 1 << frame.BPP); + m_frames.Add (frame); + + var layer_nodes = layers.SelectNodes ("Layer"); + foreach (XmlNode node in layer_nodes) + { + attr = node.Attributes; + bool alpha_on = attr["AlphaOn"].Value != "0"; + int layer_size = m_input.ReadInt32(); + var layer = new Layer(); + layer.Pixels = UnpackLayer (frame, layer_size); + if (alpha_on) + { + int alpha_size = m_input.ReadInt32(); + layer.Alpha = UnpackLayer (frame, alpha_size, true); + } + frame.Layers.Add (layer); + } + Flatten (0); + } + } +}