Browse Source

WIP: trying to get fully transparent alpha to not render

tznind 10 months ago
parent
commit
b67c662b20

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

@@ -113,13 +113,24 @@ public class SixelEncoder
         for (int x = 0; x < width; ++x)
         for (int x = 0; x < width; ++x)
         {
         {
             Array.Clear (code, 0, usedColorIdx.Count);
             Array.Clear (code, 0, usedColorIdx.Count);
+            bool anyNonTransparentPixel = false;  // Track if any non-transparent pixels are found in this column
 
 
             // Process each row in the 6-pixel high band
             // Process each row in the 6-pixel high band
             for (int row = 0; row < bandHeight; ++row)
             for (int row = 0; row < bandHeight; ++row)
             {
             {
                 var color = pixels [x, startY + row];
                 var color = pixels [x, startY + row];
+
                 int colorIndex = Quantizer.GetNearestColor (color);
                 int colorIndex = Quantizer.GetNearestColor (color);
 
 
+                if (color.A == 0) // Skip fully transparent pixels
+                {
+                    continue;
+                }
+                else
+                {
+                    anyNonTransparentPixel = true;
+                }
+
                 if (slots [colorIndex] == -1)
                 if (slots [colorIndex] == -1)
                 {
                 {
                     targets.Add (new List<string> ());
                     targets.Add (new List<string> ());
@@ -135,6 +146,8 @@ public class SixelEncoder
                 code [slots [colorIndex]] |= (byte)(1 << row); // Accumulate SIXEL data
                 code [slots [colorIndex]] |= (byte)(1 << row); // Accumulate SIXEL data
             }
             }
 
 
+            // TODO: Handle fully empty rows better
+
             // Handle transitions between columns
             // Handle transitions between columns
             for (int j = 0; j < usedColorIdx.Count; ++j)
             for (int j = 0; j < usedColorIdx.Count; ++j)
             {
             {

+ 146 - 9
UICatalog/Scenarios/Images.cs

@@ -22,11 +22,12 @@ public class Images : Scenario
     private ImageView _imageView;
     private ImageView _imageView;
     private Point _screenLocationForSixel;
     private Point _screenLocationForSixel;
     private string _encodedSixelData;
     private string _encodedSixelData;
+    private Window _win;
 
 
     public override void Main ()
     public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
-        var win = new Window { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" };
+        _win = new Window { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" };
 
 
         bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
         bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
 
 
@@ -41,7 +42,7 @@ public class Images : Scenario
         };
         };
 
 
         var lblDriverName = new Label { X = 0, Y = 0, Text = $"Driver is {Application.Driver?.GetType ().Name}" };
         var lblDriverName = new Label { X = 0, Y = 0, Text = $"Driver is {Application.Driver?.GetType ().Name}" };
-        win.Add (lblDriverName);
+        _win.Add (lblDriverName);
 
 
         var cbSupportsTrueColor = new CheckBox
         var cbSupportsTrueColor = new CheckBox
         {
         {
@@ -51,7 +52,7 @@ public class Images : Scenario
             CanFocus = false,
             CanFocus = false,
             Text = "supports true color "
             Text = "supports true color "
         };
         };
-        win.Add (cbSupportsTrueColor);
+        _win.Add (cbSupportsTrueColor);
 
 
         var cbSupportsSixel = new CheckBox
         var cbSupportsSixel = new CheckBox
         {
         {
@@ -63,7 +64,7 @@ public class Images : Scenario
             Enabled = false,
             Enabled = false,
             Text = "Supports Sixel"
             Text = "Supports Sixel"
         };
         };
-        win.Add (cbSupportsSixel);
+        _win.Add (cbSupportsSixel);
 
 
         var cbUseTrueColor = new CheckBox
         var cbUseTrueColor = new CheckBox
         {
         {
@@ -74,10 +75,15 @@ public class Images : Scenario
             Text = "Use true color"
             Text = "Use true color"
         };
         };
         cbUseTrueColor.CheckedStateChanging += (_, evt) => Application.Force16Colors = evt.NewValue == CheckState.UnChecked;
         cbUseTrueColor.CheckedStateChanging += (_, evt) => Application.Force16Colors = evt.NewValue == CheckState.UnChecked;
-        win.Add (cbUseTrueColor);
+        _win.Add (cbUseTrueColor);
 
 
         var btnOpenImage = new Button { X = Pos.Right (cbUseTrueColor) + 2, Y = 0, Text = "Open Image" };
         var btnOpenImage = new Button { X = Pos.Right (cbUseTrueColor) + 2, Y = 0, Text = "Open Image" };
-        win.Add (btnOpenImage);
+        _win.Add (btnOpenImage);
+
+        var btnStartFire = new Button { X = Pos.Right (cbUseTrueColor) + 2, Y = 1, Text = "Start Fire" };
+        _win.Add (btnStartFire);
+
+        btnStartFire.Accept += BtnStartFireOnAccept;
 
 
         var tv = new TabView
         var tv = new TabView
         {
         {
@@ -92,11 +98,38 @@ public class Images : Scenario
 
 
         btnOpenImage.Accept += OpenImage;
         btnOpenImage.Accept += OpenImage;
 
 
-        win.Add (tv);
-        Application.Run (win);
-        win.Dispose ();
+        _win.Add (tv);
+        Application.Run (_win);
+        _win.Dispose ();
         Application.Shutdown ();
         Application.Shutdown ();
+    }
+
+    private void BtnStartFireOnAccept (object sender, HandledEventArgs e)
+    {
+        var fire = new DoomFire (_win.Frame.Width, _win.Frame.Height);
+        var encoder = new SixelEncoder ();
+        encoder.Quantizer.PaletteBuildingAlgorithm = new ConstPalette (fire.Palette);
+
+        Application.AddTimeout (
+                                TimeSpan.FromMilliseconds (500),
+                                () =>
+                                {
+                                    fire.AdvanceFrame ();
+
+                                    var bmp = fire.GetFirePixels ();
 
 
+                                    // TODO: Static way of doing this, suboptimal
+                                    Application.Sixel.Clear ();
+                                    Application.Sixel.Add (new SixelToRender
+                                    {
+                                        SixelData = encoder.EncodeSixel (bmp),
+                                        ScreenPosition = new Point (0,0)
+                                    });
+
+                                    _win.SetNeedsDisplay();
+
+                                    return true;
+                                });
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
@@ -150,6 +183,7 @@ public class Images : Scenario
             return;
             return;
         }
         }
 
 
+
         _imageView.SetImage (img);
         _imageView.SetImage (img);
         Application.Refresh ();
         Application.Refresh ();
     }
     }
@@ -465,6 +499,19 @@ public class Images : Scenario
     }
     }
 }
 }
 
 
