naps2/NAPS2.Images.Gtk/GtkImage.cs
2024-04-07 17:09:22 -07:00

156 lines
5.4 KiB
C#

using System.Globalization;
using Gdk;
using NAPS2.Images.Bitwise;
using NAPS2.Util;
namespace NAPS2.Images.Gtk;
public class GtkImage : IMemoryImage
{
public GtkImage(Pixbuf pixbuf)
{
LeakTracer.StartTracking(this);
Pixbuf = pixbuf;
HorizontalResolution = float.TryParse(pixbuf.GetOption("x-dpi"), out var xDpi) ? xDpi : 0;
VerticalResolution = float.TryParse(pixbuf.GetOption("y-dpi"), out var yDpi) ? yDpi : 0;
}
public ImageContext ImageContext { get; } = new GtkImageContext();
public Pixbuf Pixbuf { get; }
public int Width => Pixbuf.Width;
public int Height => Pixbuf.Height;
public float HorizontalResolution { get; private set; }
public float VerticalResolution { get; private set; }
public void SetResolution(float xDpi, float yDpi)
{
HorizontalResolution = xDpi;
VerticalResolution = yDpi;
}
public ImagePixelFormat PixelFormat => (Pixbuf.NChannels, Pixbuf.BitsPerSample) switch
{
(3, 8) => ImagePixelFormat.RGB24,
(4, 8) => ImagePixelFormat.ARGB32,
_ => throw new InvalidOperationException("Unsupported pixel format")
};
public ImageLockState Lock(LockMode lockMode, out BitwiseImageData imageData)
{
if (lockMode != LockMode.ReadOnly)
{
LogicalPixelFormat = ImagePixelFormat.Unknown;
}
var ptr = Pixbuf.Pixels;
var stride = Pixbuf.Rowstride;
var subPixelType = PixelFormat switch
{
ImagePixelFormat.RGB24 => SubPixelType.Rgb,
ImagePixelFormat.ARGB32 => SubPixelType.Rgba,
_ => throw new InvalidOperationException("Unsupported pixel format")
};
imageData = new BitwiseImageData(ptr, new PixelInfo(Width, Height, subPixelType, stride));
return new GtkImageLockState();
}
// TODO: Should we implement some kind of actual locking?
internal class GtkImageLockState : ImageLockState
{
public override void Dispose()
{
}
}
public ImageFileFormat OriginalFileFormat { get; set; }
public ImagePixelFormat LogicalPixelFormat { get; set; }
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unknown,
ImageSaveOptions? options = null)
{
if (imageFormat == ImageFileFormat.Unknown)
{
imageFormat = ImageContext.GetFileFormatFromExtension(path);
}
if (imageFormat == ImageFileFormat.Tiff)
{
((GtkImageContext) ImageContext).TiffIo.SaveTiff([this], path);
return;
}
ImageContext.CheckSupportsFormat(imageFormat);
options ??= new ImageSaveOptions();
var type = GetType(imageFormat);
var (keys, values) = GetSaveOptions(imageFormat, options.Quality);
using var helper = PixelFormatHelper.Create(this, options.PixelFormatHint, minFormat: ImagePixelFormat.RGB24);
helper.Image.Pixbuf.Savev(path, type, keys, values);
}
public void Save(Stream stream, ImageFileFormat imageFormat, ImageSaveOptions? options = null)
{
if (imageFormat == ImageFileFormat.Unknown)
{
throw new ArgumentException("Format required to save to a stream", nameof(imageFormat));
}
if (imageFormat == ImageFileFormat.Tiff)
{
((GtkImageContext) ImageContext).TiffIo.SaveTiff([this], stream);
return;
}
ImageContext.CheckSupportsFormat(imageFormat);
options ??= new ImageSaveOptions();
var type = GetType(imageFormat);
var (keys, values) = GetSaveOptions(imageFormat, options.Quality);
// TODO: GDK doesn't support optimizing bit depth (e.g. 1bit/8bit instead of 24bit/32bit) for BMP/PNG/JPEG.
// We'd probably need to use libpng/libjpeg etc. directly to fix that.
using var helper = PixelFormatHelper.Create(this, options.PixelFormatHint, minFormat: ImagePixelFormat.RGB24);
// TODO: Map to OutputStream directly?
stream.Write(helper.Image.Pixbuf.SaveToBuffer(type, keys, values));
}
private string GetType(ImageFileFormat fileFormat) => fileFormat switch
{
ImageFileFormat.Jpeg => "jpeg",
ImageFileFormat.Png => "png",
ImageFileFormat.Bmp => "bmp",
ImageFileFormat.Tiff => "tiff",
_ => throw new ArgumentException("Unsupported file format")
};
private (string[] keys, string[] values) GetSaveOptions(ImageFileFormat imageFormat, int quality)
{
var keys = new List<string>();
var values = new List<string>();
if (HorizontalResolution > 0 && VerticalResolution > 0)
{
keys.Add("x-dpi");
keys.Add("y-dpi");
values.Add(HorizontalResolution.ToString(CultureInfo.InvariantCulture));
values.Add(VerticalResolution.ToString(CultureInfo.InvariantCulture));
}
if (imageFormat == ImageFileFormat.Jpeg && quality != -1)
{
keys.Add("quality");
values.Add(quality.ToString());
}
return (keys.ToArray(), values.ToArray());
}
public IMemoryImage Clone() => new GtkImage((Pixbuf) Pixbuf.Clone())
{
OriginalFileFormat = OriginalFileFormat,
LogicalPixelFormat = LogicalPixelFormat,
HorizontalResolution = HorizontalResolution,
VerticalResolution = VerticalResolution
};
public void Dispose()
{
Pixbuf.Dispose();
LeakTracer.StopTracking(this);
}
}