internal APNGFrame(GraphicsDevice graphicsDevice, Frame frame) { if (frame.fcTLChunk != null) { X = (int) frame.fcTLChunk.XOffset; Y = (int) frame.fcTLChunk.YOffset; Width = (int) frame.fcTLChunk.Width; Height = (int) frame.fcTLChunk.Height; BlendOp = frame.fcTLChunk.BlendOp; DisposeOp = frame.fcTLChunk.DisposeOp; DelayTime = new TimeSpan( TimeSpan.TicksPerSecond*frame.fcTLChunk.DelayNum/frame.fcTLChunk.DelayDen); } else { X = 0; Y = 0; Width = frame.IHDRChunk.Width; Height = frame.IHDRChunk.Height; BlendOp = BlendOps.APNGBlendOpSource; DisposeOp = DisposeOps.APNGDisposeOpNone; DelayTime = TimeSpan.Zero; } // frame.GetStream() is not seekable, so we build a new MemoryStream. FrameTexture = Texture2D.FromStream( graphicsDevice, new MemoryStream(frame.GetStream().ToArray())); MultiplyAlpha(FrameTexture); }
public APNG(byte[] fileBytes) { ms = new MemoryStream(fileBytes); // check file signature. if (!Helper.IsBytesEqual(ms.ReadBytes(Frame.Signature.Length), Frame.Signature)) throw new Exception("File signature incorrect."); // Read IHDR chunk. IHDRChunk = new IHDRChunk(ms); if (IHDRChunk.ChunkType != "IHDR") throw new Exception("IHDR chunk must located before any other chunks."); // Now let's loop in chunks Chunk chunk; Frame frame = null; List<ITextChunk> text_chunks = new List<ITextChunk>(); bool isIDATAlreadyParsed = false; do { if (ms.Position == ms.Length) throw new Exception("IEND chunk expected."); chunk = new Chunk(ms); switch (chunk.ChunkType) { case "IHDR": throw new Exception("Only single IHDR is allowed."); break; case "acTL": if (IsSimplePNG) throw new Exception("acTL chunk must located before any IDAT and fdAT"); TextChunks = text_chunks.ToArray(); text_chunks.Clear(); acTLChunk = new acTLChunk(chunk); break; case "IDAT": // To be an APNG, acTL must located before any IDAT and fdAT. if (acTLChunk == null) { TextChunks = text_chunks.ToArray(); text_chunks.Clear(); IsSimplePNG = true; } // Only default image has IDAT. defaultImage.IHDRChunk = IHDRChunk; defaultImage.AddIDATChunk(new IDATChunk(chunk)); isIDATAlreadyParsed = true; break; case "fcTL": // Simple PNG should ignore this. if (IsSimplePNG) continue; if (frame != null && frame.IDATChunks.Count == 0) throw new Exception("One frame must have only one fcTL chunk."); // IDAT already parsed means this fcTL is used by FRAME IMAGE. if (isIDATAlreadyParsed) { // register current frame object and build a new frame object // for next use if (frame != null) { frame.TextChunks = text_chunks.ToArray(); frames.Add(frame); text_chunks.Clear(); } frame = new Frame { IHDRChunk = IHDRChunk, fcTLChunk = new fcTLChunk(chunk) }; } // Otherwise this fcTL is used by the DEFAULT IMAGE. else { defaultImage.fcTLChunk = new fcTLChunk(chunk); } break; case "fdAT": // Simple PNG should ignore this. if (IsSimplePNG) continue; // fdAT is only used by frame image. if (frame == null || frame.fcTLChunk == null) throw new Exception("fcTL chunk expected."); frame.AddIDATChunk(new fdATChunk(chunk).ToIDATChunk()); break; case "IEND": // register last frame object if (frame != null) { frame.TextChunks = text_chunks.ToArray(); frames.Add(frame); text_chunks.Clear(); } if (DefaultImage.IDATChunks.Count != 0) DefaultImage.IENDChunk = new IENDChunk(chunk); foreach (Frame f in frames) { f.IENDChunk = new IENDChunk(chunk); } break; case "iTXt": text_chunks.Add(new iTXtChunk(chunk)); break; case "tEXt": text_chunks.Add(new tEXtChunk(chunk)); break; case "zTXt": text_chunks.Add(new zTXtChunk(chunk)); break; default: //TODO: Handle other chunks. break; } } while (chunk.ChunkType != "IEND"); // We have one more thing to do: // If the default image if part of the animation, // we should insert it into frames list. if (defaultImage.fcTLChunk != null) { frames.Insert(0, defaultImage); DefaultImageIsAnimeated = true; } }