diff --git a/NAPS2.Images.Gdi/BitmapExtensions.cs b/NAPS2.Images.Gdi/BitmapExtensions.cs deleted file mode 100644 index b49e0d972..000000000 --- a/NAPS2.Images.Gdi/BitmapExtensions.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/NAPS2.Images.Gdi/GdiExtensions.cs b/NAPS2.Images.Gdi/GdiExtensions.cs index 9a465c75e..d97421228 100644 --- a/NAPS2.Images.Gdi/GdiExtensions.cs +++ b/NAPS2.Images.Gdi/GdiExtensions.cs @@ -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; + } + } } diff --git a/NAPS2.Images.Gdi/GdiImage.cs b/NAPS2.Images.Gdi/GdiImage.cs index 169ec4004..fe21bde8c 100644 --- a/NAPS2.Images.Gdi/GdiImage.cs +++ b/NAPS2.Images.Gdi/GdiImage.cs @@ -13,6 +13,9 @@ public class GdiImage : IMemoryImage Bitmap = bitmap ?? throw new ArgumentNullException(nameof(bitmap)); } + /// + /// Gets the underlying System.Drawing.Bitmap object for this image. + /// 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(); + } } \ No newline at end of file diff --git a/NAPS2.Images/IMemoryImage.cs b/NAPS2.Images/IMemoryImage.cs index bfc4598e1..68ca90227 100644 --- a/NAPS2.Images/IMemoryImage.cs +++ b/NAPS2.Images/IMemoryImage.cs @@ -6,25 +6,73 @@ /// public interface IMemoryImage : IImageStorage { + /// + /// Gets the image's width in pixels. + /// int Width { get; } + /// + /// Gets the image's height in pixels. + /// int Height { get; } + /// + /// Gets the image's horizontal resolution in pixels per inch. + /// float HorizontalResolution { get; } + /// + /// Gets the image's vertical resolution in pixels per inch. + /// float VerticalResolution { get; } + /// + /// Sets the image's horizontal and vertical resolution in pixels per inch. This has no effect for invalid (zero or + /// negative) values. + /// + /// The horizontal resolution. + /// The vertical resolution. void SetResolution(float xDpi, float yDpi); + /// + /// Gets the bits per pixel for the image's underlying binary data. + /// ImagePixelFormat PixelFormat { get; } - ImageFileFormat OriginalFileFormat { get; } - + /// + /// Obtains access to the underlying binary data for the image. + /// + /// The access level (read/write) needed. + /// A pointer to the start of the binary data. + /// The number of bytes per row in the binary data. + /// An object that, when disposed, releases the lock. ImageLockState Lock(LockMode lockMode, out IntPtr scan0, out int stride); + /// + /// Gets the original image file's format (e.g. png/jpeg) if known. + /// + ImageFileFormat OriginalFileFormat { get; } + + /// + /// Saves the image to the given file path. If the file format is unspecified, it will be inferred from the + /// file extension if possible. + /// + /// The path to save the image file to. + /// The file format to use. + /// The quality parameter for JPEG compression, if applicable. -1 for default. void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unspecified, int quality = -1); + /// + /// Saves the image to the given stream. The file format must be specified. + /// + /// The stream to save the image to. + /// The file format to use. + /// The quality parameter for JPEG compression, if applicable. -1 for default. void Save(Stream stream, ImageFileFormat imageFormat, int quality = -1); + /// + /// Creates a copy of the image so that one can be edited or disposed without affecting the other. + /// + /// The copy. IMemoryImage Clone(); } \ No newline at end of file