Clean up ShpReader code.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#region Copyright & License Information
|
#region Copyright & License Information
|
||||||
/*
|
/*
|
||||||
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
|
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
|
||||||
* This file is part of OpenRA, which is free software. It is made
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
* available to you under the terms of the GNU General Public License
|
* available to you under the terms of the GNU General Public License
|
||||||
* as published by the Free Software Foundation. For more information,
|
* as published by the Free Software Foundation. For more information,
|
||||||
@@ -15,10 +15,12 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace OpenRA.FileFormats
|
namespace OpenRA.FileFormats
|
||||||
{
|
{
|
||||||
|
enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 }
|
||||||
|
|
||||||
class ImageHeader : ISpriteFrame
|
class ImageHeader : ISpriteFrame
|
||||||
{
|
{
|
||||||
public Size Size { get; private set; }
|
public Size Size { get { return reader.Size; } }
|
||||||
public Size FrameSize { get { return Size; } }
|
public Size FrameSize { get { return reader.Size; } }
|
||||||
public float2 Offset { get { return float2.Zero; } }
|
public float2 Offset { get { return float2.Zero; } }
|
||||||
public byte[] Data { get; set; }
|
public byte[] Data { get; set; }
|
||||||
|
|
||||||
@@ -29,22 +31,21 @@ namespace OpenRA.FileFormats
|
|||||||
public Format RefFormat;
|
public Format RefFormat;
|
||||||
public ImageHeader RefImage;
|
public ImageHeader RefImage;
|
||||||
|
|
||||||
|
ShpReader reader;
|
||||||
// Used by ShpWriter
|
// Used by ShpWriter
|
||||||
public ImageHeader() { }
|
public ImageHeader() { }
|
||||||
|
|
||||||
public ImageHeader(BinaryReader reader, Size size)
|
public ImageHeader(Stream stream, ShpReader reader)
|
||||||
{
|
{
|
||||||
var data = reader.ReadUInt32();
|
this.reader = reader;
|
||||||
Size = size;
|
var data = stream.ReadUInt32();
|
||||||
FileOffset = data & 0xffffff;
|
FileOffset = data & 0xffffff;
|
||||||
Format = (Format)(data >> 24);
|
Format = (Format)(data >> 24);
|
||||||
|
|
||||||
RefOffset = reader.ReadUInt16();
|
RefOffset = stream.ReadUInt16();
|
||||||
RefFormat = (Format)reader.ReadUInt16();
|
RefFormat = (Format)stream.ReadUInt16();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly int SizeOnDisk = 8;
|
|
||||||
|
|
||||||
public void WriteTo(BinaryWriter writer)
|
public void WriteTo(BinaryWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(FileOffset | ((uint)Format << 24));
|
writer.Write(FileOffset | ((uint)Format << 24));
|
||||||
@@ -53,106 +54,96 @@ namespace OpenRA.FileFormats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Format { Format20 = 0x20, Format40 = 0x40, Format80 = 0x80 }
|
|
||||||
|
|
||||||
public class ShpReader : ISpriteSource
|
public class ShpReader : ISpriteSource
|
||||||
{
|
{
|
||||||
public readonly int ImageCount;
|
|
||||||
public readonly ushort Width;
|
|
||||||
public readonly ushort Height;
|
|
||||||
|
|
||||||
public Size Size { get { return new Size(Width, Height); } }
|
|
||||||
|
|
||||||
readonly List<ImageHeader> headers = new List<ImageHeader>();
|
readonly List<ImageHeader> headers = new List<ImageHeader>();
|
||||||
public IEnumerable<ISpriteFrame> Frames { get { return headers.Cast<ISpriteFrame>(); } }
|
public IEnumerable<ISpriteFrame> Frames { get { return headers.Cast<ISpriteFrame>(); } }
|
||||||
|
public readonly Size Size;
|
||||||
|
|
||||||
int recurseDepth = 0;
|
int recurseDepth = 0;
|
||||||
|
readonly int imageCount;
|
||||||
|
|
||||||
public ShpReader(Stream stream)
|
public ShpReader(Stream stream)
|
||||||
{
|
{
|
||||||
using (var reader = new BinaryReader(stream))
|
imageCount = stream.ReadUInt16();
|
||||||
|
stream.Position += 4;
|
||||||
|
var width = stream.ReadUInt16();
|
||||||
|
var height = stream.ReadUInt16();
|
||||||
|
Size = new Size(width, height);
|
||||||
|
|
||||||
|
stream.Position += 4;
|
||||||
|
for (var i = 0; i < imageCount; i++)
|
||||||
|
headers.Add(new ImageHeader(stream, this));
|
||||||
|
|
||||||
|
// Skip eof and zero headers
|
||||||
|
stream.Position += 16;
|
||||||
|
|
||||||
|
var offsets = headers.ToDictionary(h => h.FileOffset, h => h);
|
||||||
|
for (var i = 0; i < imageCount; i++)
|
||||||
{
|
{
|
||||||
ImageCount = reader.ReadUInt16();
|
var h = headers[i];
|
||||||
reader.ReadUInt16();
|
if (h.Format == Format.Format20)
|
||||||
reader.ReadUInt16();
|
h.RefImage = headers[i - 1];
|
||||||
Width = reader.ReadUInt16();
|
|
||||||
Height = reader.ReadUInt16();
|
|
||||||
reader.ReadUInt32();
|
|
||||||
|
|
||||||
var size = new Size(Width, Height);
|
else if (h.Format == Format.Format40 && !offsets.TryGetValue(h.RefOffset, out h.RefImage))
|
||||||
for (int i = 0 ; i < ImageCount ; i++)
|
throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.FileOffset, h.RefOffset));
|
||||||
headers.Add(new ImageHeader(reader, size));
|
|
||||||
|
|
||||||
new ImageHeader(reader, size); // end-of-file header
|
|
||||||
new ImageHeader(reader, size); // all-zeroes header
|
|
||||||
|
|
||||||
var offsets = headers.ToDictionary(h => h.FileOffset, h =>h);
|
|
||||||
|
|
||||||
for (int i = 0 ; i < ImageCount ; i++)
|
|
||||||
{
|
|
||||||
var h = headers[ i ];
|
|
||||||
if (h.Format == Format.Format20)
|
|
||||||
h.RefImage = headers[i - 1];
|
|
||||||
|
|
||||||
else if (h.Format == Format.Format40)
|
|
||||||
if (!offsets.TryGetValue(h.RefOffset, out h.RefImage))
|
|
||||||
throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.FileOffset, h.RefOffset));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ImageHeader h in headers)
|
|
||||||
Decompress(stream, h);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Decompress(Stream stream, ImageHeader h)
|
foreach (var h in headers)
|
||||||
{
|
Decompress(stream, h);
|
||||||
if (recurseDepth > ImageCount)
|
|
||||||
throw new InvalidDataException("Format20/40 headers contain infinite loop");
|
|
||||||
|
|
||||||
switch(h.Format)
|
|
||||||
{
|
|
||||||
case Format.Format20:
|
|
||||||
case Format.Format40:
|
|
||||||
{
|
|
||||||
if (h.RefImage.Data == null)
|
|
||||||
{
|
|
||||||
++recurseDepth;
|
|
||||||
Decompress(stream, h.RefImage);
|
|
||||||
--recurseDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Data = CopyImageData(h.RefImage.Data);
|
|
||||||
Format40.DecodeInto(ReadCompressedData(stream, h), h.Data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Format.Format80:
|
|
||||||
{
|
|
||||||
var imageBytes = new byte[Width * Height];
|
|
||||||
Format80.DecodeInto(ReadCompressedData(stream, h), imageBytes);
|
|
||||||
h.Data = imageBytes;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new InvalidDataException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] ReadCompressedData(Stream stream, ImageHeader h)
|
static byte[] ReadCompressedData(Stream stream, ImageHeader h)
|
||||||
{
|
{
|
||||||
stream.Position = h.FileOffset;
|
stream.Position = h.FileOffset;
|
||||||
// TODO: Actually, far too big. There's no length field with the correct length though :(
|
|
||||||
var compressedLength = (int)(stream.Length - stream.Position);
|
|
||||||
|
|
||||||
var compressedBytes = new byte[ compressedLength ];
|
// Actually, far too big. There's no length field with the correct length though :(
|
||||||
stream.Read( compressedBytes, 0, compressedLength );
|
var compressedLength = (int)(stream.Length - stream.Position);
|
||||||
|
var compressedBytes = new byte[compressedLength];
|
||||||
|
stream.Read(compressedBytes, 0, compressedLength);
|
||||||
|
|
||||||
return compressedBytes;
|
return compressedBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Decompress(Stream stream, ImageHeader h)
|
||||||
|
{
|
||||||
|
if (recurseDepth > imageCount)
|
||||||
|
throw new InvalidDataException("Format20/40 headers contain infinite loop");
|
||||||
|
|
||||||
|
switch (h.Format)
|
||||||
|
{
|
||||||
|
case Format.Format20:
|
||||||
|
case Format.Format40:
|
||||||
|
{
|
||||||
|
if (h.RefImage.Data == null)
|
||||||
|
{
|
||||||
|
++recurseDepth;
|
||||||
|
Decompress(stream, h.RefImage);
|
||||||
|
--recurseDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Data = CopyImageData(h.RefImage.Data);
|
||||||
|
Format40.DecodeInto(ReadCompressedData(stream, h), h.Data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Format.Format80:
|
||||||
|
{
|
||||||
|
var imageBytes = new byte[Size.Width * Size.Height];
|
||||||
|
Format80.DecodeInto(ReadCompressedData(stream, h), imageBytes);
|
||||||
|
h.Data = imageBytes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidDataException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
byte[] CopyImageData(byte[] baseImage)
|
byte[] CopyImageData(byte[] baseImage)
|
||||||
{
|
{
|
||||||
var imageData = new byte[Width * Height];
|
var imageData = new byte[Size.Width * Size.Height];
|
||||||
for (int i = 0 ; i < Width * Height ; i++)
|
for (var i = 0; i < Size.Width * Size.Height; i++)
|
||||||
imageData[i] = baseImage[i];
|
imageData[i] = baseImage[i];
|
||||||
|
|
||||||
return imageData;
|
return imageData;
|
||||||
@@ -164,21 +155,21 @@ namespace OpenRA.FileFormats
|
|||||||
return new ShpReader(s);
|
return new ShpReader(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(Stream s, int width, int height, IEnumerable<byte[]> frames)
|
public static void Write(Stream s, Size size, IEnumerable<byte[]> frames)
|
||||||
{
|
{
|
||||||
var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray();
|
var compressedFrames = frames.Select(f => Format80.Encode(f)).ToArray();
|
||||||
|
|
||||||
// note: end-of-file and all-zeroes headers
|
// note: end-of-file and all-zeroes headers
|
||||||
var dataOffset = 14 + (compressedFrames.Length + 2) * ImageHeader.SizeOnDisk;
|
var dataOffset = 14 + (compressedFrames.Length + 2) * 8;
|
||||||
|
|
||||||
using (var bw = new BinaryWriter(s))
|
using (var bw = new BinaryWriter(s))
|
||||||
{
|
{
|
||||||
bw.Write((ushort)compressedFrames.Length);
|
bw.Write((ushort)compressedFrames.Length);
|
||||||
bw.Write((ushort)0); // unused
|
bw.Write((ushort)0);
|
||||||
bw.Write((ushort)0); // unused
|
bw.Write((ushort)0);
|
||||||
bw.Write((ushort)width);
|
bw.Write((ushort)size.Width);
|
||||||
bw.Write((ushort)height);
|
bw.Write((ushort)size.Height);
|
||||||
bw.Write((uint)0); // unused
|
bw.Write((uint)0);
|
||||||
|
|
||||||
foreach (var f in compressedFrames)
|
foreach (var f in compressedFrames)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ namespace OpenRA.Utility
|
|||||||
throw new InvalidOperationException("All frames must be the same size");
|
throw new InvalidOperationException("All frames must be the same size");
|
||||||
|
|
||||||
using (var destStream = File.Create(dest))
|
using (var destStream = File.Create(dest))
|
||||||
ShpReader.Write(destStream, size.Width, size.Height, frames.Select(f => f.ToBytes()));
|
ShpReader.Write(destStream, size, frames.Select(f => f.ToBytes()));
|
||||||
|
|
||||||
Console.WriteLine(dest + " saved.");
|
Console.WriteLine(dest + " saved.");
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ namespace OpenRA.Utility
|
|||||||
var srcImage = ShpReader.Load(args[3]);
|
var srcImage = ShpReader.Load(args[3]);
|
||||||
|
|
||||||
using (var destStream = File.Create(args[4]))
|
using (var destStream = File.Create(args[4]))
|
||||||
ShpReader.Write(destStream, srcImage.Width, srcImage.Height,
|
ShpReader.Write(destStream, srcImage.Size,
|
||||||
srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray()));
|
srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,8 +236,7 @@ namespace OpenRA.Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
using (var destStream = File.Create(args[2]))
|
using (var destStream = File.Create(args[2]))
|
||||||
ShpReader.Write(destStream, srcImage.Width, srcImage.Height,
|
ShpReader.Write(destStream, srcImage.Size, destFrames.Select(f => f.Data));
|
||||||
destFrames.Select(f => f.Data));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static string FriendlyTypeName(Type t)
|
static string FriendlyTypeName(Type t)
|
||||||
|
|||||||
Reference in New Issue
Block a user