From e628127547ceb14212b6a6343af33187d35b06cc Mon Sep 17 00:00:00 2001 From: Ben Olden-Cooligan Date: Sat, 17 Sep 2022 00:06:57 -0700 Subject: [PATCH] Gtk: Tiff stream support --- NAPS2.Images.Gtk/GtkImageContext.cs | 44 +++++----- NAPS2.Images.Gtk/LibTiff.cs | 43 +++++++++- NAPS2.Images.Gtk/LibTiffStreamClient.cs | 104 ++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 NAPS2.Images.Gtk/LibTiffStreamClient.cs diff --git a/NAPS2.Images.Gtk/GtkImageContext.cs b/NAPS2.Images.Gtk/GtkImageContext.cs index 50a5f45ae..b89cc3941 100644 --- a/NAPS2.Images.Gtk/GtkImageContext.cs +++ b/NAPS2.Images.Gtk/GtkImageContext.cs @@ -1,6 +1,5 @@ using System.Threading; using Gdk; -using NAPS2.Images.Bitwise; namespace NAPS2.Images.Gtk; @@ -44,24 +43,24 @@ public class GtkImageContext : ImageContext public override IEnumerable LoadFrames(Stream stream, out int count) { - // TODO + var format = GetFileFormatFromFirstBytes(stream); + if (format == ImageFileFormat.Tiff) + { + return LoadTiff(stream, out count); + } count = 1; return new[] { Load(stream) }; } public override IEnumerable LoadFrames(string path, out int count) { - // TODO var format = GetFileFormatFromExtension(path, true); if (format == ImageFileFormat.Tiff) { return LoadTiff(path, out count); } - else - { - count = 1; - return new[] { Load(path) }; - } + count = 1; + return new[] { Load(path) }; } public override bool SaveTiff(IList images, string path, TiffCompressionType compression = TiffCompressionType.Auto, @@ -76,28 +75,34 @@ public class GtkImageContext : ImageContext throw new NotImplementedException(); } + private IEnumerable LoadTiff(Stream stream, out int count) + { + var c = new LibTiffStreamClient(stream); + var tiff = c.TIFFClientOpen("r"); + count = LibTiff.TIFFNumberOfDirectories(tiff); + return EnumerateTiffFrames(tiff, c); + } + private IEnumerable LoadTiff(string path, out int count) { var tiff = LibTiff.TIFFOpen(path, "r"); count = LibTiff.TIFFNumberOfDirectories(tiff); - return EnumerateTiffFrames(tiff); + return EnumerateTiffFrames(tiff, null); } - private IEnumerable EnumerateTiffFrames(IntPtr tiff) + private IEnumerable EnumerateTiffFrames(IntPtr tiff, LibTiffStreamClient? client) { + // We keep a reference to the client to avoid garbage collection try { do { LibTiff.TIFFGetField(tiff, 256, out var w); LibTiff.TIFFGetField(tiff, 257, out var h); - var buffer = new byte[w * h * 4]; - ReadTiffFrame(buffer, tiff, w, h); - var bufferInfo = new PixelInfo(w, h, SubPixelType.Rgba) { InvertY = true }; - // TODO: Get pixel format from tiff var img = Create(w, h, ImagePixelFormat.ARGB32); img.OriginalFileFormat = ImageFileFormat.Tiff; - new CopyBitwiseImageOp().Perform(buffer, bufferInfo, img); + using var imageLock = img.Lock(LockMode.WriteOnly, out var data); + ReadTiffFrame(data.safePtr, tiff, w, h); yield return img; } while (LibTiff.TIFFReadDirectory(tiff) == 1); } @@ -107,13 +112,10 @@ public class GtkImageContext : ImageContext } } - private static unsafe void ReadTiffFrame(byte[] buffer, IntPtr tiff, int w, int h) + private static void ReadTiffFrame(IntPtr buffer, IntPtr tiff, int w, int h) { - fixed (void* ptr = &buffer[0]) - { - int r = LibTiff.TIFFReadRGBAImage(tiff, w, h, (IntPtr) ptr, 0); - // TODO: Check return value - } + int r = LibTiff.TIFFReadRGBAImageOriented(tiff, w, h, buffer, 1, 0); + // TODO: Check return value } public override IMemoryImage Create(int width, int height, ImagePixelFormat pixelFormat) diff --git a/NAPS2.Images.Gtk/LibTiff.cs b/NAPS2.Images.Gtk/LibTiff.cs index ec972265f..524c153ca 100644 --- a/NAPS2.Images.Gtk/LibTiff.cs +++ b/NAPS2.Images.Gtk/LibTiff.cs @@ -1,4 +1,8 @@ using System.Runtime.InteropServices; +using toff_t = System.IntPtr; +using tsize_t = System.IntPtr; +using thandle_t = System.IntPtr; +using tdata_t = System.IntPtr; namespace NAPS2.Images.Gtk; @@ -8,6 +12,38 @@ public static class LibTiff [DllImport("libtiff.so.5")] public static extern IntPtr TIFFOpen(string filename, string mode); + [DllImport("libtiff.so.5")] + public static extern IntPtr TIFFSetErrorHandler(TIFFErrorHandler handler); + + [DllImport("libtiff.so.5")] + public static extern IntPtr TIFFSetWarningHandler(TIFFErrorHandler handler); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void TIFFErrorHandler(string x, string y, IntPtr va_args); + + [DllImport("libtiff.so.5")] + public static extern IntPtr TIFFClientOpen(string filename, string mode, IntPtr clientdata, + TIFFReadWriteProc readproc, TIFFReadWriteProc writeproc, TIFFSeekProc seekproc, TIFFCloseProc closeproc, + TIFFSizeProc sizeproc, TIFFMapFileProc mapproc, TIFFUnmapFileProc unmapproc); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate tsize_t TIFFReadWriteProc(thandle_t clientdata, tdata_t data, tsize_t size); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate toff_t TIFFSeekProc(thandle_t clientdata, toff_t off, int c); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int TIFFCloseProc(thandle_t clientdata); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate toff_t TIFFSizeProc(thandle_t clientdata); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int TIFFMapFileProc(thandle_t clientdata, ref tdata_t a, ref toff_t b); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void TIFFUnmapFileProc(thandle_t clientdata, tdata_t a, toff_t b); + [DllImport("libtiff.so.5")] public static extern IntPtr TIFFClose(IntPtr tiff); @@ -21,7 +57,12 @@ public static class LibTiff public static extern int TIFFGetField(IntPtr tiff, int tag, out int field); [DllImport("libtiff.so.5")] - public static extern int TIFFReadRGBAImage(IntPtr tiff, int w, int h, IntPtr raster, int stopOnError); + public static extern int TIFFReadRGBAImage( + IntPtr tiff, int w, int h, IntPtr raster, int stopOnError); + + [DllImport("libtiff.so.5")] + public static extern int TIFFReadRGBAImageOriented( + IntPtr tiff, int w, int h, IntPtr raster, int orientation, int stopOnError); // TODO: For streams // https://linux.die.net/man/3/tiffclientopen diff --git a/NAPS2.Images.Gtk/LibTiffStreamClient.cs b/NAPS2.Images.Gtk/LibTiffStreamClient.cs new file mode 100644 index 000000000..8155688e9 --- /dev/null +++ b/NAPS2.Images.Gtk/LibTiffStreamClient.cs @@ -0,0 +1,104 @@ +using System.Runtime.InteropServices; +using toff_t = System.IntPtr; +using tsize_t = System.IntPtr; +using thandle_t = System.IntPtr; +using tdata_t = System.IntPtr; + +namespace NAPS2.Images.Gtk; + +public class LibTiffStreamClient +{ + private readonly Stream _stream; + private readonly LibTiff.TIFFErrorHandler _error; + private readonly LibTiff.TIFFErrorHandler _warning; + private readonly LibTiff.TIFFReadWriteProc _read; + private readonly LibTiff.TIFFReadWriteProc _write; + private readonly LibTiff.TIFFSeekProc _seek; + private readonly LibTiff.TIFFCloseProc _close; + private readonly LibTiff.TIFFSizeProc _size; + private readonly LibTiff.TIFFMapFileProc _map; + private readonly LibTiff.TIFFUnmapFileProc _unmap; + + public LibTiffStreamClient(Stream stream) + { + _stream = stream; + // We need to keep explicit references to the delegates to avoid garbage collection + _error = Error; + _warning = Warning; + _read = Read; + _write = Write; + _seek = Seek; + _close = Close; + _size = Size; + _map = Map; + _unmap = UnMap; + } + + public IntPtr TIFFClientOpen(string mode) + { + LibTiff.TIFFSetErrorHandler(_error); + LibTiff.TIFFSetWarningHandler(_warning); + return LibTiff.TIFFClientOpen("placeholder", mode, IntPtr.Zero, + _read, _write, _seek, _close, _size, _map, _unmap); + } + + private void Warning(string x, string y, IntPtr va_args) + { + } + + private void Error(string x, string y, IntPtr va_args) + { + } + + public tsize_t Read(thandle_t clientdata, tdata_t data, tsize_t size) + { + var buffer = new byte[(int) size]; + var count = _stream.Read(buffer); + Marshal.Copy(buffer, 0, data, count); + return (tsize_t) count; + } + + public tsize_t Write(thandle_t clientdata, tdata_t data, tsize_t size) + { + var buffer = new byte[(int) size]; + Marshal.Copy(data, buffer, 0, buffer.Length); + _stream.Write(buffer); + return (tsize_t) buffer.Length; + } + + public toff_t Seek(thandle_t clientdata, toff_t off, int c) + { + if (c == 0) + { + _stream.Seek((long) off, SeekOrigin.Begin); + } + if (c == 1) + { + _stream.Seek((long) off, SeekOrigin.Current); + } + if (c == 2) + { + _stream.Seek((long) off, SeekOrigin.End); + } + return (toff_t) _stream.Position; + } + + public int Close(thandle_t clientdata) + { + return 0; + } + + public toff_t Size(thandle_t clientdata) + { + return (toff_t) _stream.Length; + } + + public int Map(thandle_t clientdata, ref tdata_t a, ref toff_t b) + { + return 0; + } + + public void UnMap(thandle_t clientdata, tdata_t a, toff_t b) + { + } +} \ No newline at end of file