mirror of
https://github.com/cyanfish/naps2.git
synced 2024-11-10 14:28:12 +03:00
353 lines
13 KiB
C#
353 lines
13 KiB
C#
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Drawing.Imaging;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace NAPS2.Images.Gdi;
|
|
|
|
public class GdiImageTransformer : AbstractImageTransformer<GdiImage>
|
|
{
|
|
public GdiImageTransformer(ImageContext imageContext) : base(imageContext)
|
|
{
|
|
}
|
|
|
|
protected override GdiImage PerformTransform(GdiImage image, ContrastTransform transform)
|
|
{
|
|
float contrastAdjusted = transform.Contrast / 1000f + 1.0f;
|
|
|
|
EnsurePixelFormat(ref image);
|
|
var bitmap = image.Bitmap;
|
|
using (var g = Graphics.FromImage(bitmap))
|
|
{
|
|
var attrs = new ImageAttributes();
|
|
attrs.SetColorMatrix(new ColorMatrix
|
|
{
|
|
Matrix00 = contrastAdjusted,
|
|
Matrix11 = contrastAdjusted,
|
|
Matrix22 = contrastAdjusted
|
|
});
|
|
g.DrawImage(bitmap,
|
|
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
|
|
0,
|
|
0,
|
|
bitmap.Width,
|
|
bitmap.Height,
|
|
GraphicsUnit.Pixel,
|
|
attrs);
|
|
}
|
|
return image;
|
|
}
|
|
|
|
|
|
protected override GdiImage PerformTransform(GdiImage image, SaturationTransform transform)
|
|
{
|
|
double saturationAdjusted = transform.Saturation / 1000.0 + 1;
|
|
|
|
EnsurePixelFormat(ref image);
|
|
var bitmap = image.Bitmap;
|
|
int bytesPerPixel;
|
|
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
|
|
{
|
|
bytesPerPixel = 3;
|
|
}
|
|
else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
|
|
{
|
|
bytesPerPixel = 4;
|
|
}
|
|
else
|
|
{
|
|
return image;
|
|
}
|
|
|
|
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
|
|
var stride = Math.Abs(data.Stride);
|
|
for (int y = 0; y < data.Height; y++)
|
|
{
|
|
for (int x = 0; x < data.Width; x++)
|
|
{
|
|
int r = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel);
|
|
int g = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel + 1);
|
|
int b = Marshal.ReadByte(data.Scan0 + stride * y + x * bytesPerPixel + 2);
|
|
|
|
Color c = Color.FromArgb(255, r, g, b);
|
|
ColorHelper.ColorToHSL(c, out double h, out double s, out double v);
|
|
|
|
s = Math.Min(s * saturationAdjusted, 1);
|
|
|
|
c = ColorHelper.ColorFromHSL(h, s, v);
|
|
|
|
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel, c.R);
|
|
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel + 1, c.G);
|
|
Marshal.WriteByte(data.Scan0 + stride * y + x * bytesPerPixel + 2, c.B);
|
|
}
|
|
}
|
|
bitmap.UnlockBits(data);
|
|
|
|
return image;
|
|
}
|
|
|
|
|
|
protected override GdiImage PerformTransform(GdiImage image, SharpenTransform transform)
|
|
{
|
|
double sharpnessAdjusted = transform.Sharpness / 1000.0;
|
|
|
|
EnsurePixelFormat(ref image);
|
|
var bitmap = image.Bitmap;
|
|
int bytesPerPixel;
|
|
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
|
|
{
|
|
bytesPerPixel = 3;
|
|
}
|
|
else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
|
|
{
|
|
bytesPerPixel = 4;
|
|
}
|
|
else
|
|
{
|
|
return image;
|
|
}
|
|
|
|
// From https://stackoverflow.com/a/17596299
|
|
|
|
int width = bitmap.Width;
|
|
int height = bitmap.Height;
|
|
|
|
// Create sharpening filter.
|
|
const int filterSize = 5;
|
|
|
|
var filter = new double[,]
|
|
{
|
|
{-1, -1, -1, -1, -1},
|
|
{-1, 2, 2, 2, -1},
|
|
{-1, 2, 16, 2, -1},
|
|
{-1, 2, 2, 2, -1},
|
|
{-1, -1, -1, -1, -1}
|
|
};
|
|
|
|
double bias = 1.0 - sharpnessAdjusted;
|
|
double factor = sharpnessAdjusted / 16.0;
|
|
|
|
const int s = filterSize / 2;
|
|
|
|
var result = new Color[bitmap.Width, bitmap.Height];
|
|
|
|
// Lock image bits for read/write.
|
|
BitmapData pbits = bitmap.LockBits(new Rectangle(0, 0, width, height),
|
|
ImageLockMode.ReadWrite,
|
|
bitmap.PixelFormat);
|
|
|
|
// Declare an array to hold the bytes of the bitmap.
|
|
int bytes = pbits.Stride * height;
|
|
var rgbValues = new byte[bytes];
|
|
|
|
// Copy the RGB values into the array.
|
|
Marshal.Copy(pbits.Scan0, rgbValues, 0, bytes);
|
|
|
|
int rgb;
|
|
// Fill the color array with the new sharpened color values.
|
|
for (int x = s; x < width - s; x++)
|
|
{
|
|
for (int y = s; y < height - s; y++)
|
|
{
|
|
double red = 0.0, green = 0.0, blue = 0.0;
|
|
|
|
for (int filterX = 0; filterX < filterSize; filterX++)
|
|
{
|
|
for (int filterY = 0; filterY < filterSize; filterY++)
|
|
{
|
|
int imageX = (x - s + filterX + width) % width;
|
|
int imageY = (y - s + filterY + height) % height;
|
|
|
|
rgb = imageY * pbits.Stride + bytesPerPixel * imageX;
|
|
|
|
red += rgbValues[rgb + 2] * filter[filterX, filterY];
|
|
green += rgbValues[rgb + 1] * filter[filterX, filterY];
|
|
blue += rgbValues[rgb + 0] * filter[filterX, filterY];
|
|
}
|
|
|
|
rgb = y * pbits.Stride + bytesPerPixel * x;
|
|
|
|
int r = Math.Min(Math.Max((int)(factor * red + (bias * rgbValues[rgb + 2])), 0), 255);
|
|
int g = Math.Min(Math.Max((int)(factor * green + (bias * rgbValues[rgb + 1])), 0), 255);
|
|
int b = Math.Min(Math.Max((int)(factor * blue + (bias * rgbValues[rgb + 0])), 0), 255);
|
|
|
|
result[x, y] = Color.FromArgb(r, g, b);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the image with the sharpened pixels.
|
|
for (int x = s; x < width - s; x++)
|
|
{
|
|
for (int y = s; y < height - s; y++)
|
|
{
|
|
rgb = y * pbits.Stride + bytesPerPixel * x;
|
|
|
|
rgbValues[rgb + 2] = result[x, y].R;
|
|
rgbValues[rgb + 1] = result[x, y].G;
|
|
rgbValues[rgb + 0] = result[x, y].B;
|
|
}
|
|
}
|
|
|
|
// Copy the RGB values back to the bitmap.
|
|
Marshal.Copy(rgbValues, 0, pbits.Scan0, bytes);
|
|
// Release image bits.
|
|
bitmap.UnlockBits(pbits);
|
|
|
|
return image;
|
|
}
|
|
|
|
protected override GdiImage PerformTransform(GdiImage image, RotationTransform transform)
|
|
{
|
|
if (Math.Abs(transform.Angle - 0.0) < RotationTransform.TOLERANCE)
|
|
{
|
|
return image;
|
|
}
|
|
if (Math.Abs(transform.Angle - 90.0) < RotationTransform.TOLERANCE)
|
|
{
|
|
image.Bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
|
|
return image;
|
|
}
|
|
if (Math.Abs(transform.Angle - 180.0) < RotationTransform.TOLERANCE)
|
|
{
|
|
image.Bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
|
|
return image;
|
|
}
|
|
if (Math.Abs(transform.Angle - 270.0) < RotationTransform.TOLERANCE)
|
|
{
|
|
image.Bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
|
|
return image;
|
|
}
|
|
Bitmap result;
|
|
if (transform.Angle > 45.0 && transform.Angle < 135.0 || transform.Angle > 225.0 && transform.Angle < 315.0)
|
|
{
|
|
result = new Bitmap(image.Height, image.Width, PixelFormat.Format24bppRgb);
|
|
result.SafeSetResolution(image.VerticalResolution, image.HorizontalResolution);
|
|
}
|
|
else
|
|
{
|
|
result = new Bitmap(image.Width, image.Height, PixelFormat.Format24bppRgb);
|
|
result.SafeSetResolution(image.HorizontalResolution, image.VerticalResolution);
|
|
}
|
|
using (var g = Graphics.FromImage(result))
|
|
{
|
|
g.Clear(Color.White);
|
|
g.TranslateTransform(result.Width / 2.0f, result.Height / 2.0f);
|
|
g.RotateTransform((float)transform.Angle);
|
|
g.TranslateTransform(-image.Width / 2.0f, -image.Height / 2.0f);
|
|
g.DrawImage(image.Bitmap, new Rectangle(0, 0, image.Width, image.Height));
|
|
}
|
|
var resultImage = new GdiImage(result);
|
|
OptimizePixelFormat(image, ref resultImage);
|
|
image.Dispose();
|
|
return resultImage;
|
|
}
|
|
|
|
protected override GdiImage PerformTransform(GdiImage image, CropTransform transform)
|
|
{
|
|
double xScale = image.Width / (double)(transform.OriginalWidth ?? image.Width),
|
|
yScale = image.Height / (double)(transform.OriginalHeight ?? image.Height);
|
|
|
|
int x = (int)Math.Round(transform.Left * xScale);
|
|
int y = (int)Math.Round(transform.Top * yScale);
|
|
int width = Math.Max(image.Width - (int)Math.Round((transform.Left + transform.Right) * xScale), 1);
|
|
int height = Math.Max(image.Height - (int)Math.Round((transform.Top + transform.Bottom) * yScale), 1);
|
|
|
|
var result = new Bitmap(width, height, image.Bitmap.PixelFormat);
|
|
var resultImage = new GdiImage(result);
|
|
result.SafeSetResolution(image.HorizontalResolution, image.VerticalResolution);
|
|
if (image.Bitmap.PixelFormat == PixelFormat.Format1bppIndexed)
|
|
{
|
|
result.Palette.Entries[0] = image.Bitmap.Palette.Entries[0];
|
|
result.Palette.Entries[1] = image.Bitmap.Palette.Entries[1];
|
|
}
|
|
UnsafeImageOps.RowWiseCopy(image, resultImage, x, y, width, height);
|
|
image.Dispose();
|
|
return resultImage;
|
|
}
|
|
|
|
protected override GdiImage PerformTransform(GdiImage image, ScaleTransform transform)
|
|
{
|
|
int realWidth = (int)Math.Round(image.Width * transform.ScaleFactor);
|
|
int realHeight = (int)Math.Round(image.Height * transform.ScaleFactor);
|
|
|
|
double horizontalRes = image.HorizontalResolution * transform.ScaleFactor;
|
|
double verticalRes = image.VerticalResolution * transform.ScaleFactor;
|
|
|
|
var result = new Bitmap(realWidth, realHeight, PixelFormat.Format24bppRgb);
|
|
using Graphics g = Graphics.FromImage(result);
|
|
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
g.DrawImage(image.Bitmap, 0, 0, realWidth, realHeight);
|
|
result.SafeSetResolution((float)horizontalRes, (float)verticalRes);
|
|
return new GdiImage(result);
|
|
}
|
|
|
|
protected override GdiImage PerformTransform(GdiImage image, ThumbnailTransform transform)
|
|
{
|
|
var result = new Bitmap(transform.Size, transform.Size);
|
|
using (Graphics g = Graphics.FromImage(result))
|
|
{
|
|
// The location and dimensions of the old bitmap, scaled and positioned within the thumbnail bitmap
|
|
int left, top, width, height;
|
|
|
|
// We want a nice thumbnail, so use the maximum quality interpolation
|
|
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
|
|
if (image.Width > image.Height)
|
|
{
|
|
// Fill the new bitmap's width
|
|
width = transform.Size;
|
|
left = 0;
|
|
// Scale the drawing height to match the original bitmap's aspect ratio
|
|
height = (int)(image.Height * (transform.Size / (double)image.Width));
|
|
// Center the drawing vertically
|
|
top = (transform.Size - height) / 2;
|
|
}
|
|
else
|
|
{
|
|
// Fill the new bitmap's height
|
|
height = transform.Size;
|
|
top = 0;
|
|
// Scale the drawing width to match the original bitmap's aspect ratio
|
|
width = (int)(image.Width * (transform.Size / (double)image.Height));
|
|
// Center the drawing horizontally
|
|
left = (transform.Size - width) / 2;
|
|
}
|
|
|
|
// Draw the original bitmap onto the new bitmap, using the calculated location and dimensions
|
|
// Note that there may be some padding if the aspect ratios don't match
|
|
var destRect = new RectangleF(left, top, width, height);
|
|
var srcRect = new RectangleF(0, 0, image.Width, image.Height);
|
|
g.DrawImage(image.Bitmap, destRect, srcRect, GraphicsUnit.Pixel);
|
|
// Draw a border around the original bitmap's content, inside the padding
|
|
g.DrawRectangle(Pens.Black, left, top, width - 1, height - 1);
|
|
}
|
|
return new GdiImage(result);
|
|
}
|
|
|
|
public override void EnsurePixelFormat(ref GdiImage image)
|
|
{
|
|
if (image.PixelFormat == ImagePixelFormat.BW1)
|
|
{
|
|
// Copy B&W over to grayscale
|
|
var bitmap2 = new Bitmap(image.Width, image.Height, PixelFormat.Format24bppRgb);
|
|
bitmap2.SafeSetResolution(image.HorizontalResolution, image.VerticalResolution);
|
|
using (var g = Graphics.FromImage(bitmap2))
|
|
{
|
|
g.DrawImage(image.Bitmap, 0, 0);
|
|
}
|
|
image.Dispose();
|
|
image = new GdiImage(bitmap2);
|
|
}
|
|
}
|
|
|
|
protected override void OptimizePixelFormat(GdiImage original, ref GdiImage result)
|
|
{
|
|
if (original.PixelFormat == ImagePixelFormat.BW1)
|
|
{
|
|
var bitmap2 = (Bitmap)BitmapHelper.CopyToBpp(result.Bitmap, 1).Clone();
|
|
result.Dispose();
|
|
result = new GdiImage(bitmap2);
|
|
}
|
|
}
|
|
} |