diff --git a/NAPS2.Images.Gtk/LibTiff.cs b/NAPS2.Images.Gtk/LibTiff.cs index 8be839e53..5919af837 100644 --- a/NAPS2.Images.Gtk/LibTiff.cs +++ b/NAPS2.Images.Gtk/LibTiff.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using NAPS2.Util; using toff_t = System.IntPtr; using tsize_t = System.IntPtr; using thandle_t = System.IntPtr; @@ -8,24 +9,64 @@ namespace NAPS2.Images.Gtk; internal static class LibTiff { - // TODO: String marshalling? - [DllImport("libtiff.so.5")] - public static extern IntPtr TIFFOpen(string filename, string mode); + private const int RTLD_LAZY = 1; + private const int RTLD_GLOBAL = 8; - [DllImport("libtiff.so.5")] - public static extern IntPtr TIFFSetErrorHandler(TIFFErrorHandler handler); + private static readonly Dictionary FuncCache = new(); - [DllImport("libtiff.so.5")] - public static extern IntPtr TIFFSetWarningHandler(TIFFErrorHandler handler); + private static readonly Lazy LibraryHandle = new(() => + { + var handle = dlopen("libtiff.so.5", RTLD_LAZY | RTLD_GLOBAL); + if (handle == IntPtr.Zero) + { + handle = dlopen("libtiff.so.6", RTLD_LAZY | RTLD_GLOBAL); + } + if (handle == IntPtr.Zero) + { + var error = dlerror(); + throw new InvalidOperationException($"Could not load library: \"libtiff\". Error: {error}"); + } + return handle; + }); + + public static T Load() + { + return (T) FuncCache.Get(typeof(T), () => Marshal.GetDelegateForFunctionPointer(LoadFunc())!); + } + + private static IntPtr LoadFunc() + { + var symbol = typeof(T).Name.Split("_")[0]; + var ptr = dlsym(LibraryHandle.Value, symbol); + if (ptr == IntPtr.Zero) + { + var error = dlerror(); + throw new InvalidOperationException($"Could not load symbol: \"{symbol}\". Error: {error}"); + } + return ptr; + } + + public delegate IntPtr TIFFOpen_d(string filename, string mode); + + public static TIFFOpen_d TIFFOpen => Load(); + + public delegate IntPtr TIFFSetErrorHandler_d(TIFFErrorHandler handler); + + public static TIFFSetErrorHandler_d TIFFSetErrorHandler => Load(); + + public delegate IntPtr TIFFSetWarningHandler_d(TIFFErrorHandler handler); + + public static TIFFSetWarningHandler_d TIFFSetWarningHandler => Load(); [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, + public delegate IntPtr TIFFClientOpen_d(string filename, string mode, IntPtr clientdata, TIFFReadWriteProc readproc, TIFFReadWriteProc writeproc, TIFFSeekProc seekproc, TIFFCloseProc closeproc, TIFFSizeProc sizeproc, TIFFMapFileProc mapproc, TIFFUnmapFileProc unmapproc); + public static TIFFClientOpen_d TIFFClientOpen => Load(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate tsize_t TIFFReadWriteProc(thandle_t clientdata, tdata_t data, tsize_t size); @@ -44,49 +85,71 @@ internal static class LibTiff [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); + public delegate IntPtr TIFFClose_d(IntPtr tiff); - [DllImport("libtiff.so.5")] - public static extern short TIFFNumberOfDirectories(IntPtr tiff); + public static TIFFClose_d TIFFClose => Load(); - [DllImport("libtiff.so.5")] - public static extern int TIFFReadDirectory(IntPtr tiff); + public delegate short TIFFNumberOfDirectories_d(IntPtr tiff); - [DllImport("libtiff.so.5")] - public static extern int TIFFWriteDirectory(IntPtr tiff); + public static TIFFNumberOfDirectories_d TIFFNumberOfDirectories => Load(); - // TODO: Clean these overloads up - [DllImport("libtiff.so.5")] - public static extern int TIFFGetField(IntPtr tiff, TiffTag tag, out int field); + public delegate int TIFFReadDirectory_d(IntPtr tiff); - [DllImport("libtiff.so.5")] - public static extern int TIFFGetField(IntPtr tiff, TiffTag tag, out float field); + public static TIFFReadDirectory_d TIFFReadDirectory => Load(); - [DllImport("libtiff.so.5")] - public static extern int TIFFGetField(IntPtr tiff, TiffTag tag, out double field); + public delegate int TIFFWriteDirectory_d(IntPtr tiff); - [DllImport("libtiff.so.5")] - public static extern int TIFFSetField(IntPtr tiff, TiffTag tag, int field); + public static TIFFWriteDirectory_d TIFFWriteDirectory => Load(); - [DllImport("libtiff.so.5")] - public static extern int TIFFSetField(IntPtr tiff, TiffTag tag, float field); + public delegate int TIFFGetField_d1(IntPtr tiff, TiffTag tag, out int field); - [DllImport("libtiff.so.5")] - public static extern int TIFFSetField(IntPtr tiff, TiffTag tag, double field); + public static TIFFGetField_d1 TIFFGetFieldInt => Load(); - [DllImport("libtiff.so.5")] - public static extern int TIFFSetField(IntPtr tiff, TiffTag tag, short field, short[] array); + public delegate int TIFFGetField_d2(IntPtr tiff, TiffTag tag, out float field); - [DllImport("libtiff.so.5")] - public static extern int TIFFWriteScanline( + public static TIFFGetField_d2 TIFFGetFieldFloat => Load(); + + public delegate int TIFFGetField_d3(IntPtr tiff, TiffTag tag, out double field); + + public static TIFFGetField_d3 TIFFGetFieldDouble => Load(); + + public delegate int TIFFSetField_d1(IntPtr tiff, TiffTag tag, int field); + + public static TIFFSetField_d1 TIFFSetFieldInt => Load(); + + public delegate int TIFFSetField_d2(IntPtr tiff, TiffTag tag, float field); + + public static TIFFSetField_d2 TIFFSetFieldFloat => Load(); + + public delegate int TIFFSetField_d3(IntPtr tiff, TiffTag tag, double field); + + public static TIFFSetField_d3 TIFFSetFieldDouble => Load(); + + public delegate int TIFFSetField_d4(IntPtr tiff, TiffTag tag, short field, short[] array); + + public static TIFFSetField_d4 TIFFSetFieldShortArray => Load(); + + public delegate int TIFFWriteScanline_d( IntPtr tiff, tdata_t buf, int row, short sample); - [DllImport("libtiff.so.5")] - public static extern int TIFFReadRGBAImage( + public static TIFFWriteScanline_d TIFFWriteScanline => Load(); + + public delegate int TIFFReadRGBAImage_d( IntPtr tiff, int w, int h, IntPtr raster, int stopOnError); - [DllImport("libtiff.so.5")] - public static extern int TIFFReadRGBAImageOriented( + public static TIFFReadRGBAImage_d TIFFReadRGBAImage => Load(); + + public delegate int TIFFReadRGBAImageOriented_d( IntPtr tiff, int w, int h, IntPtr raster, int orientation, int stopOnError); + + public static TIFFReadRGBAImageOriented_d TIFFReadRGBAImageOriented => Load(); + + [DllImport("libdl.so.2")] + public static extern IntPtr dlopen(string filename, int flags); + + [DllImport("libdl.so.2")] + public static extern string dlerror(); + + [DllImport("libdl.so.2")] + public static extern IntPtr dlsym(IntPtr handle, string symbol); } \ No newline at end of file diff --git a/NAPS2.Images.Gtk/LibTiffIo.cs b/NAPS2.Images.Gtk/LibTiffIo.cs index a222ce36a..346027ab5 100644 --- a/NAPS2.Images.Gtk/LibTiffIo.cs +++ b/NAPS2.Images.Gtk/LibTiffIo.cs @@ -85,10 +85,10 @@ internal class LibTiffIo : ITiffWriter { // 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); - LibTiff.TIFFSetField(tiff, TiffTag.Compression, (int) (compression switch + LibTiff.TIFFSetFieldInt(tiff, TiffTag.ImageWidth, image.Width); + LibTiff.TIFFSetFieldInt(tiff, TiffTag.ImageHeight, image.Height); + LibTiff.TIFFSetFieldInt(tiff, TiffTag.PlanarConfig, 1); + LibTiff.TIFFSetFieldInt(tiff, TiffTag.Compression, (int) (compression switch { TiffCompressionType.Ccitt4 => TiffCompression.G4, TiffCompressionType.Lzw => TiffCompression.Lzw, @@ -97,29 +97,29 @@ internal class LibTiffIo : ITiffWriter ? TiffCompression.G4 : TiffCompression.Lzw })); - LibTiff.TIFFSetField(tiff, TiffTag.Orientation, 1); - LibTiff.TIFFSetField(tiff, TiffTag.BitsPerSample, pixelFormat == ImagePixelFormat.BW1 ? 1 : 8); - LibTiff.TIFFSetField(tiff, TiffTag.SamplesPerPixel, pixelFormat switch + LibTiff.TIFFSetFieldInt(tiff, TiffTag.Orientation, 1); + LibTiff.TIFFSetFieldInt(tiff, TiffTag.BitsPerSample, pixelFormat == ImagePixelFormat.BW1 ? 1 : 8); + LibTiff.TIFFSetFieldInt(tiff, TiffTag.SamplesPerPixel, pixelFormat switch { ImagePixelFormat.RGB24 => 3, ImagePixelFormat.ARGB32 => 4, _ => 1 }); - LibTiff.TIFFSetField(tiff, TiffTag.Photometric, (int) (pixelFormat switch + LibTiff.TIFFSetFieldInt(tiff, TiffTag.Photometric, (int) (pixelFormat switch { ImagePixelFormat.RGB24 or ImagePixelFormat.ARGB32 => TiffPhotometric.Rgb, _ => TiffPhotometric.MinIsBlack })); if (pixelFormat == ImagePixelFormat.ARGB32) { - LibTiff.TIFFSetField(tiff, TiffTag.ExtraSamples, 1, new short[] { 2 }); + LibTiff.TIFFSetFieldShortArray(tiff, TiffTag.ExtraSamples, 1, new short[] { 2 }); } if (image.HorizontalResolution != 0 && image.VerticalResolution != 0) { - LibTiff.TIFFSetField(tiff, TiffTag.ResolutionUnit, 2); + LibTiff.TIFFSetFieldInt(tiff, TiffTag.ResolutionUnit, 2); // 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); + LibTiff.TIFFSetFieldDouble(tiff, TiffTag.XResolution, image.HorizontalResolution); + LibTiff.TIFFSetFieldDouble(tiff, TiffTag.YResolution, image.VerticalResolution); } } @@ -143,11 +143,11 @@ internal class LibTiffIo : ITiffWriter do { if (progress.IsCancellationRequested) break; - LibTiff.TIFFGetField(tiff, TiffTag.ImageWidth, out int w); - LibTiff.TIFFGetField(tiff, TiffTag.ImageHeight, out int h); + LibTiff.TIFFGetFieldInt(tiff, TiffTag.ImageWidth, out int w); + LibTiff.TIFFGetFieldInt(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); + LibTiff.TIFFGetFieldFloat(tiff, TiffTag.XResolution, out float xres); + LibTiff.TIFFGetFieldFloat(tiff, TiffTag.YResolution, out float yres); var img = _imageContext.Create(w, h, ImagePixelFormat.ARGB32); img.SetResolution(xres, yres); img.OriginalFileFormat = ImageFileFormat.Tiff;