From 05757ebf40971c752618cff3edf816ec981bba3f Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 12 Jun 2025 18:15:25 +0800 Subject: [PATCH] feature: supports to view `.tiff` images Signed-off-by: leo --- src/Models/ImageDecoder.cs | 3 +- src/SourceGit.csproj | 1 + src/ViewModels/ImageSource.cs | 150 ++++++++++++++++++++-------------- 3 files changed, 90 insertions(+), 64 deletions(-) diff --git a/src/Models/ImageDecoder.cs b/src/Models/ImageDecoder.cs index ce3a44c1..6fe0f428 100644 --- a/src/Models/ImageDecoder.cs +++ b/src/Models/ImageDecoder.cs @@ -4,6 +4,7 @@ { None = 0, Builtin, - Pfim + Pfim, + Tiff, } } diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj index 62ec6255..5205ae4f 100644 --- a/src/SourceGit.csproj +++ b/src/SourceGit.csproj @@ -47,6 +47,7 @@ + diff --git a/src/ViewModels/ImageSource.cs b/src/ViewModels/ImageSource.cs index cd81a1f8..f94b0c95 100644 --- a/src/ViewModels/ImageSource.cs +++ b/src/ViewModels/ImageSource.cs @@ -7,6 +7,7 @@ using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; +using BitMiracle.LibTiff.Classic; using Pfim; namespace SourceGit.ViewModels @@ -39,6 +40,9 @@ namespace SourceGit.ViewModels case ".tga": case ".dds": return Models.ImageDecoder.Pfim; + case ".tif": + case ".tiff": + return Models.ImageDecoder.Tiff; default: return Models.ImageDecoder.None; } @@ -70,10 +74,22 @@ namespace SourceGit.ViewModels var size = stream.Length; if (size > 0) { - if (decoder == Models.ImageDecoder.Builtin) - return DecodeWithAvalonia(stream, size); - else if (decoder == Models.ImageDecoder.Pfim) - return DecodeWithPfim(stream, size); + try + { + switch (decoder) + { + case Models.ImageDecoder.Builtin: + return DecodeWithAvalonia(stream, size); + case Models.ImageDecoder.Pfim: + return DecodeWithPfim(stream, size); + case Models.ImageDecoder.Tiff: + return DecodeWithTiff(stream, size); + } + } + catch (Exception e) + { + Console.Out.WriteLine(e.Message); + } } return new ImageSource(null, 0); @@ -81,75 +97,83 @@ namespace SourceGit.ViewModels private static ImageSource DecodeWithAvalonia(Stream stream, long size) { - try - { - var bitmap = new Bitmap(stream); - return new ImageSource(bitmap, size); - } - catch - { - return new ImageSource(null, 0); - } + var bitmap = new Bitmap(stream); + return new ImageSource(bitmap, size); } private static ImageSource DecodeWithPfim(Stream stream, long size) { - try + using (var pfiImage = Pfimage.FromStream(stream)) { - using (var pfiImage = Pfimage.FromStream(stream)) + var data = pfiImage.Data; + var stride = pfiImage.Stride; + + var pixelFormat = PixelFormats.Bgra8888; + var alphaFormat = AlphaFormat.Opaque; + switch (pfiImage.Format) { - var data = pfiImage.Data; - var stride = pfiImage.Stride; + case ImageFormat.Rgb8: + pixelFormat = PixelFormats.Gray8; + break; + case ImageFormat.R5g5b5: + case ImageFormat.R5g5b5a1: + pixelFormat = PixelFormats.Bgr555; + break; + case ImageFormat.R5g6b5: + pixelFormat = PixelFormats.Bgr565; + break; + case ImageFormat.Rgb24: + pixelFormat = PixelFormats.Bgr24; + break; + case ImageFormat.Rgba16: + var pixels2 = pfiImage.DataLen / 2; + data = new byte[pixels2 * 4]; + stride = pfiImage.Width * 4; + for (var i = 0; i < pixels2; i++) + { + var src = BitConverter.ToUInt16(pfiImage.Data, i * 2); + data[i * 4 + 0] = (byte)Math.Round((src & 0x0F) / 15F * 255); // B + data[i * 4 + 1] = (byte)Math.Round(((src >> 4) & 0x0F) / 15F * 255); // G + data[i * 4 + 2] = (byte)Math.Round(((src >> 8) & 0x0F) / 15F * 255); // R + data[i * 4 + 3] = (byte)Math.Round(((src >> 12) & 0x0F) / 15F * 255); // A + } - var pixelFormat = PixelFormats.Bgra8888; - var alphaFormat = AlphaFormat.Opaque; - switch (pfiImage.Format) - { - case ImageFormat.Rgb8: - pixelFormat = PixelFormats.Gray8; - break; - case ImageFormat.R5g5b5: - case ImageFormat.R5g5b5a1: - pixelFormat = PixelFormats.Bgr555; - break; - case ImageFormat.R5g6b5: - pixelFormat = PixelFormats.Bgr565; - break; - case ImageFormat.Rgb24: - pixelFormat = PixelFormats.Bgr24; - break; - case ImageFormat.Rgba16: - var pixels2 = pfiImage.DataLen / 2; - data = new byte[pixels2 * 4]; - stride = pfiImage.Width * 4; - for (var i = 0; i < pixels2; i++) - { - var src = BitConverter.ToUInt16(pfiImage.Data, i * 2); - data[i * 4 + 0] = (byte)Math.Round((src & 0x0F) / 15F * 255); // B - data[i * 4 + 1] = (byte)Math.Round(((src >> 4) & 0x0F) / 15F * 255); // G - data[i * 4 + 2] = (byte)Math.Round(((src >> 8) & 0x0F) / 15F * 255); // R - data[i * 4 + 3] = (byte)Math.Round(((src >> 12) & 0x0F) / 15F * 255); // A - } - - alphaFormat = AlphaFormat.Premul; - break; - case ImageFormat.Rgba32: - alphaFormat = AlphaFormat.Premul; - break; - default: - return new ImageSource(null, 0); - } - - var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(data, 0); - var pixelSize = new PixelSize(pfiImage.Width, pfiImage.Height); - var dpi = new Vector(96, 96); - var bitmap = new Bitmap(pixelFormat, alphaFormat, ptr, pixelSize, dpi, stride); - return new ImageSource(bitmap, size); + alphaFormat = AlphaFormat.Premul; + break; + case ImageFormat.Rgba32: + alphaFormat = AlphaFormat.Premul; + break; + default: + return new ImageSource(null, 0); } + + var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(data, 0); + var pixelSize = new PixelSize(pfiImage.Width, pfiImage.Height); + var dpi = new Vector(96, 96); + var bitmap = new Bitmap(pixelFormat, alphaFormat, ptr, pixelSize, dpi, stride); + return new ImageSource(bitmap, size); } - catch + } + + private static ImageSource DecodeWithTiff(Stream stream, long size) + { + using (var tiff = Tiff.ClientOpen($"{Guid.NewGuid()}.tif", "r", stream, new TiffStream())) { - return new ImageSource(null, 0); + if (tiff == null) + return new ImageSource(null, 0); + + var width = tiff.GetField(TiffTag.IMAGEWIDTH)[0].ToInt(); + var height = tiff.GetField(TiffTag.IMAGELENGTH)[0].ToInt(); + var pixels = new int[width * height]; + + // Currently only supports image when its `BITSPERSAMPLE` is one in [1,2,4,8,16] + tiff.ReadRGBAImageOriented(width, height, pixels, Orientation.TOPLEFT); + + var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0); + var pixelSize = new PixelSize(width, height); + var dpi = new Vector(96, 96); + var bitmap = new Bitmap(PixelFormats.Rgba8888, AlphaFormat.Premul, ptr, pixelSize, dpi, width * 4); + return new ImageSource(bitmap, size); } } }