Browse Source

Initial exploration of how to sixel encode

tznind 11 months ago
parent
commit
4d1d740e17

+ 2 - 0
Terminal.Gui/Application/Application.Driver.cs

@@ -26,4 +26,6 @@ public static partial class Application // Driver abstractions
     /// </remarks>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
     public static string ForceDriver { get; set; } = string.Empty;
+
+    public static string Sixel;
 }

File diff suppressed because it is too large
+ 3 - 0
Terminal.Gui/ConsoleDrivers/NetDriver.cs


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

@@ -0,0 +1,70 @@
+using System.Reflection.Metadata;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Encodes a images into the sixel console image output format.
+/// </summary>
+public class SixelEncoder
+{
+
+
+    /// <summary>
+    /// Encode the given bitmap into sixel encoding
+    /// </summary>
+    /// <param name="pixels"></param>
+    /// <returns></returns>
+    public string EncodeSixel (Color [,] pixels)
+    {
+
+        const string start = "\u001bP"; // Start sixel sequence
+
+        const string defaultRatios = "0;0;0"; // Defaults for aspect ratio and grid size
+        const string completeStartSequence = "q"; // Signals beginning of sixel image data
+        const string noScaling = "\"1;1;"; // no scaling factors (1x1);
+
+        string fillArea = GetFillArea (pixels);
+
+        string pallette = GetColorPallette (pixels, out var dictionary);
+
+        const string pixelData =
+            "~~~~$-"
+            + // First 6 rows of red pixels
+            "~~~~$-";  // Next 6 rows of red pixels
+
+        const string terminator = "\u001b\\"; // End sixel sequence
+
+        return start + defaultRatios + completeStartSequence + noScaling + fillArea + pallette + pixelData + terminator;
+    }
+
+    private string GetColorPallette (Color [,] pixels, out Dictionary<Color, int> dictionary)
+    {
+
+        dictionary = new Dictionary<Color, int>
+        {
+            {new Color(255,0,0),0}
+        };
+
+        // Red color definition in the format "#<index>;<type>;<R>;<G>;<B>" - 2 means RGB.  The values range 0 to 100
+        return "#0;2;100;0;0";
+    }
+
+    private string GetFillArea (Color [,] pixels)
+    {
+        int widthInChars = GetWidthInChars (pixels);
+        int heightInChars = GetHeightInChars (pixels);
+
+        return $"{widthInChars};{heightInChars}";
+    }
+
+    private int GetHeightInChars (Color [,] pixels)
+    {
+        // TODO
+        return 2;
+    }
+
+    private int GetWidthInChars (Color [,] pixels)
+    {
+        return 3;
+    }
+}

+ 38 - 1
UICatalog/Scenarios/Images.cs

@@ -48,7 +48,7 @@ public class Images : Scenario
 
         var btnOpenImage = new Button { X = Pos.Right (cbUseTrueColor) + 2, Y = 0, Text = "Open Image" };
         win.Add (btnOpenImage);
-
+        
         var imageView = new ImageView
         {
             X = 0, Y = Pos.Bottom (lblDriverName), Width = Dim.Fill (), Height = Dim.Fill ()
@@ -103,11 +103,16 @@ public class Images : Scenario
                                    Application.Refresh ();
                                };
 
+        var btnSixel = new Button () { X = Pos.Right (btnOpenImage) + 2, Y = 0, Text = "Output Sixel" };
+        btnSixel.Accept += (s, e) => { imageView.OutputSixel ();};
+        win.Add (btnSixel);
+
         Application.Run (win);
         win.Dispose ();
         Application.Shutdown ();
     }
 
+
     private class ImageView : View
     {
         private readonly ConcurrentDictionary<Rgba32, Attribute> _cache = new ();
@@ -155,5 +160,37 @@ public class Images : Scenario
             _fullResImage = image;
             SetNeedsDisplay ();
         }
+
+        public void OutputSixel ()
+        {
+            if (_fullResImage == null)
+            {
+                return;
+            }
+
+            var encoder = new SixelEncoder ();
+
+            var encoded = encoder.EncodeSixel (ConvertToColorArray (_fullResImage));
+
+            Application.Sixel = encoded;
+        }
+        public static Color [,] ConvertToColorArray (Image<Rgba32> image)
+        {
+            int width = image.Width;
+            int height = image.Height;
+            Color [,] colors = new Color [width, height];
+
+            // Loop through each pixel and convert Rgba32 to System.Drawing.Color
+            for (int x = 0; x < width; x++)
+            {
+                for (int y = 0; y < height; y++)
+                {
+                    var pixel = image [x, y];
+                    colors [x, y] = new Color (pixel.A, pixel.R, pixel.G, pixel.B); // Convert Rgba32 to System.Drawing.Color
+                }
+            }
+
+            return colors;
+        }
     }
 }

+ 44 - 0
UnitTests/Drawing/SixelEncoderTests.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Color = Terminal.Gui.Color;
+
+namespace UnitTests.Drawing;
+
+public class SixelEncoderTests
+{
+
+    [Fact]
+    public void EncodeSixel_RedSquare12x12_ReturnsExpectedSixel ()
+    {
+
+        var expected = "\u001bP" + // Start sixel sequence
+                            "0;0;0" + // Defaults for aspect ratio and grid size
+                            "q" + // Signals beginning of sixel image data
+                            "\"1;1;3;2" + // no scaling factors (1x1) and filling 3 runes horizontally and 2 vertically
+                            "#0;2;100;0;0" + // Red color definition in the format "#<index>;<type>;<R>;<G>;<B>" - 2 means RGB.  The values range 0 to 100
+                            "~~~~$-" + // First 6 rows of red pixels
+                            "~~~~$-" + // Next 6 rows of red pixels
+                            "\u001b\\"; // End sixel sequence
+
+        // Arrange: Create a 12x12 bitmap filled with red
+        var pixels = new Color [12, 12];
+        for (int x = 0; x < 12; x++)
+        {
+            for (int y = 0; y < 12; y++)
+            {
+                pixels [x, y] = new Color(255,0,0);
+            }
+        }
+
+        // Act: Encode the image
+        var encoder = new SixelEncoder (); // Assuming SixelEncoder is the class that contains the EncodeSixel method
+        string result = encoder.EncodeSixel (pixels);
+
+
+        Assert.Equal (expected, result);
+    }
+
+}

Some files were not shown because too many files changed in this diff