Toolkit.UI.Controls.Avalonia
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia;
|
||||
using SukiUI.Utilities.Background;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Toolkit.UI.Controls.Avalonia;
|
||||
|
||||
public class FastNoiseBackgroundRenderer
|
||||
{
|
||||
private static readonly FastNoiseLite NoiseGen = new();
|
||||
private static readonly Random Rand = new();
|
||||
private readonly float accentAlpha;
|
||||
private readonly object lockObj = new();
|
||||
private readonly float primaryAlpha;
|
||||
private readonly float scale;
|
||||
private readonly float xSeed;
|
||||
private readonly float ySeed;
|
||||
private uint accentColour;
|
||||
private float aOffsetX;
|
||||
private float aOffsetY;
|
||||
private uint baseColour;
|
||||
private bool isRedrawing;
|
||||
private float pOffsetX;
|
||||
private float pOffsetY;
|
||||
private uint themeColour;
|
||||
|
||||
public FastNoiseBackgroundRenderer(FastNoiseRendererOptions? options = null)
|
||||
{
|
||||
FastNoiseRendererOptions opt = options ??
|
||||
new FastNoiseRendererOptions(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
|
||||
NoiseGen.SetNoiseType(opt.Type);
|
||||
scale = opt.NoiseScale * 100f;
|
||||
|
||||
xSeed = opt.XSeed;
|
||||
ySeed = opt.YSeed;
|
||||
primaryAlpha = opt.PrimaryAlpha;
|
||||
accentAlpha = opt.AccentAlpha;
|
||||
}
|
||||
|
||||
public async void Render(WriteableBitmap bitmap)
|
||||
{
|
||||
pOffsetX += xSeed;
|
||||
pOffsetY += ySeed;
|
||||
aOffsetX -= xSeed;
|
||||
aOffsetY -= ySeed;
|
||||
|
||||
if (isRedrawing) return;
|
||||
lock (lockObj) { isRedrawing = true; }
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using ILockedFramebuffer frameBuffer = bitmap.Lock();
|
||||
PixelSize frameSize = frameBuffer.Size;
|
||||
float frameScale = 1f / frameSize.Height * scale;
|
||||
unsafe
|
||||
{
|
||||
uint* backBuffer = (uint*)frameBuffer.Address.ToPointer();
|
||||
int stride = frameBuffer.RowBytes / 4;
|
||||
|
||||
Parallel.For(0, frameSize.Height, (long scanline) =>
|
||||
{
|
||||
for (int x = 0; x < frameSize.Width; x++)
|
||||
{
|
||||
float noise = NoiseGen.GetNoise((pOffsetX + x) * frameScale, (pOffsetY + scanline) * frameScale);
|
||||
noise = (noise + 1f) / 2f * primaryAlpha; // noise returns -1 to +1 which isn't useful.
|
||||
byte alpha = (byte)(noise * 255);
|
||||
uint firstLayer = BlendPixelOverlay(WithAlpha(themeColour, alpha), baseColour);
|
||||
|
||||
noise = NoiseGen.GetNoise((aOffsetX + x) * frameScale, (aOffsetY + scanline) * frameScale);
|
||||
noise = (noise + 1f) / 2f * accentAlpha;
|
||||
alpha = (byte)(noise * 255);
|
||||
|
||||
(backBuffer + scanline * stride + 0)[x] = BlendPixel(WithAlpha(accentColour, alpha), firstLayer);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
lock (lockObj) { isRedrawing = false; }
|
||||
}
|
||||
|
||||
public void UpdateValues(Color primary,
|
||||
Color accent,
|
||||
ThemeVariant baseTheme)
|
||||
{
|
||||
themeColour = ToUInt32(primary);
|
||||
accentColour = ToUInt32(accent);
|
||||
|
||||
baseColour = baseTheme == ThemeVariant.Light
|
||||
? new Color(255, 241, 241, 241).ToUInt32()
|
||||
: GetBackgroundColour(primary);
|
||||
|
||||
pOffsetX = Rand.Next(1000);
|
||||
pOffsetY = Rand.Next(1000);
|
||||
|
||||
aOffsetY = Rand.Next(1000);
|
||||
aOffsetX = Rand.Next(1000);
|
||||
}
|
||||
|
||||
private static byte A(uint col) => (byte)(col >> 24);
|
||||
|
||||
private static uint ARGB(byte a, byte r, byte g, byte b) =>
|
||||
(uint)(a << 24 | r << 16 | g << 8 | b << 0);
|
||||
|
||||
private static byte B(uint col) => (byte)col;
|
||||
|
||||
private static uint BlendPixel(uint fore, uint back)
|
||||
{
|
||||
float alphaF = A(fore) / 255.0f;
|
||||
|
||||
byte resultR = (byte)(R(fore) * alphaF + R(back) * (1 - alphaF));
|
||||
byte resultG = (byte)(G(fore) * alphaF + G(back) * (1 - alphaF));
|
||||
byte resultB = (byte)(B(fore) * alphaF + B(back) * (1 - alphaF));
|
||||
byte resultA = A(back);
|
||||
|
||||
return ARGB(resultA, resultR, resultG, resultB);
|
||||
}
|
||||
|
||||
private static uint BlendPixelOverlay(uint fore, uint back)
|
||||
{
|
||||
float alphaF = A(fore) / 255.0f;
|
||||
|
||||
byte resultR = OverlayComponentBlend(R(fore), R(back), alphaF);
|
||||
byte resultG = OverlayComponentBlend(G(fore), G(back), alphaF);
|
||||
byte resultB = OverlayComponentBlend(B(fore), B(back), alphaF);
|
||||
|
||||
return ARGB(A(back), resultR, resultG, resultB);
|
||||
}
|
||||
|
||||
private static byte G(uint col) =>
|
||||
(byte)(col >> 8);
|
||||
|
||||
private static uint GetBackgroundColour(Color input)
|
||||
{
|
||||
int r = input.R;
|
||||
int g = input.G;
|
||||
int b = input.B;
|
||||
|
||||
int minValue = Math.Min(Math.Min(r, g), b);
|
||||
int maxValue = Math.Max(Math.Max(r, g), b);
|
||||
|
||||
r = r == minValue ? 30 : r == maxValue ? 30 : 22;
|
||||
g = g == minValue ? 30 : g == maxValue ? 30 : 22;
|
||||
b = b == minValue ? 30 : b == maxValue ? 30 : 22;
|
||||
return ARGB(255, (byte)r, (byte)g, (byte)b);
|
||||
}
|
||||
|
||||
private static byte OverlayComponentBlend(byte componentF, byte componentB, float alphaF)
|
||||
{
|
||||
float result = componentB <= 128
|
||||
? 2 * componentF * componentB / 255.0f
|
||||
: 255 - 2 * (255 - componentF) * (255 - componentB) / 255.0f;
|
||||
|
||||
return (byte)(result * alphaF + componentB * (1 - alphaF));
|
||||
}
|
||||
|
||||
private static byte R(uint col) => (byte)(col >> 16);
|
||||
|
||||
private static uint ToUInt32(Color colour) =>
|
||||
(uint)(colour.A << 24 | colour.R << 16 | colour.G << 8 | colour.B);
|
||||
|
||||
private static uint WithAlpha(uint col, byte a) => col & 0x00FFFFFF | (uint)(a << 24);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace SukiUI.Utilities.Background;
|
||||
|
||||
public readonly struct FastNoiseRendererOptions(
|
||||
FastNoiseLite.NoiseType type,
|
||||
float noiseScale = 1.5f,
|
||||
float xSeed = 2f,
|
||||
float ySeed = 1f,
|
||||
float primaryAlpha = 0.7f,
|
||||
float accentAlpha = 0.04f,
|
||||
float seedScale = 0.1f)
|
||||
{
|
||||
public float AccentAlpha { get; } = accentAlpha;
|
||||
|
||||
public float NoiseScale { get; } = noiseScale;
|
||||
|
||||
public float PrimaryAlpha { get; } = primaryAlpha;
|
||||
|
||||
public FastNoiseLite.NoiseType Type { get; } = type;
|
||||
|
||||
public float XSeed { get; } = xSeed * seedScale;
|
||||
|
||||
public float YSeed { get; } = ySeed * seedScale;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace Toolkit.UI.Controls.Avalonia;
|
||||
|
||||
public class FastRendererBackground :
|
||||
Image, IDisposable
|
||||
{
|
||||
private const int ImageWidth = 100;
|
||||
private const int ImageHeight = 100;
|
||||
|
||||
private readonly WriteableBitmap bitmap = new(new PixelSize(ImageWidth, ImageHeight),
|
||||
new Vector(96, 96), PixelFormat.Bgra8888);
|
||||
|
||||
private readonly FastNoiseBackgroundRenderer renderer = new();
|
||||
|
||||
public FastRendererBackground()
|
||||
{
|
||||
Source = bitmap;
|
||||
Stretch = Stretch.UniformToFill;
|
||||
}
|
||||
|
||||
public override void EndInit()
|
||||
{
|
||||
base.EndInit();
|
||||
if (Application.Current?.ActualThemeVariant is ThemeVariant theme)
|
||||
{
|
||||
renderer.UpdateValues((Color)Application.Current.FindResource("SystemAccentColorLight3"),
|
||||
(Color)Application.Current.FindResource("SystemAccentColorDark3"), theme);
|
||||
}
|
||||
|
||||
renderer.Render(bitmap);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
bitmap.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user