void WriteFrame(Frame frame) { Trace.WriteLine(string.Format("Gif89a.WriteFrame [{0}]", System.Threading.Thread.CurrentThread.ManagedThreadId)); var sw = new Stopwatch(); sw.Start(); if (frame.OpaqueFramePixelBytes == null) { return; } // if we're discarding duplicate frames if (frame.OpaqueFramePixelBytes.Length == 0 && this.optimization.HasFlag(FrameOptimization.DiscardDuplicates)) { // hang on to where we currently are in the output stream var here = this.output.Position; // the last written GCE might be more than one frame back, if there's a bunch of duplicates var prev = frame; while (prev.OutputStreamGCEIndex == 0) { prev = this.frames[this.frames.IndexOf(prev) - 1]; } // jump back to the GCE in the previous frame this.output.Position = prev.OutputStreamGCEIndex; prev.Delay += frame.Delay; GifFileFormat.WriteGraphicControlExtension(output, prev, prev.transIndex); // jump forward to where we just were to continue on normally this.output.Position = here; return; } if (frame.IndexedPixels.Length == 0) { return; } if (frame == this.frames.First()) { // logical screen descriptor GifFileFormat.WriteLogicalScreenDescriptor(this.output, (ushort)this.Size.Width, (ushort)this.Size.Height, frame.ColorTableBytes.Length); // global color table GifFileFormat.WriteColorTable(this.output, frame.ColorTableBytes); if (this.Repeat >= 0) { // use NS app extension to indicate reps GifFileFormat.WriteNetscapeApplicationExtension(this.output, this.Repeat); } } // write graphic control extension frame.OutputStreamGCEIndex = this.output.Position; GifFileFormat.WriteGraphicControlExtension(output, frame, frame.transIndex); // image descriptor GifFileFormat.WriteImageDescriptor(this.output, frame.ChangeRect, frame.ColorTableBytes.Length, frame == this.frames.First()); if (frame != this.frames.First()) { // local color table GifFileFormat.WriteColorTable(this.output, frame.ColorTableBytes); } // encode and write pixel data var lzw = new LZWEncoder(frame.ChangeRect.Width, frame.ChangeRect.Height, frame.IndexedPixels, 8); lzw.encode(this.output); sw.Stop(); Trace.WriteLine($"Done ({ sw.Elapsed })", string.Format("Gif89a.WriteFrame [{0}]", System.Threading.Thread.CurrentThread.ManagedThreadId)); }
void WriteGifToStream() { Trace.WriteLine(string.Format("Gif89a.WriteGifToStream [{0}]", System.Threading.Thread.CurrentThread.ManagedThreadId)); GifFileFormat.WriteFileHeader(this.output); var nextFrameIndex = 0; var pendingFrames = new List <Frame>(); while (!FrameWriteQueue.IsCompleted) { Frame someFrame = null; try { someFrame = FrameWriteQueue.Take(); } catch (InvalidOperationException) { Trace.WriteLine("No more frames to write", string.Format("Gif89a.WriteGifToStream [{0}]", System.Threading.Thread.CurrentThread.ManagedThreadId)); break; } // frame analysis could can (and does!) occur out-of-order // so every time a frame is popped from the FrameWriteQueue, it might not be the "next" frame // no problem: just hang on to the writable frames and then write as much as possible in order pendingFrames.Add(someFrame); while (nextFrameIndex < this.frames.Count && pendingFrames.Contains(this.frames[nextFrameIndex])) { this.WriteFrame(this.frames[nextFrameIndex]); // no need to hang on to it here any more pendingFrames.Remove(someFrame); // we're done with the raw frame data, so let it unload if (nextFrameIndex > 0) { // don't dispose the current frame, since it could be a "prev" frame for a frame that's currently being analyzed this.frames[nextFrameIndex - 1].Dispose(); } // allow more frames to be loaded FrameLoadingDelay.Set(); this.OnFrameWriteProgress?.Invoke(this, new FrameWriteProgressEventArgs((float)nextFrameIndex * 100.0f / (float)(this.frames.Count + this.FrameLoadingQueue.Count))); nextFrameIndex++; } output.Flush(); } // the final frame is now safe to dispose since there's no "next" for it to be a "prev" for if (this.frames.Any()) { this.frames.Last().Dispose(); } GifFileFormat.WriteFileTrailer(output); output.Flush(); Trace.WriteLine("Done", string.Format("Gif89a.WriteGifToStream [{0}]", System.Threading.Thread.CurrentThread.ManagedThreadId)); // announce that stream writing is done FrameWriteComplete.Set(); }