Gtk: Tiff stream support

This commit is contained in:
Ben Olden-Cooligan 2022-09-17 00:06:57 -07:00
parent f01bf7e414
commit e628127547
3 changed files with 169 additions and 22 deletions

View File

@ -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<IMemoryImage> 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<IMemoryImage> 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<IMemoryImage> images, string path, TiffCompressionType compression = TiffCompressionType.Auto,
@ -76,28 +75,34 @@ public class GtkImageContext : ImageContext
throw new NotImplementedException();
}
private IEnumerable<IMemoryImage> 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<IMemoryImage> 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<IMemoryImage> EnumerateTiffFrames(IntPtr tiff)
private IEnumerable<IMemoryImage> 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)

View File

@ -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

View File

@ -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)
{
}
}