-
-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement quad pixels characters for drawing bitmaps #137
Conversation
WalkthroughThe changes in the Changes
Possibly related PRs
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Co-authored-by: <[email protected]>
This is very cool! I recently added Sixel support to Terminal.Gui - this could make a great fallback approach for terminals that do not support it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (3)
src/Consolonia.Core/Drawing/DrawingContextImpl.cs (3)
79-83
: Eliminate duplicate code when initializing pixel arrays.The arrays
quadColors
andquadPixels
are both initialized with the same pixel values. This duplication can be avoided to improve maintainability.You can reuse
quadColors
for both purposes:var quadColors = new[] { bitmap.GetPixel(x, y), bitmap.GetPixel(x + 1, y), bitmap.GetPixel(x, y + 1), bitmap.GetPixel(x + 1, y + 1) }; - // map it to a single char to represent the 4 pixels char quadPixel = GetQuadPixelCharacter(quadColors); - // get the combined colors for the quad pixel - var quadPixels = new[] - { - bitmap.GetPixel(x, y), bitmap.GetPixel(x + 1, y), - bitmap.GetPixel(x, y + 1), bitmap.GetPixel(x + 1, y + 1) - }; - Color foreground = GetForegroundColorForQuadPixel(quadPixels, quadPixel); + // reuse quadColors + Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel);Also applies to: 89-93
561-586
: Avoid code duplication inGetForegroundColorForQuadPixel
.The method contains a switch statement similar to the one in
GetBackgroundColorForQuadPixel
. Consider refactoring to avoid duplication.You could extract common logic or parameterize the method to handle both foreground and background color calculations.
681-684
: Invert 'if' statement to reduce nesting.The code can be simplified by inverting the
if
condition, reducing nesting and improving readability.Apply this diff:
- if (distance < minDistance) - { - minDistance = distance; - closestCluster = i; - } + if (distance >= minDistance) + continue; + minDistance = distance; + closestCluster = i;This follows the static analysis suggestion to invert the
if
statement.🧰 Tools
🪛 GitHub Check: build
[notice] 681-681:
"[InvertIf] Invert 'if' statement to reduce nesting" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(681,17)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
src/Consolonia.Core/Drawing/DrawingContextImpl.cs
(3 hunks)
🧰 Additional context used
🪛 GitHub Check: build
src/Consolonia.Core/Drawing/DrawingContextImpl.cs
[notice] 681-681:
"[InvertIf] Invert 'if' statement to reduce nesting" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(681,17)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (2)
src/Consolonia.Core/Drawing/DrawingContextImpl.cs (2)
70-106
: Refactor the quad pixel processing logic into a separate method.Consider extracting the quad pixel processing logic into a separate method to improve readability and maintainability. The new method can take the bitmap, target rectangle, and any other necessary parameters as input and handle the quad pixel mapping and rendering.
- for (int y = 0; y < bitmap.Info.Height; y += 2) - { - for (int x = 0; x < bitmap.Info.Width; x += 2) - { - // quad pixel processing logic - ... - } - } + ProcessQuadPixels(bitmap, targetRect); + private void ProcessQuadPixels(SKBitmap bitmap, Rect targetRect) + { + for (int y = 0; y < bitmap.Info.Height; y += 2) + { + for (int x = 0; x < bitmap.Info.Width; x += 2) + { + // quad pixel processing logic + ... + } + } + }🧰 Tools
🪛 GitHub Check: build
[warning] 80-80:
"[RedundantExplicitArrayCreation] Redundant explicit array type specification" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(80,42)
[warning] 90-90:
"[RedundantExplicitArrayCreation] Redundant explicit array type specification" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(90,42)
483-494
: Improve tab character handling.The current implementation handles tab characters by rendering spaces for a fixed tab size. Consider enhancing this to support configurable tab sizes and alignment based on the console window's tab settings.
Do you want me to provide an example implementation for handling configurable tab sizes and alignment?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
src/Consolonia.Core/Drawing/DrawingContextImpl.cs
(4 hunks)
🧰 Additional context used
🪛 GitHub Check: build
src/Consolonia.Core/Drawing/DrawingContextImpl.cs
[warning] 67-67:
"[RedundantCast] Type cast is redundant" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(67,60)
[warning] 67-67:
"[RedundantCast] Type cast is redundant" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(67,81)
[warning] 80-80:
"[RedundantExplicitArrayCreation] Redundant explicit array type specification" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(80,42)
[warning] 90-90:
"[RedundantExplicitArrayCreation] Redundant explicit array type specification" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(90,42)
[notice] 682-682:
"[ArrangeRedundantParentheses] Redundant parentheses" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(682,27)
[notice] 550-550:
"[ArrangeTrailingCommaInMultilineLists] Remove trailing comma to conform to code style" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(550,57)
[notice] 636-636:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(636,23)
[notice] 688-688:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(688,20)
[notice] 706-706:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(706,23)
[notice] 715-715:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(715,24)
[notice] 724-724:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(724,23)
[warning] 659-659:
"[UnusedParameter.Local] Parameter 'c' is never used" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(659,57)
🔇 Additional comments (11)
src/Consolonia.Core/Drawing/DrawingContextImpl.cs (11)
530-553
: LGTM!
The GetQuadPixelCharacter
method correctly maps the quad pixel colors to the corresponding Unicode block characters. The switch statement covers all possible color patterns.
🧰 Tools
🪛 GitHub Check: build
[notice] 550-550:
"[ArrangeTrailingCommaInMultilineLists] Remove trailing comma to conform to code style" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(550,57)
563-590
: LGTM!
The GetForegroundColorForQuadPixel
method correctly combines the colors based on the quad pixel character to determine the foreground color. The switch statement handles all possible quad pixel characters.
600-623
: LGTM!
The GetBackgroundColorForQuadPixel
method correctly combines the colors based on the quad pixel character to determine the background color. The switch statement handles all possible quad pixel characters.
625-628
: LGTM!
The CombineColors
method correctly calculates the average color by combining the red, green, and blue components of the input colors.
688-704
: LGTM!
The GetColorCluster
method correctly determines the closest cluster for a given color by calculating the color distance to each cluster center.
🧰 Tools
🪛 GitHub Check: build
[notice] 688-688:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(688,20)
706-713
: LGTM!
The GetColorDistance
method correctly calculates the Euclidean distance between two colors in the RGB color space.
🧰 Tools
🪛 GitHub Check: build
[notice] 706-706:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(706,23)
715-722
: LGTM!
The GetAverageColor
method correctly calculates the average color by taking the average of the red, green, and blue components of the input colors.
🧰 Tools
🪛 GitHub Check: build
[notice] 715-715:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(715,24)
724-727
: LGTM!
The GetColorBrightness
method correctly calculates the perceived brightness of a color using the luminance formula.
🧰 Tools
🪛 GitHub Check: build
[notice] 724-724:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(724,23)
499-505
: Consider handling newline characters.
The commented-out code suggests that newline characters may not need to be explicitly rendered. However, it's important to ensure that the pixel buffer is updated correctly when encountering newline characters to maintain proper text layout.
#!/bin/bash
# Description: Verify newline character handling.
# Test: Search for newline character handling. Expect: Consistent behavior across the codebase.
rg -A 10 $'\n'
63-67
: Verify the bitmap resizing logic.
The bitmap is being resized to double the dimensions of the target rectangle to support quad pixel mapping. However, ensure that this resizing logic is consistently applied throughout the codebase and does not introduce any unintended side effects or performance issues.
✅ Verification successful
Let me generate additional verification scripts to better understand the bitmap resizing logic and its usage:
The previous search had syntax issues, and we need more context about bitmap handling across the codebase to verify if the quad pixel mapping approach is consistently implemented.
Bitmap resizing implementation is correctly applied for quad pixel mapping
The verification shows that the bitmap resizing logic is consistently implemented:
- The x2 size increase in
DrawingContextImpl.cs
is specifically designed for quad pixel mapping, with proper documentation and implementation - The code correctly processes quad pixels in 2x2 blocks (x, y, x+1, y+1)
- The scaling is properly compensated by dividing coordinates by 2 when mapping to the target rectangle
- Other bitmap resizing operations in the codebase (like in
BitmapImpl.cs
) serve different purposes and don't interfere with this quad pixel mapping logic
The implementation is sound and doesn't introduce any unintended side effects.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify bitmap resizing logic is consistently applied.
# Test 1: Search for bitmap resizing logic. Expect: Only occurrences in DrawBitmap method.
rg -A 5 $'bitmap = new SKBitmap\(\(int\)targetRect\.Width \* 2, \(int\)targetRect\.Height \* 2\);'
# Test 2: Search for usages of DrawBitmap method. Expect: Consistent usage with resized bitmaps.
rg -A 10 $'DrawBitmap\('
Length of output: 4685
Script:
#!/bin/bash
# Search for bitmap resizing logic with better pattern matching
rg -A 5 'new SKBitmap.*Width.*Height'
# Search for quad pixel related comments or logic
rg -A 3 'quad pixel'
# Search for bitmap scaling or resizing related code
rg -A 5 'bitmap.*scale|scale.*bitmap|resize.*bitmap'
# Look for other potential bitmap size manipulations
rg -A 5 'bitmap\..*Width|bitmap\..*Height'
Length of output: 6031
🧰 Tools
🪛 GitHub Check: build
[warning] 67-67:
"[RedundantCast] Type cast is redundant" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(67,60)
[warning] 67-67:
"[RedundantCast] Type cast is redundant" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(67,81)
636-686
: Verify the color clustering logic.
The GetColorsPattern
method performs color clustering to determine the pattern of colors for the quad pixel. However, ensure that the clustering algorithm produces accurate results across different color combinations and edge cases.
✅ Verification successful
Color clustering implementation appears robust and well-implemented
The color clustering implementation is well-designed for its purpose of mapping quad pixels to Unicode block characters. The code:
- Uses a proper k-means clustering approach with two centers
- Implements correct color distance calculation using Euclidean distance in RGB space
- Has appropriate convergence checks and iteration limits
- Maps clusters to characters in a consistent way based on brightness
- Is used correctly in the context of quad pixel rendering with proper character mappings (▘,▝,▖,▗,▚,▞,▌,▐)
The implementation is suitable for its purpose of determining which parts of a quad pixel should be rendered as foreground vs background in terminal graphics.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify color clustering logic.
# Test 1: Search for usages of GetColorsPattern method. Expect: Consistent usage with valid color inputs.
rg -A 10 $'GetColorsPattern\('
# Test 2: Search for test cases covering different color combinations. Expect: Adequate test coverage.
fd 'GetColorsPattern' Tests/
Length of output: 2026
Script:
#!/bin/bash
# Let's verify the color clustering implementation and its dependencies
# Check for the helper methods used in GetColorsPattern
ast-grep --pattern 'static $_ GetColorCluster($_,$_) { $$$ }'
ast-grep --pattern 'static $_ GetAverageColor($_) { $$$ }'
ast-grep --pattern 'static $_ GetColorBrightness($_) { $$$ }'
# Look for test files in the entire repository
fd -e cs -e csproj test
Length of output: 1467
Script:
#!/bin/bash
# Let's check the implementation of helper methods and look for potential test cases
# Search for the helper method implementations in the same file
rg -A 10 "static.*GetColorCluster|static.*GetAverageColor|static.*GetColorBrightness" src/Consolonia.Core/Drawing/DrawingContextImpl.cs
# Check PixelTests.cs as it might contain relevant test cases
cat src/Tests/Consolonia.TestsCore/PixelTests.cs
Length of output: 5791
Script:
#!/bin/bash
# Let's check for the GetColorDistance method and any other relevant implementations
# Search for GetColorDistance implementation
rg -A 10 "static.*GetColorDistance" src/Consolonia.Core/Drawing/DrawingContextImpl.cs
# Look for actual usage of the quad pixel characters to understand the context
rg "▘|▝|▖|▗|▚|▞|▌|▐" src/Consolonia.Core/Drawing/
Length of output: 2992
🧰 Tools
🪛 GitHub Check: build
[notice] 682-682:
"[ArrangeRedundantParentheses] Redundant parentheses" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(682,27)
[notice] 636-636:
"[ArrangeTypeMemberModifiers] Inconsistent modifiers style: missing 'private' modifier" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(636,23)
[warning] 659-659:
"[UnusedParameter.Local] Parameter 'c' is never used" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/DrawingContextImpl.cs(659,57)
Sexsel looks cool! I was looking for something like that but didn't find it! |
…lbuffer background.
Co-authored-by: <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Outside diff range and nitpick comments (6)
src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs (2)
8-12
: Consider adding null validation.The constructor implementation is correct, but consider adding a null check for the color parameter to prevent potential issues.
public PixelBackground(Color color) { + if (color == null) + throw new ArgumentNullException(nameof(color)); Mode = color.A == 0 ? PixelBackgroundMode.Transparent : PixelBackgroundMode.Colored; Color = color; }
8-8
: Add XML documentation for the new constructor.Consider adding XML documentation to describe the purpose and behavior of this constructor, especially its handling of the alpha channel for transparency.
+/// <summary> +/// Initializes a new instance of the <see cref="PixelBackground"/> struct. +/// </summary> +/// <param name="color">The color to initialize with. The alpha channel determines if the background is transparent.</param> public PixelBackground(Color color)src/Consolonia.Core/Drawing/DrawingContextImpl.cs (4)
64-93
: Consider performance optimizations for bitmap processing.The current implementation resizes the bitmap and processes pixels individually. Consider these optimizations:
- Add bitmap size limits to prevent processing extremely large images
- Consider processing pixels in batches using SIMD operations if available
- Cache the resized bitmap if the same source is rendered multiple times
616-622
: Remove commented out code.The commented implementation of
CombineColors
should be removed as it's been replaced with a more sophisticated version.
662-685
: Extract clustering configuration to constants.The clustering algorithm uses magic numbers that should be extracted to named constants for better maintainability:
+ private const int MaxClusteringIterations = 10; + private const int ClusterCount = 2; - for (int iteration = 0; iteration < 10; iteration++) + for (int iteration = 0; iteration < MaxClusteringIterations; iteration++) { // Assign colors to the closest cluster center for (int i = 0; i < colors.Length; i++) clusters[i] = GetColorCluster(colors[i], clusterCenters); // Recalculate cluster centers - var newClusterCenters = new SKColor[2]; - for (int cluster = 0; cluster < 2; cluster++) + var newClusterCenters = new SKColor[ClusterCount]; + for (int cluster = 0; cluster < ClusterCount; cluster++)
736-739
: Document the color brightness formula.The brightness calculation uses specific coefficients (0.299, 0.587, 0.114) which are standard luminance weights. Consider adding a comment explaining this.
private static double GetColorBrightness(SKColor color) { + // Using standard luminance weights (Rec. 601) with additional alpha component return 0.299 * color.Red + 0.587 * color.Green + 0.114 * color.Blue + color.Alpha; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
src/Consolonia.Gallery/Resources/happy.png
is excluded by!**/*.png
📒 Files selected for processing (4)
src/Consolonia.Core/Drawing/DrawingContextImpl.cs
(3 hunks)src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs
(2 hunks)src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs
(1 hunks)src/Consolonia.Gallery/Gallery/GalleryViews/GalleryImage.axaml
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/Consolonia.Gallery/Gallery/GalleryViews/GalleryImage.axaml
🧰 Additional context used
📓 Learnings (1)
src/Consolonia.Core/Drawing/DrawingContextImpl.cs (1)
Learnt from: tomlm
PR: jinek/Consolonia#137
File: src/Consolonia.Core/Drawing/DrawingContextImpl.cs:70-71
Timestamp: 2024-11-03T23:26:46.802Z
Learning: In the `DrawBitmap` method of `DrawingContextImpl.cs`, the bitmap dimensions are scaled by 2, ensuring that the width and height are always even. This guarantees that accessing `x + 1` and `y + 1` inside the loops is safe and won't cause index out of range exceptions.
🔇 Additional comments (2)
src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs (1)
8-12
: Verify usage in quad pixel implementation.
Since this constructor is part of the quad pixel character implementation, let's verify its usage in the drawing context.
✅ Verification successful
Let me gather more context about the color blending scenarios and quad pixel implementation.
Constructor properly integrated with quad pixel implementation
The verification shows that the PixelBackground
constructor is correctly integrated into the quad pixel implementation:
- It's used in
DrawingContextImpl.cs
for handling quad pixel characters where it properly sets background colors from combined quad pixel colors - The transparency check in the constructor (
color.A == 0
) aligns with the quad pixel color processing where transparent colors are handled - The constructor is used in color blending operations through the
GetBackgroundColorForQuadPixel
method which combines multiple SKColors for the background
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the usage of the new PixelBackground constructor in quad pixel implementation
# Search for references to PixelBackground in the drawing context
echo "Searching for PixelBackground usage in drawing context..."
rg -l "PixelBackground" "src/Consolonia.Core/Drawing"
# Search for color blending operations that might use this constructor
echo "Searching for potential color blending operations..."
ast-grep --pattern 'new PixelBackground($_)'
Length of output: 1324
Script:
#!/bin/bash
# Check the implementation of color blending in Pixel.cs
echo "Checking color blending implementation in Pixel.cs..."
rg -A 10 "MergeWith" "src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs"
# Check DrawingContextImpl.cs for quad pixel usage
echo "Checking quad pixel implementation in DrawingContextImpl.cs..."
rg -A 5 "quad|Quad" "src/Consolonia.Core/Drawing/DrawingContextImpl.cs"
# Check how PixelBackground is used in color operations
echo "Checking PixelBackground usage in color operations..."
rg -A 3 "Color = " "src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs"
Length of output: 5375
src/Consolonia.Core/Drawing/DrawingContextImpl.cs (1)
521-544
:
Improve error handling for unknown pixel patterns.
Instead of throwing NotImplementedException
for unknown patterns, consider providing a fallback character that best represents the pattern. This would make the method more robust in production.
private static char GetQuadPixelCharacter(params SKColor[] colors)
{
char character = GetColorsPattern(colors) switch
{
"FFFF" => ' ',
// ... existing patterns ...
- _ => throw new NotImplementedException()
+ _ => '█' // Use full block as fallback for unknown patterns
};
return character;
}
Likely invalid or redundant comment.
The old algo mapped every pixel to a character. It looked like this:
The new algo maps every 4 pixels to a single character using block characters
▘▝▖▗ ▚ ▞ ▌ ▐ ▄ ▀ ▛ ▜ ▙ ▟ █
This effectively gives us 4x the resolution of the old algo as every 4x4 pixel is mapped to a single character + backgroundColor and foregroundColor.
Images now look like this:
Summary by CodeRabbit
New Features
PixelBackground
to simplify color initialization.Bug Fixes