From 5319000ad52dd2aa193e9f143d1ded6a1b79bb3f Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 26 Mar 2015 06:57:33 +0400 Subject: [PATCH] BMD image format implementation. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/ImageBMD.cs | 360 ++++++++++++++++++++++++++ ArcFormats/Properties/AssemblyInfo.cs | 4 +- 3 files changed, 363 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/ImageBMD.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 3cc76428..b2d6c4fb 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -122,6 +122,7 @@ CreateYPFWidget.xaml + diff --git a/ArcFormats/ImageBMD.cs b/ArcFormats/ImageBMD.cs new file mode 100644 index 00000000..9d905cec --- /dev/null +++ b/ArcFormats/ImageBMD.cs @@ -0,0 +1,360 @@ +//! \file ImageBMD.cs +//! \date Wed Mar 25 09:35:06 2015 +//! \brief Black Rainbow BMD image format implementation. +// +// Copyright (C) 2015 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Utility; + +namespace GameRes.Formats.BlackRainbow +{ + internal class BmdMetaData : ImageMetaData + { + public uint PackedSize; + public int Flag; + } + + [Export(typeof(ImageFormat))] + public class BmdFormat : ImageFormat + { + public override string Tag { get { return "BMD"; } } + public override string Description { get { return "Black Rainbow bitmap format"; } } + public override uint Signature { get { return 0x444d425fu; } } // '_BMD' + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x14]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + + return new BmdMetaData + { + Width = LittleEndian.ToUInt32 (header, 8), + Height = LittleEndian.ToUInt16 (header, 12), + BPP = 32, + PackedSize = LittleEndian.ToUInt32 (header, 4), + Flag = LittleEndian.ToInt32 (header, 0x10), + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as BmdMetaData; + if (null == meta) + throw new ArgumentException ("BmdFormat.Read should be supplied with BmdMetaData", "info"); + + stream.Position = 0x14; + using (var reader = new Reader (stream, meta)) + { + PixelFormat format = meta.Flag != 0 ? PixelFormats.Bgra32 : PixelFormats.Bgr32; + reader.Unpack(); + byte[] pixels = reader.Data; + var bitmap = BitmapSource.Create ((int)meta.Width, (int)meta.Height, 96, 96, + format, null, pixels, (int)meta.Width*4); + bitmap.Freeze(); + return new ImageData (bitmap, meta); + } + } + + public override void Write (Stream file, ImageData image) + { + using (var output = new BinaryWriter (file, Encoding.ASCII, true)) + using (var writer = new Writer (image.Bitmap)) + { + writer.Pack(); + output.Write (Signature); + output.Write (writer.Size); + output.Write (image.Width); + output.Write (image.Height); + output.Write (writer.HasAlpha ? 1 : 0); + output.Write (writer.Data, 0, (int)writer.Size); + } + } + + internal class Reader : IDisposable + { + BinaryReader m_input; + byte[] m_output; + uint m_size; + + public byte[] Data { get { return m_output; } } + + public Reader (Stream file, BmdMetaData info) + { + m_input = new BinaryReader (file, Encoding.ASCII, true); + m_output = new byte[info.Width*info.Height*4]; + m_size = info.PackedSize; + } + + public void Unpack () + { + int dst = 0; + int remaining = (int)m_size; + byte[] frame = new byte[0x1000]; + int frame_pos = 0x1000-18; + while (remaining > 0) + { + int ctl = m_input.ReadByte(); + --remaining; + for (int bit = 1; remaining > 0 && bit != 0x100; bit <<= 1) + { + if (dst >= m_output.Length) + return; + if (0 != (ctl & bit)) + { + byte b = m_input.ReadByte(); + --remaining; + frame[frame_pos++] = b; + frame_pos &= 0xfff; + m_output[dst++] = b; + } + else + { + if (remaining < 2) + return; + int lo = m_input.ReadByte(); + int hi = m_input.ReadByte(); + remaining -= 2; + int offset = (hi & 0xf0) << 4 | lo; + for (int count = 3 + (hi & 0xF); count != 0; --count) + { + if (dst >= m_output.Length) + break; + byte v = frame[offset++]; + offset &= 0xfff; + frame[frame_pos++] = v; + frame_pos &= 0xfff; + m_output[dst++] = v; + } + } + } + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + { + m_input.Dispose(); + } + disposed = true; + } + } + #endregion + } + + internal class Writer : IDisposable + { + const int MinChunkSize = 3; + const int MaxChunkSize = MinChunkSize+0xf; + const int FrameSize = 0x1000; + + byte[] m_input; + MemoryStream m_output; + bool m_has_alpha = false; + byte[] m_frame = new byte[FrameSize]; + + public byte[] Data { get { return m_output.GetBuffer(); } } + public uint Size { get { return (uint)m_output.Length; } } + public bool HasAlpha { get { return m_has_alpha; } } + + public Writer (BitmapSource bitmap) + { + if (bitmap.Format != PixelFormats.Bgra32) + bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgra32, null, 0); + + m_input = new byte[bitmap.PixelWidth*bitmap.PixelHeight*4]; + bitmap.CopyPixels (m_input, bitmap.PixelWidth*4, 0); + for (int i = 3; i < m_input.Length; i += 4) + { + if (0xff != m_input[i]) + { + m_has_alpha = true; + break; + } + } + if (!m_has_alpha) + for (int i = 3; i < m_input.Length; i += 4) + m_input[i] = 0; + m_output = new MemoryStream(); + } + + public void Pack () + { + int frame_pos = 0x1000 - 18; + int src = 0; + while (src < m_input.Length) + { + int chunk_size; + int offset = FindChunk (src, out chunk_size); + if (-1 == offset) + { + PutByte (m_input[src]); + chunk_size = 1; + } + else + PutChunk (offset, chunk_size); + for (int i = 0; i < chunk_size; ++i) + { + m_frame[frame_pos++] = m_input[src++]; + frame_pos &= 0xfff; + } + } + Flush(); + } + + struct Chunk + { + public short Offset; + public byte Data; + + public Chunk (byte b) + { + Offset = -1; + Data = b; + } + + public Chunk (int offset, int count) + { + Debug.Assert (offset < 0x1000 && count >= MinChunkSize && count <= MaxChunkSize); + Offset = (short)offset; + Data = (byte)((count - MinChunkSize) & 0x0f); + } + } + + List m_queue = new List (8); + + void PutByte (byte b) + { + m_queue.Add (new Chunk (b)); + if (8 == m_queue.Count) + Flush(); + } + + void PutChunk (int offset, int size) + { + m_queue.Add (new Chunk (offset, size)); + if (8 == m_queue.Count) + Flush(); + } + + void Flush () + { + if (0 == m_queue.Count) + return; + int ctl = 0; + int bit = 1; + for (int i = 0; i < m_queue.Count; ++i) + { + if (m_queue[i].Offset < 0) + ctl |= bit; + bit <<= 1; + } + m_output.WriteByte ((byte)ctl); + for (int i = 0; i < m_queue.Count; ++i) + { + var chunk = m_queue[i]; + if (chunk.Offset >= 0) + { + byte lo = (byte)(chunk.Offset & 0xff); + byte hi = (byte)((chunk.Offset & 0xf00) >> 4); + hi |= chunk.Data; + m_output.WriteByte (lo); + m_output.WriteByte (hi); + } + else + m_output.WriteByte (chunk.Data); + } + m_queue.Clear(); + } + + private int FindChunk (int pos, out int size) + { + size = 0; + int chunk_limit = Math.Min (MaxChunkSize, m_input.Length-pos); + if (chunk_limit < MinChunkSize) + return -1; + int offset = -1; + for (int i = 0; i < m_frame.Length; ) + { + int first = Array.IndexOf (m_frame, m_input[pos], i); + if (-1 == first) + break; + int j = 1; + while (j < chunk_limit && m_frame[(first+j)&0xfff] == m_input[pos+j]) + ++j; + if (j > size && j >= MinChunkSize) + { + offset = first; + size = j; + if (chunk_limit == j) + break; + } + i = first + 1; + } + return offset; + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + { + m_output.Dispose(); + } + disposed = true; + } + } + #endregion + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index 7720a934..215e0801 100644 --- a/ArcFormats/Properties/AssemblyInfo.cs +++ b/ArcFormats/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.0.4.32")] -[assembly: AssemblyFileVersion ("1.0.4.32")] +[assembly: AssemblyVersion ("1.0.4.33")] +[assembly: AssemblyFileVersion ("1.0.4.33")]