diff --git a/src/Sixel/Protocols/HalfBlock.cs b/src/Sixel/Protocols/HalfBlock.cs
new file mode 100644
index 0000000..0b3be35
--- /dev/null
+++ b/src/Sixel/Protocols/HalfBlock.cs
@@ -0,0 +1,146 @@
+using Sixel.Terminal;
+using Sixel.Terminal.Models;
+using System.Text;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Quantization;
+
+namespace Sixel.Protocols;
+
+public static class HalfBlock
+{
+    private static Rgba32 _backgroundColor;
+    public static string ImageToAscii(Image<Rgba32> image, int cellWidth, int frame = 0, bool returnCursorToTopLeft = false)
+    {
+        _backgroundColor = GetConsoleBackgroundColor();
+        var cellSize = Compatibility.GetCellSize();
+        var maxWidth = Console.WindowWidth - 2;
+        var characterWidth = (int)Math.Ceiling((double)image.Width / cellSize.PixelWidth);
+        var characterHeight = (int)Math.Ceiling((double)image.Height / cellSize.PixelHeight);
+
+        if (cellWidth <= 0 || cellWidth > maxWidth)
+        {
+            cellWidth = Math.Min(characterWidth, maxWidth);
+        }
+        image.Mutate(ctx =>
+        {
+            // Calculate target size in pixels based on character dimensions
+            var pixelHeight = (int)Math.Round((double)image.Height / image.Width * cellWidth);
+            ctx.Resize(new ResizeOptions
+            {
+                Size = new Size(cellWidth, pixelHeight),
+                Sampler = KnownResamplers.Bicubic,
+                PremultiplyAlpha = false
+            });
+        });
+        var size = new Size(image.Width, image.Height);
+        var targetFrame = image.Frames[frame];
+        return ProcessFrame(targetFrame, size, returnCursorToTopLeft);
+    }
+    private static string ProcessFrame(ImageFrame<Rgba32> frame, Size size, bool returnCursorToTopLeft)
+    {
+        var _buffer = new StringBuilder();
+
+        for (int y = 0; y < size.Height; y += 2)
+        {
+            if (y + 1 >= size.Height)
+            {
+                _buffer.AppendLine();
+                break;
+            }
+
+            for (int x = 0; x < size.Width; x++)
+            {
+                var topPixel = frame[x, y];
+                var bottomPixel = frame[x, y + 1];
+
+                _buffer.ProcessPixelPairs(topPixel, bottomPixel);
+            }
+            _buffer.AppendLine();
+        }
+        if (returnCursorToTopLeft)
+        {
+
+            // Move the cursor back to the top left of the image.
+            _buffer.Append($"{Constants.ESC}[{size.Height}A");
+        }
+        return _buffer.ToString();
+    }
+    private static void ProcessPixelPairs(this StringBuilder _buffer, Rgba32 top, Rgba32 bottom)
+    {
+        bool topTransparent = IsTransparent(top);
+        bool bottomTransparent = IsTransparent(bottom);
+
+        if (topTransparent && bottomTransparent)
+        {
+            // Both pixels are transparent
+            _buffer.Append(' ');
+        }
+        else if (topTransparent)
+        {
+            // Only bottom pixel is opaque, use lower half block
+            var bottomRgb = BlendPixels(bottom);
+            _buffer.Append($"{Constants.ESC}{Constants.VTFG}{bottomRgb.R};{bottomRgb.G};{bottomRgb.B}m{Constants.LowerHalfBlock}{Constants.ESC}[0m");
+        }
+        else if (bottomTransparent)
+        {
+            // Only top pixel is opaque, use upper half block
+            var topRgb = BlendPixels(top);
+            _buffer.Append($"{Constants.ESC}{Constants.VTFG}{topRgb.R};{topRgb.G};{topRgb.B}m{Constants.UpperHalfBlock}{Constants.ESC}[0m");
+        }
+        else
+        {
+            // Both pixels are opaque, set foreground and background colors, use upper half block
+            var topRgb = BlendPixels(top);
+            var bottomRgb = BlendPixels(bottom);
+            _buffer.Append($"{Constants.ESC}{Constants.VTFG}{topRgb.R};{topRgb.G};{topRgb.B}m");
+            _buffer.Append($"{Constants.ESC}{Constants.VTBG}{bottomRgb.R};{bottomRgb.G};{bottomRgb.B}m{Constants.UpperHalfBlock}{Constants.ESC}[0m");
+        }
+    }
+
+    private static (byte R, byte G, byte B) BlendPixels(Rgba32 pixel)
+    {
+        // fast path for fully transparent pixels
+        if (pixel.A == 0)
+        {
+            return (pixel.R, pixel.G, pixel.B);
+        }
+        // var foregroundMultiplier = pixel.A / 255;
+        // var backgroundMultiplier = 100 - foregroundMultiplier;
+        // (byte)(pixel.R * foregroundMultiplier + _backgroundColor.R * backgroundMultiplier);
+        float amount = pixel.A / 255f;
+
+        byte r = (byte)(pixel.R * amount + _backgroundColor.R * (1 - amount));
+        byte g = (byte)(pixel.G * amount + _backgroundColor.G * (1 - amount));
+        byte b = (byte)(pixel.B * amount + _backgroundColor.B * (1 - amount));
+
+        return (r, g, b);
+    }
+    private static bool IsTransparent(Rgba32 pixel) => pixel.A < 5;
+
+    private static Rgba32 GetConsoleBackgroundColor()
+    {
+        var color = Console.BackgroundColor switch
+        {
+            ConsoleColor.Black => Color.FromRgb(0, 0, 0),
+            ConsoleColor.Blue => Color.FromRgb(0, 0, 170),
+            ConsoleColor.Cyan => Color.FromRgb(0, 170, 170),
+            ConsoleColor.DarkBlue => Color.FromRgb(0, 0, 85),
+            ConsoleColor.DarkCyan => Color.FromRgb(0, 85, 85),
+            ConsoleColor.DarkGray => Color.FromRgb(85, 85, 85),
+            ConsoleColor.DarkGreen => Color.FromRgb(0, 85, 0),
+            ConsoleColor.DarkMagenta => Color.FromRgb(85, 0, 85),
+            ConsoleColor.DarkRed => Color.FromRgb(85, 0, 0),
+            ConsoleColor.DarkYellow => Color.FromRgb(85, 85, 0),
+            ConsoleColor.Gray => Color.FromRgb(170, 170, 170),
+            ConsoleColor.Green => Color.FromRgb(0, 170, 0),
+            ConsoleColor.Magenta => Color.FromRgb(170, 0, 170),
+            ConsoleColor.Red => Color.FromRgb(170, 0, 0),
+            ConsoleColor.White => Color.FromRgb(255, 255, 255),
+            ConsoleColor.Yellow => Color.FromRgb(170, 170, 0),
+            _ => Color.FromRgb(0, 0, 0)
+        };
+        return color.ToPixel<Rgba32>();
+    }
+}
diff --git a/src/Sixel/Protocols/ascii.cs b/src/Sixel/Protocols/ascii.cs
deleted file mode 100644
index 243fa78..0000000
--- a/src/Sixel/Protocols/ascii.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using Sixel.Terminal;
-using Sixel.Terminal.Models;
-using System.Text;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
-
-namespace Sixel.Protocols;
-
-// ignore this, nothing to see here.. move along.
-// not finished..
-public static class HalfBlockCell
-{
-    private static Rgba32 _backgroundColor;
-    public static string ImageToAscii(Image<Rgba32> image, int cellWidth, int frame = 0, bool returnCursorToTopLeft = false)
-    {
-        _backgroundColor = GetConsoleBackgroundColor();
-        var cellSize = Compatibility.GetCellSize();
-        var maxWidth = Console.WindowWidth - 2;
-        var characterWidth = (int)Math.Ceiling((double)image.Width / cellSize.PixelWidth);
-        var characterHeight = (int)Math.Ceiling((double)image.Height / cellSize.PixelHeight);
-
-        if (cellWidth <= 0 || cellWidth > maxWidth)
-        {
-            cellWidth = Math.Min(characterWidth, maxWidth);
-        }
-        image.Mutate(ctx => {
-            // Calculate target size in pixels based on character dimensions
-            var pixelHeight = (int)Math.Round((double)image.Height / image.Width * cellWidth);
-            ctx.Resize(new ResizeOptions {
-                Size = new Size(cellWidth, pixelHeight),
-                Sampler = KnownResamplers.Bicubic,
-                PremultiplyAlpha = false
-            });
-        });
-        var size = new Size(image.Width, image.Height);
-        var targetFrame = image.Frames[frame];
-        return FrameToAsciiString(targetFrame, size, returnCursorToTopLeft);
-    }
-    private static string FrameToAsciiString(ImageFrame<Rgba32> frame, Size size, bool returnCursorToTopLeft)
-    {
-        var _buffer = new StringBuilder();
-        frame.ProcessPixelRows(accessor =>
-        {
-            for (int y = 0; y < accessor.Height; y += 2)
-            {
-                if (y + 1 >= accessor.Height)
-                {
-                    _buffer.AppendLine();
-                    break;
-                }
-
-                Span<Rgba32> topRow = accessor.GetRowSpan(y);
-                Span<Rgba32> bottomRow = y + 1 < accessor.Height ? accessor.GetRowSpan(y + 1) : default;
-
-                for (int i = 0; i < topRow.Length; i++)
-                {
-                    ref Rgba32 topPixel = ref topRow[i];
-                    ref Rgba32 bottomPixel = ref bottomRow.IsEmpty ? ref topPixel : ref bottomRow[i];
-
-                    _buffer.ProcessPixelPair(topPixel, bottomPixel);
-                }
-                _buffer.AppendLine();
-            }
-        });
-        return _buffer.ToString().Trim();
-    }
-    private static void ProcessPixelPair(this StringBuilder _buffer, Rgba32 top, Rgba32 bottom)
-    {
-        var topRgb = BlendPixel(top);
-        var bottomRgb = BlendPixel(bottom);
-        if (IsTransparent(bottom))
-        {
-            _buffer.Append($"{Constants.ESC}[0m");
-        }
-        else
-        {
-            _buffer.Append($"{Constants.ESC}[38;2;{bottomRgb.R};{bottomRgb.G};{bottomRgb.B}m");
-        }
-
-        if (IsTransparent(top))
-        {
-            _buffer.Append($"{Constants.ESC}[0m ");
-        }
-        else
-        {
-            _buffer.Append($"{Constants.ESC}[48;2;{topRgb.R};{topRgb.G};{topRgb.B}m{Constants.LowerHalfBlock}{Constants.ESC}[0m");
-        }
-
-
-    }
-
-    private static (byte R, byte G, byte B) BlendPixel(Rgba32 pixel)
-    {
-        if (pixel.A == 0)
-        {
-            return (pixel.R, pixel.G, pixel.B);
-        }
-        var foregroundMultiplier = pixel.A / 255;
-        var backgroundMultiplier = 100 - foregroundMultiplier;
-        //var foregroundMultiplier = pixel.A / 255f;
-        //var backgroundMultiplier = 1.0f - foregroundMultiplier;
-        //var backgroundMultiplier = (255 - pixel.A) / 255f;
-        return (
-            (byte)Math.Min(255, (pixel.R * foregroundMultiplier + _backgroundColor.R * backgroundMultiplier)),
-            (byte)Math.Min(255, (pixel.G * foregroundMultiplier + _backgroundColor.G * backgroundMultiplier)),
-            (byte)Math.Min(255, (pixel.B * foregroundMultiplier + _backgroundColor.B * backgroundMultiplier))
-        );
-
-    }
-
-    private static bool IsTransparent(Rgba32 pixel) => pixel.A < 5;
-
-    private static Rgba32 GetConsoleBackgroundColor()
-    {
-        var color = Console.BackgroundColor switch {
-            ConsoleColor.Black => Color.FromRgb(0, 0, 0),
-            ConsoleColor.Blue => Color.FromRgb(0, 0, 170),
-            ConsoleColor.Cyan => Color.FromRgb(0, 170, 170),
-            ConsoleColor.DarkBlue => Color.FromRgb(0, 0, 85),
-            ConsoleColor.DarkCyan => Color.FromRgb(0, 85, 85),
-            ConsoleColor.DarkGray => Color.FromRgb(85, 85, 85),
-            ConsoleColor.DarkGreen => Color.FromRgb(0, 85, 0),
-            ConsoleColor.DarkMagenta => Color.FromRgb(85, 0, 85),
-            ConsoleColor.DarkRed => Color.FromRgb(85, 0, 0),
-            ConsoleColor.DarkYellow => Color.FromRgb(85, 85, 0),
-            ConsoleColor.Gray => Color.FromRgb(170, 170, 170),
-            ConsoleColor.Green => Color.FromRgb(0, 170, 0),
-            ConsoleColor.Magenta => Color.FromRgb(170, 0, 170),
-            ConsoleColor.Red => Color.FromRgb(170, 0, 0),
-            ConsoleColor.White => Color.FromRgb(255, 255, 255),
-            ConsoleColor.Yellow => Color.FromRgb(170, 170, 0),
-            _ => Color.FromRgb(0, 0, 0)
-        };
-        return color.ToPixel<Rgba32>();
-    }
-}
diff --git a/src/Sixel/Terminal/Constants.cs b/src/Sixel/Terminal/Constants.cs
index 91611e3..a9d870c 100644
--- a/src/Sixel/Terminal/Constants.cs
+++ b/src/Sixel/Terminal/Constants.cs
@@ -90,23 +90,46 @@ internal static class Constants
     /// Kitty raster
     /// </summary>
     internal const string KittyPos = "a=T,f=100,";
