画像データの拡大縮小とおまけでシャープネスです
Lanczos() と Spline36() は下のサイトを参考にアルファ値も処理するように変更しました。
https://www.rainorshine.asia/2015/10/12/post2602.html
PixelMixing() はネットで概要を知り自分で実装したものです。
縮小専用です。
【サンプル画像】
【縮小画像】縦横1/3
上段左: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
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;
}
}
}
}