Skip to content
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

190-strange-coloring-ega-mode #233

Merged
merged 27 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e06c08b
Half way to remove ConsoleBrush
jinek Dec 25, 2024
5de905a
the rest
jinek Dec 25, 2024
8390c50
last bug left
jinek Dec 25, 2024
7887e43
better like this. But something happens to drawing box lines
jinek Dec 25, 2024
58e4f42
Seems everything works
jinek Dec 25, 2024
6e693a4
Now looks better
jinek Dec 25, 2024
275069e
brought back space != whitespace
jinek Dec 25, 2024
5c54ff9
final adjustments
jinek Dec 25, 2024
49a312a
code rabbit
jinek Dec 25, 2024
8a04477
pr fix
jinek Dec 25, 2024
07b0469
more linter
jinek Dec 25, 2024
fab6f72
Automated JetBrains cleanup
github-actions[bot] Dec 25, 2024
52c55b2
more blending tests
jinek Dec 25, 2024
0fc7c74
Automated JetBrains cleanup
github-actions[bot] Dec 25, 2024
99959c8
Merge branch 'main' into 190-strange-coloring-remove-console-brush
tomlm Dec 25, 2024
f8adc28
pr comment fixes
jinek Dec 25, 2024
1c32687
Automated JetBrains cleanup
github-actions[bot] Dec 25, 2024
3bfff06
Ega color implemented
jinek Dec 26, 2024
66e72ea
al works fine
jinek Dec 26, 2024
beb2fbf
tests fixed
jinek Dec 26, 2024
387ca1a
Merge branch 'main' into 190-strange-coloring-ega-mode
jinek Dec 26, 2024
bf44216
merge
jinek Dec 26, 2024
5fb6096
More fixes
jinek Dec 26, 2024
25384c6
resharper PR fix
jinek Dec 26, 2024
febcb3b
Automated JetBrains cleanup
github-actions[bot] Dec 26, 2024
1bedda1
Merge branch 'main' into 190-strange-coloring-ega-mode
jinek Dec 26, 2024
f105ff3
Merge remote-tracking branch 'origin/190-strange-coloring-ega-mode' i…
jinek Dec 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/Consolonia.Core/ApplicationStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
using Consolonia.Core.Drawing;
using Consolonia.Core.Drawing.PixelBufferImplementation;
using Consolonia.Core.Drawing.PixelBufferImplementation.EgaConsoleColor;
using Consolonia.Core.Infrastructure;

// ReSharper disable UnusedMember.Global //todo: how to disable it for public methods?
Expand All @@ -15,26 +17,32 @@ public static class ApplicationStartup
{
public static void StartConsolonia<TApp>(params string[] args) where TApp : Application, new()
{
StartConsolonia<TApp>(new DefaultNetConsole(), args);
StartConsolonia<TApp>(new DefaultNetConsole(), new EgaConsoleColorMode(), args);
}

public static void StartConsolonia<TApp>(IConsole console, params string[] args) where TApp : Application, new()
public static void StartConsolonia<TApp>(IConsole console, IConsoleColorMode consoleColorMode,
params string[] args) where TApp : Application, new()
{
ClassicDesktopStyleApplicationLifetime lifetime = BuildLifetime<TApp>(console, args);
ClassicDesktopStyleApplicationLifetime lifetime = BuildLifetime<TApp>(console, consoleColorMode, args);

lifetime.Start(args);
}

public static AppBuilder UseStandardConsole(this AppBuilder builder)
{
return builder.UseConsole(new DefaultNetConsole());
return builder.UseConsole(new DefaultNetConsole()).UseConsoleColorMode(new EgaConsoleColorMode());
}

public static AppBuilder UseConsole(this AppBuilder builder, IConsole console)
{
return builder.With(console);
}

public static AppBuilder UseConsoleColorMode(this AppBuilder builder, IConsoleColorMode consoleColorMode)
{
return builder.With(consoleColorMode);
}

public static AppBuilder UseConsolonia(this AppBuilder builder)
{
return builder
Expand All @@ -49,12 +57,14 @@ public static AppBuilder UseConsolonia(this AppBuilder builder)
}, nameof(ConsoloniaRenderInterface));
}

