From e9d3040b649c45edba22f757ba917d8b2c34f4b9 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 14 Jan 2017 16:48:06 +0100 Subject: [PATCH] Added AngularBrush --- Artemis/Artemis/Artemis.csproj | 13 ++ .../AngularBrushPropertiesModel.cs | 28 ++++ .../AngularBrushPropertiesView.xaml | 80 ++++++++++ .../AngularBrushPropertiesView.xaml.cs | 12 ++ .../AngularBrushPropertiesViewModel.cs | 90 +++++++++++ .../Types/AngularBrush/AngularBrushType.cs | 110 +++++++++++++ .../AngularBrush/Drawing/GradientDrawer.cs | 149 ++++++++++++++++++ 7 files changed, 482 insertions(+) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 6452856de..c2b02415d 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -56,6 +56,7 @@ prompt 4 false + true x64 @@ -65,6 +66,7 @@ TRACE prompt 4 + true EAC088BE27A2DE790AE6F37A020409F4A1B5EC0E @@ -491,6 +493,13 @@ + + + AngularBrushPropertiesView.xaml + + + + @@ -861,6 +870,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs new file mode 100644 index 000000000..58fdce801 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Windows.Media; +using Artemis.Profiles.Layers.Models; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public class AngularBrushPropertiesModel : LayerPropertiesModel + { + #region Properties & Fields + + public IList> GradientStops { get; set; } + + #endregion + + #region Constructors + + public AngularBrushPropertiesModel(LayerPropertiesModel properties = null) + : base(properties) + { } + + #endregion + + #region Methods + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml new file mode 100644 index 000000000..b93018706 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs new file mode 100644 index 000000000..e5edeea15 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public partial class AngularBrushPropertiesView : UserControl + { + public AngularBrushPropertiesView() + { + InitializeComponent(); + } + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs new file mode 100644 index 000000000..0b4b792d7 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using System.Windows.Media; +using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Interfaces; +using Artemis.Utilities; +using Artemis.ViewModels; +using Artemis.ViewModels.Profiles; +using Caliburn.Micro; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public class AngularBrushPropertiesViewModel : LayerPropertiesViewModel + { + #region Properties & Fields + + private ILayerAnimation _selectedLayerAnimation; + + public BindableCollection DataModelProps { get; set; } + public BindableCollection LayerAnimations { get; set; } + public LayerDynamicPropertiesViewModel HeightProperties { get; set; } + public LayerDynamicPropertiesViewModel WidthProperties { get; set; } + public LayerDynamicPropertiesViewModel OpacityProperties { get; set; } + public LayerTweenViewModel LayerTweenViewModel { get; set; } + + public ILayerAnimation SelectedLayerAnimation + { + get { return _selectedLayerAnimation; } + set + { + if (Equals(value, _selectedLayerAnimation)) return; + _selectedLayerAnimation = value; + NotifyOfPropertyChange(() => SelectedLayerAnimation); + } + } + + #endregion + + #region Constructors + + public AngularBrushPropertiesViewModel(LayerEditorViewModel editorVm) + : base(editorVm) + { + LayerAnimations = new BindableCollection(editorVm.LayerAnimations); + + HeightProperties = new LayerDynamicPropertiesViewModel("Height", editorVm); + WidthProperties = new LayerDynamicPropertiesViewModel("Width", editorVm); + OpacityProperties = new LayerDynamicPropertiesViewModel("Opacity", editorVm); + LayerTweenViewModel = new LayerTweenViewModel(editorVm); + + SelectedLayerAnimation = + LayerAnimations.FirstOrDefault(l => l.Name == editorVm.ProposedLayer.LayerAnimation?.Name) ?? + LayerAnimations.First(l => l.Name == "None"); + } + + #endregion + + #region Methods + + public override void ApplyProperties() + { + HeightProperties.Apply(LayerModel); + WidthProperties.Apply(LayerModel); + OpacityProperties.Apply(LayerModel); + + ((AngularBrushPropertiesModel)LayerModel.Properties).GradientStops = GetGradientStops().Select(x => new Tuple(x.Offset, x.Color)).ToList(); + + LayerModel.LayerAnimation = SelectedLayerAnimation; + } + + private GradientStopCollection GetGradientStops() + { + LinearGradientBrush linearBrush = Brush as LinearGradientBrush; + if (linearBrush != null) + return linearBrush.GradientStops; + + RadialGradientBrush radialBrush = Brush as RadialGradientBrush; + if (radialBrush != null) + return radialBrush.GradientStops; + + SolidColorBrush solidBrush = Brush as SolidColorBrush; + if (solidBrush != null) + return new GradientStopCollection(new[] { new GradientStop(solidBrush.Color, 0), new GradientStop(solidBrush.Color, 1) }); + + return null; + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs new file mode 100644 index 000000000..6fc65b2ca --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs @@ -0,0 +1,110 @@ +using System.Windows; +using System.Windows.Media; +using Artemis.Modules.Abstract; +using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Animations; +using Artemis.Profiles.Layers.Interfaces; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.AngularBrush.Drawing; +using Artemis.ViewModels; + +namespace Artemis.Profiles.Layers.Types.AngularBrush +{ + public class AngularBrushType : ILayerType + { + #region Properties & Fields + + private GradientDrawer _gradientDrawer; + + public string Name => "Angular Brush"; + public bool ShowInEdtor => true; + public DrawType DrawType => DrawType.Keyboard; + + #endregion + + public AngularBrushType() + { + _gradientDrawer = new GradientDrawer(); + } + + #region Methods + + public ImageSource DrawThumbnail(LayerModel layer) + { + //TODO DarthAffe 14.01.2017: This could be replaced with the real brush but it complaints about the thread too + Rect thumbnailRect = new Rect(0, 0, 18, 18); + DrawingVisual visual = new DrawingVisual(); + using (DrawingContext c = visual.RenderOpen()) + if (layer.Properties.Brush != null) + c.DrawRectangle(layer.Properties.Brush, + new Pen(new SolidColorBrush(Colors.White), 1), + thumbnailRect); + + DrawingImage image = new DrawingImage(visual.Drawing); + return image; + } + + public void Draw(LayerModel layerModel, DrawingContext c) + { + AngularBrushPropertiesModel properties = layerModel.Properties as AngularBrushPropertiesModel; + if (properties == null) return; + + Brush origBrush = layerModel.Brush; + + _gradientDrawer.GradientStops = properties.GradientStops; + _gradientDrawer.Update(); + layerModel.Brush = _gradientDrawer.Brush.Clone(); + + // If an animation is present, let it handle the drawing + if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) + { + layerModel.LayerAnimation.Draw(layerModel, c); + return; + } + + // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush + Rect rect = layerModel.Properties.Contain + ? layerModel.LayerRect() + : new Rect(layerModel.Properties.X * 4, layerModel.Properties.Y * 4, + layerModel.Properties.Width * 4, layerModel.Properties.Height * 4); + + Rect clip = layerModel.LayerRect(); + + // Can't meddle with the original brush because it's frozen. + Brush brush = layerModel.Brush.Clone(); + brush.Opacity = layerModel.Opacity; + + c.PushClip(new RectangleGeometry(clip)); + c.DrawRectangle(brush, null, rect); + c.Pop(); + + layerModel.Brush = origBrush; + } + + public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) + { + layerModel.ApplyProperties(true); + if (isPreview || dataModel == null) + return; + + // If not previewing, apply dynamic properties according to datamodel + foreach (DynamicPropertiesModel dynamicProperty in layerModel.Properties.DynamicProperties) + dynamicProperty.ApplyProperty(dataModel, layerModel); + } + + public void SetupProperties(LayerModel layerModel) + { + if (layerModel.Properties is AngularBrushPropertiesModel) + return; + + layerModel.Properties = new AngularBrushPropertiesModel(layerModel.Properties); + } + + public LayerPropertiesViewModel SetupViewModel(LayerEditorViewModel layerEditorViewModel, LayerPropertiesViewModel layerPropertiesViewModel) + { + return (layerPropertiesViewModel as AngularBrushPropertiesViewModel) ?? new AngularBrushPropertiesViewModel(layerEditorViewModel); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs new file mode 100644 index 000000000..42dd4df72 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing +{ + public class GradientDrawer + { + #region Constants + + private static readonly double ORIGIN = Math.Atan2(-1, 0); + + #endregion + + #region Properties & Fields + + private WriteableBitmap _bitmap; + + private IList> _gradientStops; + public IList> GradientStops + { + set { _gradientStops = FixGradientStops(value); } + } + + public Brush Brush { get; private set; } + + #endregion + + #region Methods + + public void Update() + { + if (_bitmap == null) + CreateBrush(); + + unsafe + { + _bitmap.Lock(); + byte* buffer = (byte*)_bitmap.BackBuffer.ToPointer(); + + int width = _bitmap.PixelWidth; + double widthHalf = width / 2.0; + + int height = _bitmap.PixelHeight; + double heightHalf = height / 2.0; + + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + int offset = (((y * width) + x) * 4); + + double gradientOffset = CalculateGradientOffset(x, y, widthHalf, heightHalf); + GetColor(_gradientStops, gradientOffset, + ref buffer[offset + 3], ref buffer[offset + 2], + ref buffer[offset + 1], ref buffer[offset]); + } + + _bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height)); + _bitmap.Unlock(); + } + } + + private void CreateBrush() + { + _bitmap = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgra32, null); + Brush = new ImageBrush(_bitmap) { Stretch = Stretch.UniformToFill }; + } + + private double CalculateGradientOffset(double x, double y, double centerX, double centerY) + { + double angle = Math.Atan2(y - centerY, x - centerX) - ORIGIN; + if (angle < 0) angle += Math.PI * 2; + return angle / (Math.PI * 2); + } + + private static void GetColor(IList> gradientStops, double offset, ref byte colA, ref byte colR, ref byte colG, ref byte colB) + { + if (gradientStops.Count == 0) + { + colA = 0; + colR = 0; + colG = 0; + colB = 0; + return; + } + if (gradientStops.Count == 1) + { + Color color = gradientStops.First().Item2; + colA = color.A; + colR = color.R; + colG = color.G; + colB = color.B; + return; + } + + Tuple beforeStop = null; + double afterOffset = -1; + Color afterColor = default(Color); + + for (int i = 0; i < gradientStops.Count; i++) + { + Tuple gradientStop = gradientStops[i]; + double o = gradientStop.Item1; + if (o <= offset) + beforeStop = gradientStop; + + if (o >= offset) + { + afterOffset = gradientStop.Item1; + afterColor = gradientStop.Item2; + break; + } + } + double beforeOffset = beforeStop.Item1; + Color beforeColor = beforeStop.Item2; + + double blendFactor = 0f; + if (beforeOffset != afterOffset) + blendFactor = ((offset - beforeOffset) / (afterOffset - beforeOffset)); + + colA = (byte)((afterColor.A - beforeColor.A) * blendFactor + beforeColor.A); + colR = (byte)((afterColor.R - beforeColor.R) * blendFactor + beforeColor.R); + colG = (byte)((afterColor.G - beforeColor.G) * blendFactor + beforeColor.G); + colB = (byte)((afterColor.B - beforeColor.B) * blendFactor + beforeColor.B); + } + + private IList> FixGradientStops(IList> gradientStops) + { + if (gradientStops == null) return new List>(); + + List> stops = gradientStops.OrderBy(x => x.Item1).ToList(); + + Tuple firstStop = stops.First(); + if (firstStop.Item1 > 0) + stops.Insert(0, new Tuple(0, firstStop.Item2)); + + Tuple lastStop = stops.Last(); + if (lastStop.Item1 < 1) + stops.Add(new Tuple(1, lastStop.Item2)); + + return stops; + } + + #endregion + } +}