Clean up ShpReader code.

This commit is contained in:
Paul Chote
2013-11-30 10:00:56 +13:00
parent ed163aea82
commit 3f2be59056
2 changed files with 86 additions and 96 deletions

View File

@@ -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)
{ {

View File

@@ -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)