diff --git a/SpineViewerCLI/CanvasAscii.cs b/SpineViewerCLI/CanvasAscii.cs index bdb5052..b46dbcf 100644 --- a/SpineViewerCLI/CanvasAscii.cs +++ b/SpineViewerCLI/CanvasAscii.cs @@ -1,4 +1,5 @@ -using Spectre.Console; +using SkiaSharp; +using Spectre.Console; using Spectre.Console.Rendering; using System; using System.Collections.Generic; @@ -10,7 +11,7 @@ namespace SpineViewerCLI { public class CanvasAscii : Renderable { - private readonly Color?[,] _pixels; + private readonly SKColor?[,] _pixels; /// /// Gets the width of the canvas. @@ -39,9 +40,14 @@ namespace SpineViewerCLI public int PixelWidth { get; set; } = 2; /// - /// Gets or sets the pixel lettters, ordered by transparency. + /// Gets or sets the pixel characters, ordered by transparency. /// - public string PixelLetters { get; set; } = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,^`'."; // ".:-=+*#%@" ... "@%#*+=-:." + public string PixelCharacters { get; set; } = ".,:;-=+*?oSXBGWM$&%#@"; + + /// + /// Whether to use pixel characters instead of spaces. + /// + public bool UsePixelCharacters { get; set; } = false; /// /// Initializes a new instance of the class. @@ -63,7 +69,7 @@ namespace SpineViewerCLI Width = width; Height = height; - _pixels = new Color?[Width, Height]; + _pixels = new SKColor?[Width, Height]; } /// @@ -73,7 +79,7 @@ namespace SpineViewerCLI /// The Y coordinate for the pixel. /// The pixel color. /// The same instance so that multiple calls can be chained. - public CanvasAscii SetPixel(int x, int y, Color color) + public CanvasAscii SetPixel(int x, int y, SKColor color) { _pixels[x, y] = color; return this; @@ -105,7 +111,7 @@ namespace SpineViewerCLI throw new InvalidOperationException("Pixel width must be greater than zero."); } - if (PixelLetters.Length <= 0) + if (UsePixelCharacters && PixelCharacters.Length <= 0) { throw new InvalidOperationException("Pixel letters can't be empty."); } @@ -141,28 +147,53 @@ namespace SpineViewerCLI pixels = ScaleDown(width, height); } - for (var y = 0; y < height; y++) + if (UsePixelCharacters) { - for (var x = 0; x < width; x++) + for (var y = 0; y < height; y++) { - var color = pixels[x, y]; - if (color.HasValue) + for (var x = 0; x < width; x++) { - yield return new Segment(emptyPixel, new Style(background: color)); + var color = pixels[x, y]; + if (color.HasValue) + { + var c = color.Value; + yield return new Segment(GetPixelChar(c), new Style(foreground: new(c.Red, c.Green, c.Blue))); + } + else + { + yield return new Segment(emptyPixel); + } } - else - { - yield return new Segment(emptyPixel); - } - } - yield return Segment.LineBreak; + yield return Segment.LineBreak; + } + } + else + { + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var color = pixels[x, y]; + if (color.HasValue) + { + var c = color.Value; + yield return new Segment(emptyPixel, new Style(background: new(c.Red, c.Green, c.Blue))); + } + else + { + yield return new Segment(emptyPixel); + } + } + + yield return Segment.LineBreak; + } } } - private Color?[,] ScaleDown(int newWidth, int newHeight) + private SKColor?[,] ScaleDown(int newWidth, int newHeight) { - var buffer = new Color?[newWidth, newHeight]; + var buffer = new SKColor?[newWidth, newHeight]; var xRatio = ((Width << 16) / newWidth) + 1; var yRatio = ((Height << 16) / newHeight) + 1; @@ -177,12 +208,10 @@ namespace SpineViewerCLI return buffer; } - private static float GetBrightness(Color c) => (0.299f * c.R + 0.587f * c.G + 0.114f * c.B) / 255f; - - private string GetPixelLetter(Color c) + private string GetPixelChar(SKColor c) { - var index = Math.Min((int)(GetBrightness(c) * PixelLetters.Length), PixelLetters.Length - 1); - return new(PixelLetters[index], PixelWidth); + var index = Math.Min((int)(c.Alpha / 255f * PixelCharacters.Length), PixelCharacters.Length - 1); + return new(PixelCharacters[index], PixelWidth); } } } diff --git a/SpineViewerCLI/CanvasImageAscii.cs b/SpineViewerCLI/CanvasImageAscii.cs index 16d9f09..e57bc63 100644 --- a/SpineViewerCLI/CanvasImageAscii.cs +++ b/SpineViewerCLI/CanvasImageAscii.cs @@ -33,6 +33,16 @@ namespace SpineViewerCLI /// 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. @@ -127,13 +137,15 @@ namespace SpineViewerCLI { MaxWidth = MaxWidth, PixelWidth = PixelWidth, + PixelCharacters = PixelCharacters, + UsePixelCharacters = UsePixelCharacters, Scale = false, }; // XXX: 也许是 SkiaSharp@3.119.0 的 bug, 此处像素值一定是非预乘的格式 - for (var x = 0; x < image.Width; x++) + for (var y = 0; y < image.Height; y++) { - 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; @@ -141,7 +153,7 @@ namespace SpineViewerCLI 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)); + canvas.SetPixel(x, y, new(r, g, b, p.Alpha)); } } diff --git a/SpineViewerCLI/PreviewCommand.cs b/SpineViewerCLI/PreviewCommand.cs index 3a8c954..94911f3 100644 --- a/SpineViewerCLI/PreviewCommand.cs +++ b/SpineViewerCLI/PreviewCommand.cs @@ -53,6 +53,11 @@ namespace SpineViewerCLI DefaultValueFactory = _ => 0f, }; + public Option OptUseChars { get; } = new("--use-chars") + { + Description = "Whether to use characters instead of colored spaces for pixels", + }; + public PreviewCommand() : base(_name, _desc) { OptTime.Validators.Add(r => @@ -99,7 +104,7 @@ namespace SpineViewerCLI using var exporter = GetExporterFilledWithArgs(result, spine); using var skImage = exporter.ExportMemoryImage(spine); - var img = new CanvasImageAscii(skImage); + var img = new CanvasImageAscii(skImage) { UsePixelCharacters = result.GetValue(OptUseChars) }; AnsiConsole.Write(img); }