Gtk: Fix image resolution

This commit is contained in:
Ben Olden-Cooligan 2022-09-17 14:04:38 -07:00
parent 2d7d572a38
commit e42f012188
5 changed files with 72 additions and 46 deletions

View File

@ -1,3 +1,4 @@
using System.Globalization;
using Gdk;
using NAPS2.Images.Bitwise;
@ -10,6 +11,8 @@ public class GtkImage : IMemoryImage
ImageContext = imageContext ?? throw new ArgumentNullException(nameof(imageContext));
Pixbuf = pixbuf;
LogicalPixelFormat = logicalPixelFormat;
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; }
@ -26,7 +29,6 @@ public class GtkImage : IMemoryImage
public void SetResolution(float xDpi, float yDpi)
{
// TODO: ?
HorizontalResolution = xDpi;
VerticalResolution = yDpi;
}
@ -71,14 +73,8 @@ public class GtkImage : IMemoryImage
imageFormat = ImageContext.GetFileFormatFromExtension(path);
}
var type = GetType(imageFormat);
if (imageFormat == ImageFileFormat.Jpeg && quality != -1)
{
Pixbuf.Savev(path, type, new[] { "quality" }, new[] { quality.ToString() });
}
else
{
Pixbuf.Save(path, type);
}
var (keys, values) = GetSaveOptions(imageFormat, quality);
Pixbuf.Savev(path, type, keys, values);
}
public void Save(Stream stream, ImageFileFormat imageFormat, int quality = -1)
@ -88,17 +84,9 @@ public class GtkImage : IMemoryImage
throw new ArgumentException("Format required to save to a stream", nameof(imageFormat));
}
var type = GetType(imageFormat);
var (keys, values) = GetSaveOptions(imageFormat, quality);
// TODO: Map to OutputStream directly?
byte[] buffer;
if (imageFormat == ImageFileFormat.Jpeg && quality != -1)
{
buffer = Pixbuf.SaveToBuffer(type, new[] { "quality" }, new[] { quality.ToString() });
}
else
{
buffer = Pixbuf.SaveToBuffer(type);
}
stream.Write(buffer);
stream.Write(Pixbuf.SaveToBuffer(type, keys, values));
}
private string GetType(ImageFileFormat fileFormat) => fileFormat switch
@ -110,6 +98,26 @@ public class GtkImage : IMemoryImage
_ => throw new ArgumentException("Unsupported file format")
};
private (string[] keys, string[] values) GetSaveOptions(ImageFileFormat imageFormat, int quality)
{
var keys = new List<string>
{
"x-dpi",
"y-dpi"
};
var values = new List<string>
{
HorizontalResolution.ToString(CultureInfo.InvariantCulture),
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(ImageContext, (Pixbuf) Pixbuf.Clone(), LogicalPixelFormat)
{
OriginalFileFormat = OriginalFileFormat

View File

@ -82,6 +82,8 @@ internal class LibTiffIo : ITiffWriter
private static void WriteTiffMetadata(IntPtr tiff, ImagePixelFormat pixelFormat,
TiffCompressionType compression, IMemoryImage image)
{
// TODO: A lot of these types are wrong (e.g. int32 instead of int16)
// http://www.libtiff.org/man/TIFFSetField.3t.html
LibTiff.TIFFSetField(tiff, TiffTag.ImageWidth, image.Width);
LibTiff.TIFFSetField(tiff, TiffTag.ImageHeight, image.Height);
LibTiff.TIFFSetField(tiff, TiffTag.PlanarConfig, 1);
@ -110,13 +112,15 @@ internal class LibTiffIo : ITiffWriter
}));
if (pixelFormat == ImagePixelFormat.ARGB32)
{
// TODO: I think this is completely wrong
LibTiff.TIFFSetField(tiff, TiffTag.ExtraSamples, 1);
}
if (image.HorizontalResolution != 0 && image.VerticalResolution != 0)
{
LibTiff.TIFFSetField(tiff, TiffTag.ResolutionUnit, 2);
LibTiff.TIFFSetField(tiff, TiffTag.XResolution, (int) image.HorizontalResolution);
LibTiff.TIFFSetField(tiff, TiffTag.YResolution, (int) image.VerticalResolution);
// TODO: Why do we need to write as a double? It's supposed to be a float.
LibTiff.TIFFSetField(tiff, TiffTag.XResolution, (double) image.HorizontalResolution);
LibTiff.TIFFSetField(tiff, TiffTag.YResolution, (double) image.VerticalResolution);
}
}
@ -142,9 +146,13 @@ internal class LibTiffIo : ITiffWriter
{
do
{
LibTiff.TIFFGetField(tiff, TiffTag.ImageWidth, out var w);
LibTiff.TIFFGetField(tiff, TiffTag.ImageHeight, out var h);
LibTiff.TIFFGetField(tiff, TiffTag.ImageWidth, out int w);
LibTiff.TIFFGetField(tiff, TiffTag.ImageHeight, out int h);
// TODO: Check return values
LibTiff.TIFFGetField(tiff, TiffTag.XResolution, out float xres);
LibTiff.TIFFGetField(tiff, TiffTag.YResolution, out float yres);
var img = _imageContext.Create(w, h, ImagePixelFormat.ARGB32);
img.SetResolution(xres, yres);
img.OriginalFileFormat = ImageFileFormat.Tiff;
using var imageLock = img.Lock(LockMode.WriteOnly, out var data);
ReadTiffFrame(data.safePtr, tiff, w, h);

View File

@ -56,12 +56,25 @@ internal static class LibTiff
[DllImport("libtiff.so.5")]
public static extern int TIFFWriteDirectory(IntPtr tiff);
// TODO: Clean these overloads up
[DllImport("libtiff.so.5")]
public static extern int TIFFGetField(IntPtr tiff, TiffTag tag, out int field);
[DllImport("libtiff.so.5")]
public static extern int TIFFGetField(IntPtr tiff, TiffTag tag, out float field);
[DllImport("libtiff.so.5")]
public static extern int TIFFGetField(IntPtr tiff, TiffTag tag, out double field);
[DllImport("libtiff.so.5")]
public static extern int TIFFSetField(IntPtr tiff, TiffTag tag, int field);
[DllImport("libtiff.so.5")]
public static extern int TIFFSetField(IntPtr tiff, TiffTag tag, float field);
[DllImport("libtiff.so.5")]
public static extern int TIFFSetField(IntPtr tiff, TiffTag tag, double field);
[DllImport("libtiff.so.5")]
public static extern int TIFFWriteScanline(
IntPtr tiff, tdata_t buf, int row, short sample);

View File

@ -10,27 +10,27 @@ public class LoadSaveTests : ContextualTests
[Theory]
[MemberData(nameof(TestCases))]
public void LoadFromFile(ImageFileFormat format, string ext, string resource, string[] compare)
public void LoadFromFile(ImageFileFormat format, string ext, string resource, string[] compare, bool ignoreRes)
{
var path = CopyResourceToFile(GetResource(resource), $"image{ext}");
using var image = ImageContext.Load(path);
Assert.Equal(format, image.OriginalFileFormat);
ImageAsserts.Similar(GetResource(compare[0]), image);
ImageAsserts.Similar(GetResource(compare[0]), image, ignoreResolution: ignoreRes);
}
[Theory]
[MemberData(nameof(TestCases))]
public void LoadFromStream(ImageFileFormat format, string ext, string resource, string[] compare)
public void LoadFromStream(ImageFileFormat format, string ext, string resource, string[] compare, bool ignoreRes)
{
var stream = new MemoryStream(GetResource(resource));
using var image = ImageContext.Load(stream);
Assert.Equal(format, image.OriginalFileFormat);
ImageAsserts.Similar(GetResource(compare[0]), image);
ImageAsserts.Similar(GetResource(compare[0]), image, ignoreResolution: ignoreRes);
}
[Theory]
[MemberData(nameof(TestCases))]
public void LoadFramesFromFile(ImageFileFormat format, string ext, string resource, string[] compare)
public void LoadFramesFromFile(ImageFileFormat format, string ext, string resource, string[] compare, bool ignoreRes)
{
var path = CopyResourceToFile(GetResource(resource), $"image{ext}");
var images = ImageContext.LoadFrames(path, out var count).ToArray();
@ -39,13 +39,13 @@ public class LoadSaveTests : ContextualTests
for (int i = 0; i < images.Length; i++)
{
Assert.Equal(format, images[i].OriginalFileFormat);
ImageAsserts.Similar(GetResource(compare[i]), images[i]);
ImageAsserts.Similar(GetResource(compare[i]), images[i], ignoreResolution: ignoreRes);
}
}
[Theory]
[MemberData(nameof(TestCases))]
public void LoadFramesFromStream(ImageFileFormat format, string ext, string resource, string[] compare)
public void LoadFramesFromStream(ImageFileFormat format, string ext, string resource, string[] compare, bool ignoreRes)
{
var stream = new MemoryStream(GetResource(resource));
var images = ImageContext.LoadFrames(stream, out var count).ToArray();
@ -54,32 +54,32 @@ public class LoadSaveTests : ContextualTests
for (int i = 0; i < images.Length; i++)
{
Assert.Equal(format, images[i].OriginalFileFormat);
ImageAsserts.Similar(GetResource(compare[i]), images[i]);
ImageAsserts.Similar(GetResource(compare[i]), images[i], ignoreResolution: ignoreRes);
}
}
[Theory]
[MemberData(nameof(TestCases))]
public void SaveToFile(ImageFileFormat format, string ext, string resource, string[] compare)
public void SaveToFile(ImageFileFormat format, string ext, string resource, string[] compare, bool ignoreRes)
{
var image = LoadImage(GetResource(resource));
var path = Path.Combine(FolderPath, $"image{ext}");
image.Save(path);
var image2 = ImageContext.Load(path);
Assert.Equal(format, image2.OriginalFileFormat);
ImageAsserts.Similar(GetResource(compare[0]), image2);
ImageAsserts.Similar(GetResource(compare[0]), image2, ignoreResolution: ignoreRes);
}
[Theory]
[MemberData(nameof(TestCases))]
public void SaveToStream(ImageFileFormat format, string ext, string resource, string[] compare)
public void SaveToStream(ImageFileFormat format, string ext, string resource, string[] compare, bool ignoreRes)
{
var image = LoadImage(GetResource(resource));
var stream = new MemoryStream();
image.Save(stream, format);
var image2 = ImageContext.Load(stream);
Assert.Equal(format, image2.OriginalFileFormat);
ImageAsserts.Similar(GetResource(compare[0]), image2);
ImageAsserts.Similar(GetResource(compare[0]), image2, ignoreResolution: ignoreRes);
}
private static byte[] GetResource(string resource) =>
@ -90,22 +90,22 @@ public class LoadSaveTests : ContextualTests
new object[]
{
ImageFileFormat.Png, ".png", "color_image_png",
new[] { "color_image" }
new[] { "color_image" }, false
},
new object[]
{
ImageFileFormat.Jpeg, ".jpg", "color_image",
new[] { "color_image" }
new[] { "color_image" }, false
},
new object[]
{
ImageFileFormat.Bmp, ".bmp", "color_image_bw_invertpal",
new[] { "color_image_bw" }
new[] { "color_image_bw" }, true
},
new object[]
{
ImageFileFormat.Tiff, ".tiff", "color_image_tiff",
new[] { "color_image", "color_image_h_p300", "stock_cat" }
new[] { "color_image", "color_image_h_p300", "stock_cat" }, false
},
};
}

