2022-09-18 00:04:38 +03:00
|
|
|
using System.Globalization;
|
2022-09-12 03:22:14 +03:00
|
|
|
using Gdk;
|
|
|
|
using NAPS2.Images.Bitwise;
|
2022-11-03 22:08:58 +03:00
|
|
|
using NAPS2.Util;
|
2022-09-12 03:22:14 +03:00
|
|
|
|
|
|
|
namespace NAPS2.Images.Gtk;
|
|
|
|
|
|
|
|
public class GtkImage : IMemoryImage
|
|
|
|
{
|
2024-04-01 10:25:25 +03:00
|
|
|
public GtkImage(Pixbuf pixbuf)
|
2022-09-12 03:22:14 +03:00
|
|
|
{
|
2022-11-03 22:08:58 +03:00
|
|
|
LeakTracer.StartTracking(this);
|
2022-09-12 03:22:14 +03:00
|
|
|
Pixbuf = pixbuf;
|
2022-09-18 00:04:38 +03:00
|
|
|
HorizontalResolution = float.TryParse(pixbuf.GetOption("x-dpi"), out var xDpi) ? xDpi : 0;
|
|
|
|
VerticalResolution = float.TryParse(pixbuf.GetOption("y-dpi"), out var yDpi) ? yDpi : 0;
|
2022-09-12 03:22:14 +03:00
|
|
|
}
|
|
|
|
|
2024-04-01 10:25:25 +03:00
|
|
|
public ImageContext ImageContext { get; } = new GtkImageContext();
|
2022-09-12 03:22:14 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2024-01-04 02:52:19 +03:00
|
|
|
if (lockMode != LockMode.ReadOnly)
|
|
|
|
{
|
2024-01-04 02:55:02 +03:00
|
|
|
LogicalPixelFormat = ImagePixelFormat.Unknown;
|
2024-01-04 02:52:19 +03:00
|
|
|
}
|
2022-09-12 03:22:14 +03:00
|
|
|
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?
|
2024-04-08 03:09:22 +03:00
|
|
|
internal class GtkImageLockState : ImageLockState
|
2022-09-12 03:22:14 +03:00
|
|
|
{
|
|
|
|
public override void Dispose()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public ImageFileFormat OriginalFileFormat { get; set; }
|
|
|
|
|
2022-09-15 07:10:55 +03:00
|
|
|
public ImagePixelFormat LogicalPixelFormat { get; set; }
|
|
|
|
|
2024-01-04 02:53:38 +03:00
|
|
|
public void Save(string path, ImageFileFormat imageFormat = ImageFileFormat.Unknown,
|
2023-02-06 00:54:21 +03:00
|
|
|
ImageSaveOptions? options = null)
|
2022-09-12 03:22:14 +03:00
|
|
|
{
|
2024-01-04 02:53:38 +03:00
|
|
|
if (imageFormat == ImageFileFormat.Unknown)
|
2022-09-12 03:22:14 +03:00
|
|
|
{
|
|
|
|
imageFormat = ImageContext.GetFileFormatFromExtension(path);
|
|
|
|
}
|
2022-12-19 05:39:10 +03:00
|
|
|
if (imageFormat == ImageFileFormat.Tiff)
|
|
|
|
{
|
2023-12-07 07:17:50 +03:00
|
|
|
((GtkImageContext) ImageContext).TiffIo.SaveTiff([this], path);
|
2022-12-19 05:39:10 +03:00
|
|
|
return;
|
|
|
|
}
|
2022-10-11 04:25:59 +03:00
|
|
|
ImageContext.CheckSupportsFormat(imageFormat);
|
2023-02-06 00:54:21 +03:00
|
|
|
options ??= new ImageSaveOptions();
|
2022-09-12 03:22:14 +03:00
|
|
|
var type = GetType(imageFormat);
|
2023-02-06 00:54:21 +03:00
|
|
|
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);
|
2022-09-12 03:22:14 +03:00
|
|
|
}
|
|
|
|
|
2023-02-06 00:54:21 +03:00
|
|
|
public void Save(Stream stream, ImageFileFormat imageFormat, ImageSaveOptions? options = null)
|
2022-09-12 03:22:14 +03:00
|
|
|
{
|
2024-01-04 02:53:38 +03:00
|
|
|
if (imageFormat == ImageFileFormat.Unknown)
|
2022-09-12 03:22:14 +03:00
|
|
|
{
|
|
|
|
throw new ArgumentException("Format required to save to a stream", nameof(imageFormat));
|
|
|
|
}
|
2022-12-19 05:39:10 +03:00
|
|
|
if (imageFormat == ImageFileFormat.Tiff)
|
|
|
|
{
|
2023-12-07 07:17:50 +03:00
|
|
|
((GtkImageContext) ImageContext).TiffIo.SaveTiff([this], stream);
|
2022-12-19 05:39:10 +03:00
|
|
|
return;
|
|
|
|
}
|
2022-10-11 04:25:59 +03:00
|
|
|
ImageContext.CheckSupportsFormat(imageFormat);
|
2023-02-06 00:54:21 +03:00
|
|
|
options ??= new ImageSaveOptions();
|
2022-09-12 03:22:14 +03:00
|
|
|
var type = GetType(imageFormat);
|
2023-02-06 00:54:21 +03:00
|
|
|
var (keys, values) = GetSaveOptions(imageFormat, options.Quality);
|
2023-02-12 22:02:34 +03:00
|
|
|
// 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.
|
2023-02-06 00:54:21 +03:00
|
|
|
using var helper = PixelFormatHelper.Create(this, options.PixelFormatHint, minFormat: ImagePixelFormat.RGB24);
|
2023-02-12 22:02:34 +03:00
|
|
|
// TODO: Map to OutputStream directly?
|
2023-02-06 00:54:21 +03:00
|
|
|
stream.Write(helper.Image.Pixbuf.SaveToBuffer(type, keys, values));
|
2022-09-12 03:22:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private string GetType(ImageFileFormat fileFormat) => fileFormat switch
|
|
|
|
{
|
|
|
|
ImageFileFormat.Jpeg => "jpeg",
|
|
|
|
ImageFileFormat.Png => "png",
|
|
|
|
ImageFileFormat.Bmp => "bmp",
|
2022-09-16 05:02:09 +03:00
|
|
|
ImageFileFormat.Tiff => "tiff",
|
2022-09-12 03:22:14 +03:00
|
|
|
_ => throw new ArgumentException("Unsupported file format")
|
|
|
|
};
|
|
|
|
|
2022-09-18 00:04:38 +03:00
|
|
|
private (string[] keys, string[] values) GetSaveOptions(ImageFileFormat imageFormat, int quality)
|
|
|
|
{
|
2022-09-18 19:29:02 +03:00
|
|
|
var keys = new List<string>();
|
|
|
|
var values = new List<string>();
|
|
|
|
if (HorizontalResolution > 0 && VerticalResolution > 0)
|
2022-09-18 00:04:38 +03:00
|
|
|
{
|
2022-09-18 19:29:02 +03:00
|
|
|
keys.Add("x-dpi");
|
|
|
|
keys.Add("y-dpi");
|
|
|
|
values.Add(HorizontalResolution.ToString(CultureInfo.InvariantCulture));
|
|
|
|
values.Add(VerticalResolution.ToString(CultureInfo.InvariantCulture));
|
|
|
|
}
|
2022-09-18 00:04:38 +03:00
|
|
|
if (imageFormat == ImageFileFormat.Jpeg && quality != -1)
|
|
|
|
{
|
|
|
|
keys.Add("quality");
|
|
|
|
values.Add(quality.ToString());
|
|
|
|
}
|
|
|
|
return (keys.ToArray(), values.ToArray());
|
|
|
|
}
|
|
|
|
|
2024-04-01 10:25:25 +03:00
|
|
|
public IMemoryImage Clone() => new GtkImage((Pixbuf) Pixbuf.Clone())
|
2022-09-15 07:10:55 +03:00
|
|
|
{
|
2022-09-18 22:58:43 +03:00
|
|
|
OriginalFileFormat = OriginalFileFormat,
|
2022-12-19 04:33:23 +03:00
|
|
|
LogicalPixelFormat = LogicalPixelFormat,
|
|
|
|
HorizontalResolution = HorizontalResolution,
|
|
|
|
VerticalResolution = VerticalResolution
|
2022-09-15 07:10:55 +03:00
|
|
|
};
|
2022-09-12 03:22:14 +03:00
|
|
|
|
2022-11-03 22:08:58 +03:00
|
|
|
public void Dispose()
|
|
|
|
{
|
|
|
|
Pixbuf.Dispose();
|
|
|
|
LeakTracer.StopTracking(this);
|
|
|
|
}
|
2022-09-12 03:22:14 +03:00
|
|
|
}
|