Skip to content

Commit bf6350e

Browse files
committed
Merge branch 'dev' of https://github.com/antonpup/Aurora into feature/variable-modul
# Conflicts: # Project-Aurora/Project-Aurora/Profiles/LightingStateManager.cs
2 parents dbc62ef + ab45073 commit bf6350e

18 files changed

+1284
-54
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<UserControl x:Class="Aurora.Controls.Control_LayerPreview"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
7+
xmlns:u="clr-namespace:Aurora.Utils"
8+
mc:Ignorable="d"
9+
d:DesignHeight="450" d:DesignWidth="800"
10+
Loaded="UserControl_Loaded" Unloaded="UserControl_Unloaded">
11+
<UserControl.Resources>
12+
<u:ColorToBrushConverter x:Key="ColorToBrushConv" />
13+
</UserControl.Resources>
14+
15+
<DockPanel LastChildFill="True">
16+
<DockPanel LastChildFill="True" DockPanel.Dock="Bottom" Height="28" Margin="0,8,0,0">
17+
<Label Content="Preview background" />
18+
<xctk:ColorPicker x:Name="previewBg" Margin="4,2" SelectedColor="Black" />
19+
</DockPanel>
20+
<Border Background="{Binding ElementName=previewBg, Path=SelectedColor, Converter={StaticResource ColorToBrushConv}}">
21+
<Image x:Name="imagePreview" />
22+
</Border>
23+
</DockPanel>
24+
</UserControl>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Aurora.Settings.Layers;
2+
using System.IO;
3+
using System.Windows;
4+
using System.Windows.Controls;
5+
using System.Windows.Media.Imaging;
6+
7+
namespace Aurora.Controls {
8+
9+
/// <summary>
10+
/// A control that is capable of rendering the preview of any layer that correctly implements <see cref="INotifyRender"/>.
11+
/// </summary>
12+
public partial class Control_LayerPreview : UserControl {
13+
14+
private bool eventsAttached;
15+
16+
public Control_LayerPreview() {
17+
InitializeComponent();
18+
}
19+
20+
#region Target Layer Property
21+
public INotifyRender TargetLayer {
22+
get => (INotifyRender)GetValue(TargetLayerProperty);
23+
set => SetValue(TargetLayerProperty, value);
24+
}
25+
public static readonly DependencyProperty TargetLayerProperty =
26+
DependencyProperty.Register("TargetLayer", typeof(INotifyRender), typeof(Control_LayerPreview), new PropertyMetadata(null, TargetLayerChanged));
27+
28+
private static void TargetLayerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
29+
var control = (Control_LayerPreview)d;
30+
// If the events are currently attached (I.E. a preview is currently running, then remove the handler from the old target and add to the new one)
31+
if (control.eventsAttached) {
32+
if (e.OldValue is INotifyRender old)
33+
old.LayerRender -= control.RenderLayerPreview;
34+
if (e.NewValue is INotifyRender @new)
35+
@new.LayerRender += control.RenderLayerPreview;
36+
}
37+
}
38+
#endregion
39+
40+
// Start listening for when the particle layer is rendered so we can update the preview
41+
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
42+
if (TargetLayer != null)
43+
TargetLayer.LayerRender += RenderLayerPreview;
44+
eventsAttached = true;
45+
}
46+
47+
// Stop listenting for when the particle layer is rendered (since you can't see the image now anyways)
48+
private void UserControl_Unloaded(object sender, RoutedEventArgs e) {
49+
if (TargetLayer != null)
50+
TargetLayer.LayerRender -= RenderLayerPreview;
51+
eventsAttached = false;
52+
}
53+
54+
// Take the bitmap from the layer and transform it into a format that can be used by WPF
55+
private void RenderLayerPreview(object sender, System.Drawing.Bitmap bitmap) =>
56+
Dispatcher.Invoke(delegate {
57+
using (var ms = new MemoryStream()) {
58+
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
59+
ms.Position = 0;
60+
var bitmapImg = new BitmapImage();
61+
bitmapImg.BeginInit();
62+
bitmapImg.StreamSource = ms;
63+
bitmapImg.CacheOption = BitmapCacheOption.OnLoad;
64+
bitmapImg.EndInit();
65+
imagePreview.Source = bitmapImg;
66+
}
67+
});
68+
}
69+
}

Project-Aurora/Project-Aurora/Controls/KeySequence.xaml.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ public bool FreestyleEnabled
120120
}
121121
}
122122

