WIP: trying to get fully transparent alpha to not render

This commit is contained in:
tznind
2024-09-30 21:04:29 +01:00
parent 08aa9925b5
commit b67c662b20
3 changed files with 241 additions and 9 deletions

View File

@@ -113,13 +113,24 @@ public class SixelEncoder
for (int x = 0; x < width; ++x)
{
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
for (int row = 0; row < bandHeight; ++row)
{
var color = pixels [x, startY + row];
int colorIndex = Quantizer.GetNearestColor (color);
if (color.A == 0) // Skip fully transparent pixels
{
continue;
}
else
{
anyNonTransparentPixel = true;
}
if (slots [colorIndex] == -1)
{
targets.Add (new List<string> ());
@@ -135,6 +146,8 @@ public class SixelEncoder
code [slots [colorIndex]] |= (byte)(1 << row); // Accumulate SIXEL data
}
// TODO: Handle fully empty rows better
// Handle transitions between columns
for (int j = 0; j < usedColorIdx.Count; ++j)
{

View File

@@ -22,11 +22,12 @@ public class Images : Scenario
private ImageView _imageView;
private Point _screenLocationForSixel;
private string _encodedSixelData;
private Window _win;
public override void Main ()
{
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;
@@ -41,7 +42,7 @@ public class Images : Scenario
};
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
{
@@ -51,7 +52,7 @@ public class Images : Scenario
CanFocus = false,
Text = "supports true color "
};
win.Add (cbSupportsTrueColor);
_win.Add (cbSupportsTrueColor);
var cbSupportsSixel = new CheckBox
{
@@ -63,7 +64,7 @@ public class Images : Scenario
Enabled = false,
Text = "Supports Sixel"
};
win.Add (cbSupportsSixel);
_win.Add (cbSupportsSixel);
var cbUseTrueColor = new CheckBox
{
@@ -74,10 +75,15 @@ public class Images : Scenario
Text = "Use true color"
};
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" };
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
{
@@ -92,11 +98,38 @@ public class Images : Scenario
btnOpenImage.Accept += OpenImage;
win.Add (tv);
Application.Run (win);
win.Dispose ();
_win.Add (tv);
Application.Run (_win);
_win.Dispose ();
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 />
@@ -150,6 +183,7 @@ public class Images : Scenario
return;
}
_imageView.SetImage (img);
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
{
// 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);
}
}
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;
}
}

View File

@@ -149,4 +149,86 @@ public class SixelEncoderTests
// Compare the generated SIXEL string with the expected one
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);
}
}