d1f03b161f
Rework UI from scratch with proper themes and custom controls. MDI windows are now used for workspaces, comparing docs, and multiple usages. Tabs organise multiple workspaces and you can keep mdi windows maximized if you want to only use tabs. Themes currently include dark and white theme but plan to have XML files with list of color and styles Alot of things optimized. UI is very fast and snappy now Dae rigging fixed. Dae bones can be imported. Dae with textures can be imported and exported to a folder Custom sampler editor for sampler data. Texture refs, shader options, params, render info, and basically all material data can be added/removed and edited User data editor Update opengl framework by JuPaHe64 to the newest. Includes an origintation cube, multiple models in a scene, and many improvements Skeleton can be viewed GFPAK with some fixes in saving NUTEXB has proper mip map viewing PTCL Editor (Wii U and Switch). Can edit colors ( Wii U) and view textures. Also EFFN files in smash ultimate can be previewed Files can be associated with the program and opened with on clicking them ASTC textures can be viewed UVs can be viewed. Includes wrap modes and also translating and scaling for some basic edits Textures use a new editor. It includes channel viewing and some new editing options Fixed black textures on some wii u bfres Fixed saving sarcs in sarcs Shortcut keys have been added in. CTRL + S can save the active file in the currently used window Fix more issues with bfres crashing File - New includes BNTX for creating new bntx files from scatch Raw shader binaries can be extracted from bnsh and bfsha. Yuzu and Ryujinx can decompile these Sharc files can have source data previewed and shader programs in XML Aamp v1 and v2 data can be previewed. v1 can be edited and saved atm, v2 will be at a later update Byaml uses it's own editor instead of a seperate window for easy saving within sarcs Archives have a hex viewer Dae exporting greatly improved and can export rigged meshes Scene, shader param, srt, color, and texture pattern animations can all be previewed (in a list) Memory usage is greatly improved Narc (Nitro Archives) can be viewed and extracted. Fixed importing TGA images Support importing ASTC textures for bntx Added in PBR lighting for bfres from my implimentaion in forge Added gradient background for viewport. This can be edited in the settings Added skybox background option for viewport. Can load cubemaps Added grid with customizable cells for viewport. DDS decompression no longer requires Direct X tex. Zlib decompression has been improved for opening files that use it Rigid bones are properly ordered on importing a mesh. May fix some exploding issues. Endianness for KCL can be toggled for saving. Will be set to what it was using orignally Tangents can be filled with a constant value. Will allow them to not cause seams nor flat lighting however normal maps may not work as good Vertex buffers can be added and removed. Also re encoded Parameters now use drop down panels with values for easier editing Reworked the bone editor. Everything for a bone can be fully edited now besides the index, billboard index and parent index which get set automatically Fixed animation scaling for skeletal animations finally! Textures can be loaded in a tab now with thumbnail displaying for easy real time edits while previewing in the viewport Fixed support for audio files to be big endian in BARS Textures for switch now use their own folder. You can easily add textures to this and add textures to bfres that have no bntx. If there are no textures then the bfres will automatically not have one on save. Animations are split into multiple sub sections for switch's material animation for easier access Bfres for wii u has better binary exporting and is fully compatiable with Wexos Toolbox (to and from) Every section can be added in as new for both wii u and switch. Every section can be renamed properly and mostly everything can be edited. (Key frame editing and a more in depth curve editor later) Added option to copy UV channel Bone weights can be previewed Tons of fixes for the switch bfres library with more games working. Splatoon 2 (more work now), BOTW, Kirby Star Allies, and more! Fixed 3.3 Wii U bfres from not opening Wii U Sharcfb files can have shader program data previewed (XML) And possibly alot more things i missed! All this is still experimental but will improve over the next few weeks
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 Switch_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);
|
|
}
|
|
}
|
|
}
|