Browse Source

Output at specific position

tznind 10 months ago
parent
commit
9322b9af7c

+ 1 - 1
Terminal.Gui/Application/Application.Driver.cs

@@ -27,5 +27,5 @@ public static partial class Application // Driver abstractions
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
     public static string ForceDriver { get; set; } = string.Empty;
 
-    public static string Sixel;
+    public static List<SixelToRender> Sixel = new List<SixelToRender> ();
 }

+ 6 - 3
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1021,10 +1021,13 @@ internal class NetDriver : ConsoleDriver
                 Console.Write (output);
             }
 
-            if (!string.IsNullOrWhiteSpace(Application.Sixel))
+            foreach (var s in Application.Sixel)
             {
-                Console.SetCursorPosition (0,0);
-                Console.Write (Application.Sixel);
+                if (!string.IsNullOrWhiteSpace (s.SixelData))
+                {
+                    SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y);
+                    Console.Write (s.SixelData);
+                }
             }
         }
 

+ 0 - 1
Terminal.Gui/Drawing/SixelEncoder.cs

@@ -8,7 +8,6 @@ using Terminal.Gui;
 
 namespace Terminal.Gui;
 
-
 /// <summary>
 /// Encodes a images into the sixel console image output format.
 /// </summary>

+ 19 - 0
Terminal.Gui/Drawing/SixelToRender.cs

@@ -0,0 +1,19 @@
+namespace Terminal.Gui;
+
+/// <summary>
+/// Describes a request to render a given <see cref="SixelData"/> at a given <see cref="ScreenPosition"/>.
+/// Requires that the terminal and <see cref="ConsoleDriver"/> both support sixel.
+/// </summary>
+public class SixelToRender
+{
+    /// <summary>
+    /// gets or sets the encoded sixel data. Use <see cref="SixelEncoder"/> to convert bitmaps
+    /// into encoded sixel data.
+    /// </summary>
+    public string SixelData { get; set; }
+
+    /// <summary>
+    /// gets or sets where to move the cursor to before outputting the <see cref="SixelData"/>.
+    /// </summary>
+    public Point ScreenPosition { get; set; }
+}

+ 105 - 23
UICatalog/Scenarios/Images.cs

