/// <summary> /// Adds an image as the next frame. /// </summary> /// <param name="image">Png frame.</param> public void AddFrame(Image image) { //TODO: Handle different sizes //Temporarily reject improper sizes. if (IHDRChunk != null && (image.Width > IHDRChunk.Width || image.Height > IHDRChunk.Height)) { throw new InvalidDataException("Frame must be less than or equal to the size of the other frames."); } APNG apng = APNG.FromImage(image); if (IHDRChunk == null) { IHDRChunk = apng.IHDRChunk; } //Create acTL Chunk. if (acTLChunk == null) { acTLChunk = new acTLChunk(); acTLChunk.PlayCount = 0; } uint sequenceNumber = (frames.Count == 0) ? 0 : (uint)(frames[frames.Count - 1].fcTLChunk.SequenceNumber + frames[frames.Count - 1].IDATChunks.Count); //Create fcTL Chunk fcTLChunk fctl = new fcTLChunk { SequenceNumber = sequenceNumber, Width = (uint)image.Width, Height = (uint)image.Height, XOffset = 0, YOffset = 0, DelayNumerator = 100, DelayDenominator = 1000, DisposeOp = DisposeOps.APNGDisposeOpNone, BlendOp = BlendOps.APNGBlendOpSource }; //Set the default image if needed. if (defaultImage.IDATChunks.Count == 0) { defaultImage = apng.DefaultImage; defaultImage.fcTLChunk = fctl; DefaultImageIsAnimated = true; } //Add all the frames from the png. if (apng.IsSimplePNG) { Frame frame = apng.DefaultImage; frame.fcTLChunk = fctl; foreach (OtherChunk chunk in frame.OtherChunks) { if (!defaultImage.OtherChunks.Contains(chunk)) { defaultImage.OtherChunks.Add(chunk); } } frame.OtherChunks.Clear(); frames.Add(frame); } else { for (int i = 0; i < apng.FrameCount; ++i) { Frame frame = apng.Frames[i]; frame.fcTLChunk.SequenceNumber = sequenceNumber; foreach (OtherChunk chunk in frame.OtherChunks) { if (!defaultImage.OtherChunks.Contains(chunk)) { defaultImage.OtherChunks.Add(chunk); } } frame.OtherChunks.Clear(); frames.Add(frame); } } List <OtherChunk> otherChunks = defaultImage.OtherChunks; // Now we should apply every chunk in otherChunks to every frame. if (defaultImage != frames[0]) { frames.ForEach(f => otherChunks.ForEach(f.AddOtherChunk)); } else { for (int i = 1; i < frames.Count; ++i) { otherChunks.ForEach(frames[i].AddOtherChunk); } } acTLChunk.FrameCount = (uint)frames.Count; }
/// <summary> /// Load the specified stream. /// </summary> /// <param name="stream">Streamrepresentation of the png file.</param> internal void Load(MemoryStream stream) { ms = stream; // 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."); } viewSize = new Size(IHDRChunk.Width, IHDRChunk.Height); // Now let's loop in chunks Chunk chunk; Frame frame = null; var otherChunks = new List <OtherChunk>(); 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."); case "acTL": if (IsSimplePNG) { throw new Exception("acTL chunk must located before any IDAT and fdAT"); } acTLChunk = new acTLChunk(chunk); break; case "IDAT": // To be an APNG, acTL must located before any IDAT and fdAT. if (acTLChunk == null) { 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) { frames.Add(frame); } 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) { frames.Add(frame); } if (DefaultImage.IDATChunks.Count != 0) { DefaultImage.IENDChunk = new IENDChunk(chunk); } foreach (Frame f in frames) { f.IENDChunk = new IENDChunk(chunk); } break; default: otherChunks.Add(new OtherChunk(chunk)); break; } } while (chunk.ChunkType != "IEND"); // We have one more thing to do: // If the default image is part of the animation, // we should insert it into frames list. if (defaultImage.fcTLChunk != null) { frames.Insert(0, defaultImage); DefaultImageIsAnimated = true; } else //If it isn't animated it still needs the other chunks. { otherChunks.ForEach(defaultImage.AddOtherChunk); } // Now we should apply every chunk in otherChunks to every frame. frames.ForEach(f => otherChunks.ForEach(f.AddOtherChunk)); }