mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2026-02-10 04:13:32 +01:00
Add half-block support to Canvas
This commit is contained in:
committed by
Patrik Svensson
parent
0608bac360
commit
fa071a9368
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Processing.Processors.Transforms;
|
||||
@@ -33,6 +34,7 @@ public sealed class CanvasImage : Renderable
|
||||
/// <summary>
|
||||
/// Gets or sets the render width of the canvas.
|
||||
/// </summary>
|
||||
[Obsolete("Not used anymore. Will be removed in future update.")]
|
||||
public int PixelWidth { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
@@ -73,27 +75,23 @@ public sealed class CanvasImage : Renderable
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (PixelWidth < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Pixel width must be greater than zero.");
|
||||
}
|
||||
|
||||
var pixelWidth = options.Unicode ? 1 : 2;
|
||||
var width = MaxWidth ?? Width;
|
||||
if (maxWidth < width * PixelWidth)
|
||||
if (maxWidth < width * pixelWidth)
|
||||
{
|
||||
return new Measurement(maxWidth, maxWidth);
|
||||
}
|
||||
|
||||
return new Measurement(width * PixelWidth, width * PixelWidth);
|
||||
return new Measurement(width * pixelWidth, width * pixelWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var image = Image;
|
||||
|
||||
var width = Width;
|
||||
var height = Height;
|
||||
var pixelWidth = options.Unicode ? 1 : 2;
|
||||
|
||||
// Got a max width?
|
||||
if (MaxWidth != null)
|
||||
@@ -103,10 +101,10 @@ public sealed class CanvasImage : Renderable
|
||||
}
|
||||
|
||||
// Exceed the max width when we take pixel width into account?
|
||||
if (width * PixelWidth > maxWidth)
|
||||
if (width * pixelWidth > maxWidth)
|
||||
{
|
||||
height = (int)(height * (maxWidth / (float)(width * PixelWidth)));
|
||||
width = maxWidth / PixelWidth;
|
||||
height = (int)(height * (maxWidth / (float)(width * pixelWidth)));
|
||||
width = maxWidth / pixelWidth;
|
||||
}
|
||||
|
||||
// Need to rescale the pixel buffer?
|
||||
@@ -120,7 +118,6 @@ public sealed class CanvasImage : Renderable
|
||||
var canvas = new Canvas(width, height)
|
||||
{
|
||||
MaxWidth = MaxWidth,
|
||||
PixelWidth = PixelWidth,
|
||||
Scale = false,
|
||||
};
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ public static class CanvasImageExtensions
|
||||
/// <param name="image">The canvas image.</param>
|
||||
/// <param name="width">The pixel width.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
[Obsolete("Not used anymore. Will be removed in future update.")]
|
||||
public static CanvasImage PixelWidth(this CanvasImage image, int width)
|
||||
{
|
||||
if (image is null)
|
||||
|
||||
@@ -29,6 +29,18 @@ public static partial class TestConsoleExtensions
|
||||
return console;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether or not Unicode is supported.
|
||||
/// </summary>
|
||||
/// <param name="console">The console.</param>
|
||||
/// <param name="enable">Whether or not Unicode is supported.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TestConsole SupportsUnicode(this TestConsole console, bool enable)
|
||||
{
|
||||
console.Profile.Capabilities.Unicode = enable;
|
||||
return console;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes the console interactive.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[91m▀[0m [32m▀[0m
|
||||
|
||||
[94m▀[0m [93m▀[0m
|
||||
@@ -0,0 +1,3 @@
|
||||
[96m▀[0m
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[96m▀[0m
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
┌────┐
|
||||
│ [96m▀[0m[90m▄[0m │
|
||||
└────┘
|
||||
@@ -28,13 +28,16 @@ public class CanvasTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
[Expectation("Render")]
|
||||
public async Task Should_Render_Canvas_Correctly()
|
||||
public async Task Should_Render_Canvas_Correctly(bool supportsUnicode)
|
||||
{
|
||||
// Given
|
||||
var console = new TestConsole()
|
||||
.Colors(ColorSystem.Standard)
|
||||
.SupportsUnicode(supportsUnicode)
|
||||
.EmitAnsiSequences();
|
||||
|
||||
var canvas = new Canvas(width: 5, height: 5);
|
||||
@@ -47,16 +50,21 @@ public class CanvasTests
|
||||
console.Write(canvas);
|
||||
|
||||
// Then
|
||||
await Verifier.Verify(console.Output);
|
||||
await Verifier
|
||||
.Verify(console.Output)
|
||||
.UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
[Expectation("Render_Nested")]
|
||||
public async Task Simple_Measure()
|
||||
public async Task Simple_Measure(bool supportsUnicode)
|
||||
{
|
||||
// Given
|
||||
var console = new TestConsole()
|
||||
.Colors(ColorSystem.Standard)
|
||||
.SupportsUnicode(supportsUnicode)
|
||||
.EmitAnsiSequences();
|
||||
|
||||
var panel = new Panel(new Canvas(width: 2, height: 2)
|
||||
@@ -67,17 +75,22 @@ public class CanvasTests
|
||||
console.Write(panel);
|
||||
|
||||
// Then
|
||||
await Verifier.Verify(console.Output);
|
||||
await Verifier
|
||||
.Verify(console.Output)
|
||||
.UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
[Expectation("Render_NarrowTerminal")]
|
||||
public async Task Should_Scale_Down_Canvas_Is_Bigger_Than_Terminal()
|
||||
public async Task Should_Scale_Down_Canvas_Is_Bigger_Than_Terminal(bool supportsUnicode)
|
||||
{
|
||||
// Given
|
||||
var console = new TestConsole()
|
||||
.Width(10)
|
||||
.Colors(ColorSystem.Standard)
|
||||
.SupportsUnicode(supportsUnicode)
|
||||
.EmitAnsiSequences();
|
||||
|
||||
var canvas = new Canvas(width: 20, height: 10);
|
||||
@@ -88,19 +101,27 @@ public class CanvasTests
|
||||
console.Write(canvas);
|
||||
|
||||
// Then
|
||||
await Verifier.Verify(console.Output);
|
||||
await Verifier
|
||||
.Verify(console.Output)
|
||||
.UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
[Expectation("Render_MaxWidth")]
|
||||
public async Task Should_Scale_Down_Canvas_If_MaxWidth_Is_Set()
|
||||
public async Task Should_Scale_Down_Canvas_If_MaxWidth_Is_Set(bool supportsUnicode)
|
||||
{
|
||||
// Given
|
||||
var console = new TestConsole()
|
||||
.Colors(ColorSystem.Standard)
|
||||
.SupportsUnicode(supportsUnicode)
|
||||
.EmitAnsiSequences();
|
||||
|
||||
var canvas = new Canvas(width: 20, height: 10) { MaxWidth = 10 };
|
||||
var canvas = new Canvas(width: 20, height: 10)
|
||||
{
|
||||
MaxWidth = 10
|
||||
};
|
||||
canvas.SetPixel(0, 0, Color.Aqua);
|
||||
canvas.SetPixel(19, 9, Color.Aqua);
|
||||
|
||||
@@ -108,16 +129,22 @@ public class CanvasTests
|
||||
console.Write(canvas);
|
||||
|
||||
// Then
|
||||
await Verifier.Verify(console.Output);
|
||||
await Verifier
|
||||
.Verify(console.Output)
|
||||
.UseMethodName(supportsUnicode ? "Unicode" : "NonUnicode");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Render_Canvas_If_Canvas_Cannot_Be_Scaled_Down()
|
||||
[Theory]
|
||||
[InlineData(true, 5)]
|
||||
[InlineData(false, 10)]
|
||||
public void Should_Not_Render_Canvas_If_Canvas_Cannot_Be_Scaled_Down(
|
||||
bool supportsUnicode, int consoleWidth)
|
||||
{
|
||||
// Given
|
||||
var console = new TestConsole()
|
||||
.Width(10)
|
||||
.Width(consoleWidth)
|
||||
.Colors(ColorSystem.Standard)
|
||||
.SupportsUnicode(supportsUnicode)
|
||||
.EmitAnsiSequences();
|
||||
|
||||
var canvas = new Canvas(width: 20, height: 2);
|
||||
|
||||
@@ -7,6 +7,11 @@ public sealed class Canvas : Renderable
|
||||
{
|
||||
private readonly Color?[,] _pixels;
|
||||
|
||||
private const string Transparent = " ";
|
||||
private const string DoubleTransparent = " ";
|
||||
private const string UpperHalfBlock = "▀";
|
||||
private const string LowerHalfBlock = "▄";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the canvas.
|
||||
/// </summary>
|
||||
@@ -31,6 +36,7 @@ public sealed class Canvas : Renderable
|
||||
/// <summary>
|
||||
/// Gets or sets the pixel width.
|
||||
/// </summary>
|
||||
[Obsolete("Not used anymore. Will be removed in future update.")]
|
||||
public int PixelWidth { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
@@ -72,31 +78,25 @@ public sealed class Canvas : Renderable
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (PixelWidth < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Pixel width must be greater than zero.");
|
||||
}
|
||||
|
||||
var pixelWidth = options.Unicode ? 1 : 2;
|
||||
var width = MaxWidth ?? Width;
|
||||
|
||||
if (maxWidth < width * PixelWidth)
|
||||
{
|
||||
return new Measurement(maxWidth, maxWidth);
|
||||
}
|
||||
|
||||
return new Measurement(width * PixelWidth, width * PixelWidth);
|
||||
return maxWidth < width * pixelWidth
|
||||
? new Measurement(maxWidth, maxWidth)
|
||||
: new Measurement(width * pixelWidth, width * pixelWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (PixelWidth < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Pixel width must be greater than zero.");
|
||||
return options.Unicode
|
||||
? RenderUnicode(maxWidth)
|
||||
: RenderNonUnicode(maxWidth);
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> RenderUnicode(int maxWidth)
|
||||
{
|
||||
var pixels = _pixels;
|
||||
var pixel = new string(' ', PixelWidth);
|
||||
var width = Width;
|
||||
var height = Height;
|
||||
|
||||
@@ -108,14 +108,86 @@ public sealed class Canvas : Renderable
|
||||
}
|
||||
|
||||
// Exceed the max width when we take pixel width into account?
|
||||
if (width * PixelWidth > maxWidth)
|
||||
if (width > maxWidth)
|
||||
{
|
||||
height = (int)(height * (maxWidth / (float)(width * PixelWidth)));
|
||||
width = maxWidth / PixelWidth;
|
||||
height = (int)(height * (maxWidth / (float)(width)));
|
||||
width = maxWidth;
|
||||
|
||||
// If it's not possible to scale the canvas sufficiently, it's too small to render.
|
||||
if (height == 0)
|
||||
{
|
||||
// If it's not possible to scale the canvas sufficiently, it's too small to render.
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to rescale the pixel buffer?
|
||||
if (Scale && (width != Width || height != Height))
|
||||
{
|
||||
pixels = ScaleDown(width, height);
|
||||
}
|
||||
|
||||
for (var y = 0; y < height; y += 2)
|
||||
{
|
||||
for (var x = 0; x < width; x++)
|
||||
{
|
||||
var upper = pixels[x, y];
|
||||
var lower = y < height - 1 ? pixels[x, y + 1] : null;
|
||||
|
||||
if (upper == null && lower == null)
|
||||
{
|
||||
// None visible
|
||||
yield return new Segment(Transparent);
|
||||
}
|
||||
else if (upper != null && lower != null)
|
||||
{
|
||||
// Both pixels visible
|
||||
yield return new Segment(
|
||||
UpperHalfBlock, new Style(
|
||||
foreground: upper,
|
||||
background: lower));
|
||||
}
|
||||
else if (upper != null)
|
||||
{
|
||||
// Upper visible
|
||||
yield return new Segment(
|
||||
UpperHalfBlock, new Style(
|
||||
foreground: upper));
|
||||
}
|
||||
else if (lower != null)
|
||||
{
|
||||
// Lower visible
|
||||
yield return new Segment(
|
||||
LowerHalfBlock, new Style(
|
||||
foreground: lower));
|
||||
}
|
||||
}
|
||||
|
||||
yield return Segment.LineBreak;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> RenderNonUnicode(int maxWidth)
|
||||
{
|
||||
var pixels = _pixels;
|
||||
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 * 2 > maxWidth)
|
||||
{
|
||||
height = (int)(height * (maxWidth / (float)(width * 2)));
|
||||
width = maxWidth / 2;
|
||||
|
||||
if (height == 0)
|
||||
{
|
||||
// If it's not possible to scale the canvas sufficiently, it's too small to render.
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -133,11 +205,11 @@ public sealed class Canvas : Renderable
|
||||
var color = pixels[x, y];
|
||||
if (color != null)
|
||||
{
|
||||
yield return new Segment(pixel, new Style(background: color));
|
||||
yield return new Segment(DoubleTransparent, new Style(background: color));
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new Segment(pixel);
|
||||
yield return new Segment(DoubleTransparent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user