using SkiaSharp; using Spectre.Console; using Spectre.Console.Rendering; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SpineViewerCLI { internal class CanvasImageAscii : Renderable { private static readonly SKSamplingOptions _defaultSamplingOptions = new(new SKCubicResampler()); /// /// Gets the image width. /// public int Width => Image.Width; /// /// Gets the image height. /// public int Height => Image.Height; /// /// Gets or sets the render width of the canvas. /// public int? MaxWidth { get; set; } /// /// Gets or sets the render width of the canvas. /// public int PixelWidth { get; set; } = 2; /// /// Gets or sets the pixel characters, ordered by transparency. /// public string PixelCharacters { get; set; } = ".,:;-=+*?oSXBGWM$&%#@"; /// /// Whether to use pixel characters instead of spaces. /// public bool UsePixelCharacters { get; set; } = false; /// /// Gets or sets the that should /// be used when scaling the image. Defaults to bicubic sampling. /// public SKSamplingOptions? SamplingOptions { get; set; } internal SKBitmap Image { get; } /// /// Initializes a new instance of the class. /// /// The image filename. public CanvasImageAscii(string filename) { Image = SKBitmap.Decode(filename); } /// /// Initializes a new instance of the class. /// /// Buffer containing an image. public CanvasImageAscii(ReadOnlySpan data) { Image = SKBitmap.Decode(data); } /// /// Initializes a new instance of the class. /// /// Stream containing an image. public CanvasImageAscii(Stream data) { Image = SKBitmap.Decode(data); } /// /// Initializes a new instance of the class. /// /// The object. public CanvasImageAscii(SKImage image) { Image = SKBitmap.FromImage(image); } /// protected override Measurement Measure(RenderOptions options, int maxWidth) { if (PixelWidth < 0) { throw new InvalidOperationException("Pixel width must be greater than zero."); } var width = MaxWidth ?? Width; if (maxWidth < width * PixelWidth) { return new Measurement(maxWidth, maxWidth); } return new Measurement(width * PixelWidth, width * PixelWidth); } /// protected override IEnumerable Render(RenderOptions options, int maxWidth) { var image = Image; var width = Width; var height = Height; // Got a max width? if (MaxWidth != null) { height = (int)(height * ((float)MaxWidth.Value) / Width); width = MaxWidth.Value; } // Exceed the max width when we take pixel width into account? if (width * PixelWidth > maxWidth) { height = (int)(height * (maxWidth / (float)(width * PixelWidth))); width = maxWidth / PixelWidth; } // Need to rescale the pixel buffer? if (width != Width || height != Height) { var samplingOptions = SamplingOptions ?? _defaultSamplingOptions; image = image.Resize(new SKSizeI(width, height), samplingOptions); } var canvas = new CanvasAscii(width, height) { MaxWidth = MaxWidth, PixelWidth = PixelWidth, PixelCharacters = PixelCharacters, UsePixelCharacters = UsePixelCharacters, Scale = false, }; // XXX: 也许是 SkiaSharp@3.119.0 的 bug, 此处像素值一定是非预乘的格式 for (var y = 0; y < image.Height; y++) { for (var x = 0; x < image.Width; x++) { var p = image.GetPixel(x, y); if (p.Alpha == 0) continue; float a = p.Alpha / 255f; byte r = (byte)(p.Red * a); byte g = (byte)(p.Green * a); byte b = (byte)(p.Blue * a); canvas.SetPixel(x, y, new(r, g, b, p.Alpha)); } } return ((IRenderable)canvas).Render(options, maxWidth); } } }