Browse Source

Switch to a new WriteSixel algorithm

tznind 10 months ago
parent
commit
891adec260

+ 1 - 1
Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

@@ -50,7 +50,7 @@ public class ColorQuantizer
 
 
     public int GetNearestColor (Color toTranslate)
     public int GetNearestColor (Color toTranslate)
     {
     {
-        // Simple nearest color matching based on Euclidean distance in RGB space
+        // Simple nearest color matching based on DistanceAlgorithm
         double minDistance = double.MaxValue;
         double minDistance = double.MaxValue;
         int nearestIndex = 0;
         int nearestIndex = 0;
 
 

+ 86 - 97
Terminal.Gui/Drawing/SixelEncoder.cs

@@ -30,7 +30,7 @@ public class SixelEncoder
 
 
         string fillArea = GetFillArea (pixels);
         string fillArea = GetFillArea (pixels);
 
 
-        string pallette = GetColorPallette (pixels );
+        string pallette = GetColorPalette (pixels );
 
 
         string pixelData = WriteSixel (pixels);
         string pixelData = WriteSixel (pixels);
 
 
@@ -52,6 +52,7 @@ public class SixelEncoder
        [ ]  - Bit 5 (bottom-most pixel)
        [ ]  - Bit 5 (bottom-most pixel)
     */
     */
 
 
+
     private string WriteSixel (Color [,] pixels)
     private string WriteSixel (Color [,] pixels)
     {
     {
         StringBuilder sb = new StringBuilder ();
         StringBuilder sb = new StringBuilder ();
@@ -60,131 +61,121 @@ public class SixelEncoder
         int n = 1; // Used for checking when to add the line terminator
         int n = 1; // Used for checking when to add the line terminator
 
 
         // Iterate over each row of the image
         // Iterate over each row of the image
-        for (int y = 0; y < height; y++)
+        for (int y = 0; y < height; y += 6)
+        {
+            sb.Append (ProcessBand (pixels, y, Math.Min (6, height - y), width));
+
+            // Line separator between bands
+            if (y + 6 < height) // Only add separator if not the last band
+            {
+                sb.Append ("-");
+            }
+        }
+
+        return sb.ToString ();
+    }
+
+    private string ProcessBand (Color [,] pixels, int startY, int bandHeight, int width)
+    {
+        var last = new sbyte [Quantizer.Palette.Count + 1];
+        var code = new byte [Quantizer.Palette.Count + 1];
+        var accu = new ushort [Quantizer.Palette.Count + 1];
+        var slots = new short [Quantizer.Palette.Count + 1];
+
+        Array.Fill (last, (sbyte)-1);
+        Array.Fill (accu, (ushort)1);
+        Array.Fill (slots, (short)-1);
+
+        var usedColorIdx = new List<int> ();
+        var targets = new List<List<string>> ();
+
+        // Process columns within the band
+        for (int x = 0; x < width; ++x)
         {
         {
-            int p = y * width;
-            Color cachedColor = pixels [0, y];
-            int cachedColorIndex = Quantizer.GetNearestColor (cachedColor );
-            int count = 1;
-            int c = -1;
-
-            // Iterate through each column in the row
-            for (int x = 0; x < width; x++)
+            Array.Clear (code, 0, usedColorIdx.Count);
+
+            // Process each row in the 6-pixel high band
+            for (int row = 0; row < bandHeight; ++row)
             {
             {
-                Color color = pixels [x, y];
+                var color = pixels [x, startY + row];
                 int colorIndex = Quantizer.GetNearestColor (color);
                 int colorIndex = Quantizer.GetNearestColor (color);
 
 
-                if (colorIndex == cachedColorIndex)
+                if (slots [colorIndex] == -1)
                 {
                 {
-                    count++;
-                }
-                else
-                {
-                    // Output the cached color first
-                    if (cachedColorIndex == -1)
-                    {
-                        c = 0x3f; // Key color or transparent
-                    }
-                    else
+                    targets.Add (new List<string> ());
+                    if (x > 0)
                     {
                     {
-                        c = 0x3f + n;
-                        sb.AppendFormat ("#{0}", cachedColorIndex);
+                        last [usedColorIdx.Count] = 0;
+                        accu [usedColorIdx.Count] = (ushort)x;
                     }
                     }
-
-                    // If count is less than 3, we simply repeat the character
-                    if (count < 3)
-                    {
-                        sb.Append ((char)c, count);
-                    }
-                    else
-                    {
-                        // RLE if count is greater than 3
-                        sb.AppendFormat ("!{0}{1}", count, (char)c);
-                    }
-
-                    // Reset for the new color
-                    count = 1;
-                    cachedColorIndex = colorIndex;
+                    slots [colorIndex] = (short)usedColorIdx.Count;
+                    usedColorIdx.Add (colorIndex);
                 }
                 }
+
+                code [slots [colorIndex]] |= (byte)(1 << row); // Accumulate SIXEL data
             }
             }
 
 
-            // Handle the last run of the color
-            if (c != -1 && count > 1)
+            // Handle transitions between columns
+            for (int j = 0; j < usedColorIdx.Count; ++j)
             {
             {
-                if (cachedColorIndex == -1)
-                {
-                    c = 0x3f; // Key color
-                }
-                else
-                {
-                    sb.AppendFormat ("#{0}", cachedColorIndex);
-                }
-
-                if (count < 3)
+                if (code [j] == last [j])
                 {
                 {
-                    sb.Append ((char)c, count);
+                    accu [j]++;
                 }
                 }
                 else
                 else
                 {
                 {
-                    sb.AppendFormat ("!{0}{1}", count, (char)c);
+                    if (last [j] != -1)
+                    {
+                        targets [j].Add (CodeToSixel (last [j], accu [j]));
+                    }
+                    last [j] = (sbyte)code [j];
+                    accu [j] = 1;
                 }
                 }
             }
             }
+        }
 
 
-            // Line terminator or separator depending on `n`
-            if (n == 32)
-            {
-                /*
-                 2. Line Separator (-):
-                   
-                   The line separator instructs the sixel renderer to move to the next row of sixels.
-                   After a -, the renderer will start a new row from the leftmost column. This marks the end of one line of sixel data and starts a new line.
-                   This ensures that the sixel data drawn after the separator appears below the previous row rather than overprinting it.
-               
-                   Use case: When you want to start drawing a new line of sixels (e.g., after completing a row of sixel columns).
-                */
-
-                n = 1;
-                sb.Append ("-"); // Write sixel line separator
-            }
-            else
+        // Process remaining data for this band
+        for (int j = 0; j < usedColorIdx.Count; ++j)
+        {
+            if (last [j] != 0)
             {
             {
-                /*
-                 *1. Line Terminator ($):
-                   
-                   The line terminator instructs the sixel renderer to return to the start of the current row but allows subsequent sixel characters to be overprinted on the same row.
-                   This is used when you are working with multiple color layers or want to continue drawing in the same row but with a different color.
-                   The $ allows you to overwrite sixel characters in the same vertical position by using different colors, effectively allowing you to combine colors on a per-sixel basis.
-                   
-                   Use case: When you need to draw multiple colors within the same vertical slice of 6 pixels.
-                 */
-
-                n <<= 1;
-                sb.Append ("$"); // Write line terminator
+                targets [j].Add (CodeToSixel (last [j], accu [j]));
             }
             }
         }
         }
 
 
-        return sb.ToString ();
-    }
+        // Build the final output for this band
+        var result = new StringBuilder ();
+        for (int j = 0; j < usedColorIdx.Count; ++j)
+        {
+            result.Append ($"#{usedColorIdx [j]}{string.Join ("", targets [j])}$");
+        }
 
 
+        return result.ToString ();
+    }
 
 
+    private static string CodeToSixel (int code, int repeat)
+    {
+        char c = (char)(code + 63);
+        if (repeat > 3) return "!" + repeat + c;
+        if (repeat == 3) return c.ToString () + c + c;
+        if (repeat == 2) return c.ToString () + c;
+        return c.ToString ();
+    }
 
 
-    private string GetColorPallette (Color [,] pixels)
+    private string GetColorPalette (Color [,] pixels)
     {
     {
         Quantizer.BuildPalette (pixels);
         Quantizer.BuildPalette (pixels);
 
 
-
-        // Color definitions in the format "#<index>;<type>;<R>;<G>;<B>" - For type the 2 means RGB.  The values range 0 to 100
-
         StringBuilder paletteSb = new StringBuilder ();
         StringBuilder paletteSb = new StringBuilder ();
 
 
         for (int i = 0; i < Quantizer.Palette.Count; i++)
         for (int i = 0; i < Quantizer.Palette.Count; i++)
         {
         {
             var color = Quantizer.Palette.ElementAt (i);
             var color = Quantizer.Palette.ElementAt (i);
             paletteSb.AppendFormat ("#{0};2;{1};{2};{3}",
             paletteSb.AppendFormat ("#{0};2;{1};{2};{3}",
-                                    i,
-                                    color.R * 100 / 255,
-                                    color.G * 100 / 255,
-                                    color.B * 100 / 255);
+                i,
+                color.R * 100 / 255,
+                color.G * 100 / 255,
+                color.B * 100 / 255);
         }
         }
 
 
         return paletteSb.ToString ();
         return paletteSb.ToString ();
@@ -197,17 +188,15 @@ public class SixelEncoder
 
 
         return $"{widthInChars};{heightInChars}";
         return $"{widthInChars};{heightInChars}";
     }
     }
+
     private int GetHeightInChars (Color [,] pixels)
     private int GetHeightInChars (Color [,] pixels)
     {
     {
-        // Height in pixels is equal to the number of rows in the pixel array
         int height = pixels.GetLength (1);
         int height = pixels.GetLength (1);
-
-        // Each SIXEL character represents 6 pixels vertically
-        return (height + 5) / 6; // Equivalent to ceiling(height / 6)
+        return (height + 5) / 6;
     }
     }
+
     private int GetWidthInChars (Color [,] pixels)
     private int GetWidthInChars (Color [,] pixels)
     {
     {
-        // Width in pixels is equal to the number of columns in the pixel array
         return pixels.GetLength (0);
         return pixels.GetLength (0);
     }
     }
 }
 }

+ 0 - 1
UICatalog/Scenarios/Images.cs

@@ -172,7 +172,6 @@ public class Images : Scenario
             }
             }
 
 
             var encoder = new SixelEncoder ();
             var encoder = new SixelEncoder ();
-            encoder.Quantizer.PaletteBuildingAlgorithm = new KMeansPaletteBuilder (new EuclideanColorDistance());
 
 
             var encoded = encoder.EncodeSixel (ConvertToColorArray (_fullResImage));
             var encoded = encoder.EncodeSixel (ConvertToColorArray (_fullResImage));