using System; using System.IO; using Artemis.Core; using Avalonia; using Avalonia.Media; using Avalonia.Media.Imaging; using RGB.NET.Core; using Color = Avalonia.Media.Color; using SolidColorBrush = Avalonia.Media.SolidColorBrush; namespace Artemis.UI.Shared.Extensions; /// /// Provides extension methods for the type. /// public static class ArtemisLayoutExtensions { /// /// Renders the layout to a bitmap. /// /// The layout to render /// A value indicating whether or not to draw LEDs on the image. /// The scale at which to draw the layout. /// The resulting bitmap. public static RenderTargetBitmap RenderLayout(this ArtemisLayout layout, bool previewLeds, int scale = 2) { string? path = layout.Image?.LocalPath; // Create a bitmap that'll be used to render the device and LED images just once // Render 4 times the actual size of the device to make sure things look sharp when zoomed in RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) layout.RgbLayout.Width * scale, (int) layout.RgbLayout.Height * scale)); using DrawingContext context = renderTargetBitmap.CreateDrawingContext(); // Draw device background if (path != null && File.Exists(path)) { using Bitmap bitmap = new(path); using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(renderTargetBitmap.PixelSize); context.DrawImage(scaledBitmap, new Rect(scaledBitmap.Size)); } // Draw LED images foreach (ArtemisLedLayout led in layout.Leds) { string? ledPath = led.Image?.LocalPath; if (ledPath == null || !File.Exists(ledPath)) continue; using Bitmap bitmap = new(ledPath); using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize((led.RgbLayout.Width * scale).RoundToInt(), (led.RgbLayout.Height * scale).RoundToInt())); context.DrawImage(scaledBitmap, new Rect(led.RgbLayout.X * scale, led.RgbLayout.Y * scale, scaledBitmap.Size.Width, scaledBitmap.Size.Height)); } if (!previewLeds) return renderTargetBitmap; // Draw LED geometry using a rainbow gradient ColorGradient colors = ColorGradient.GetUnicornBarf(); colors.ToggleSeamless(); context.PushTransform(Matrix.CreateScale(scale, scale)); foreach (ArtemisLedLayout led in layout.Leds) { Geometry? geometry = CreateLedGeometry(led); if (geometry == null) continue; Color color = colors.GetColor((led.RgbLayout.X + led.RgbLayout.Width / scale) / layout.RgbLayout.Width).ToColor(); SolidColorBrush fillBrush = new() {Color = color, Opacity = 0.4}; SolidColorBrush penBrush = new() {Color = color}; Pen pen = new(penBrush) {LineJoin = PenLineJoin.Round}; context.DrawGeometry(fillBrush, pen, geometry); } return renderTargetBitmap; } private static Geometry? CreateLedGeometry(ArtemisLedLayout led) { // The minimum required size for geometry to be created if (led.RgbLayout.Width < 2 || led.RgbLayout.Height < 2) return null; switch (led.RgbLayout.Shape) { case Shape.Custom: if (led.DeviceLayout.RgbLayout.Type is RGBDeviceType.Keyboard or RGBDeviceType.Keypad) return CreateCustomGeometry(led, 2.0); return CreateCustomGeometry(led, 1.0); case Shape.Rectangle: if (led.DeviceLayout.RgbLayout.Type is RGBDeviceType.Keyboard or RGBDeviceType.Keypad) return CreateKeyCapGeometry(led); return CreateRectangleGeometry(led); case Shape.Circle: return CreateCircleGeometry(led); default: throw new ArgumentOutOfRangeException(); } } private static RectangleGeometry CreateRectangleGeometry(ArtemisLedLayout led) { return new RectangleGeometry(new Rect(led.RgbLayout.X + 0.5, led.RgbLayout.Y + 0.5, led.RgbLayout.Width - 1, led.RgbLayout.Height - 1)); } private static EllipseGeometry CreateCircleGeometry(ArtemisLedLayout led) { return new EllipseGeometry(new Rect(led.RgbLayout.X + 0.5, led.RgbLayout.Y + 0.5, led.RgbLayout.Width - 1, led.RgbLayout.Height - 1)); } private static RectangleGeometry CreateKeyCapGeometry(ArtemisLedLayout led) { return new RectangleGeometry(new Rect(led.RgbLayout.X + 1, led.RgbLayout.Y + 1, led.RgbLayout.Width - 2, led.RgbLayout.Height - 2)); } private static Geometry? CreateCustomGeometry(ArtemisLedLayout led, double deflateAmount) { try { if (led.RgbLayout.ShapeData == null) return null; double width = led.RgbLayout.Width - deflateAmount; double height = led.RgbLayout.Height - deflateAmount; Geometry geometry = Geometry.Parse(led.RgbLayout.ShapeData); geometry.Transform = new TransformGroup { Children = new Transforms { new ScaleTransform(width, height), new TranslateTransform(led.RgbLayout.X + deflateAmount / 2, led.RgbLayout.Y + deflateAmount / 2) } }; return geometry; } catch (Exception) { return CreateRectangleGeometry(led); } } }