123+
#region ShowOnCanvas property
124+
// Drawn freeform object bounds will only appear if this is true.
125+
public bool ShowOnCanvas {
126+
get => (bool)GetValue(ShowOnCanvasProperty);
127+
set => SetValue(ShowOnCanvasProperty, value);
128+
}
129+
130+
public static readonly DependencyProperty ShowOnCanvasProperty =
131+
DependencyProperty.Register("ShowOnCanvas", typeof(bool), typeof(KeySequence), new FrameworkPropertyMetadata(true, ShowOnCanvasPropertyChanged));
132+
133+
private static void ShowOnCanvasPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) =>
134+
((KeySequence)target).sequence_updateToLayerEditor();
135+
#endregion
136+
137+
123138
/// <summary>Fired whenever the KeySequence object is changed or re-created. Does NOT trigger when keys are changed.</summary>
124139
public event EventHandler SequenceUpdated;
125140
/// <summary>Fired whenever keys are changed.</summary>
@@ -129,7 +144,15 @@ public bool FreestyleEnabled
129144
public KeySequence()
130145
{
131146
InitializeComponent();
132-
this.DataContext = this;
147+
148+
/* BAD BAD BAD!!! Don't do this! Doing this overrides the DataContext of the control, so if you were to use a binding on this control
149+
* from another control, the binding would try access 'this' instead. E.G., in the following example, the binding is attempting to
150+
* access KeySequence.SomeProperty, which is not what is expected. By looking at this code (and if it were a proper control), the
151+
* binding should be accessing SomeContext.SomeProperty.
152+
* <Grid DataContext="SomeContext">
153+
* <KeySequence Sequence="{Binding SomeProperty}" />
154+
* </Grid> */
155+
//this.DataContext = this;
133156
}
134157