View File

@ -9,8 +9,7 @@ public class PdfiumPdfRendererTests : ContextualTests
[Fact]
public void RenderPdfFromWord()
{
var path = Path.Combine(FolderPath, "test.pdf");
File.WriteAllBytes(path, PdfResources.word_generated_pdf);
var path = CopyResourceToFile(PdfResources.word_generated_pdf, "test.pdf");
var images = new PdfiumPdfRenderer().Render(ImageContext, path, PdfRenderSize.Default).ToList();
@ -22,21 +21,19 @@ public class PdfiumPdfRendererTests : ContextualTests
[Fact]
public void RenderPlainImagePdf()
{
var path = Path.Combine(FolderPath, "test.pdf");
File.WriteAllBytes(path, PdfResources.image_pdf);
var path = CopyResourceToFile(PdfResources.image_pdf, "test.pdf");
var images = new PdfiumPdfRenderer().Render(ImageContext, path, PdfRenderSize.Default).ToList();
Assert.Single(images);
// This also verifies that the renderer gets the actual image dpi (72)
// This also verifies that the renderer gets the actual image dpi (72)
ImageAsserts.Similar(ImageResources.color_image, images[0]);
}
[Fact]
public void RenderImageWithTextPdf()
{
var path = Path.Combine(FolderPath, "test.pdf");
File.WriteAllBytes(path, PdfResources.image_with_text_pdf);
var path = CopyResourceToFile(PdfResources.image_with_text_pdf, "test.pdf");
var images = new PdfiumPdfRenderer().Render(ImageContext, path, PdfRenderSize.Default).ToList();