OSDN Git Service

Generate GIF animations twice as fast as ever before
authorKazuhiro <fujieda@users.osdn.me>
Fri, 30 Oct 2015 12:06:44 +0000 (21:06 +0900)
committerKazuhiro <fujieda@users.osdn.me>
Fri, 30 Oct 2015 12:06:44 +0000 (21:06 +0900)
BurageSnap/AnimationGifEncoder.cs
BurageSnap/NeuQuant.cs

index cb8d707..cd1c8b6 100644 (file)
 // along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 using System;
+using System.Collections.Concurrent;
 using System.Drawing;
 using System.Drawing.Drawing2D;
 using System.Drawing.Imaging;
 using System.IO;
 using System.Text;
+using System.Threading.Tasks;
 
 namespace BurageSnap
 {
@@ -30,6 +32,21 @@ namespace BurageSnap
         private double _scale;
         private bool _firstFrame;
         private int[,] _prevFrame;
+        private readonly BlockingCollection<Frame> _originalFrame = new BlockingCollection<Frame>();
+        private readonly BlockingCollection<Frame> _scaledFrame = new BlockingCollection<Frame>();
+        private readonly BlockingCollection<Frame> _optimizedFrame = new BlockingCollection<Frame>();
+        private readonly BlockingCollection<Frame> _learnedNeuQuant = new BlockingCollection<Frame>();
+        private readonly BlockingCollection<Frame> _bpp8Frame = new BlockingCollection<Frame>();
+        private readonly Task[] _tasks = new Task[5];
+
+        private class Frame
+        {
+            public Bitmap Bitmap;
+            public NeuQuant NeuQuant;
+            public int Delay;
+            public Rectangle Rectangle;
+            public int[] Pixels;
+        }
 
         public bool IsLoop { get; set; } = true;
 
@@ -39,25 +56,115 @@ namespace BurageSnap
             _stream = new BinaryWriter(stream);
             _stream.Write(Encoding.ASCII.GetBytes("GIF89a"));
             _firstFrame = true;
+            StartPipeline();
+        }
+
+        private void StartPipeline()
+        {
+            _tasks[0] = Task.Run(() =>
+            {
+                try
+                {
+                    while (true)
+                    {
+                        var frame = _originalFrame.Take();
+                        frame.Bitmap = ScaleImage(frame.Bitmap, _scale);
+                        _scaledFrame.Add(frame);
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                    _scaledFrame.CompleteAdding();
+                }
+            });
+            _tasks[1] = Task.Run(() =>
+            {
+                try
+                {
+                    while (true)
+                    {
+                        var frame = _scaledFrame.Take();
+                        _optimizedFrame.Add(OptimizeFrame(frame));
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                    _optimizedFrame.CompleteAdding();
+                }
+            });
+            _tasks[2] = Task.Run(() =>
+            {
+                try
+                {
+                    while (true)
+                    {
+                        var frame = _optimizedFrame.Take();
+                        frame.NeuQuant = frame.Pixels == null
+                            ? new NeuQuant(frame.Bitmap, 10)
+                            : new NeuQuant(frame.Pixels, frame.Rectangle.Width, frame.Rectangle.Height, 10);
+                        frame.NeuQuant.Init();
+                        _learnedNeuQuant.Add(frame);
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                    _learnedNeuQuant.CompleteAdding();
+                }
+            });
+            _tasks[3] = Task.Run(() =>
+            {
+                try
+                {
+                    while (true)
+                    {
+                        var frame = _learnedNeuQuant.Take();
+                        frame.Bitmap.Dispose();
+                        frame.Bitmap = frame.NeuQuant.CreateBitmap();
+                        _bpp8Frame.Add(frame);
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                    _bpp8Frame.CompleteAdding();
+                }
+            });
+            _tasks[4] = Task.Run(() =>
+            {
+                try
+                {
+                    while (true)
+                    {
+                        var frame = _bpp8Frame.Take();
+                        AddFrame(frame.Bitmap, frame.Rectangle, frame.Delay);
+                        frame.Bitmap.Dispose();
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                }
+            });
         }
 
         public void AddFrame(Bitmap bmp, int delay)
         {
-            // ReSharper disable once CompareOfFloatsByEqualityOperator
-            var scaled = _scale == 1.0 ? bmp : ScaleImage(bmp, _scale);
+            _originalFrame.Add(new Frame {Bitmap = bmp, Delay = delay});
+        }
+
+        private Frame OptimizeFrame(Frame frame)
+        {
             if (_prevFrame == null)
             {
-                _prevFrame = ConvertBitmapToArray(scaled);
-                AddFrame(scaled, new Rectangle(0, 0, scaled.Width, scaled.Height), delay);
+                _prevFrame = ConvertBitmapToArray(frame.Bitmap);
+                frame.Rectangle = new Rectangle(0, 0, frame.Bitmap.Width, frame.Bitmap.Height);
             }
             else
             {
-                var cur = ConvertBitmapToArray(scaled);
-                AddOptimizedFrame(cur, _prevFrame, delay);
+                var cur = ConvertBitmapToArray(frame.Bitmap);
+                frame.Bitmap.Dispose();
+                frame = DifferentialFrame(cur, _prevFrame, frame);
                 _prevFrame = cur;
             }
-            if (scaled != bmp)
-                scaled.Dispose();
+            return frame;
         }
 
         private Bitmap ScaleImage(Bitmap bmp, double scale)
@@ -76,11 +183,11 @@ namespace BurageSnap
         private void AddFrame(Bitmap bmp, Rectangle rect, int delay)
         {
             using (var gif = new MemoryStream())
-            using (var bmp8 = NeuQuant.Quantize(bmp, 10))
             {
-                bmp8.Save(gif, ImageFormat.Gif);
+                bmp.Save(gif, ImageFormat.Gif);
                 AddFrame(gif, rect, delay);
             }
+            bmp.Dispose();
         }
 
         private void AddFrame(MemoryStream gif, Rectangle rect, int delay)
@@ -176,32 +283,24 @@ namespace BurageSnap
             return array;
         }
 
-        private void AddOptimizedFrame(int[,] cur, int[,] prev, int delay)
+        private Frame DifferentialFrame(int[,] cur, int[,] prev, Frame frame)
         {
             var rect = FindDifferentBounds(cur, prev);
             if (rect.IsEmpty)
                 rect = new Rectangle(0, 0, 1, 1);
-            using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
+            frame.Pixels = new int[rect.Width * rect.Height];
+            for (var y = 0; y < rect.Height; y++)
             {
-                var data = bmp.LockBits(new Rectangle(0, 0, rect.Width, rect.Height),
-                    ImageLockMode.WriteOnly, bmp.PixelFormat);
-                for (var y = 0; y < rect.Height; y++)
+                var y1 = y + rect.Y;
+                for (var x = 0; x < rect.Width; x++)
                 {
-                    var y1 = y + rect.Y;
-                    for (var x = 0; x < rect.Width; x++)
-                    {
-                        var x1 = x + rect.X;
-                        unsafe
-                        {
-                            var ptr = (int*)((byte*)data.Scan0 + y * data.Stride + x * 4);
-                            var col = cur[y1, x1];
-                            *ptr = col != prev[y1, x1] ? col : 0;
-                        }
-                    }
+                    var x1 = x + rect.X;
+                    var col = cur[y1, x1];
+                    frame.Pixels[y * rect.Width + x] = col != prev[y1, x1] ? col : 0;
                 }
-                bmp.UnlockBits(data);
-                AddFrame(bmp, rect, delay);
             }
+            frame.Rectangle = rect;
+            return frame;
         }
 
         private Rectangle FindDifferentBounds(int[,] cur, int[,] prev)
@@ -261,6 +360,8 @@ namespace BurageSnap
         {
             if (_stream == null)
                 return;
+            _originalFrame.CompleteAdding();
+            Task.WaitAll(_tasks);
             _stream.Write((byte)0x3b);
             _stream.Close();
         }
index 2ea67b2..716c995 100644 (file)
@@ -75,47 +75,55 @@ namespace BurageSnap
         private const int Prime3 = 487;
         private const int Prime4 = 503;
 
-        private int[] _pixels;
+        private readonly int[] _pixels;
         private readonly int _sampleFac;
+        private readonly int _width;
+        private readonly int _height;
 
-        public NeuQuant(Bitmap bmp)
-        {
-            _sampleFac = 1;
-            SetPixels(bmp);
-            SetUpArrays();
-        }
-
-        public NeuQuant(Bitmap bmp, int sample)
+        public NeuQuant(int[] pixels, int width, int height, int sample)
         {
             if (sample < 1 || 30 < sample)
                 throw new ArgumentException();
             _sampleFac = sample;
-            SetPixels(bmp);
+            _width = width;
+            _height = height;
+            _pixels = pixels;
             SetUpArrays();
         }
 
+        public NeuQuant(Bitmap bmp) : this(bmp, 1)
+        {
+        }
+
+        public NeuQuant(Bitmap bmp, int sample) : this(GetPixels(bmp), bmp.Width, bmp.Height, sample)
+        {
+        }
+
         public static Bitmap Quantize(Bitmap bmp, int sample)
         {
             var nq = new NeuQuant(bmp, sample);
             nq.Init();
-            var width = bmp.Width;
-            var height = bmp.Height;
-            var bmp8 = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
+            return nq.CreateBitmap();
+        }
+
+        public Bitmap CreateBitmap()
+        {
+            var bmp8 = new Bitmap(_width, _height, PixelFormat.Format8bppIndexed);
             var palette = bmp8.Palette;
             palette.Entries[0] = Color.FromArgb(0, 0, 0, 0); // 0 is the transparent index
             for (var i = 0; i < NetSize; i++)
-                palette.Entries[i + 1] = nq.GetColor(i);
+                palette.Entries[i + 1] = GetColor(i);
             bmp8.Palette = palette;
-            var data8 = bmp8.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp8.PixelFormat);
-            Parallel.For(0, height, h =>
+            var data8 = bmp8.LockBits(new Rectangle(0, 0, _width, _height), ImageLockMode.WriteOnly, bmp8.PixelFormat);
+            Parallel.For(0, _height, h =>
             {
-                for (var x = 0; x < width; x++)
+                for (var x = 0; x < _width; x++)
                 {
                     unsafe
                     {
-                        var pix = nq._pixels[h * width + x];
+                        var pix = _pixels[h * _width + x];
                         var ptr = (byte*)data8.Scan0 + h * data8.Stride + x;
-                        *ptr = pix == 0 ? (byte)0 : (byte)(nq.Lookup(pix) + 1);
+                        *ptr = pix == 0 ? (byte)0 : (byte)(Lookup(pix) + 1);
                     }
                 }
             });
@@ -157,15 +165,16 @@ namespace BurageSnap
                 _colormap[i] = new int[4];
         }
 
-        private void SetPixels(Bitmap bmp)
+        private static int[] GetPixels(Bitmap bmp)
         {
             var width = bmp.Width;
             var height = bmp.Height;
-            _pixels = new int[width * height];
+            var pixels = new int[width * height];
             var data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
                 PixelFormat.Format32bppArgb);
-            Marshal.Copy(data.Scan0, _pixels, 0, width * height);
+            Marshal.Copy(data.Scan0, pixels, 0, width * height);
             bmp.UnlockBits(data);
+            return pixels;
         }
 
         public void Init()