Clean up IMemoryImage/GdiImage/GdiExtensions

This commit is contained in:
Ben Olden-Cooligan 2022-07-28 20:57:37 -07:00
parent 9394262da4
commit 7e162d82bd
4 changed files with 107 additions and 60 deletions

View File

@ -1,14 +0,0 @@
using System.Drawing;
namespace NAPS2.Images.Gdi;
public static class BitmapExtensions
{
public static void SafeSetResolution(this Bitmap image, float xDpi, float yDpi)
{
if (xDpi > 0 && yDpi > 0)
{
image.SetResolution(xDpi, yDpi);
}
}
}

View File

@ -5,6 +5,14 @@ namespace NAPS2.Images.Gdi;
public static class GdiExtensions
{
public static void SafeSetResolution(this Bitmap image, float xDpi, float yDpi)
{
if (xDpi > 0 && yDpi > 0)
{
image.SetResolution(xDpi, yDpi);
}
}
public static Bitmap AsBitmap(this IMemoryImage image)
{
var gdiImage = image as GdiImage ?? throw new ArgumentException("Expected a GdiImage", nameof(image));
@ -56,4 +64,32 @@ public static class GdiExtensions
}
throw new ArgumentException("Unsupported pixel format: " + pixelFormat);
}
public static ImagePixelFormat AsImagePixelFormat(this PixelFormat pixelFormat)
{
switch (pixelFormat)
{
case PixelFormat.Format24bppRgb:
return ImagePixelFormat.RGB24;
case PixelFormat.Format32bppArgb:
return ImagePixelFormat.ARGB32;
case PixelFormat.Format1bppIndexed:
return ImagePixelFormat.BW1;
default:
return ImagePixelFormat.Unsupported;
}
}
public static ImageLockMode AsImageLockMode(this LockMode lockMode)
{
switch (lockMode)
{
case LockMode.ReadOnly:
return ImageLockMode.ReadOnly;
case LockMode.WriteOnly:
return ImageLockMode.WriteOnly;
default:
return ImageLockMode.ReadWrite;
}
}
}

View File

@ -13,6 +13,9 @@ public class GdiImage : IMemoryImage
Bitmap = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
}
/// <summary>
/// Gets the underlying System.Drawing.Bitmap object for this image.
/// </summary>
public Bitmap Bitmap { get; }
public int Width => Bitmap.Width;
@ -25,48 +28,19 @@ public class GdiImage : IMemoryImage
public void SetResolution(float xDpi, float yDpi) => Bitmap.SafeSetResolution(xDpi, yDpi);
public ImagePixelFormat PixelFormat
{
get
{
switch (Bitmap.PixelFormat)
{
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
return ImagePixelFormat.RGB24;
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
return ImagePixelFormat.ARGB32;
case System.Drawing.Imaging.PixelFormat.Format1bppIndexed:
return ImagePixelFormat.BW1;
default:
return ImagePixelFormat.Unsupported;
}
}
}
public ImagePixelFormat PixelFormat => Bitmap.PixelFormat.AsImagePixelFormat();
public ImageFileFormat OriginalFileFormat => Bitmap.RawFormat.AsImageFileFormat();
public ImageLockState Lock(LockMode lockMode, out IntPtr scan0, out int stride)
{
var bitmapData = Bitmap.LockBits(new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), GetGdiLockMode(lockMode),
var bitmapData = Bitmap.LockBits(new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), lockMode.AsImageLockMode(),
Bitmap.PixelFormat);
scan0 = bitmapData.Scan0;
stride = Math.Abs(bitmapData.Stride);
return new GdiImageLockState(Bitmap, bitmapData);
}
private ImageLockMode GetGdiLockMode(LockMode lockMode)
{
switch (lockMode)
{
case LockMode.ReadOnly:
return ImageLockMode.ReadOnly;
case LockMode.WriteOnly:
return ImageLockMode.WriteOnly;
default:
return ImageLockMode.ReadWrite;
}
}
public void Save(string path, ImageFileFormat imageFileFormat = ImageFileFormat.Unspecified, int quality = -1)
{
if (imageFileFormat == ImageFileFormat.Unspecified)
@ -75,10 +49,7 @@ public class GdiImage : IMemoryImage
}
if (imageFileFormat == ImageFileFormat.Jpeg && quality != -1)
{
quality = Math.Max(Math.Min(quality, 100), 0);
var encoder = ImageCodecInfo.GetImageEncoders().First(x => x.FormatID == ImageFormat.Jpeg.Guid);
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
var (encoder, encoderParams) = GetJpegSaveArgs(quality);
Bitmap.Save(path, encoder, encoderParams);
}
else
@ -95,10 +66,7 @@ public class GdiImage : IMemoryImage
}
if (imageFileFormat == ImageFileFormat.Jpeg && quality != -1)
{
quality = Math.Max(Math.Min(quality, 100), 0);
var encoder = ImageCodecInfo.GetImageEncoders().First(x => x.FormatID == ImageFormat.Jpeg.Guid);
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
var (encoder, encoderParams) = GetJpegSaveArgs(quality);
Bitmap.Save(stream, encoder, encoderParams);
}
else
@ -107,6 +75,15 @@ public class GdiImage : IMemoryImage
}
}
private static (ImageCodecInfo, EncoderParameters) GetJpegSaveArgs(int quality)
{
quality = Math.Max(Math.Min(quality, 100), 0);
var encoder = ImageCodecInfo.GetImageEncoders().First(x => x.FormatID == ImageFormat.Jpeg.Guid);
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
return (encoder, encoderParams);
}
private ImageFileFormat GetFileFormatFromExtension(string path)
{
return Path.GetExtension(path).ToLowerInvariant() switch
@ -118,13 +95,13 @@ public class GdiImage : IMemoryImage
};
}
public void Dispose()
{
Bitmap.Dispose();
}
public IMemoryImage Clone()
{
return new GdiImage((Bitmap) Bitmap.Clone());
}
public void Dispose()
{
Bitmap.Dispose();
}
}

