diff --git a/Project-Aurora/Project-Aurora/EffectsEngine/EffectLayer.cs b/Project-Aurora/Project-Aurora/EffectsEngine/EffectLayer.cs index e80dfcec7..de02522f9 100755 --- a/Project-Aurora/Project-Aurora/EffectsEngine/EffectLayer.cs +++ b/Project-Aurora/Project-Aurora/EffectsEngine/EffectLayer.cs @@ -475,6 +475,87 @@ public EffectLayer Set(KeySequence sequence, Color color) return this; } + /// + /// Allows drawing some arbitrary content to the sequence bounds, including translation, scaling and rotation. + /// Usage: + /// someEffectLayer.DrawTransformed(Properties.Sequence,
+ /// m => {
+ /// // We are prepending the transformations since we want the mirroring to happen BEFORE the rotation and scaling happens.
+ /// m.Translate(100, 0, MatrixOrder.Prepend); // These two are backwards because we are Prepending (so this is prepended first)
+ /// m.Scale(-1, 1, MatrixOrder.Prepend); // Then this is prepended before the tranlate.
+ /// },
+ /// gfx => {
+ /// gfx.FillRectangle(Brushes.Red, 0, 0, 30, 100);
+ /// gfx.FillRectangle(Brushes.Blue, 70, 0, 30, 100);
+ /// }, + /// new RectangleF(0, 0, 100, 100);
+ /// This code will draw an X-mirrored image of a red stipe and a blue stripe (with a transparent gap in between) to the target keysequence area. + ///
+ /// The target sequence whose bounds will be used as the target location on the drawing canvas. + /// An action that further configures the transformation matrix before render is called. + /// An action that receives a transformed graphics context and can render whatever it needs to. + /// The source region of the rendered content. This is used when calculating the transformation matrix, so that this + /// rectangle in the render context is transformed to the keysequence bounds in the layer's context. Note that no clipping is performed. + public EffectLayer DrawTransformed(KeySequence sequence, Action configureMatrix, Action render, RectangleF sourceRegion) { + // The matrix represents the transformation that will be applied to the rendered content + var matrix = new Matrix(); + + // The bounds represent the target position of the render part + // Note that we round the X and Y off to properly imitate the above `Set(KeySequence, Color)` method. Unsure exactly why this is done, but it _is_ done to replicate behaviour properly. + // Also unsure why the X and Y are rounded using math.Round but Width and Height are just truncated using an int cast?? + var boundsRaw = sequence.GetAffectedRegion(); + var bounds = new RectangleF((int)Math.Round(boundsRaw.X), (int)Math.Round(boundsRaw.Y), (int)boundsRaw.Width, (int)boundsRaw.Height); + + using (var gfx = Graphics.FromImage(colormap)) { + + // First, calculate the scaling required to transform the sourceRect's size into the bounds' size + float sx = bounds.Width / sourceRegion.Width, sy = bounds.Height / sourceRegion.Height; + + // Perform this scale first + // Note: that if the scale is zero, when setting the graphics transform to the matrix, it throws an error, so we must have NON-ZERO values + // Note 2: Also tried using float.Epsilon but this also caused the exception, so a somewhat small number will have to suffice. Not noticed any visual issues with 0.001f. + matrix.Scale(sx == 0 ? .001f : sx, sy == 0 ? .001f : sy, MatrixOrder.Append); + + // Second, for freeform objects, apply the rotation. This needs to be done AFTER the scaling, else the scaling is applied to the rotated object, which skews it + // We rotate around the central point of the source region, but we need to take the scaling of the dimensions into account + if (sequence.type == KeySequenceType.FreeForm) + matrix.RotateAt(sequence.freeform.Angle, new PointF((sourceRegion.Left + (sourceRegion.Width / 2f)) * sx, (sourceRegion.Top + (sourceRegion.Height / 2f)) * sy), MatrixOrder.Append); + + // Third, we can translate the matrix from the source to the target location. + matrix.Translate(bounds.X - sourceRegion.Left, bounds.Y - sourceRegion.Top, MatrixOrder.Append); + + // Finally, call the custom matrix configure action + configureMatrix(matrix); + + // Apply the matrix transform to the graphics context and then render + gfx.Transform = matrix; + render(gfx); + } + + return this; + } + + /// + /// Allows drawing some arbitrary content to the sequence bounds, including translation, scaling and rotation. + /// See for usage. + /// + /// The target sequence whose bounds will be used as the target location on the drawing canvas. + /// An action that receives a transformed graphics context and can render whatever it needs to. + /// The source region of the rendered content. This is used when calculating the transformation matrix, so that this + /// rectangle in the render context is transformed to the keysequence bounds in the layer's context. Note that no clipping is performed. + public EffectLayer DrawTransformed(KeySequence sequence, Action render, RectangleF sourceRegion) + => DrawTransformed(sequence, _ => { }, render, sourceRegion); + + /// + /// Allows drawing some arbitrary content to the sequence bounds, including translation, scaling and rotation. + /// Uses the full canvas size as the source region. + /// See for usage. + /// + /// The target sequence whose bounds will be used as the target location on the drawing canvas. + /// An action that receives a transformed graphics context and can render whatever it needs to. + public EffectLayer DrawTransformed(KeySequence sequence, Action render) => + DrawTransformed(sequence, render, new RectangleF(0, 0, Effects.canvas_width, Effects.canvas_height)); + /// /// Sets one DeviceKeys key with a specific color on the bitmap /// diff --git a/Project-Aurora/Project-Aurora/Settings/Layers/EqualizerLayerHandler.cs b/Project-Aurora/Project-Aurora/Settings/Layers/EqualizerLayerHandler.cs index 8b20ba459..41d7bf2b3 100644 --- a/Project-Aurora/Project-Aurora/Settings/Layers/EqualizerLayerHandler.cs +++ b/Project-Aurora/Project-Aurora/Settings/Layers/EqualizerLayerHandler.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; +using System.Drawing.Drawing2D; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -152,6 +153,11 @@ public class EqualizerLayerHandler : LayerHandler { + // Here we draw the equalizer relative to our source rectangle and the DrawTransformed method handles sizing and positioning it correctly for us - using (Graphics g = equalizer_layer.GetGraphics()) - { - g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy; + // Draw a rectangle background over the entire source rect if bg is enabled + if (BgEnabled) + g.FillRectangle(new SolidBrush(Properties.DimColor), sourceRect); - int wave_step_amount = _local_fft.Length / (int)rect.Width; + g.CompositingMode = CompositingMode.SourceCopy; + + int wave_step_amount = _local_fft.Length / (int)sourceRect.Width; - switch (Properties.EQType) - { + switch (Properties.EQType) { case EqualizerType.Waveform: - for (int x = 0; x < (int)rect.Width; x++) - { + var halfHeight = sourceRect.Height / 2f; + for (int x = 0; x < (int)sourceRect.Width; x++) { float fft_val = _local_fft.Length > x * wave_step_amount ? _local_fft[x * wave_step_amount].X : 0.0f; - - Brush brush = GetBrush(fft_val, x, rect.Width); - - g.DrawLine(new Pen(brush), x + rect.X, (rect.Height / 2) + rect.Y, x + rect.X, (rect.Height / 2) + rect.Y - Math.Max(Math.Min(fft_val / scaled_max_amplitude * 500.0f, rect.Height / 2), -rect.Height / 2)); + Brush brush = GetBrush(fft_val, x, sourceRect.Width); + var yOff = -Math.Max(Math.Min(fft_val / scaled_max_amplitude * 500.0f, halfHeight), -halfHeight); + g.DrawLine(new Pen(brush), x, halfHeight, x, halfHeight + yOff); } break; + case EqualizerType.Waveform_Bottom: - for (int x = 0; x < (int)rect.Width; x++) - { + for (int x = 0; x < (int)sourceRect.Width; x++) { float fft_val = _local_fft.Length > x * wave_step_amount ? _local_fft[x * wave_step_amount].X : 0.0f; - - Brush brush = GetBrush(fft_val, x, rect.Width); - - g.DrawLine(new Pen(brush), x + rect.X, rect.Height + rect.Y, x + rect.X, rect.Height + rect.Y - Math.Min(Math.Abs(fft_val / scaled_max_amplitude) * 1000.0f, rect.Height)); + Brush brush = GetBrush(fft_val, x, sourceRect.Width); + g.DrawLine(new Pen(brush), x, sourceRect.Height, x, sourceRect.Height - Math.Min(Math.Abs(fft_val / scaled_max_amplitude) * 1000.0f, sourceRect.Height)); } break; - case EqualizerType.PowerBars: + case EqualizerType.PowerBars: //Perform FFT again to get frequencies FastFourierTransform.FFT(false, (int)Math.Log(fftLength, 2.0), _local_fft); while (flux_array.Count < freqs.Length) - { flux_array.Add(0.0f); - } int startF = 0; int endF = 0; @@ -340,7 +336,7 @@ public override EffectLayer Render(IGameState gamestate) //System.Diagnostics.Debug.WriteLine($"flux max: {flux_array.Max()}"); - float bar_width = rect.Width / (float)(freqs.Length - 1); + float bar_width = sourceRect.Width / (float)(freqs.Length - 1); for (int f_x = 0; f_x < freq_results.Length - 1; f_x++) { @@ -351,9 +347,9 @@ public override EffectLayer Render(IGameState gamestate) if (previous_freq_results[f_x] - fft_val > 0.10) fft_val = previous_freq_results[f_x] - 0.15f; - float x = (f_x * bar_width) + rect.X; - float y = rect.Height + rect.Y; - float height = fft_val * rect.Height; + float x = f_x * bar_width; + float y = sourceRect.Height; + float height = fft_val * sourceRect.Height; previous_freq_results[f_x] = fft_val; @@ -361,10 +357,11 @@ public override EffectLayer Render(IGameState gamestate) g.FillRectangle(brush, x, y - height, bar_width, height); } - break; } - } + + }, sourceRect); + var hander = NewLayerRender; if (hander != null) @@ -420,7 +417,6 @@ private int freqToBin(float freq) private Brush GetBrush(float value, float position, float max_position) { - var rect = Properties.Sequence.GetAffectedRegion(); if (Properties.ViewType == EqualizerPresentationType.AlternatingColor) { if (value >= 0) @@ -432,9 +428,10 @@ private Brush GetBrush(float value, float position, float max_position) return new SolidBrush(Properties.Gradient.GetColorSpectrum().GetColorAt(position, max_position)); else if (Properties.ViewType == EqualizerPresentationType.GradientHorizontal) { - EffectBrush e_brush = new EffectBrush(Properties.Gradient.GetColorSpectrum()); - e_brush.start = new PointF(rect.X, 0); - e_brush.end = new PointF(rect.Width + rect.X, 0); + EffectBrush e_brush = new EffectBrush(Properties.Gradient.GetColorSpectrum()) { + start = PointF.Empty, + end = new PointF(sourceRect.Width, 0) + }; return e_brush.GetDrawingBrush(); } @@ -442,9 +439,10 @@ private Brush GetBrush(float value, float position, float max_position) return new SolidBrush(Properties.Gradient.GetColorSpectrum().GetColorAt(Utils.Time.GetMilliSeconds(), 1000)); else if (Properties.ViewType == EqualizerPresentationType.GradientVertical) { - EffectBrush e_brush = new EffectBrush(Properties.Gradient.GetColorSpectrum()); - e_brush.start = new PointF(0, rect.Height + rect.Y); - e_brush.end = new PointF(0, rect.Y); + EffectBrush e_brush = new EffectBrush(Properties.Gradient.GetColorSpectrum()) { + start = new PointF(0, sourceRect.Height), + end = PointF.Empty + }; return e_brush.GetDrawingBrush(); }