+internal class ConstPalette : IPaletteBuilder
+{
+    private readonly List<Color>  _palette;
+
+    public ConstPalette (Color [] palette) { _palette = palette.ToList (); }
+
+    /// <inheritdoc />
+    public List<Color> BuildPalette (List<Color> colors, int maxColors)
+    {
+        return _palette;
+    }
+}
+
 public abstract class LabColorDistance : IColorDistance
 public abstract class LabColorDistance : IColorDistance
 {
 {
     // Reference white point for D65 illuminant (can be moved to constants)
     // Reference white point for D65 illuminant (can be moved to constants)
@@ -677,3 +724,93 @@ public class MedianCutPaletteBuilder : IPaletteBuilder
         return (maxR - minR) * (maxG - minG) * (maxB - minB);
         return (maxR - minR) * (maxG - minG) * (maxB - minB);
     }
     }
 }
 }
+
+
+public class DoomFire
+{
+    private int _width;
+    private int _height;
+    private Color [,] _firePixels;
+    private static Color [] _palette;
+    public Color [] Palette => _palette;
+
+    public DoomFire (int width, int height)
+    {
+        _width = width;
+        _height = height;
+        _firePixels = new Color [width, height];
+        InitializePalette ();
+        InitializeFire ();
+    }
+
+    private void InitializePalette ()
+    {
+        // Initialize a basic fire palette. You can modify these colors as needed.
+        _palette = new Color [37]; // Using 37 colors as per the original Doom fire palette scale.
+
+        // First color is transparent black
+        _palette [0] = new Color (0, 0, 0, 0); // Transparent black (ARGB)
+
+        // The rest of the palette is fire colors
+        for (int i = 1; i < 37; i++)
+        {
+            byte r = (byte)Math.Min (255, i * 7);
+            byte g = (byte)Math.Min (255, i * 5);
+            byte b = (byte)Math.Min (255, i * 2);
+            _palette [i] = new Color (r, g, b); // Full opacity
+        }
+    }
+
+    public void InitializeFire ()
+    {
+        // Set the bottom row to full intensity (simulate the base of the fire).
+        for (int x = 0; x < _width; x++)
+        {
+            _firePixels [x, _height - 1] = _palette [36]; // Max intensity fire.
+        }
+
+        // Set the rest of the pixels to black (transparent).
+        for (int y = 0; y < _height - 1; y++)
+        {
+            for (int x = 0; x < _width; x++)
+            {
+                _firePixels [x, y] = _palette [0]; // Transparent black
+            }
+        }
+    }
+
+    public void AdvanceFrame ()
+    {
+        // Process every pixel except the bottom row
+        for (int x = 0; x < _width; x++)
+        {
+            for (int y = 1; y < _height; y++) // Skip the last row (which is always max intensity)
+            {
+                int srcX = x;
+                int srcY = y;
+                int dstY = y - 1;
+
+                // Spread fire upwards with randomness
+                int decay = new Random ().Next (0, 3);
+                int dstX = Math.Max (0, srcX - decay);
+
+                // Get the fire color from below and reduce its intensity
+                Color srcColor = _firePixels [srcX, srcY];
+                int intensity = Array.IndexOf (_palette, srcColor) - decay;
+
+                if (intensity < 0)
+                {
+                    intensity = 0;
+                }
+
+                _firePixels [dstX, dstY] = _palette [intensity];
+            }
+        }
+    }
+
+    public Color [,] GetFirePixels ()
+    {
+        return _firePixels;
+    }
+}
+

