/// <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 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); }