using System; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; using BitMiracle.LibTiff.Classic; using Pfim; using StbImageSharp; namespace SourceGit.ViewModels { public class ImageSource { public Bitmap Bitmap { get; } public long Size { get; } public ImageSource(Bitmap bitmap, long size) { Bitmap = bitmap; Size = size; } public static Models.ImageDecoder GetDecoder(string file) { var ext = (Path.GetExtension(file) ?? ".invalid_img").ToLower(CultureInfo.CurrentCulture); return ext switch { ".ico" or ".bmp" or ".gif" or ".jpg" or ".jpeg" or ".png" or ".webp" => Models.ImageDecoder.Builtin, ".tga" or ".dds" => Models.ImageDecoder.Pfim, ".tif" or ".tiff" => Models.ImageDecoder.Tiff, ".psd" => Models.ImageDecoder.StbImage, _ => Models.ImageDecoder.None, }; } public static async Task FromFileAsync(string fullpath, Models.ImageDecoder decoder) { if (!File.Exists(fullpath)) return new ImageSource(null, 0); await using var stream = File.OpenRead(fullpath); return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false); } public static async Task FromRevisionAsync(string repo, string revision, string file, Models.ImageDecoder decoder) { // If revision is empty, it means we are reading file in worktree. if (string.IsNullOrEmpty(revision)) return await FromFileAsync(Path.Combine(repo, file), decoder).ConfigureAwait(false); var emptyTreeHash = Models.EmptyTreeHash.Guess(revision); if (emptyTreeHash.Equals(revision, StringComparison.Ordinal)) return new ImageSource(null, 0); await using var stream = await Commands.QueryFileContent.RunAsync(repo, revision, file).ConfigureAwait(false); return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false); } public static async Task FromLFSObjectAsync(string repo, Models.LFSObject lfs, Models.ImageDecoder decoder) { if (string.IsNullOrEmpty(lfs.Oid) || lfs.Size == 0) return new ImageSource(null, 0); var commonDir = await new Commands.QueryGitCommonDir(repo).GetResultAsync().ConfigureAwait(false); var localFile = Path.Combine(commonDir, "lfs", "objects", lfs.Oid.Substring(0, 2), lfs.Oid.Substring(2, 2), lfs.Oid); if (File.Exists(localFile)) return await FromFileAsync(localFile, decoder).ConfigureAwait(false); await using var stream = await Commands.QueryFileContent.FromLFSAsync(repo, lfs.Oid, lfs.Size).ConfigureAwait(false); return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false); } private static ImageSource LoadFromStream(Stream stream, Models.ImageDecoder decoder) { var size = stream.Length; if (size > 0) { 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); case Models.ImageDecoder.StbImage: return DecodeWithStbImage(stream, size); } } catch (Exception e) { Console.Out.WriteLine(e.Message); } } return new ImageSource(null, 0); } private static ImageSource DecodeWithAvalonia(Stream stream, long size) { var bitmap = new Bitmap(stream); return new ImageSource(bitmap, size); } private static ImageSource DecodeWithPfim(Stream stream, long size) { 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) { case ImageFormat.Rgb8: pixelFormat = PixelFormats.Gray8; break; case ImageFormat.R16f: pixelFormat = PixelFormats.Gray16; break; case ImageFormat.R32f: pixelFormat = PixelFormats.Gray32Float; break; case ImageFormat.R5g5b5: pixelFormat = PixelFormats.Bgr555; break; case ImageFormat.R5g5b5a1: var pixels1 = pfiImage.DataLen / 2; data = new byte[pixels1 * 4]; stride = pfiImage.Width * 4; for (var i = 0; i < pixels1; i++) { var src = BitConverter.ToUInt16(pfiImage.Data, i * 2); data[i * 4 + 0] = (byte)Math.Round((src & 0x1F) / 31F * 255); // B data[i * 4 + 1] = (byte)Math.Round(((src >> 5) & 0x1F) / 31F * 255); // G data[i * 4 + 2] = (byte)Math.Round(((src >> 10) & 0x1F) / 31F * 255); // R data[i * 4 + 3] = (byte)((src >> 15) * 255F); // A } alphaFormat = AlphaFormat.Unpremul; 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.Unpremul; break; case ImageFormat.Rgba32: alphaFormat = AlphaFormat.Unpremul; break; default: return new ImageSource(null, 0); } var handle = GCHandle.Alloc(data, GCHandleType.Pinned); try { 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 { return new ImageSource(null, 0); } finally { handle.Free(); } } } private static ImageSource DecodeWithTiff(Stream stream, long size) { using (var tiff = Tiff.ClientOpen($"{Guid.NewGuid()}.tif", "r", stream, new TiffStream())) { if (tiff == null) return new ImageSource(null, 0); // Currently only supports image when its `BITSPERSAMPLE` is one in [1,2,4,8,16] var width = tiff.GetField(TiffTag.IMAGEWIDTH)[0].ToInt(); var height = tiff.GetField(TiffTag.IMAGELENGTH)[0].ToInt(); var pixels = new int[width * height]; tiff.ReadRGBAImageOriented(width, height, pixels, Orientation.TOPLEFT); var pixelSize = new PixelSize(width, height); var dpi = new Vector(96, 96); var bitmap = new WriteableBitmap(pixelSize, dpi, PixelFormats.Rgba8888, AlphaFormat.Unpremul); using var frameBuffer = bitmap.Lock(); Marshal.Copy(pixels, 0, frameBuffer.Address, pixels.Length); return new ImageSource(bitmap, size); } } private static ImageSource DecodeWithStbImage(Stream stream, long size) { var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); var data = image.Data; var stride = image.Width * (int)image.Comp; var pixelSize = new PixelSize(image.Width, image.Height); var dpi = new Vector(96, 96); var bitmap = new WriteableBitmap(pixelSize, dpi, PixelFormats.Rgba8888, AlphaFormat.Unpremul); using var frameBuffer = bitmap.Lock(); Marshal.Copy(data, 0, frameBuffer.Address, data.Length); return new ImageSource(bitmap, size); } } }