+
     /// <summary>
     /// Kitty more chunks
     /// </summary>
     internal const string KittyMore = "m=1";
+
     /// <summary>
     /// Kitty last chunk
     /// </summary>
     internal const string KittyFinish = "m=0";
+
     /// <summary>
     /// Kitty chunksize
     /// </summary>
     internal const int KittychunkSize = 4096;
 
     /// <summary>
-    /// half block character
+    /// lower half block character
+    /// ▄
+    /// this allows you to color the top and bottom of a cell.
+    /// foreground colors the lower block and background colors the space above the block in the same cell.
     /// </summary>
-    ///
     internal const char LowerHalfBlock = '\u2584';
 
+    /// <summary>
+    /// upper half block character
+    /// ▀
+    /// this allows you to color the top and bottom of a cell.
+    /// foreground colors the upper block and background colors the space below the block in the same cell.
+    /// </summary>
+    internal const char UpperHalfBlock = '\u2580';
+
+    /// <summary>
+    /// background color escape sequence
+    /// </summary>
+    internal const string VTBG = "[48;2;";
+
+    /// <summary>
+    /// foreground color escape sequence
+    /// </summary>
+    internal const string VTFG = "[38;2;";
+
 }
diff --git a/src/Sixel/Terminal/Load.cs b/src/Sixel/Terminal/Load.cs
index 2aa8e7e..1d60df7 100644
--- a/src/Sixel/Terminal/Load.cs
+++ b/src/Sixel/Terminal/Load.cs
@@ -49,7 +49,7 @@ public static class Load
     if (imageProtocol == ImageProtocol.None)
     {
         using var _image = Image.Load<Rgba32>(imageStream);
-        return Protocols.HalfBlockCell.ImageToAscii(_image, width);
+        return Protocols.HalfBlock.ImageToAscii(_image, width);
     }
      return null;
   }