public static unsafe Bitmap Sobel(Bitmap source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            var pixelFormat = source.PixelFormat;
            var bpp         = (byte)Image.GetPixelFormatSize(pixelFormat) / 8;
            var hasAlpha    = bpp > 3;

            if (hasAlpha)
            {
                source = GrayscaleRgba(source);
            }
            else
            {
                source = GrayscaleRgb(source);
            }

            var target   = new Bitmap(source.Width, source.Height, source.PixelFormat);
            var imageBox = new Rectangle(0, 0, source.Width, source.Height);

            var sourceData = source.LockBits(imageBox, ImageLockMode.ReadOnly, pixelFormat);

            try
            {
                var targetData = target.LockBits(imageBox, ImageLockMode.WriteOnly, pixelFormat);

                try
                {
                    var s0 = (byte *)sourceData.Scan0;
                    var t0 = (byte *)targetData.Scan0;
                    var s  = sourceData.Stride;

                    for (var y = 0; y < source.Height; y++)
                    {
                        var yn = Math.Max(0, y - 1);
                        var ym = Math.Min(source.Height - 1, y + 1);

                        var ybn = yn * bpp;
                        var yb  = y * bpp;
                        var ybm = ym * bpp;

                        var rn = s0 + yn * s;
                        var r  = s0 + y * s;
                        var rm = s0 + ym * s;

                        var tr = t0 + y * s;

                        for (var x = 0; x < source.Width; x++)
                        {
                            var xn = Math.Max(0, x - 1);
                            var xm = Math.Min(source.Width - 1, x + 1);

                            var xbn = xn * bpp;
                            var xb  = x * bpp;
                            var xbm = xm * bpp;

                            var dx = rn[xbn] * -1 + rn[xbm] + r[xbn] * -2 + r[xbm] * 2 + rm[xbn] * -1 + rm[xbm];
                            var dy = rn[xbn] + rn[xb] * 2 + rn[xbm] + rm[xbn] * -1 + rm[xb] * -2 + rm[xbm] * -1;

                            var mag = Math.Sqrt(dx * dx + dy * dy);
                            if (mag > byte.MaxValue)
                            {
                                tr[xb]     = byte.MaxValue;
                                tr[xb + 1] = byte.MaxValue;
                                tr[xb + 2] = byte.MaxValue;
                            }
                            else if (mag < 0)
                            {
                                tr[xb]     = 0;
                                tr[xb + 1] = 0;
                                tr[xb + 2] = 0;
                            }
                            else
                            {
                                var c = (byte)mag;

                                tr[xb]     = c;
                                tr[xb + 1] = c;
                                tr[xb + 2] = c;
                            }
                        }
                    }

                    if (bpp > 3)
                    {
                        Raw.FillAlpha(targetData);
                    }
                }
                finally
                {
                    target.UnlockBits(targetData);
                }
            }
            finally
            {
                source.UnlockBits(sourceData);
                source.Dispose();
            }

            return(target);
        }
        public static unsafe Bitmap Buckets(Bitmap source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            var pixelFormat = source.PixelFormat;
            var bpp         = (byte)Image.GetPixelFormatSize(pixelFormat) / 8;
            var hasAlpha    = bpp > 3;

            var target   = new Bitmap(source.Width, source.Height, source.PixelFormat);
            var imageBox = new Rectangle(0, 0, source.Width, source.Height);

            var sourceData = source.LockBits(imageBox, ImageLockMode.ReadOnly, pixelFormat);

            try
            {
                var targetData = target.LockBits(imageBox, ImageLockMode.WriteOnly, pixelFormat);

                try
                {
                    var s0 = (byte *)sourceData.Scan0;
                    var t0 = (byte *)targetData.Scan0;
                    var s  = sourceData.Stride;

                    for (var y = 0; y < source.Height; y++)
                    {
                        var srow = s0 + y * s;
                        var trow = t0 + y * s;

                        for (var x = 0; x < source.Width; x++)
                        {
                            var p = x * bpp;
                            var b = srow[p];
                            var g = srow[p + 1];
                            var r = srow[p + 2];
                            var a = hasAlpha ? srow[p + 3] : byte.MaxValue;

                            var v = Color.FromArgb(a, r, g, b).ToColorBucket();
                            var c = v.ToColorValue();

                            trow[p]     = c;
                            trow[p + 1] = c;
                            trow[p + 2] = c;
                        }
                    }

                    if (bpp > 3)
                    {
                        Raw.FillAlpha(targetData);
                    }
                }
                finally
                {
                    target.UnlockBits(targetData);
                }
            }
            finally
            {
                source.UnlockBits(sourceData);
            }

            return(target);
        }