using System; using System.Collections.Generic; using Spectre.Console.Rendering; namespace Spectre.Console { /// /// Represents a renderable canvas. /// public sealed class Canvas : Renderable { private readonly Color?[,] _pixels; /// /// Gets the width of the canvas. /// public int Width { get; } /// /// Gets the height of the canvas. /// public int Height { get; } /// /// Gets or sets the render width of the canvas. /// public int? MaxWidth { get; set; } /// /// Gets or sets a value indicating whether or not /// to scale the canvas when rendering. /// public bool Scale { get; set; } = true; /// /// Gets or sets the pixel width. /// public int PixelWidth { get; set; } = 2; /// /// Initializes a new instance of the class. /// /// The canvas width. /// The canvas height. public Canvas(int width, int height) { if (width < 1) { throw new ArgumentException("Must be > 1", nameof(width)); } if (height < 1) { throw new ArgumentException("Must be > 1", nameof(height)); } Width = width; Height = height; _pixels = new Color?[Width, Height]; } /// /// Sets a pixel with the specified color in the canvas at the specified location. /// /// The X coordinate for the pixel. /// The Y coordinate for the pixel. /// The pixel color. /// The same instance so that multiple calls can be chained. public Canvas SetPixel(int x, int y, Color color) { _pixels[x, y] = color; return this; } /// protected override Measurement Measure(RenderContext context, 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(RenderContext context, int maxWidth) { if (PixelWidth < 0) { throw new InvalidOperationException("Pixel width must be greater than zero."); } var pixels = _pixels; var pixel = new string(' ', PixelWidth); 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; // If it's not possible to scale the canvas sufficiently, it's too small to render. if (height == 0) { 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++) { for (var x = 0; x < width; x++) { var color = pixels[x, y]; if (color != null) { yield return new Segment(pixel, new Style(background: color)); } else { yield return new Segment(pixel); } } yield return Segment.LineBreak; } } private Color?[,] ScaleDown(int newWidth, int newHeight) { var buffer = new Color?[newWidth, newHeight]; var xRatio = ((Width << 16) / newWidth) + 1; var yRatio = ((Height << 16) / newHeight) + 1; for (var i = 0; i < newHeight; i++) { for (var j = 0; j < newWidth; j++) { buffer[j, i] = _pixels[(j * xRatio) >> 16, (i * yRatio) >> 16]; } } return buffer; } } }