naps2/NAPS2.Images/ImageExtensions.cs
2024-03-01 10:43:21 -08:00

244 lines
9.8 KiB
C#

using System.Diagnostics.CodeAnalysis;
using NAPS2.Images.Bitwise;
namespace NAPS2.Images;
public static class ImageExtensions
{
public static IMemoryImage Render(this IRenderableImage image)
{
return image.ImageContext.Render(image);
}
/// <summary>
/// Checks if we can copy the source JPEG directly rather than re-encoding and suffering JPEG degradation.
/// </summary>
/// <param name="image"></param>
/// <param name="jpegPath"></param>
/// <returns></returns>
internal static bool IsUntransformedJpegFile(this IRenderableImage image, out string jpegPath)
{
if (image is { Storage: ImageFileStorage fileStorage, TransformState.IsEmpty: true } &&
ImageContext.GetFileFormatFromExtension(fileStorage.FullPath) == ImageFileFormat.Jpeg)
{
jpegPath = fileStorage.FullPath;
return true;
}
jpegPath = null!;
return false;
}
/// <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="image">The image to save.</param>
/// <param name="path">The path to save the image file to.</param>
/// <param name="imageFormat">The file format to use.</param>
/// <param name="options">Options for saving, e.g. JPEG quality.</param>
public static void Save(this IRenderableImage image, string path,
ImageFileFormat imageFormat = ImageFileFormat.Unknown, ImageSaveOptions? options = null)
{
if (imageFormat == ImageFileFormat.Unknown)
{
imageFormat = ImageContext.GetFileFormatFromExtension(path);
}
if (imageFormat == ImageFileFormat.Jpeg && image.IsUntransformedJpegFile(out var jpegPath))
{
File.Copy(jpegPath, path);
return;
}
using var renderedImage = image.Render();
renderedImage.Save(path, imageFormat, options);
}
/// <summary>
/// Saves the image to the given stream. The file format must be specified.
/// </summary>
/// <param name="image">The image to save.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="imageFormat">The file format to use.</param>
/// <param name="options">Options for saving, e.g. JPEG quality.</param>
public static void Save(this IRenderableImage image, Stream stream,
ImageFileFormat imageFormat = ImageFileFormat.Unknown, ImageSaveOptions? options = null)
{
if (imageFormat == ImageFileFormat.Unknown)
{
throw new ArgumentException("Format required to save to a stream", nameof(imageFormat));
}
if (imageFormat == ImageFileFormat.Jpeg && image.IsUntransformedJpegFile(out var jpegPath))
{
using var fileStream = File.OpenRead(jpegPath);
fileStream.CopyTo(stream);
return;
}
using var renderedImage = image.Render();
renderedImage.Save(stream, imageFormat, options);
}
/// <summary>
/// Saves the image to a new MemoryStream object. The file format must be specified.
/// </summary>
/// <param name="image">The image to save.</param>
/// <param name="imageFormat">The file format to use.</param>
/// <param name="options">Options for saving, e.g. JPEG quality.</param>
public static MemoryStream SaveToMemoryStream(this IMemoryImage image, ImageFileFormat imageFormat,
ImageSaveOptions? options = null)
{
var stream = new MemoryStream();
image.Save(stream, imageFormat, options);
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Saves the image to a new MemoryStream object. The file format must be specified.
/// </summary>
/// <param name="image">The image to save.</param>
/// <param name="imageFormat">The file format to use.</param>
/// <param name="options">Options for saving, e.g. JPEG quality.</param>
public static MemoryStream SaveToMemoryStream(this IRenderableImage image, ImageFileFormat imageFormat,
ImageSaveOptions? options = null)
{
var stream = new MemoryStream();
image.Save(stream, imageFormat, options);
return stream;
}
public static IMemoryImage PerformTransform(this IMemoryImage image, Transform transform)
{
return image.ImageContext.PerformTransform(image, transform);
}
public static IMemoryImage PerformAllTransforms(this IMemoryImage image, IEnumerable<Transform> transforms)
{
return image.ImageContext.PerformAllTransforms(image, transforms);
}
public static ImagePixelFormat UpdateLogicalPixelFormat(this IMemoryImage image)
{
if (image.LogicalPixelFormat != ImagePixelFormat.Unknown)
{
return image.LogicalPixelFormat;
}
var op = new LogicalPixelFormatOp();
op.Perform(image);
image.LogicalPixelFormat = op.LogicalPixelFormat;
return image.LogicalPixelFormat;
}
/// <summary>
/// Copies the content of this image to the destination image. It does not need to be the same pixel format, but if it's different,
/// there may be some loss of information (e.g. when converting color to gray or black/white).
/// </summary>
/// <param name="source"></param>
/// <param name="destination"></param>
public static void CopyTo(this IMemoryImage source, IMemoryImage destination)
{
new CopyBitwiseImageOp().Perform(source, destination);
}
/// <summary>
/// Creates a new image with the same content, dimensions, and resolution as this image.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static IMemoryImage Copy(this IMemoryImage source)
{
return source.CopyWithPixelFormat(source.PixelFormat);
}
/// <summary>
/// Creates a new image with the same content, dimensions, and resolution as this image.
/// </summary>
/// <param name="source"></param>
/// <param name="imageContext"></param>
/// <returns></returns>
public static IMemoryImage Copy(this IMemoryImage source, ImageContext imageContext)
{
return source.CopyWithPixelFormat(imageContext, source.PixelFormat);
}
/// <summary>
/// Creates a new image with the same content, dimensions, and resolution as this image, but possibly with a different pixel format.
/// This can result in some loss of information (e.g. when converting color to gray or black/white).
/// </summary>
/// <param name="source"></param>
/// <param name="pixelFormat"></param>
/// <returns></returns>
public static IMemoryImage CopyWithPixelFormat(this IMemoryImage source, ImagePixelFormat pixelFormat)
{
return source.CopyWithPixelFormat(source.ImageContext, pixelFormat);
}
/// <summary>
/// Creates a new image with the same content, dimensions, and resolution as this image, but possibly with a different pixel format.
/// This can result in some loss of information (e.g. when converting color to gray or black/white).
/// </summary>
/// <param name="source"></param>
/// <param name="imageContext"></param>
/// <param name="pixelFormat"></param>
/// <returns></returns>
public static IMemoryImage CopyWithPixelFormat(this IMemoryImage source, ImageContext imageContext,
ImagePixelFormat pixelFormat)
{
if (pixelFormat == ImagePixelFormat.Unknown) throw new ArgumentException();
var newImage = source.CopyBlankWithPixelFormat(imageContext, pixelFormat);
new CopyBitwiseImageOp().Perform(source, newImage);
newImage.OriginalFileFormat = source.OriginalFileFormat;
if (source.LogicalPixelFormat != ImagePixelFormat.Unknown)
{
newImage.LogicalPixelFormat =
source.LogicalPixelFormat < pixelFormat ? source.LogicalPixelFormat : pixelFormat;
}
return newImage;
}
/// <summary>
/// Creates a new (empty) image with the same dimensions, pixel format, and resolution as this image.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static IMemoryImage CopyBlank(this IMemoryImage source)
{
return source.CopyBlankWithPixelFormat(source.PixelFormat);
}
/// <summary>
/// Creates a new (empty) image with the same dimensions and resolution as this image, and the specified pixel format.
/// </summary>
/// <param name="source"></param>
/// <param name="pixelFormat"></param>
/// <returns></returns>
public static IMemoryImage CopyBlankWithPixelFormat(this IMemoryImage source, ImagePixelFormat pixelFormat)
{
return CopyBlankWithPixelFormat(source, source.ImageContext, pixelFormat);
}
/// <summary>
/// Creates a new (empty) image with the same dimensions and resolution as this image, and the specified pixel format.
/// </summary>
/// <param name="source"></param>
/// <param name="imageContext"></param>
/// <param name="pixelFormat"></param>
/// <returns></returns>
public static IMemoryImage CopyBlankWithPixelFormat(this IMemoryImage source, ImageContext imageContext,
ImagePixelFormat pixelFormat)
{
if (pixelFormat == ImagePixelFormat.Unknown) throw new ArgumentException();
var newImage = imageContext.Create(source.Width, source.Height, pixelFormat);
newImage.SetResolution(source.HorizontalResolution, source.VerticalResolution);
return newImage;
}
public static string AsTypeHint(this ImageFileFormat imageFormat)
{
return imageFormat switch
{
ImageFileFormat.Bmp => ".bmp",
ImageFileFormat.Jpeg => ".jpg",
ImageFileFormat.Png => ".png",
_ => ""
};
}
}