画像処理(Lanczos, Spline36, PixelMixing)

画像データの拡大縮小とおまけでシャープネスです

Lanczos() と Spline36() は下のサイトを参考にアルファ値も処理するように変更しました。
https://www.rainorshine.asia/2015/10/12/post2602.html

PixelMixing() はネットで概要を知り自分で実装したものです。
縮小専用です。

【サンプル画像】
![ファイル共有-Windowsの機能-SMB1.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464753/b27fbe76-84b7-4684-adc1-a6e3f8016633.jpeg)
【縮小画像】縦横1/3
![sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464753/0a79c0a7-004f-436c-a277-e9cf297cb273.jpeg)
上段左：Lanczos
上段中：Spline36
上段右：PixelMixing
中段左：InterpolationMode.HighQualityBicubic
中段中：InterpolationMode.Bicubic
中段右：InterpolationMode.HighQualityBilinear
下段左：InterpolationMode.Bilinear
下段中：InterpolationMode.NearestNeighbor

PixelMixing と InterpolationMode.Bilinear 以外は
左端と上端のピクセルの色が濃くなり、右端と下端が薄くなっているのが分かります

Sharpness() は下のサイトを参考にしました
https://dk521123.hatenablog.com/entry/37837353#google_vignette

```c#:ImageFilter.cs
using System.Diagnostics;
using System.Drawing.Imaging;

namespace System.Drawing
{
    public static class ImageFilter
    {
        static byte ClipByte(int value)
        {
            if (value > 255)
            {
                return (byte)255;
            }

            if (value < 0)
            {
                return (byte)0;
            }

            return (byte)value;
        }

        static byte ClipByte(long value)
        {
            if (value > 255)
            {
                return (byte)255;
            }

            if (value < 0)
            {
                return (byte)0;
            }

            return (byte)value;
        }

        static byte ClipByte(double value)
        {
            if (value > 255.0)
            {
                return (byte)255;
            }

            if (value < 0.0)
            {
                return (byte)0;
            }

            return (byte)value;
        }

        const string format1_unsupported_pixel_format = "未対応の PixelFormat です ({0})";

        static double sinc(double x)
        {
            return Math.Sin(x * Math.PI) / (x * Math.PI);
        }

        static double lanczosWeight(double d, int n = 3)
        {
            return d == 0 ? 1 : (Math.Abs(d) < n ? sinc(d) * sinc(d / n) : 0);
        }

        public static Bitmap? Lanczos(Bitmap src_bitmap, int dst_width, int dst_height, out CustomError? ce, int n = 3)
        {
            int bpp;

            PixelFormat pixel_format;

            if (src_bitmap.PixelFormat == PixelFormat.Format24bppRgb
            || src_bitmap.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                bpp = 3;

                pixel_format = PixelFormat.Format24bppRgb;
            }
            else if (src_bitmap.PixelFormat == PixelFormat.Format32bppArgb)
            {
                bpp = 4;

                pixel_format = PixelFormat.Format32bppArgb;
            }
            else
            {
                ce = new CustomError(format1_unsupported_pixel_format, src_bitmap.PixelFormat);

                return null;
            }

            Bitmap? dst_bitmap = null;

            try
            {
                int src_width = src_bitmap.Width;
                int src_height = src_bitmap.Height;

                double wf = (double)src_width / (double)dst_width;
                double hf = (double)src_height / (double)dst_height;

                dst_bitmap = new Bitmap(dst_width, dst_height, pixel_format);

                var src_bitmapdata = src_bitmap.LockBits(new Rectangle(Point.Empty, src_bitmap.Size), ImageLockMode.ReadOnly, pixel_format);
                var dst_bitmapdata = dst_bitmap.LockBits(new Rectangle(Point.Empty, dst_bitmap.Size), ImageLockMode.WriteOnly, pixel_format);

                int src_stride = src_bitmapdata.Stride;
                int dst_stride = dst_bitmapdata.Stride;

                unsafe
                {
                    byte* src_data = (byte*)src_bitmapdata.Scan0;
                    byte* dst_data = (byte*)dst_bitmapdata.Scan0;

                    Parallel.For(0, dst_height, iy =>
                    {
                        double[] value = new double[bpp];

                        for (var ix = 0; ix < dst_width; ix++)
                        {
                            var wfx = wf * ix;
                            var wfy = hf * iy;

                            var x = (int)wfx;
                            var y = (int)wfy;

                            for (int i = 0; i < bpp; i++)
                            {
                                value[i] = 0;
                            }

                            for (int jy = y - n + 1; jy <= y + n; jy++)
                            {
                                for (int jx = x - n + 1; jx <= x + n; jx++)
                                {
                                    var w = lanczosWeight(wfx - jx, n) * lanczosWeight(wfy - jy, n);

                                    if (w == 0) continue;

                                    var sx = (jx < 0 || jx >= src_width) ? x : jx;
                                    var sy = (jy < 0 || jy >= src_height) ? y : jy;

                                    var src_pos = bpp * sx + src_stride * sy;

                                    for (int i = 0; i < bpp; i++)
                                    {
                                        value[i] += src_data[src_pos + i] * w;
                                    }
                                }
                            }

                            var dst_pos = bpp * ix + dst_stride * iy;

                            for (int i = 0; i < bpp; i++)
                            {
                                dst_data[dst_pos + i] = ClipByte(value[i]);
                            }
                        }
                    });
                }

                src_bitmap.UnlockBits(src_bitmapdata);
                dst_bitmap.UnlockBits(dst_bitmapdata);

                ce = null;

                return dst_bitmap;
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                if (dst_bitmap != null)
                {
                    dst_bitmap.Dispose();
                }

                return null;
            }
        }

        static double spline36Weight(double d)
        {
            d = Math.Abs(d);

            if (d < 1.0)
            {
                return (((247.0 * d - 453.0) * d - 3.0) * d + 209.0) / 209.0;
            }

            if (d < 2.0)
            {
                return (((-114.0 * d + 612.0) * d - 1038.0) * d + 540.0) / 209.0;
            }

            if (d < 3.0)
            {
                return (((19.0 * d - 159.0) * d + 434.0) * d - 384.0) / 209.0;
            }

            return 0.0;
        }

        public static Bitmap? Spline36(Bitmap src_bitmap, int dst_width, int dst_height, out CustomError? ce, int n = 3)
        {
            int bpp;

            PixelFormat pixel_format;

            if (src_bitmap.PixelFormat == PixelFormat.Format24bppRgb
            || src_bitmap.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                bpp = 3;

                pixel_format = PixelFormat.Format24bppRgb;
            }
            else if (src_bitmap.PixelFormat == PixelFormat.Format32bppArgb)
            {
                bpp = 4;

                pixel_format = PixelFormat.Format32bppArgb;
            }
            else
            {
                ce = new CustomError(format1_unsupported_pixel_format, src_bitmap.PixelFormat);

                return null;
            }

            Bitmap? dst_bitmap = null;

            try
            {
                int src_width = src_bitmap.Width;
                int src_height = src_bitmap.Height;

                double wf = (double)src_width / (double)dst_width;
                double hf = (double)src_height / (double)dst_height;

                dst_bitmap = new Bitmap(dst_width, dst_height, pixel_format);

                var src_bitmapdata = src_bitmap.LockBits(new Rectangle(Point.Empty, src_bitmap.Size), ImageLockMode.ReadOnly, pixel_format);
                var dst_bitmapdata = dst_bitmap.LockBits(new Rectangle(Point.Empty, dst_bitmap.Size), ImageLockMode.WriteOnly, pixel_format);

                int src_stride = src_bitmapdata.Stride;
                int dst_stride = dst_bitmapdata.Stride;

                unsafe
                {
                    byte* src_data = (byte*)src_bitmapdata.Scan0;
                    byte* dst_data = (byte*)dst_bitmapdata.Scan0;

                    Parallel.For(0, dst_height, iy =>
                    {
                        double[] value = new double[bpp];

                        for (var ix = 0; ix < dst_width; ix++)
                        {
                            var wfx = wf * ix;
                            var wfy = hf * iy;

                            var x = (int)wfx;
                            var y = (int)wfy;

                            for (int i = 0; i < bpp; i++)
                            {
                                value[i] = 0;
                            }

                            for (int jy = y - n + 1; jy <= y + n; jy++)
                            {
                                for (int jx = x - n + 1; jx <= x + n; jx++)
                                {
                                    var w = spline36Weight(wfx - jx) * spline36Weight(wfy - jy);

                                    if (w == 0) continue;

                                    var sx = (jx < 0 || jx >= src_width) ? x : jx;
                                    var sy = (jy < 0 || jy >= src_height) ? y : jy;

                                    var src_pos = bpp * sx + src_stride * sy;

                                    for (int i = 0; i < bpp; i++)
                                    {
                                        value[i] += src_data[src_pos + i] * w;
                                    }
                                }
                            }

                            var dst_pos = bpp * ix + dst_stride * iy;

                            for (int i = 0; i < bpp; i++)
                            {
                                dst_data[dst_pos + i] = ClipByte(value[i]);
                            }
                        }
                    });
                }

                src_bitmap.UnlockBits(src_bitmapdata);
                dst_bitmap.UnlockBits(dst_bitmapdata);

                ce = null;

                return dst_bitmap;
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                if (dst_bitmap != null)
                {
                    dst_bitmap.Dispose();
                }

                return null;
            }
        }

        public static Bitmap? PixelMixing(Bitmap src_bitmap, int dst_width, int dst_height, out CustomError? ce)
        {
            int bpp;

            PixelFormat pixel_format;

            if (src_bitmap.PixelFormat == PixelFormat.Format24bppRgb
            || src_bitmap.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                bpp = 3;

                pixel_format = PixelFormat.Format24bppRgb;
            }
            else if (src_bitmap.PixelFormat == PixelFormat.Format32bppArgb)
            {
                bpp = 4;

                pixel_format = PixelFormat.Format32bppArgb;
            }
            else
            {
                ce = new CustomError(format1_unsupported_pixel_format, src_bitmap.PixelFormat);

                return null;
            }

            Bitmap? dst_bitmap = null;

            try
            {
                int src_width = src_bitmap.Width;
                int src_height = src_bitmap.Height;

                double wf = (double)src_width / (double)dst_width;
                double hf = (double)src_height / (double)dst_height;

                dst_bitmap = new Bitmap(dst_width, dst_height, pixel_format);

                var src_bitmapdata = src_bitmap.LockBits(new Rectangle(Point.Empty, src_bitmap.Size), ImageLockMode.ReadOnly, pixel_format);
                var dst_bitmapdata = dst_bitmap.LockBits(new Rectangle(Point.Empty, dst_bitmap.Size), ImageLockMode.WriteOnly, pixel_format);

                int src_stride = src_bitmapdata.Stride;
                int dst_stride = dst_bitmapdata.Stride;

                unsafe
                {
                    byte* src_data = (byte*)src_bitmapdata.Scan0;
                    byte* dst_data = (byte*)dst_bitmapdata.Scan0;

                    //for (int dy = 0; dy < dst_height; dy++)
                    //{
                    Parallel.For(0, dst_height, dy =>
                    {
                        double sy1 = dy * hf;
                        double sy2 = sy1 + hf;

                        int isy1 = (int)sy1;
                        int isy2 = (int)sy2;

                        double[] value = new double[bpp];

                        for (int dx = 0; dx < dst_width; dx++)
                        {
                            double sx1 = dx * wf;
                            double sx2 = sx1 + wf;

                            int isx1 = (int)sx1;
                            int isx2 = (int)sx2;

                            for (int i = 0; i < bpp; i++)
                            {
                                value[i] = 0;
                            }

                            double w_sum = 0;

                            double wy = (1.0 - (sy1 - isy1));
                            double wx = (1.0 - (sx1 - isx1));

                            double w = wy * wx;

                            int src_pos = bpp * isx1 + src_stride * isy1;

                            for (int i = 0; i < bpp; i++)
                            {
                                value[i] += src_data[src_pos + i] * w;
                            }

                            w_sum += w;

                            for (int x = isx1 + 1; x < isx2; x++)
                            {
                                src_pos = bpp * x + src_stride * isy1;

                                for (int i = 0; i < bpp; i++)
                                {
                                    value[i] += src_data[src_pos + i] * wy;
                                }

                                w_sum += wy;
                            }

                            for (int y = isy1 + 1; y < isy2; y++)
                            {
                                src_pos = bpp * isx1 + src_stride * y;

                                for (int i = 0; i < bpp; i++)
                                {
                                    value[i] += src_data[src_pos + i] * wx;
                                }

                                w_sum += wx;
                            }

                            for (int y = isy1 + 1; y < isy2; y++)
                            {
                                for (int x = isx1 + 1; x < isx2; x++)
                                {
                                    src_pos = bpp * x + src_stride * y;

                                    for (int i = 0; i < bpp; i++)
                                    {
                                        value[i] += src_data[src_pos + i];
                                    }

                                    w_sum += 1.0;
                                }
                            }

                            if (isx2 < src_width)
                            {
                                w = sx2 - isx2;

                                for (int y = isy1 + 1; y < isy2; y++)
                                {
                                    src_pos = bpp * isx2 + src_stride * y;

                                    for (int i = 0; i < bpp; i++)
                                    {
                                        value[i] += src_data[src_pos + i] * w;
                                    }

                                    w_sum += w;
                                }
                            }

                            if (isy2 < src_height)
                            {
                                w = sy2 - isy2;

                                for (int x = isx1 + 1; x < isx2; x++)
                                {
                                    src_pos = bpp * x + src_stride * isy2;

                                    for (int i = 0; i < bpp; i++)
                                    {
                                        value[i] += src_data[src_pos + i] * w;
                                    }

                                    w_sum += w;
                                }
                            }

                            if (isx2 < src_width && isy2 < src_height)
                            {
                                w = (sy2 - isy2) * (sx2 - isx2);

                                src_pos = bpp * isx2 + src_stride * isy2;

                                for (int i = 0; i < bpp; i++)
                                {
                                    value[i] += src_data[src_pos + i] * w;
                                }

                                w_sum += w;
                            }

                            int dst_pos = bpp * dx + dst_stride * dy;

                            for (int i = 0; i < bpp; i++)
                            {
                                dst_data[dst_pos + i] = ClipByte(value[i] / w_sum);
                            }
                        }
                    });
                    //}
                }

                src_bitmap.UnlockBits(src_bitmapdata);
                dst_bitmap.UnlockBits(dst_bitmapdata);

                ce = null;

                return dst_bitmap;
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                if (dst_bitmap != null)
                {
                    dst_bitmap.Dispose();
                }

                return null;
            }
        }

        public static Bitmap? Sharpness(Bitmap src_bitmap, double level, out CustomError? ce)
        {
            int bpp;

            PixelFormat pixel_format;

            if (src_bitmap.PixelFormat == PixelFormat.Format24bppRgb
            || src_bitmap.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                bpp = 3;

                pixel_format = PixelFormat.Format24bppRgb;
            }
            else if (src_bitmap.PixelFormat == PixelFormat.Format32bppArgb)
            {
                bpp = 4;

                pixel_format = PixelFormat.Format32bppArgb;
            }
            else
            {
                ce = new CustomError(format1_unsupported_pixel_format, src_bitmap.PixelFormat);

                return null;
            }

            Bitmap? dst_bitmap = null;

            try
            {

                int width = src_bitmap.Width;
                int height = src_bitmap.Height;

                dst_bitmap = new Bitmap(width, height, pixel_format);

                var src_bitmapdata = src_bitmap.LockBits(new Rectangle(Point.Empty, src_bitmap.Size), ImageLockMode.ReadOnly, pixel_format);
                var dst_bitmapdata = dst_bitmap.LockBits(new Rectangle(Point.Empty, dst_bitmap.Size), ImageLockMode.WriteOnly, pixel_format);

                int stride = src_bitmapdata.Stride;

                unsafe
                {
                    byte* src_data = (byte*)src_bitmapdata.Scan0;
                    byte* dst_data = (byte*)dst_bitmapdata.Scan0;

                    Parallel.For(1, height - 1, y =>
                    {
                        int dy1 = (y - 1) * stride;
                        int dy = y * stride;
                        int dy2 = (y + 1) * stride;

                        for (int x = 1; x < width - 1; x++)
                        {
                            int dx1 = (x - 1) * bpp;
                            int dx = x * bpp;
                            int dx2 = (x + 1) * bpp;

                            for (int k = 0; k < 3; k++)
                            {
                                double value = (src_data[dy + dx + k] * (1.0 + level * 4)) - ((src_data[dy1 + dx + k] + src_data[dy + dx1 + k] + src_data[dy + dx2 + k] + src_data[dy2 + dx + k]) * level);

                                dst_data[dy + dx + k] = ClipByte(value);
                            }

                            if (bpp == 4)
                            {
                                dst_data[dy + dx + 3] = src_data[dy + dx + 3];
                            }
                        }
                    });

                    int offset = stride * (height - 1);

                    for (int x = 0; x < width; x++)
                    {
                        int p1 = x * bpp;

                        for (int i = 0; i < bpp; i++)
                        {
                            dst_data[p1 + i] = src_data[p1 + i];
                        }

                        int p2 = offset + p1;

                        for (int i = 0; i < bpp; i++)
                        {
                            dst_data[p2 + i] = src_data[p2 + i];
                        }
                    }

                    offset = bpp * (width - 1);

                    for (int y = 1; y < height - 1; y++)
                    {
                        int p1 = stride * y;

                        for (int i = 0; i < bpp; i++)
                        {
                            dst_data[p1 + i] = src_data[p1 + i];
                        }

                        int p2 = offset + p1;

                        for (int i = 0; i < bpp; i++)
                        {
                            dst_data[p2 + i] = src_data[p2 + i];
                        }
                    }
                }

                src_bitmap.UnlockBits(src_bitmapdata);
                dst_bitmap.UnlockBits(dst_bitmapdata);

                ce = null;

                return dst_bitmap;
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                if (dst_bitmap != null)
                {
                    dst_bitmap.Dispose();
                }

                return null;
            }
        }
    }
}
```