922 lines
30 KiB
C#
922 lines
30 KiB
C#
//http://en.wikipedia.org/wiki/APNG
|
|
//From https://www.codeproject.com/Articles/36179/APNG-Viewer
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Collections;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
|
|
namespace Toolbox.Library
|
|
{
|
|
internal class APNGUtility
|
|
{
|
|
/// <summary>
|
|
/// Attempts to read count bytes of data from the supplied stream.
|
|
/// </summary>
|
|
/// <param name="stream">The stream to read from</param>
|
|
/// <param name="count">The number of bytes to read</param>
|
|
/// <returns>A byte[] containing the data or null if an error occurred</returns>
|
|
public static byte[] ReadStream(Stream stream, uint count)
|
|
{
|
|
byte[] bytes = new byte[count];
|
|
try
|
|
{
|
|
stream.Read(bytes, 0, (int)count);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
throw;
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to parse an unsigned integer value from the array of bytes
|
|
/// provided. The most significant byte of the unsigned integer is
|
|
/// parsed from the first element in the array.
|
|
/// </summary>
|
|
/// <param name="buffer">An array of bytes from which the value is to be extracted</param>
|
|
/// <param name="uintLengthInBytes">The number of bytes to parse (must be <= sizeof(uint))</param>
|
|
/// <returns>The extracted unsigned integer returned in a uint</returns>
|
|
public static uint ParseUint(byte[] buffer, int uintLengthInBytes)
|
|
{
|
|
int offset = 0;
|
|
return ParseUint(buffer, uintLengthInBytes, ref offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to parse an unsigned integer value from the array of bytes
|
|
/// provided. The most significant byte of the unsigned integer is
|
|
/// parsed from the specified offset in the array.
|
|
/// </summary>
|
|
/// <param name="buffer">An array of bytes from which the value is to be extracted</param>
|
|
/// <param name="uintLengthInBytes">The number of bytes to parse (must be <= sizeof(uint))</param>
|
|
/// <param name="offset">The offset in the array of bytes where parsing shall begin</param>
|
|
/// <returns>The extracted unsigned integer returned in a uint</returns>
|
|
public static uint ParseUint(byte[] buffer, int uintLengthInBytes, ref int offset)
|
|
{
|
|
uint value = 0;
|
|
if (uintLengthInBytes > sizeof(uint))
|
|
throw new ArgumentException(
|
|
String.Format("Function can only be used to parse up to {0} bytes from the buffer",
|
|
sizeof(uint)));
|
|
if (buffer.Length - offset < uintLengthInBytes)
|
|
throw new ArgumentException(
|
|
String.Format("buffer is not long enough to extract {0} bytes at offset {1}",
|
|
sizeof(uint), offset));
|
|
int i, j;
|
|
for (i = offset + uintLengthInBytes - 1, j = 0; i >= offset; i--, j++)
|
|
value |= (uint)(buffer[i] << (8 * j));
|
|
offset += uintLengthInBytes;
|
|
return value;
|
|
}
|
|
}
|
|
|
|
internal class APNGHeader
|
|
{
|
|
/// <summary>
|
|
/// The first 8 bytes of an APNG encoding
|
|
/// </summary>
|
|
static byte[] expectedSignature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
|
/// <summary>
|
|
/// The signature parsed from the input stream
|
|
/// </summary>
|
|
private byte[] signature;
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
public APNGHeader()
|
|
{
|
|
signature = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to read an APNG Header chunk from the supplied stream.
|
|
/// </summary>
|
|
/// <param name="stream">The stream containing the APNG Header</param>
|
|
public void Read(Stream stream)
|
|
{
|
|
// Stream must be readable
|
|
if (!stream.CanRead)
|
|
throw new ArgumentException("Stream is not readable");
|
|
|
|
// Read the signature
|
|
try
|
|
{
|
|
signature = APNGUtility.ReadStream(stream, 8);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Re-throw any exceptions
|
|
throw;
|
|
}
|
|
|
|
// Test signature for validity
|
|
if (signature.Length == expectedSignature.Length)
|
|
{
|
|
for (int i = 0; i < expectedSignature.Length; i++)
|
|
{
|
|
// Invalid signature
|
|
if (expectedSignature[i] != signature[i])
|
|
throw new ApplicationException("APNG signature not found.");
|
|
}
|
|
}
|
|
else
|
|
// Invalid signature
|
|
throw new ApplicationException("APNG signature not found.");
|
|
}
|
|
}
|
|
|
|
internal class MENDChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "MEND";
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public MENDChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class TERMChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "TERM";
|
|
|
|
private uint terminationAction;
|
|
private uint actionAfterTermination;
|
|
private uint delay;
|
|
private uint iterationMax;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public TERMChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts various fields specific to this chunk from the APNG's
|
|
/// data field
|
|
/// </summary>
|
|
/// <param name="chunkData">An array of bytes representing the APNG's data field</param>
|
|
protected override void ParseData(byte[] chunkData)
|
|
{
|
|
int offset = 0;
|
|
terminationAction = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
// If the data length is > 1 then read 9 more bytes
|
|
if (chunkData.Length > 1)
|
|
{
|
|
actionAfterTermination = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
delay = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
iterationMax = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class BKGDChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "bKGD";
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public BKGDChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class BACKChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "BACK";
|
|
|
|
private uint redBackground;
|
|
private uint greenBackground;
|
|
private uint blueBackground;
|
|
private uint mandatoryBackground;
|
|
private uint backgroundImageId;
|
|
private uint backgroundTiling;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public BACKChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts various fields specific to this chunk from the APNG's
|
|
/// data field
|
|
/// </summary>
|
|
/// <param name="chunkData">An array of bytes representing the APNG's data field</param>
|
|
protected override void ParseData(byte[] chunkData)
|
|
{
|
|
int offset = 0;
|
|
redBackground = APNGUtility.ParseUint(chunkData, 2, ref offset);
|
|
greenBackground = APNGUtility.ParseUint(chunkData, 2, ref offset);
|
|
blueBackground = APNGUtility.ParseUint(chunkData, 2, ref offset);
|
|
|
|
// If the data length is > 6 then read 1 more byte
|
|
if (chunkData.Length > 6)
|
|
{
|
|
mandatoryBackground = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
}
|
|
// If the data length is > 7 then read 2 more bytes
|
|
if (chunkData.Length > 7)
|
|
{
|
|
backgroundImageId = APNGUtility.ParseUint(chunkData, 2, ref offset);
|
|
}
|
|
// If the data length is > 9 then read 1 more byte
|
|
if (chunkData.Length > 9)
|
|
{
|
|
backgroundTiling = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class IHDRChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "IHDR";
|
|
|
|
private uint width;
|
|
private uint height;
|
|
private uint bitDepth;
|
|
private uint colorType;
|
|
private uint compressionMethod;
|
|
private uint filterMethod;
|
|
private uint interlaceMethod;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public IHDRChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts various fields specific to this chunk from the APNG's
|
|
/// data field
|
|
/// </summary>
|
|
/// <param name="chunkData">An array of bytes representing the APNG's data field</param>
|
|
protected override void ParseData(byte[] chunkData)
|
|
{
|
|
int offset = 0;
|
|
width = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
height = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
bitDepth = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
colorType = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
compressionMethod = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
filterMethod = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
interlaceMethod = APNGUtility.ParseUint(chunkData, 1, ref offset);
|
|
}
|
|
}
|
|
|
|
internal class fcTLChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "fcTL";
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public fcTLChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
|
|
public bool IsEmpty()
|
|
{
|
|
return ChunkLength == 0;
|
|
}
|
|
}
|
|
|
|
internal class IENDChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "IEND";
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public IENDChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class IDATChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "IDAT";
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public IDATChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class fdATChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "fdAT";
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public fdATChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
}
|
|
|
|
internal class acTLChunk : APNGChunk
|
|
{
|
|
/// <summary>
|
|
/// The ASCII name of the APNG chunk
|
|
/// </summary>
|
|
public const String NAME = "acTL";
|
|
|
|
/// <summary>
|
|
/// The APNG frame width in pixels
|
|
/// </summary>
|
|
private uint frameWidth;
|
|
/// <summary>
|
|
/// The APNG frame height in pixels
|
|
/// </summary>
|
|
private uint frameHeight;
|
|
///// <summary>
|
|
///// The APNG frame rate
|
|
///// </summary>
|
|
//private uint ticksPerSecond;
|
|
///// <summary>
|
|
///// The APNG layer count
|
|
///// </summary>
|
|
//private uint nominalLayerCount;
|
|
///// <summary>
|
|
///// The APNG frame count
|
|
///// </summary>
|
|
//private uint nominalFrameCount;
|
|
///// <summary>
|
|
///// The APNG play time
|
|
///// </summary>
|
|
//private uint nominalPlayTime;
|
|
///// <summary>
|
|
///// The APNG simplicity profile
|
|
///// </summary>
|
|
//private uint simplicityProfile;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="chunk">The APNG chunk containing the data for this specific chunk</param>
|
|
public acTLChunk(APNGChunk chunk)
|
|
: base(chunk, NAME)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// The APNG width in pixels
|
|
/// </summary>
|
|
public uint FrameWidth
|
|
{
|
|
get { return frameWidth; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The APNG height in pixels
|
|
/// </summary>
|
|
public uint FrameHeight
|
|
{
|
|
get { return frameHeight; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts various fields specific to this chunk from the APNG's
|
|
/// data field
|
|
/// </summary>
|
|
/// <param name="chunkData">An array of bytes representing the APNG's data field</param>
|
|
protected override void ParseData(byte[] chunkData)
|
|
{
|
|
int offset = 0;
|
|
frameWidth = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
frameHeight = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
//ticksPerSecond = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
//nominalLayerCount = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
//nominalFrameCount = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
//nominalPlayTime = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
//simplicityProfile = APNGUtility.ParseUint(chunkData, 4, ref offset);
|
|
}
|
|
}
|
|
|
|
internal class APNGChunk
|
|
{
|
|
protected String error;
|
|
protected byte[] chunkLength;
|
|
protected byte[] chunkType;
|
|
protected byte[] chunkData;
|
|
protected byte[] chunkCRC;
|
|
protected uint calculatedCRC;
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
public APNGChunk()
|
|
{
|
|
chunkLength = chunkType = chunkData = chunkCRC = null;
|
|
error = "No Error";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor which takes an existing APNGChunk object and
|
|
/// verifies that its type matches that which is expected
|
|
/// </summary>
|
|
/// <param name="chunk">The APNGChunk to copy</param>
|
|
/// <param name="expectedType">The input APNGChunk expected type</param>
|
|
public APNGChunk(APNGChunk chunk, String expectedType)
|
|
{
|
|
// Copy the existing chunks members
|
|
chunkLength = chunk.chunkLength;
|
|
chunkType = chunk.chunkType;
|
|
chunkData = chunk.chunkData;
|
|
chunkCRC = chunk.chunkCRC;
|
|
|
|
// Verify the chunk type is as expected
|
|
if (ChunkType != expectedType)
|
|
throw new ArgumentException(
|
|
String.Format("Specified chunk type is not {0} as expected", expectedType));
|
|
|
|
// Parse the chunk's data
|
|
ParseData(chunkData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts various fields specific to this chunk from the APNG's
|
|
/// data field
|
|
/// </summary>
|
|
/// <param name="chunkData">An array of bytes representing the APNG's data field</param>
|
|
protected virtual void ParseData(byte[] chunkData)
|
|
{
|
|
// Nothing specific to do here. Derived classes can override this
|
|
// to do specific field parsing.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the array of bytes which make up the APNG chunk. This includes:
|
|
/// o 4 bytes of the chunk's length
|
|
/// o 4 bytes of the chunk's type
|
|
/// o N bytes of the chunk's data
|
|
/// o 4 bytes of the chunk's CRC
|
|
/// </summary>
|
|
public byte[] Chunk
|
|
{
|
|
get
|
|
{
|
|
byte[] ba = new byte[chunkLength.Length +
|
|
chunkType.Length + chunkData.Length +
|
|
chunkCRC.Length];
|
|
chunkLength.CopyTo(ba, 0);
|
|
chunkType.CopyTo(ba, chunkLength.Length);
|
|
chunkData.CopyTo(ba, chunkLength.Length + chunkType.Length);
|
|
chunkCRC.CopyTo(ba, chunkLength.Length + chunkType.Length + chunkData.Length);
|
|
return ba;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the array of bytes which make up the chunk's data field
|
|
/// </summary>
|
|
public byte[] ChunkData
|
|
{
|
|
get
|
|
{
|
|
return chunkData;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets chunk's type field as an string
|
|
/// </summary>
|
|
public String ChunkType
|
|
{
|
|
get
|
|
{
|
|
return new String(new char[] { (char)chunkType[0], (char)chunkType[1], (char)chunkType[2], (char)chunkType[3] });
|
|
}
|
|
set
|
|
{
|
|
chunkType = Encoding.ASCII.GetBytes(value);
|
|
|
|
byte[] newChunkData = new byte[chunkData.Length - 4];
|
|
Array.Copy(chunkData, 4, newChunkData, 0, newChunkData.Length);
|
|
chunkData = newChunkData;
|
|
|
|
chunkLength = BitConverter.GetBytes(chunkData.Length);
|
|
Array.Reverse(chunkLength);
|
|
|
|
uint crc = CRC.INITIAL_CRC;
|
|
crc = CRC.UpdateCRC(crc, chunkType);
|
|
crc = CRC.UpdateCRC(crc, chunkData);
|
|
// CRC is inverted
|
|
crc = ~crc;
|
|
byte[] array = BitConverter.GetBytes(crc);
|
|
Array.Reverse(array);
|
|
chunkCRC = array;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the length field of the chunk
|
|
/// </summary>
|
|
public uint ChunkLength
|
|
{
|
|
get
|
|
{
|
|
return APNGUtility.ParseUint(chunkLength, chunkLength.Length);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the CRC field of the chunk
|
|
/// </summary>
|
|
public uint ChunkCRC
|
|
{
|
|
get
|
|
{
|
|
return APNGUtility.ParseUint(chunkCRC, chunkCRC.Length);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to parse an APNGChunk for the specified stream
|
|
/// </summary>
|
|
/// <param name="stream">The stream containing the APNG Chunk</param>
|
|
public void Read(Stream stream)
|
|
{
|
|
if (!stream.CanRead)
|
|
throw new ArgumentException("Stream is not readable");
|
|
|
|
calculatedCRC = CRC.INITIAL_CRC;
|
|
|
|
long chunkStart = stream.Position;
|
|
|
|
// Read the data Length
|
|
chunkLength = APNGUtility.ReadStream(stream, 4);
|
|
|
|
// Read the chunk type
|
|
chunkType = APNGUtility.ReadStream(stream, 4);
|
|
calculatedCRC = CRC.UpdateCRC(calculatedCRC, chunkType);
|
|
|
|
// Read the data
|
|
chunkData = APNGUtility.ReadStream(stream, ChunkLength);
|
|
calculatedCRC = CRC.UpdateCRC(calculatedCRC, chunkData);
|
|
|
|
// Read the CRC
|
|
chunkCRC = APNGUtility.ReadStream(stream, 4);
|
|
|
|
// CRC is inverted
|
|
calculatedCRC = ~calculatedCRC;
|
|
|
|
// Verify the CRC
|
|
if (ChunkCRC != calculatedCRC)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.AppendLine(String.Format("APNG Chunk CRC Mismatch. Chunk CRC = {0}, Calculated CRC = {1}.",
|
|
ChunkCRC, calculatedCRC));
|
|
sb.AppendLine(String.Format("This occurred while parsing the chunk at position {0} (0x{1:X8}) in the stream.",
|
|
chunkStart, chunkStart));
|
|
throw new ApplicationException(sb.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class PNG
|
|
{
|
|
/// <summary>
|
|
/// The PNG file signature
|
|
/// </summary>
|
|
private static byte[] header = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
|
/// <summary>
|
|
/// The PNG file's IHDR chunk
|
|
/// </summary>
|
|
private IHDRChunk ihdr;
|
|
/// <summary>
|
|
/// The PNG file's PLTE chunk (optional)
|
|
/// </summary>
|
|
private fcTLChunk plte;
|
|
/// <summary>
|
|
/// The PNG file's IDAT chunks
|
|
/// </summary>
|
|
private List<IDATChunk> idats;
|
|
/// <summary>
|
|
/// The PNG file's IEND chunk
|
|
/// </summary>
|
|
private IENDChunk iend;
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
public PNG()
|
|
{
|
|
idats = new List<IDATChunk>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the chunks making up the PNG into a single MemoryStream which
|
|
/// is suitable for writing to a PNG file or creating a Image object using
|
|
/// Bitmap.FromStream
|
|
/// </summary>
|
|
/// <returns>MemoryStream</returns>
|
|
public MemoryStream ToStream()
|
|
{
|
|
MemoryStream ms = new MemoryStream();
|
|
ms.Write(header, 0, header.Length);
|
|
ms.Write(ihdr.Chunk, 0, ihdr.Chunk.Length);
|
|
foreach (IDATChunk chunk in idats)
|
|
ms.Write(chunk.Chunk, 0, chunk.Chunk.Length);
|
|
ms.Write(iend.Chunk, 0, iend.Chunk.Length);
|
|
return ms;
|
|
}
|
|
|
|
public void SaveFile(string FileName)
|
|
{
|
|
using (FileStream writer = new FileStream(FileName, FileMode.Create))
|
|
{
|
|
MemoryStream mem = ToStream();
|
|
mem.Position = 0;
|
|
byte[] content = new byte[mem.Length];
|
|
mem.Read(content, 0, content.Length);
|
|
writer.Write(content, 0, content.Length);
|
|
writer.Close();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or Sets the PNG's IHDR chunk
|
|
/// </summary>
|
|
public IHDRChunk IHDR
|
|
{
|
|
get { return ihdr; }
|
|
set { ihdr = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or Sets the PNG's PLTE chunk
|
|
/// </summary>
|
|
public fcTLChunk FCTL
|
|
{
|
|
get { return plte; }
|
|
set { plte = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or Sets the PNG's IEND chunk
|
|
/// </summary>
|
|
public IENDChunk IEND
|
|
{
|
|
get { return iend; }
|
|
set { iend = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the list of IDAT chunk's making up the PNG
|
|
/// </summary>
|
|
public List<IDATChunk> IDATS
|
|
{
|
|
get { return idats; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the assigned IDAT chunk to the end of the PNG's list of IDAT chunks
|
|
/// </summary>
|
|
public IDATChunk IDAT
|
|
{
|
|
set { idats.Add(value); }
|
|
}
|
|
}
|
|
|
|
public class APNG
|
|
{
|
|
/// <summary>
|
|
/// List of chunks in the APNG
|
|
/// </summary>
|
|
List<APNGChunk> chunks;
|
|
/// <summary>
|
|
/// List of PNGs embedded in the APNG
|
|
/// </summary>
|
|
List<PNG> pngs;
|
|
/// <summary>
|
|
/// The APNG's MHDRChunk
|
|
/// </summary>
|
|
acTLChunk headerChunk;
|
|
|
|
IHDRChunk ihdrChunk;
|
|
|
|
/// <summary>
|
|
/// Gets the number of embedded PNGs within the APNG
|
|
/// </summary>
|
|
public int NumEmbeddedPNG
|
|
{
|
|
get { return pngs.Count; }
|
|
}
|
|
|
|
public Bitmap this[int Index]
|
|
{
|
|
get
|
|
{
|
|
return ToBitmap(Index);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Bitmap object containing the embedded PNG at the specified
|
|
/// index in the APNG's list of embedded PNGs
|
|
/// </summary>
|
|
/// <param name="index">The embedded PNG index</param>
|
|
/// <returns>Bitmap</returns>
|
|
public Bitmap ToBitmap(int index)
|
|
{
|
|
// Verify the index
|
|
if (index > NumEmbeddedPNG)
|
|
throw new ArgumentException(String.Format(
|
|
"Embedded PNG index must be between 0 and {0}", NumEmbeddedPNG - 1));
|
|
// Create the bitmap
|
|
Bitmap b = (Bitmap)Bitmap.FromStream(pngs[index].ToStream());
|
|
return b;
|
|
}
|
|
|
|
#if (DEBUG)
|
|
public void SaveFile(int index, string FileName)
|
|
{
|
|
// Verify the index
|
|
if (index > NumEmbeddedPNG)
|
|
throw new ArgumentException(String.Format(
|
|
"Embedded PNG index must be between 0 and {0}", NumEmbeddedPNG - 1));
|
|
pngs[index].SaveFile(FileName);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Creates a string containing the names of all the chunks in the APNG
|
|
/// </summary>
|
|
/// <returns>String</returns>
|
|
public override string ToString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
foreach (APNGChunk chunk in chunks)
|
|
sb.AppendLine(chunk.ChunkType);
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to load an APNG from the specified file name
|
|
/// </summary>
|
|
/// <param name="filename">Name of the APNG file to load</param>
|
|
public void Load(string filename)
|
|
{
|
|
chunks = new List<APNGChunk>();
|
|
pngs = new List<PNG>();
|
|
|
|
// Open the file for reading
|
|
Stream stream = File.OpenRead(filename);
|
|
|
|
// Create a new header (should be 1 per file) and
|
|
// read it from the stream
|
|
APNGHeader header = new APNGHeader();
|
|
try
|
|
{
|
|
header.Read(stream);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
stream.Close();
|
|
throw;
|
|
}
|
|
|
|
APNGChunk chunk;
|
|
PNG png = null;
|
|
|
|
// Read chunks from the stream until we reach the MEND chunk
|
|
do
|
|
{
|
|
// Read a generic Chunk
|
|
chunk = new APNGChunk();
|
|
try
|
|
{
|
|
chunk.Read(stream);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
stream.Close();
|
|
throw;
|
|
}
|
|
|
|
// Take a look at the chunk type and decide what derived class to
|
|
// use to create a specific chunk
|
|
switch (chunk.ChunkType)
|
|
{
|
|
case acTLChunk.NAME:
|
|
if (headerChunk != null)
|
|
throw new ApplicationException(String.Format(
|
|
"Only one chunk of type {0} is allowed", chunk.ChunkType));
|
|
chunk = headerChunk = new acTLChunk(chunk);
|
|
break;
|
|
case MENDChunk.NAME:
|
|
chunk = new MENDChunk(chunk);
|
|
break;
|
|
case TERMChunk.NAME:
|
|
chunk = new TERMChunk(chunk);
|
|
break;
|
|
case BACKChunk.NAME:
|
|
chunk = new BACKChunk(chunk);
|
|
break;
|
|
case BKGDChunk.NAME:
|
|
chunk = new BKGDChunk(chunk);
|
|
break;
|
|
case fcTLChunk.NAME:
|
|
// This is the beginning of a new embedded PNG
|
|
chunk = new fcTLChunk(chunk);
|
|
png = new PNG();
|
|
png.FCTL = chunk as fcTLChunk;
|
|
png.IHDR = ihdrChunk;
|
|
pngs.Add(png);
|
|
break;
|
|
case IHDRChunk.NAME:
|
|
chunk = new IHDRChunk(chunk);
|
|
ihdrChunk = chunk as IHDRChunk;
|
|
break;
|
|
case IDATChunk.NAME:
|
|
chunk = new IDATChunk(chunk);
|
|
if (png != null)
|
|
{
|
|
png.IDAT = chunk as IDATChunk;
|
|
}
|
|
break;
|
|
case fdATChunk.NAME:
|
|
chunk = new fdATChunk(chunk);
|
|
if (png != null)
|
|
{
|
|
chunk.ChunkType = IDATChunk.NAME;
|
|
png.IDAT = new IDATChunk(chunk);
|
|
}
|
|
break;
|
|
case IENDChunk.NAME:
|
|
chunk = new IENDChunk(chunk);
|
|
for (int i = 0; i < pngs.Count; i++)
|
|
{
|
|
pngs[i].IEND = chunk as IENDChunk;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// Add the chunk to our list of chunks
|
|
chunks.Add(chunk);
|
|
}
|
|
while (chunk.ChunkType != IENDChunk.NAME);
|
|
}
|
|
}
|
|
}
|