135158
private void sequence_remove_keys_Click(object sender, RoutedEventArgs e)
@@ -238,7 +261,7 @@ public void sequence_updateToLayerEditor()
238261
{
239262
if (Sequence != null && IsInitialized && IsVisible && IsEnabled)
240263
{
241-
if (Sequence.type == Settings.KeySequenceType.FreeForm)
264+
if (Sequence.type == Settings.KeySequenceType.FreeForm && ShowOnCanvas)
242265
{
243266
Sequence.freeform.ValuesChanged += freeform_updated;
244267
LayerEditor.AddKeySequenceElement(Sequence.freeform, Color.FromRgb(255, 255, 255), Title);

Project-Aurora/Project-Aurora/EffectsEngine/EffectLayer.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -441,12 +441,20 @@ public EffectLayer Set(Devices.DeviceKeys[] keys, Color color)
441441
/// <param name="sequence">KeySequence to specify what regions of the bitmap need to be changed</param>
442442
/// <param name="color">Color to be used</param>
443443
/// <returns>Itself</returns>
444-
public EffectLayer Set(KeySequence sequence, Color color)
444+
public EffectLayer Set(KeySequence sequence, Color color) => Set(sequence, new SolidBrush(color));
445+
446+
/// <summary>
447+
/// Sets a specific KeySequence on the bitmap with a specified brush.
448+
/// </summary>
449+
/// <param name="sequence">KeySequence to specify what regions of the bitmap need to be changed</param>
450+
/// <param name="brush">Brush to be used</param>
451+
/// <returns>Itself</returns>
452+
public EffectLayer Set(KeySequence sequence, Brush brush)
445453
{
446454
if (sequence.type == KeySequenceType.Sequence)
447455
{
448456
foreach (var key in sequence.keys)
449-
Set(key, color);
457+
SetOneKey(key, brush);
450458
}
451459
else
452460
{
@@ -468,7 +476,7 @@ public EffectLayer Set(KeySequence sequence, Color color)
468476
myMatrix.RotateAt(sequence.freeform.Angle, rotatePoint, MatrixOrder.Append);
469477

470478
g.Transform = myMatrix;
471-
g.FillRectangle(new SolidBrush(color), rect);
479+
g.FillRectangle(brush, rect);
472480
}
473481
}
474482

@@ -562,21 +570,32 @@ public EffectLayer DrawTransformed(KeySequence sequence, Action<Graphics> render
562570
/// <param name="key">DeviceKey to be set</param>
563571
/// <param name="color">Color to be used</param>
564572
/// <returns>Itself</returns>
565-
private EffectLayer SetOneKey(Devices.DeviceKeys key, Color color)
573+
private EffectLayer SetOneKey(Devices.DeviceKeys key, Color color) => SetOneKey(key, new SolidBrush(color));
574+
575+
/// <summary>
576+
/// Sets one DeviceKeys key with a specific brush on the bitmap
577+
/// </summary>
578+
/// <param name="key">DeviceKey to be set</param>
579+
/// <param name="brush">Brush to be used</param>
580+
/// <returns>Itself</returns>
581+
private EffectLayer SetOneKey(Devices.DeviceKeys key, Brush brush)
566582
{
567583
BitmapRectangle keymaping = Effects.GetBitmappingFromDeviceKey(key);
568584

569585
if (key == Devices.DeviceKeys.Peripheral)
570586
{
571-
peripheral = color;
587+
if (brush is SolidBrush solidBrush)
588+
peripheral = solidBrush.Color;
589+
// TODO Add support for this ^ to other brush types
590+
572591
using (Graphics g = Graphics.FromImage(colormap))
573592
{
574593
foreach (Devices.DeviceKeys peri_key in possible_peripheral_keys)
575594
{
576595
BitmapRectangle peri_keymaping = Effects.GetBitmappingFromDeviceKey(peri_key);
577596

578597
if (peri_keymaping.IsValid)
579-
g.FillRectangle(new SolidBrush(color), peri_keymaping.Rectangle);
598+
g.FillRectangle(brush, peri_keymaping.Rectangle);
580599
}
581600

582601
needsRender = true;
@@ -588,13 +607,13 @@ private EffectLayer SetOneKey(Devices.DeviceKeys key, Color color)
588607
keymaping.Left < 0 || keymaping.Right > Effects.canvas_width)
589608
{
590609
Global.logger.Warn("Coudln't set key color " + key.ToString());
591-
return this; ;
610+
return this;
592611
}
593612
else
594613
{
595614
using (Graphics g = Graphics.FromImage(colormap))
596615
{
597-
g.FillRectangle(new SolidBrush(color), keymaping.Rectangle);
616+
g.FillRectangle(brush, keymaping.Rectangle);
598617
needsRender = true;
599618
}
600619
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
using Aurora.Utils;
2+
using System;
3+
using System.Collections;
4+
using System.Collections.Generic;
5+
using System.Drawing;
6+
using System.Drawing.Drawing2D;
7+
using System.Linq;
8+
9+
namespace Aurora.EffectsEngine {
10+
11+
/// <summary>
12+
/// A factory that can create a segmented radial brush.
13+
/// </summary>
14+
/// <remarks>
15+
/// I originally tried creating this effect using the <see cref="PathGradientBrush"/>, however I cannot find a way of removing the central colour. This means that the
16+
/// colours gradually fade to another colour in the centre. Since the points on the path would need to be equidistant from the centre to preserve the angle and gradients,
17+
/// it means that some of the brush is cut off and the colours appear washed out. All round, not ideal for this use case, so that is the reason I have created this instead.
18+
/// </remarks>
19+
public class SegmentedRadialBrushFactory : ICloneable {
20+
21+
// The resolution of the base texture size.
22+
private const int textureSize = 200;
23+
private static readonly Rectangle renderArea = new Rectangle(0, 0, textureSize, textureSize);
24+
private static readonly SolidBrush fallback = new SolidBrush(Color.Transparent);
25+
26+
private ColorStopCollection colors;
27+
private int segmentCount = 24;
28+
private TextureBrush baseBrush;
29+
30+
public SegmentedRadialBrushFactory(ColorStopCollection colors) {
31+
this.colors = colors;
32+
CreateBaseTextureBrush();
33+
}
34+
35+
/// <summary>
36+
/// Gets or sets the colors and their orders in use by the brush.
37+
/// </summary>
38+
public ColorStopCollection Colors {
39+
get => colors;
40+
set {
41+
// If the colors are equal, don't do anything
42+
if (colors.StopsEqual(value))
43+
return;
44+
45+
// If they are not equal, create a new texture brush
46+
colors = value;
47+
CreateBaseTextureBrush();
48+
}
49+
}
50+
51+
/// <summary>
52+
/// How many segments should be created for this brush. Larger values appear smoother by may run more slowly.
53+
/// </summary>
54+
public int SegmentCount {
55+
get => segmentCount;
56+
set {
57+
if (segmentCount <= 0)
58+
throw new ArgumentOutOfRangeException(nameof(SegmentCount), "Segment count must not be lower than 1.");
59+
if (segmentCount != value) {
60+
segmentCount = value;
61+
CreateBaseTextureBrush();
62+
}
63+
}
64+
}
65+
66+
/// <summary>
67+
/// Creates a new base brush from the current properties.
68+
/// </summary>
69+
private void CreateBaseTextureBrush() {
70+
var angle = 360f / segmentCount;
71+
var segmentOffset = 1f / segmentCount; // how much each segment moves the offset forwards on the gradient
72+
73+
// Get a list of all stops in the stop collection.
74+
// We use this to optimise the interpolation of the colors.
75+
// If we were to use ColorStopCollection.GetColorAt, it may end up running numerous for loops over the same stops, but given
76+
// the special requirements here, we can eliminate that and use less for loops and make the ones we do use slightly more optimal.
77+
var stops = colors.ToList();
78+
var currentOffset = segmentOffset / 2;
79+
var stopIdx = 0;
80+
81+
// If there isn't a stop at offsets 0 and 1, create them. This makes it easier during the loop since we don't have to check if we're left/right of the first/last stops.
82+
if (stops[0].Key != 0)
83+
stops.Insert(0, new KeyValuePair<float, Color>(0f, stops[0].Value));
84+
if (stops[stops.Count - 1].Key != 1)
85+
stops.Add(new KeyValuePair<float, Color>(1f, stops[stops.Count - 1].Value));
86+
87+
// Create and draw texture
88+
var texture = new Bitmap(textureSize, textureSize);
89+
using (var gfx = Graphics.FromImage(texture)) {
90+
for (var i = 0; i < segmentCount; i++) {
91+
92+
// Move the stop index forwards if required.
93+
// - It needs to more fowards until the the stop at that index is to the left of the current offset and the point at that index+1 is to the right.
94+
// - If it is exactly on a stop, make that matched stop at that index.
95+
while (stops[stopIdx + 1].Key < currentOffset)
96+
stopIdx++;
97+
98+
// Now that stopIdx is in the right place, we can figure out which color we need.
99+
var color = stops[stopIdx].Key == currentOffset
100+
? stops[stopIdx].Value // if exactly on a stop, don't need to interpolate it
101+
: ColorUtils.BlendColors( // otherwise, we need to calculate the blend between the two stops
102+
stops[stopIdx].Value,
103+
stops[stopIdx + 1].Value,
104+
(currentOffset - stops[stopIdx].Key) / (stops[stopIdx + 1].Key - stops[stopIdx].Key)
105+
);
106+
107+
// Draw this segment
108+
gfx.FillPie(new SolidBrush(color), renderArea, i * angle, angle);
109+
110+
// Bump the offset
111+
currentOffset += segmentOffset;
112+
}
113+
}
114+
115+
// Create the texture brush from our custom bitmap texture
116+
baseBrush = new TextureBrush(texture);
117+
}
118+
119+
/// <summary>
120+
/// Gets the brush that will be centered on and sized for the specified region.
121+
/// </summary>
122+
/// <param name="region">The region which defines where the brush will be drawn and where the brush will be centered.</param>
123+
/// <param name="angle">The angle which the brush will be rendered at.</param>
124+
/// <param name="keepAspectRatio">If <c>true</c>, the scale transformation will have the same value in x as it does in y. If <c>false</c>, the scale in each dimension may be different.
125+
/// When <c>true</c>, the sizes/areas of each color may appear different (due to being cut off), however when <c>false</c>, they appear more consistent.
126+
/// If the brush is animated, <c>true</c> will make the speeed appear constant whereas <c>false</c> will cause the rotation to appear slower on the shorter side.</param>
127+
public Brush GetBrush(RectangleF region, float angle = 0, bool keepAspectRatio = true) {
128+
// Check if the region has a 0 size. If so, just return a blank brush instead (the matrix becomes invalid with 0 size scaling).
129+
if (region.Width == 0 || region.Height == 0) return fallback;
130+
131+
var brush = (TextureBrush)baseBrush.Clone(); // Clone the brush so we don't alter the transformation of it in other places accidently
132+
var mtx = new Matrix();
133+
134+
// Translate it so that the center of the texture (where all the colors meet) is at 0,0
135+
mtx.Translate(-textureSize / 2, -textureSize / 2, MatrixOrder.Append);
136+
137+
// Then, rotate it to the target angle
138+
mtx.Rotate(angle, MatrixOrder.Append);
139+
140+
// Scale it so that it'll still completely cover the textureSize area.
141+
// 1.45 is a rough approximation of SQRT(2) [it's actually 1.414 but we want to allow a bit of space incase of artifacts at the edges]
142+
mtx.Scale(1.45f, 1.45f, MatrixOrder.Append);
143+
144+
// Next we need to scale the texture so that it'll cover the area defined by the region
145+
float sx = region.Width / textureSize, sy = region.Height / textureSize;
146+
// If the aspect ratio is locked, we want to scale both dimensions up to the biggest required scale
147+
if (keepAspectRatio)
148+
sx = sy = Math.Max(sx, sy);
149+
mtx.Scale(sx, sy, MatrixOrder.Append);
150+
151+
// Finally, we need to translate the texture so that it is in the center of the region
152+
// (At this point, the center of the texture where the colors meet is still at 0,0)
153+
mtx.Translate(region.Left + (region.Width / 2), region.Top + (region.Height / 2), MatrixOrder.Append);
154+
155+
// Apply the transformation and return the texture brush
156+
brush.Transform = mtx;
157+
return brush;
158+
}
159+
160+
/// <summary>
161+
/// Creates a clone of this factory.
162+
/// </summary>
163+
public object Clone() => new SegmentedRadialBrushFactory(new ColorStopCollection(colors)) { SegmentCount = SegmentCount };
164+
}
165+
}

0 commit comments

Comments
 (0)