NGif icon indicating copy to clipboard operation
NGif copied to clipboard

I have modified the GifEncoder with new features for performance (direct byte pointer stream, parallelism, fast cancellation, direct filestream)

Open SkrFractals opened this issue 1 year ago • 0 comments

I am making a fractal generator that also encodes an infinite gif of the finished fractal zoom. This encoder works pretty well for that, but at this point where I have optimized and paralellized my image generator the encoder has become a performance bottleneck.

I will soon have finished my performance enhancements, and will share my improved gifencoder here if you want.

The issues and my improvements:

  1. The encoder required copyng an entire Bitmap and also a byte array of pixels of the same bitmap into it's AddFrame function.

My solution: Since I am generating the bitmaps with an unsafe byte* setting loop, I have rewritten the Addframe, so it can accept my finished byte pointer directly before I unlock the bitmap, so no bitmap copying and no creating of byte[] is necessary. But for compatibily or if you don't have a byte pointer I stil left the old byte[] encoding in as well.

  1. For large resolution gifs, the NeuQuant Learn function turned into a huge bottleneck, which took like 10-60 to process one AddFrame call.

My solution: I have rewriten the Encoder to enable parallel threading to handle this NeuQuant instances, while keeping the stream writing sequential to ensure the frames get written in the correct order as the processing tasks get finished. Again, for compatibility or if you don't want paralellism, you canstill use it the old synchronous way too.

  1. I added cancellation token support to allow for quicker Cancellation and restart of the encoder. So the encoder's functions will now accept the cancel token and will prematurely terminate it's lengthy tasks if it gets triggered. Again, for compatibility, there are overloaded functions without the cancellation token that cannot be cancelled.

  2. Also I have returned the ability to create a direct filestream so you don't have to copy the entire file memory stream at the end. For some reason this version has removed this feature that used to be there. And yet again, for compatibilty, I didn't remove the MemoryStream, I just let the user to choose which they prefer.

  3. Also I added a feature to add the frames out of order, in case you have the images ready to encode out of order. You'll have to supply the AddFrame with the index of the frame then. DO NOT MIX automatic ordered and manual out of order AddFrames!

Example including all my new features (highly simplified just to show the idea):

AnimatedGifEncoder e = new AnimatedGifEncoder(); e.Start(width, height, outputFileName, cancel.Token); // or with MemorySteam: e.Start(width, height); e.SetDelay(100); // 1 frame per sec unsafe { for(int bitmapsGenerated = 0; bitmapsGenerated < bitmapCount; ++bitmapsGenerated) { if(cancel.Token.isCancellationRequested) break; var bitmaps[bitmapsGenerated] = new Bitmap(width, height); var locked[bitmapsGenerated] = bitmaps[bitmapsGenerated].LockBits( new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); byte* p1, imageBytes = p = (byte*)(void*)locked.Scan0; for (var y = 0 y < height; ++y) for (var x = 0; x < width; ++x) { p[0] = (byte)BLUE; // BLUE subPixel at x,y p[1] = (byte)GREEN; // GREEN subPixel at x,y p[2] = (byte)RED; // RED subPizel at x,y p += 3; } // modification takes only directly byte* that you already have from LockBits, instead of Image+byte[] e.AddFrameParallel(imageBytes, task[taskIndex++], cancel.Token); } } // if outputFilename was supplied in Start or here, it will save a file, true closes the MemoryStream gifSuccess = false; e.Finish(); while (!cancel.Token.IsCancellationRequested && e.TryWrite() != TryWrite.Failed) if (gifSuccess = gifEncoder.IsFinished()) break; // Now that the encoder has finished writing the file, we can unlock the bits, and access the bitmaps // Or we coudl unlock them as the encoder processes them, but nit in this simple example for(int i = 0; i < bitmapsGenerated; ++i) bitmaps[i].UnlockBits(locked[i]);

...so I think it's finished, and it's working in my app. You can visib my project's githud and find the modified GifEncoder in RgbFractalGenCs/Components: https://github.com/SkrFractals/RgbFractalGen

SkrFractals avatar Jan 25 '25 23:01 SkrFractals