+ 82 - 0
UnitTests/Drawing/SixelEncoderTests.cs

@@ -149,4 +149,86 @@ public class SixelEncoderTests
         // Compare the generated SIXEL string with the expected one
         // Compare the generated SIXEL string with the expected one
         Assert.Equal (expected, result);
         Assert.Equal (expected, result);
     }
     }
+
+    [Fact]
+    public void EncodeSixel_Transparent12x12_ReturnsExpectedSixel ()
+    {
+        string expected = "\u001bP" // Start sixel sequence
+                          + "0;0;0" // Defaults for aspect ratio and grid size
+                          + "q" // Signals beginning of sixel image data
+                          + "\"1;1;12;12" // no scaling factors (1x1) and filling 12x12 pixel area
+                          + "#0;2;0;0;0" // Black transparent (TODO: Shouldn't really be output this if it is transparent)
+                          // Since all pixels are transparent, the data should just be filled with '?'
+                          + "#0!12?$-" // Fills the transparent line with byte 0 which maps to '?'
+                          + "#0!12?$" // Second band, same fully transparent pixels
+                          + "\u001b\\"; // End sixel sequence
+
+        // Arrange: Create a 12x12 bitmap filled with fully transparent pixels
+        Color [,] pixels = new Color [12, 12];
+
+        for (var x = 0; x < 12; x++)
+        {
+            for (var y = 0; y < 12; y++)
+            {
+                pixels [x, y] = new (0, 0, 0, 0); // Fully transparent
+            }
+        }
+
+        // Act: Encode the image
+        var encoder = new SixelEncoder ();
+        string result = encoder.EncodeSixel (pixels);
+
+        // Assert: Expect the result to be fully transparent encoded output
+        Assert.Equal (expected, result);
+    }
+    [Fact]
+    public void EncodeSixel_VerticalMix_TransparentAndColor_ReturnsExpectedSixel ()
+    {
+        string expected = "\u001bP" // Start sixel sequence
+                          + "0;0;0" // Defaults for aspect ratio and grid size
+                          + "q" // Signals beginning of sixel image data
+                          + "\"1;1;12;12" // No scaling factors (1x1) and filling 12x12 pixel area
+                          /*
+                           * Define the color palette:
+                           * We'll use one color (Red) for the colored pixels.
+                           */
+                          + "#0;2;100;0;0" // Red color definition (index 0: RGB 100,0,0)
+                          + "#1;2;0;0;0" // Black transparent (TODO: Shouldn't really be output this if it is transparent)
+                          /*
+                           * Start of the Pixel data
+                           * We have alternating transparent (0) and colored (red) pixels in a vertical band.
+                           * The pattern for each sixel byte is 101010, which in binary (+63) converts to ASCII character 'T'.
+                           * Since we have 12 pixels horizontally, we'll see this pattern repeat across the row so we see
+                           * the 'sequence repeat' 12 times i.e. !12 (do the next letter 'T' 12 times).
+                           */
+                          + "#0!12T$-" // First band of alternating red and transparent pixels
+                          + "#0!12T$" // Second band, same alternating red and transparent pixels
+                          + "\u001b\\"; // End sixel sequence
+
+        // Arrange: Create a 12x12 bitmap with alternating transparent and red pixels in a vertical band
+        Color [,] pixels = new Color [12, 12];
+
+        for (var x = 0; x < 12; x++)
+        {
+            for (var y = 0; y < 12; y++)
+            {
+                // For simplicity, we'll make every other row transparent
+                if (y % 2 == 0)
+                {
+                    pixels [x, y] = new (255, 0, 0); // Red pixel
+                }
+                else
+                {
+                    pixels [x, y] = new (0, 0, 0, 0); // Transparent pixel
+                }
+            }
+        }
+
+        // Act: Encode the image
+        var encoder = new SixelEncoder ();
+        string result = encoder.EncodeSixel (pixels);
+
+        // Assert: Expect the result to match the expected sixel output
+        Assert.Equal (expected, result);
+    }
 }
 }