public static ClassicDesktopStyleApplicationLifetime BuildLifetime<TApp>(IConsole console, string[] args)
public static ClassicDesktopStyleApplicationLifetime BuildLifetime<TApp>(IConsole console,
IConsoleColorMode consoleColorMode, string[] args)
where TApp : Application, new()
{
AppBuilder consoloniaAppBuilder = AppBuilder.Configure<TApp>()
.UseConsole(console)
.UseConsolonia()
.UseConsoleColorMode(consoleColorMode)
.LogToException();

return CreateLifetime(consoloniaAppBuilder, args);
Expand All @@ -68,6 +78,7 @@ private static ClassicDesktopStyleApplicationLifetime CreateLifetime(AppBuilder
ShutdownMode = ShutdownMode.OnMainWindowClose
};
builder.SetupWithLifetime(lifetime);

return lifetime;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using Avalonia.Markup.Xaml;
using Consolonia.Core.Drawing.PixelBufferImplementation.EgaConsoleColor;

// ReSharper disable once CheckNamespace
namespace Consolonia
{
//todo: define xaml namespace for out classes
/// <summary>
/// Avalonia axaml extension which consumes ConsoleColor and produces AvaloniaColor
/// </summary>
public class EgaColorExtension : MarkupExtension
{
public EgaColorExtension()
{
Color = ConsoleColor.Black;
}

public EgaColorExtension(ConsoleColor color)
{
Color = color;
}

public EgaColorExtension(EgaColorMode mode)
{
Color = ConsoleColor.Black;
Mode = mode;
}

public ConsoleColor Color { get; set; }

Check notice on line 30 in src/Consolonia.Core/Drawing/PixelBufferImplementation/EgaConsoleColor/EgaColor.cs

View workflow job for this annotation

GitHub Actions / build

"[AutoPropertyCanBeMadeGetOnly.Global] Auto-property can be made get-only" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/PixelBufferImplementation/EgaConsoleColor/EgaColor.cs(30,42)
public EgaColorMode Mode { get; set; }

public override object ProvideValue(IServiceProvider serviceProvider)
{
return EgaConsoleColorMode.ConvertToAvaloniaColor(Color, Mode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// ReSharper disable once CheckNamespace

namespace Consolonia
{
public enum EgaColorMode : byte
{
Colored,
Transparent,
Shaded
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using System;
using System.Linq;
using System.Text;
using Avalonia.Media;
using Consolonia.Core.Infrastructure;

namespace Consolonia.Core.Drawing.PixelBufferImplementation.EgaConsoleColor
{
public class EgaConsoleColorMode : IConsoleColorMode
{
private static readonly (ConsoleColor Color, (int R, int G, int B) Rgb)[] ConsoleColorMap =
[
(ConsoleColor.Black, (0, 0, 0)),
(ConsoleColor.DarkBlue, (0, 0, 128)),
(ConsoleColor.DarkGreen, (0, 128, 0)),
(ConsoleColor.DarkCyan, (0, 128, 128)),
(ConsoleColor.DarkRed, (128, 0, 0)),
(ConsoleColor.DarkMagenta, (128, 0, 128)),
(ConsoleColor.DarkYellow, (128, 128, 0)),
(ConsoleColor.Gray, (192, 192, 192)),
(ConsoleColor.DarkGray, (128, 128, 128)),
(ConsoleColor.Blue, (0, 0, 255)),
(ConsoleColor.Green, (0, 255, 0)),
(ConsoleColor.Cyan, (0, 255, 255)),
(ConsoleColor.Red, (255, 0, 0)),
(ConsoleColor.Magenta, (255, 0, 255)),
(ConsoleColor.Yellow, (255, 255, 0)),
(ConsoleColor.White, (255, 255, 255))
];

public Color Blend(Color color1, Color color2)
{
(ConsoleColor consoleColor1, EgaColorMode mode1) = ConvertToConsoleColorMode(color1);
(ConsoleColor consoleColor2, EgaColorMode mode2) = ConvertToConsoleColorMode(color2);

switch (mode2)
{
case EgaColorMode.Transparent:
return color1;

case EgaColorMode.Shaded when mode1 == EgaColorMode.Shaded:
{
ConsoleColor doubleShadedColor = Shade(Shade(consoleColor1));
return ConvertToAvaloniaColor(doubleShadedColor);
}
case EgaColorMode.Shaded:
{
ConsoleColor shadedColor = Shade(consoleColor1);
return ConvertToAvaloniaColor(shadedColor);
}
case EgaColorMode.Colored:
default:
return ConvertToAvaloniaColor(consoleColor2);
}
}

public void SetAttributes(InputLessDefaultNetConsole console, Color background, Color foreground,
FontWeight? weight)
{
(ConsoleColor backgroundConsoleColor, EgaColorMode mode) = ConvertToConsoleColorMode(background);
if (mode is not EgaColorMode.Colored)
ConsoloniaPlatform.RaiseNotSupported(62144, foreground);


(ConsoleColor foregroundConsoleColor, _) = ConvertToConsoleColorMode(foreground);
//todo: if mode is transparent, don't print foreground. if shaded - shade it

var sb = new StringBuilder();

// Append ANSI escape sequence for background color
sb.Append(GetAnsiCode(backgroundConsoleColor, true));

// Append ANSI escape sequence for foreground color
sb.Append(GetAnsiCode(foregroundConsoleColor, false));
console.WriteText(sb.ToString());
return;

// Function to map ConsoleColor to ANSI code
static string GetAnsiCode(ConsoleColor color, bool isBackground)
{
int ansiCode = color switch
{
ConsoleColor.Black => 0,
ConsoleColor.DarkRed => 1,
ConsoleColor.DarkGreen => 2,
ConsoleColor.DarkYellow => 3,
ConsoleColor.DarkBlue => 4,
ConsoleColor.DarkMagenta => 5,
ConsoleColor.DarkCyan => 6,
ConsoleColor.Gray => 7,
ConsoleColor.DarkGray => 8,
ConsoleColor.Red => 9,
ConsoleColor.Green => 10,
ConsoleColor.Yellow => 11,
ConsoleColor.Blue => 12,
ConsoleColor.Magenta => 13,
ConsoleColor.Cyan => 14,
ConsoleColor.White => 15,
_ => 7 // Default to white if unknown
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to detect default color when app starts? Maybe future enhancement.

};

return ansiCode < 8
?
// Standard colors
$"\x1b[{(isBackground ? 40 + ansiCode : 30 + ansiCode)}m"
:
// Bright colors
$"\x1b[{(isBackground ? 100 + (ansiCode - 8) : 90 + (ansiCode - 8))}m";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add to Esc.cs ?

}
}

public static (ConsoleColor, EgaColorMode) ConvertToConsoleColorMode(Color color)
{
ConsoleColor consoleColor = MapToConsoleColor(color);

EgaColorMode mode = color.A switch
{
<= 63 => EgaColorMode.Transparent,
<= 191 => EgaColorMode.Shaded,
_ => EgaColorMode.Colored
};

return (consoleColor, mode);
}

private static ConsoleColor MapToConsoleColor(Color color)
{
int r = color.R, g = color.G, b = color.B;

// Find the nearest ConsoleColor by RGB distance
return ConsoleColorMap
.OrderBy(c => Math.Pow(c.Rgb.R - r, 2) + Math.Pow(c.Rgb.G - g, 2) + Math.Pow(c.Rgb.B - b, 2))
.First().Color;
}

private static ConsoleColor Shade(ConsoleColor color)
{
return color switch
{
ConsoleColor.White => ConsoleColor.Gray,
ConsoleColor.Gray => ConsoleColor.DarkGray,
ConsoleColor.Blue => ConsoleColor.DarkBlue,
ConsoleColor.Green => ConsoleColor.DarkGreen,
ConsoleColor.Cyan => ConsoleColor.DarkCyan,
ConsoleColor.Red => ConsoleColor.DarkRed,
ConsoleColor.Magenta => ConsoleColor.DarkMagenta,
ConsoleColor.Yellow => ConsoleColor.DarkYellow,
ConsoleColor.DarkBlue or
ConsoleColor.DarkGreen or
ConsoleColor.DarkCyan or
ConsoleColor.DarkRed or
ConsoleColor.DarkMagenta or
ConsoleColor.DarkYellow or
ConsoleColor.DarkGray or
ConsoleColor.Black => ConsoleColor.Black,
_ => ConsoleColor.Black
};
}

public static Color ConvertToAvaloniaColor(ConsoleColor consoleColor,
EgaColorMode mode = EgaColorMode.Colored)
{
switch (mode)
{
case EgaColorMode.Transparent:
return Color.FromArgb(0, 0, 0, 0);
case EgaColorMode.Shaded:
return Color.FromArgb(127, 0, 0, 0);
case EgaColorMode.Colored:
(ConsoleColor _, (int R, int G, int B) rgb) = ConsoleColorMap.First(c => c.Color == consoleColor);
return Color.FromRgb((byte)rgb.R, (byte)rgb.G, (byte)rgb.B);
default:
throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Avalonia.Media;
using Consolonia.Core.Infrastructure;

namespace Consolonia.Core.Drawing.PixelBufferImplementation
{
public interface IConsoleColorMode
{
Color Blend(Color color1, Color color2);
void SetAttributes(InputLessDefaultNetConsole console, Color background, Color foreground, FontWeight? weight);
}
}
26 changes: 3 additions & 23 deletions src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia;
using Avalonia.Media;
using Newtonsoft.Json;

Expand Down Expand Up @@ -82,7 +83,7 @@

public PixelBackground Background { get; init; }

public bool IsCaret { get; init; }

Check notice on line 86 in src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs

View workflow job for this annotation

GitHub Actions / build

"[AutoPropertyCanBeMadeGetOnly.Global] Auto-property can be made get-only" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs(86,36)

[JsonIgnore] public ushort Width => Foreground.Symbol.Width;

Expand Down Expand Up @@ -149,29 +150,8 @@
/// <returns>source blended into target</returns>
private static Color MergeColors(Color target, Color source)
{
// by chatGPT o1
// Convert alpha from [0..255] to [0..1]
float fgAlpha = source.A / 255f;
float bgAlpha = target.A / 255f;

// Compute output alpha
float outAlpha = fgAlpha + bgAlpha * (1 - fgAlpha);

// If there's no alpha in the result, return transparent
if (outAlpha <= 0f) return Color.FromArgb(0, 0, 0, 0);

// Calculate the composited color channels, also converting channels to [0..1]
float outR = (source.R / 255f * fgAlpha + target.R / 255f * bgAlpha * (1 - fgAlpha)) / outAlpha;
float outG = (source.G / 255f * fgAlpha + target.G / 255f * bgAlpha * (1 - fgAlpha)) / outAlpha;
float outB = (source.B / 255f * fgAlpha + target.B / 255f * bgAlpha * (1 - fgAlpha)) / outAlpha;

// Convert back to [0..255]
byte a = (byte)(outAlpha * 255f);
byte r = (byte)(outR * 255f);
byte g = (byte)(outG * 255f);
byte b = (byte)(outB * 255f);

return Color.FromArgb(a, r, g, b);
var consoleColorMode = AvaloniaLocator.Current.GetRequiredService<IConsoleColorMode>();
return consoleColorMode.Blend(target, source);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh, sliick

}

public override bool Equals([NotNullWhen(true)] object obj)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Avalonia;
using Avalonia.Media;
using Newtonsoft.Json;

// ReSharper disable UnusedMember.Global
Expand All @@ -26,7 +27,7 @@ public PixelBuffer(ushort width, ushort height)
// blended into it.
for (ushort y = 0; y < height; y++)
for (ushort x = 0; x < width; x++)
_buffer[x, y] = Pixel.Space;
_buffer[x, y] = new Pixel(new PixelBackground(Colors.Black));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not transparent background?

}

public ushort Width { get; }
Expand Down
Loading
Loading