/// <summary> /// Attempts to read 25 bytes of the stream. /// </summary> internal static IhdrChunk Read(Stream stream) { var chunk = new IhdrChunk { Length = BitHelper.ConvertEndian(stream.ReadUInt32()), //Chunk length, 4 bytes. ChunkType = Encoding.ASCII.GetString(stream.ReadBytes(4)) //Chunk type, 4 bytes. }; if (chunk.ChunkType != "IHDR") { throw new Exception("Missing IHDR chunk."); } //var pos = stream.Position; //chunk.ChunkData = stream.ReadBytes(chunk.Length); //stream.Position = pos; //Chunk details + CRC, 13 bytes + 4 bytes. chunk.Width = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.Height = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.BitDepth = (byte)stream.ReadByte(); chunk.ColorType = (byte)stream.ReadByte(); chunk.CompressionMethod = (byte)stream.ReadByte(); chunk.FilterMethod = (byte)stream.ReadByte(); chunk.InterlaceMethod = (byte)stream.ReadByte(); chunk.Crc = BitHelper.ConvertEndian(stream.ReadUInt32()); return(chunk); }
private static IEnumerable <byte[]> GetData(Stream ms) { ms.Position = 8 + 25; var list = new List <byte[]>(); while (ms.CanRead) { var length = BitHelper.ConvertEndian(ms.ReadUInt32()); var chunkType = Encoding.ASCII.GetString(ms.ReadBytes(4)); var data = ms.ReadBytes(length); if (chunkType == "IDAT") { list.Add(data); } if (chunkType == "IEND") { break; } ms.ReadUInt32(); } return(list); }
internal void Write(Stream stream) { stream.WriteUInt32(BitHelper.ConvertEndian(Length)); //4 bytes. stream.WriteBytes(Encoding.ASCII.GetBytes(ChunkType)); //4 bytes. stream.WriteBytes(ChunkData); //XX bytes. stream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(stream.PeekBytes(stream.Position - (Length + 4), (int)Length + 4)))); //CRC, 4 bytes. }
/// <summary> /// Attempts to read 16 bytes of the stream. /// </summary> internal static ActlChunk Read(Stream stream) { var chunk = new ActlChunk { Length = BitHelper.ConvertEndian(stream.ReadUInt32()), //Chunk length, 4 bytes. ChunkType = Encoding.ASCII.GetString(stream.ReadBytes(4)) //Chunk type, 4 bytes. }; //If the second chunk is not the animation control (acTL), it means that this is a normal PNG. if (chunk.ChunkType != "acTL") { return(null); } //var pos = stream.Position; //chunk.ChunkData = stream.ReadBytes(chunk.Length); //stream.Position = pos; //Chunk details + CRC, 8 bytes + 4 bytes. chunk.NumFrames = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.NumPlays = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.Crc = BitHelper.ConvertEndian(stream.ReadUInt32()); return(chunk); }
public void Dispose() { //Write down the image bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)0)); //Global mask information size, 4 bytes. //Aditional info. //Precomposed image data? }
public void Dispose() { //IEND: The end of the Png datastream. 0 bytes (Length + Type + CRC, 4 bytes each) = 12 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //Length, 4 bytes. InternalStream.WriteBytes(Encoding.ASCII.GetBytes("IEND")); //Chunk type, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(InternalStream.PeekBytes(InternalStream.Position - 4, 4)))); //CRC, 4 bytes. InternalStream.Flush(); //Resets the stream position to save afterwards. InternalStream.Position = 0; }
internal void Write(Stream stream, bool writeAsIdat = true) { stream.WriteUInt32(BitHelper.ConvertEndian(Length)); //4 bytes. stream.WriteBytes(Encoding.ASCII.GetBytes(writeAsIdat ? "IDAT" : ChunkType)); //4 bytes. if (!writeAsIdat) { stream.WriteUInt32(BitHelper.ConvertEndian(SequenceNumber)); //4 bytes. } stream.WriteBytes(FrameData); //XX bytes. stream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(stream.PeekBytes(stream.Position - (Length + (writeAsIdat ? 4 : 8)), (int)Length + (writeAsIdat ? 4 : 8))))); //CRC, 4 bytes. }
/// <summary> /// Write the IHDR chunk to the stream. /// If a custom size is given, that's what is written. /// </summary> internal void Write(Stream stream, uint?width = null, uint?height = null) { stream.WriteUInt32(BitHelper.ConvertEndian(Length)); //4 bytes. stream.WriteBytes(Encoding.ASCII.GetBytes(ChunkType)); //4 bytes. stream.WriteUInt32(BitHelper.ConvertEndian(width ?? Width)); //4 bytes. stream.WriteUInt32(BitHelper.ConvertEndian(height ?? Height)); //4 bytes. stream.WriteByte(BitDepth); //1 byte. stream.WriteByte(ColorType); //1 byte. stream.WriteByte(CompressionMethod); //1 byte. stream.WriteByte(FilterMethod); //1 byte. stream.WriteByte(InterlaceMethod); //1 byte. stream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(stream.PeekBytes(stream.Position - (Length + 4), (int)Length + 4)))); //CRC, 4 bytes. }
/// <summary> /// Attempts to read XX bytes of the stream. /// </summary> internal static Chunk Read(Stream stream, int sequence) { var chunk = new Chunk { MasterSequence = sequence, Length = BitHelper.ConvertEndian(stream.ReadUInt32()), //Chunk length, 4 bytes. ChunkType = Encoding.ASCII.GetString(stream.ReadBytes(4)) //Chunk type, 4 bytes. }; //Chunk details + CRC, XX bytes + 4 bytes. chunk.ChunkData = stream.ReadBytes(chunk.Length); chunk.Crc = BitHelper.ConvertEndian(stream.ReadUInt32()); return(chunk); }
/// <summary> /// Attempts to read XX bytes of the stream. /// </summary> internal static FdatChunk Read(uint length, byte[] array) { var chunk = new FdatChunk { Length = length, //Chunk length, 4 bytes. ChunkType = "fdAT" //Chunk type, 4 bytes. }; using (var stream = new MemoryStream(array)) { //Chunk details, 4 bytes + XX bytes. chunk.SequenceNumber = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.FrameData = stream.ReadBytes(length - 4); //Minus 4 because that's the size of the sequence number. } return(chunk); }
/// <summary> /// Attempts to read 26 bytes of the stream. /// </summary> internal static FctlChunk Read(uint length, byte[] array) { var chunk = new FctlChunk { Length = length, //Chunk length, 4 bytes. ChunkType = "fcTL" //Chunk type, 4 bytes. }; using (var stream = new MemoryStream(array)) { //Chunk details, 26 bytes. chunk.SequenceNumber = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.Width = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.Height = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.XOffset = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.YOffset = BitHelper.ConvertEndian(stream.ReadUInt32()); chunk.DelayNum = BitHelper.ConvertEndian(stream.ReadUInt16()); chunk.DelayDen = BitHelper.ConvertEndian(stream.ReadUInt16()); chunk.DisposeOp = (Apng.DisposeOps)stream.ReadByte(); chunk.BlendOp = (Apng.BlendOps)stream.ReadByte(); } return(chunk); }
internal void Encode() { //Psd Header: 26 bytes. InternalStream.WriteBytes(Encoding.ASCII.GetBytes("8BPS")); //Chunk type, 4 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)1)); //File version, 1 - PSD, 2 - PSB, 2 bytes. InternalStream.Position += 6; //Must be zero, 6 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)4)); //Number of channels, ARGB, 2 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)Height)); //Height of the image, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)Width)); //Width of the image, 4 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)8)); //Number of bits per channel, 2 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)3)); //The color mode of the file, 3 - RGB, 2 bytes. //Color mode data. 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //The size of the color mode data block, 0 bytes for RGB mode, 4 bytes. //Image resources. XX bytes. InternalStream.WriteBytes(ImageResources.Content); //LayerAndMaskInformation. 4 + XX bytes. var layerAndMask = LayerAndMask.Content; InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)layerAndMask.Length)); //Length of the LayerAndMask block, 4 bytes. InternalStream.WriteBytes(layerAndMask); //Content of the LayerAndMask block. //ImageData. XX bytes. if (Compress) { InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)1)); //The type of encoding, PackBit/RLE, 2 bytes. foreach (var layer in LayerAndMask.LayerInfo.ImageChannelDataList) { //Writes all byte counts for all the scan lines (rows * channels), with each count stored as a two-byte value. foreach (var channel in layer.ChannelList) { foreach (var b in channel.RleCompressedContent) { InternalStream.WriteInt16(BitHelper.ConvertEndian((short)b.Length)); } } //Writes down each layer, in planar order: AAA RRR GGG BBB. foreach (var channel in layer.ChannelList) { foreach (var b in channel.RleCompressedContent) { InternalStream.WriteBytes(b); } } break; } } else { InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)0)); //The type of encoding, Raw data, 2 bytes. foreach (var layer in LayerAndMask.LayerInfo.ImageChannelDataList) { //Writes down each layer, in planar order: AAA RRR GGG BBB. foreach (var channel in layer.ChannelList) { InternalStream.WriteBytes(channel.RawContent); } break; } } }
internal ApngFrame GetFrame(int index) { //Build each frame using: //Starting blocks: IHDR, tIME, zTXt, tEXt, iTXt, pHYs, sPLT, (iCCP | sRGB), sBIT, gAMA, cHRM, PLTE, tRNS, hIST, bKGD. //Image data: IDAT. //End block: IEND. var chunks = Chunks.Where(w => w.FrameGroupId == index).ToList(); var otherChunks = Chunks.Where(w => w.FrameGroupId == -1 && w.ChunkType != "IDAT").ToList(); if (!chunks.Any()) { return(null); } var frame = new ApngFrame(); //First frame • Second frame //Default image is part of the animation: fcTL + IDAT • fcTL + fdAT //Default image isn't part of the animation: IDAT • fcTL + fdAT if (chunks[0].ChunkType == "fcTL") { var fctl = FctlChunk.Read(chunks[0].Length, chunks[0].ChunkData); frame.Delay = fctl.DelayNum == 0 ? 10 : (int)(fctl.DelayNum / (fctl.DelayDen == 0 ? 100d : fctl.DelayDen) * 1000d); frame.Width = fctl.Width; frame.Height = fctl.Height; frame.Left = fctl.XOffset; frame.Top = fctl.YOffset; frame.ColorType = Ihdr.ColorType; frame.BitDepth = Ihdr.BitDepth; frame.DisposeOp = fctl.DisposeOp; frame.BlendOp = fctl.BlendOp; using (var stream = new MemoryStream()) { //Png signature, 8 bytes. stream.WriteBytes(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }); //Image header chunk. 25 bytes. Ihdr.Write(stream, fctl.Width, fctl.Height); //Any other auxiliar chunks. foreach (var other in otherChunks) { other.Write(stream); } //Frame has multiple chunks. if (chunks.Count > 2) { var datas = new List <byte[]>(); //Data chunks. for (var i = 1; i < chunks.Count; i++) { switch (chunks[i].ChunkType) { case "fdAT": { var fdat = FdatChunk.Read(chunks[i].Length, chunks[i].ChunkData); datas.Add(fdat.FrameData); break; } case "IDAT": { var idat = IdatChunk.Read(chunks[i].Length, chunks[i].ChunkData); datas.Add(idat.FrameData); break; } } } //Write combined frame data. var length = datas.Sum(s => s.Length); stream.WriteUInt32(BitHelper.ConvertEndian((uint)length)); //4 bytes. stream.WriteBytes(Encoding.ASCII.GetBytes("IDAT")); //4 bytes. stream.WriteBytes(datas.SelectMany(s => s).ToArray()); //XX bytes. stream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(stream.PeekBytes(stream.Position - (length + 4), length + 4)))); //CRC, 4 bytes. } else { switch (chunks[1].ChunkType) { case "fdAT": { var fdat = FdatChunk.Read(chunks[1].Length, chunks[1].ChunkData); fdat.Write(stream); break; } case "IDAT": { var idat = IdatChunk.Read(chunks[1].Length, chunks[1].ChunkData); idat.Write(stream); break; } } } //End chunk. stream.WriteUInt32(BitHelper.ConvertEndian(0u)); //Chunk length, 4 bytes. stream.WriteBytes(Encoding.ASCII.GetBytes("IEND")); //Chunk type, 4 bytes. stream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(stream.PeekBytes(stream.Position - 4, 4)))); //CRC, 4 bytes. //Gets the whole Png. frame.ImageData = stream.ToArray(); } } else { //This is not supposed to happen. //All chunks with an FrameGroupId are grouped with a starting fcTL, ending with a IDAT or fdAT chunk. LogWriter.Log(new Exception("Missing fcTL on frame number " + index), $"It was not possible to read frame number {index}"); return(null); } return(frame); }
internal void AddFrame(string path, Int32Rect rect, int delay = 66) { using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (IsFirstFrame) { //Png Header: 8 bytes. InternalStream.WriteBytes(stream.ReadBytes(8)); //IHDR chunk. 13 bytes (Length + Type + CRC, 4 bytes each) = 25 bytes. InternalStream.WriteBytes(stream.ReadBytes(25)); //acTL: Animation control chunk. 8 bytes (Length + Type + CRC, 4 bytes each) = 20 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(8u)); //Length, 4 bytes. InternalStream.WriteBytes(Encoding.ASCII.GetBytes("acTL")); //Chunk type, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)FrameCount)); //NumFrames, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)RepeatCount)); //NumPlays, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(InternalStream.PeekBytes(InternalStream.Position - 12, 12)))); //CRC, 4 bytes. } //fcTL: Frame control chunk. 26 bytes (Length + Type + CRC, 4 bytes each) = 38 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(26u)); //Length, 4 bytes. InternalStream.WriteBytes(Encoding.ASCII.GetBytes("fcTL")); //Chunk type, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)SequenceNumber++)); //SequenceNumber, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)rect.Width)); //Width, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)rect.Height)); //Height, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)rect.X)); //OffsetX, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)rect.Y)); //OffsetY, 4 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)delay)); //Delay numerator, 2 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)1000)); //Delay denominator, 2 bytes. if (IsFirstFrame) { InternalStream.WriteByte((byte)DisposeOps.None); //DisposeOp, 1 byte. InternalStream.WriteByte((byte)BlendOps.Source); //BlendOp, 1 byte. } else { InternalStream.WriteByte((byte)DisposeOps.None); //DisposeOp, 1 byte. InternalStream.WriteByte((byte)BlendOps.Over); //BlendOp, 1 byte. } InternalStream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(InternalStream.PeekBytes(InternalStream.Position - 30, 30)))); //CRC, 4 bytes. //fdAT: Frame data chunk. 4 + n bytes (Length + Type + CRC, 4 bytes each) = 16 + n bytes, where n is the frame data. var dataList = GetData(stream); foreach (var data in dataList) { if (IsFirstFrame) { InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)data.Length)); //Length, 4 bytes. InternalStream.WriteBytes(Encoding.ASCII.GetBytes("IDAT")); //Chunk type, 4 bytes. InternalStream.WriteBytes(data); //Frame data, n bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(InternalStream.PeekBytes(InternalStream.Position - (data.Length + 4), data.Length + 4)))); //CRC, 4 bytes. } else { InternalStream.WriteUInt32(BitHelper.ConvertEndian(4 + (uint)data.Length)); //Length, 4 bytes. InternalStream.WriteBytes(Encoding.ASCII.GetBytes("fdAT")); //Chunk type, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)SequenceNumber++)); //SequenceNumber, 4 bytes. InternalStream.WriteBytes(data); //Frame data, n bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(CrcHelper.Calculate(InternalStream.PeekBytes(InternalStream.Position - (data.Length + 8), data.Length + 8)))); //CRC, 4 bytes. } } IsFirstFrame = false; } }
internal void AddFrame(string path, int delay = 66) { using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (IsFirstFrame) { IsFirstFrame = false; //Psd Header: XX bytes. InternalStream.WriteBytes(Encoding.ASCII.GetBytes("8BPS")); //Chunk type, 4 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)1)); //File version, 1 - PSD, 2 - PSB, 2 bytes. InternalStream.Position += 6; //Must be zero, 6 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)3)); //Number of channels, 2 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)Height)); //Height of the image, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)Width)); //Width of the image, 4 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)8)); //Number of bits per channel, 2 bytes. InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)3)); //The color mode of the file, 3 - RGB, 2 bytes. //Color mode data. 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //The size of the color mode data block, 0 bytes for RGB mode, 4 bytes. //TODO: Write the image resource block to another stream, then merge with the main one. //TODO: Or save the position and update later. //Image resources. XX bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //The size of the image resource block, 4 bytes. TODO //InternalStream.WriteBytes(Encoding.ASCII.GetBytes("8BIM")); //Chunk type, 4 bytes. //InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)0x0)); //Image Resource Id, 2 bytes. //InternalStream.WriteBytes(new byte[] { 0, 0 }); //Save the position //Layers and Masks list. XX bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //The size of the layer and global mask block, 4 bytes. TODO InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //The size of the layer block, 4 bytes. TODO InternalStream.WriteUInt16(BitHelper.ConvertEndian((ushort)0)); //The layer count, 2 bytes. TODO } InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //Top, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian(0u)); //Left, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)Height)); //Bottom, 4 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)Width)); //Right, 4 bytes. InternalStream.WriteInt16(BitHelper.ConvertEndian((short)4)); //Number of channels, 2 bytes. for (int i = -1; i < 4; i++) { InternalStream.WriteInt16(BitHelper.ConvertEndian((short)i)); //Channel ID, 2 bytes. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)0)); //Data lenght of channel, 4 bytes. TODO } InternalStream.WriteBytes(Encoding.ASCII.GetBytes("8BIM")); //Blend mode signature, 4 bytes. InternalStream.WriteInt32(BitHelper.ConvertEndian(0x6e6f726d)); //Blend mode value, Normal, 4 bytes. InternalStream.WriteByte(255); //Opacity, 1 byte. InternalStream.WriteByte(0); //Clipping, 1 byte. InternalStream.WriteByte(10); //Flags, Visible = true, 1 byte. InternalStream.WriteByte(0); //Filler, 1 byte. InternalStream.WriteUInt32(BitHelper.ConvertEndian((uint)0)); //Extra data lenght, 4 bytes. TODO InternalStream.WriteInt32(BitHelper.ConvertEndian(0)); //Layer mask size, 4 bytes. InternalStream.WriteInt32(BitHelper.ConvertEndian(0)); //Blending ranges size, 4 bytes. TODO: Check if it's possible o have this as zero. var name = $"Frame {0}".Truncate(255); InternalStream.WriteByte((byte)name.Length); //Layer name size, 1 byte. InternalStream.WriteString1252(name); //Layer name size, 1 byte. var padding = 4 - (name.Length + 1) % 4; if (padding != 4) //There's zero padding if equals to 4. { InternalStream.Position += padding; } //For each Aditional Layer Information: //InternalStream.WriteBytes(Encoding.ASCII.GetBytes("8BIM")); //Aditional Layer Information signature, 4 bytes. //InternalStream.WriteBytes(Encoding.ASCII.GetBytes("shmd")); //ALI ID, 4 bytes. var a = new TiffBitmapEncoder { Compression = TiffCompressOption.None }; a.Frames.Add(BitmapFrame.Create(path.SourceFrom())); using (var ms = new MemoryStream()) { a.Save(ms); ImageDataList.Add(ms.ToArray()); } } }