View File

@ -6,25 +6,73 @@
/// </summary>
public interface IMemoryImage : IImageStorage
{
/// <summary>
/// Gets the image's width in pixels.
/// </summary>
int Width { get; }
/// <summary>
/// Gets the image's height in pixels.
/// </summary>
int Height { get; }
/// <summary>
/// Gets the image's horizontal resolution in pixels per inch.
/// </summary>
float HorizontalResolution { get; }
/// <summary>
/// Gets the image's vertical resolution in pixels per inch.
/// </summary>
float VerticalResolution { get; }
/// <summary>
/// Sets the image's horizontal and vertical resolution in pixels per inch. This has no effect for invalid (zero or
/// negative) values.
/// </summary>
/// <param name="xDpi">The horizontal resolution.</param>
/// <param name="yDpi">The vertical resolution.</param>
void SetResolution(float xDpi, float yDpi);
/// <summary>
/// Gets the bits per pixel for the image's underlying binary data.
/// </summary>
ImagePixelFormat PixelFormat { get; }
ImageFileFormat OriginalFileFormat { get; }
/// <summary>
/// Obtains access to the underlying binary data for the image.
/// </summary>
/// <param name="lockMode">The access level (read/write) needed.</param>
/// <param name="scan0">A pointer to the start of the binary data.</param>
/// <param name="stride">The number of bytes per row in the binary data.</param>
/// <returns>An object that, when disposed, releases the lock.</returns>
ImageLockState Lock(LockMode lockMode, out IntPtr scan0, out int stride);
/// <summary>
/// Gets the original image file's format (e.g. png/jpeg) if known.
/// </summary>
ImageFileFormat OriginalFileFormat { get; }
/// <summary>
/// Saves the image to the given file path. If the file format is unspecified, it will be inferred from the
/// file extension if possible.
/// </summary>
/// <param name="path">The path to save the image file to.</param>
/// <param name="imageFormat">The file format to use.</param>
/// <param name="quality">The quality parameter for JPEG compression, if applicable. -1 for default.</param>
void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, int quality = -1);
/// <summary>
/// Saves the image to the given stream. The file format must be specified.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="imageFormat">The file format to use.</param>
/// <param name="quality">The quality parameter for JPEG compression, if applicable. -1 for default.</param>
void Save(Stream stream, ImageFileFormat imageFormat, int quality = -1);
/// <summary>
/// Creates a copy of the image so that one can be edited or disposed without affecting the other.
/// </summary>
/// <returns>The copy.</returns>
IMemoryImage Clone();
}