@@ -20,6 +20,7 @@ namespace UICatalog.Scenarios;
 public class Images : Scenario
 {
     private ImageView _imageView;
+
     public override void Main ()
     {
         Application.Init ();
@@ -27,18 +28,16 @@ public class Images : Scenario
 
         bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
 
-
-        var tabBasic = new Tab ()
+        var tabBasic = new Tab
         {
             DisplayText = "Basic"
         };
 
-        var tabSixel = new Tab ()
+        var tabSixel = new Tab
         {
             DisplayText = "Sixel"
         };
 
-
         var lblDriverName = new Label { X = 0, Y = 0, Text = $"Driver is {Application.Driver?.GetType ().Name}" };
         win.Add (lblDriverName);
 
@@ -66,7 +65,6 @@ public class Images : Scenario
         var btnOpenImage = new Button { X = Pos.Right (cbUseTrueColor) + 2, Y = 0, Text = "Open Image" };
         win.Add (btnOpenImage);
 
-
         var tv = new TabView
         {
             Y = Pos.Bottom (lblDriverName), Width = Dim.Fill (), Height = Dim.Fill ()
@@ -136,10 +134,10 @@ public class Images : Scenario
 
     private void BuildBasicTab (Tab tabBasic)
     {
-        _imageView = new ImageView
+        _imageView = new()
         {
-            Width = Dim.Fill(),
-            Height = Dim.Fill()
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
         };
 
         tabBasic.View = _imageView;
@@ -147,14 +145,64 @@ public class Images : Scenario
 
     private void BuildSixelTab (Tab tabSixel)
     {
-        tabSixel.View = new View ()
+        tabSixel.View = new()
         {
             Width = Dim.Fill (),
             Height = Dim.Fill ()
         };
-        var btnSixel = new Button { X = 0, Y = 0, Text = "Output Sixel" };
-        btnSixel.Accept += (s, e) => { _imageView.OutputSixel (); };
+
+        var btnSixel = new Button { X = 0, Y = 0, Text = "Output Sixel", Width = Dim.Auto () };
         tabSixel.View.Add (btnSixel);
+
+        var sixelView = new View
+        {
+            Y = Pos.Bottom (btnSixel),
+            Width = Dim.Percent (50),
+            Height = Dim.Fill (),
+            BorderStyle = LineStyle.Dotted
+        };
+
+        tabSixel.View.Add (sixelView);
+
+        var lblPxX = new Label
+        {
+            X = Pos.Right (sixelView),
+            Text = "Pixels per Col:"
+        };
+
+        var pxX = new NumericUpDown
+        {
+            X = Pos.Right (lblPxX),
+            Value = 12
+        };
+
+        var lblPxY = new Label
+        {
+            X = lblPxX.X,
+            Y = 1,
+            Text = "Pixels per Row:"
+        };
+
+        var pxY = new NumericUpDown
+        {
+            X = Pos.Right (lblPxY),
+            Y = 1,
+            Value = 6
+        };
+
+        tabSixel.View.Add (lblPxX);
+        tabSixel.View.Add (pxX);
+        tabSixel.View.Add (lblPxY);
+        tabSixel.View.Add (pxY);
+
+        btnSixel.Accept += (s, e) =>
+                           {
+                               _imageView.OutputSixel (
+                                                       sixelView.FrameToScreen ().Location,
+                                                       sixelView.Frame.Size,
+                                                       pxX.Value,
+                                                       pxY.Value);
+                           };
     }
 
     private class ImageView : View
@@ -205,7 +253,12 @@ public class Images : Scenario
             SetNeedsDisplay ();
         }
 
-        public void OutputSixel ()
+        public void OutputSixel (
+            Point screenPosition,
+            Size maxSize,
+            int pixelsPerCellX,
+            int pixelsPerCellY
+        )
         {
             if (_fullResImage == null)
             {
@@ -214,7 +267,21 @@ public class Images : Scenario
 
             var encoder = new SixelEncoder ();
 
-            string encoded = encoder.EncodeSixel (ConvertToColorArray (_fullResImage));
+            // Calculate the target size in pixels based on console units
+            int targetWidthInPixels = maxSize.Width * pixelsPerCellX;
+            int targetHeightInPixels = maxSize.Height * pixelsPerCellY;
+
+            // Get the original image dimensions
+            int originalWidth = _fullResImage.Width;
+            int originalHeight = _fullResImage.Height;
+
+            // Use the helper function to get the resized dimensions while maintaining the aspect ratio
+            Size newSize = CalculateAspectRatioFit (originalWidth, originalHeight, targetWidthInPixels, targetHeightInPixels);
+
+            // Resize the image to match the console size
+            Image<Rgba32> resizedImage = _fullResImage.Clone (x => x.Resize (newSize.Width, newSize.Height));
+
+            string encoded = encoder.EncodeSixel (ConvertToColorArray (resizedImage));
 
             var pv = new PaletteView (encoder.Quantizer.Palette.ToList ());
 
@@ -235,7 +302,29 @@ public class Images : Scenario
             dlg.AddButton (btn);
             Application.Run (dlg);
 
-            Application.Sixel = encoded;
+            Application.Sixel.Add (
+                                   new()
+                                   {
+                                       ScreenPosition = screenPosition,
+                                       SixelData = encoded
+                                   });
+        }
+
+        private Size CalculateAspectRatioFit (int originalWidth, int originalHeight, int targetWidth, int targetHeight)
+        {
+            // Calculate the scaling factor for width and height
+            double widthScale = (double)targetWidth / originalWidth;
+            double heightScale = (double)targetHeight / originalHeight;
+
+            // Use the smaller scaling factor to maintain the aspect ratio
+            double scale = Math.Min (widthScale, heightScale);
+
+            // Calculate the new width and height while keeping the aspect ratio
+            var newWidth = (int)(originalWidth * scale);
+            var newHeight = (int)(originalHeight * scale);
+
+            // Return the new size as a Size object
+            return new (newWidth, newHeight);
         }
 
         public static Color [,] ConvertToColorArray (Image<Rgba32> image)
@@ -260,7 +349,7 @@ public class Images : Scenario
 
     public class PaletteView : View
     {
-        private List<Color> _palette;
+        private readonly List<Color> _palette;
 
         public PaletteView (List<Color> palette)
         {
@@ -324,13 +413,6 @@ public class Images : Scenario
                 }
             }
         }
-
-        // Allows dynamically changing the palette
-        public void SetPalette (List<Color> palette)
-        {
-            _palette = palette ?? new List<Color> ();
-            SetNeedsDisplay ();
-        }
     }
 }
 
@@ -418,7 +500,7 @@ public class MedianCutPaletteBuilder : IPaletteBuilder
 
     private List<Color> MedianCut (List<Color> colors, int maxColors)
     {
-        List<List<Color>> cubes = new() { colors };
+        List<List<Color>> cubes = new () { colors };
 
         // Recursively split color regions
         while (cubes.Count < maxColors)