diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index aafe6a280..a9b4342af 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -250,6 +250,14 @@ ..\packages\Process.NET.1.0.5\lib\Process.NET.dll True + + ..\packages\SharpDX.3.1.1\lib\net45\SharpDX.dll + True + + + ..\packages\SharpDX.Direct3D9.3.1.1\lib\net45\SharpDX.Direct3D9.dll + True + ..\packages\Splat.1.6.2\lib\Net45\Splat.dll True @@ -392,6 +400,28 @@ + + + + + + + AmbientLightPropertiesView.xaml + + + + + + + + + + + + + + + AudioPropertiesView.xaml @@ -751,6 +781,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -885,6 +919,7 @@ + diff --git a/Artemis/Artemis/InjectionModules/ProfileModules.cs b/Artemis/Artemis/InjectionModules/ProfileModules.cs index a31c40b5d..bca7fa868 100644 --- a/Artemis/Artemis/InjectionModules/ProfileModules.cs +++ b/Artemis/Artemis/InjectionModules/ProfileModules.cs @@ -1,6 +1,7 @@ using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Conditions; using Artemis.Profiles.Layers.Interfaces; +using Artemis.Profiles.Layers.Types.AmbientLight; using Artemis.Profiles.Layers.Types.Audio; using Artemis.Profiles.Layers.Types.Folder; using Artemis.Profiles.Layers.Types.Generic; @@ -41,10 +42,12 @@ namespace Artemis.InjectionModules Bind().To(); Bind().To(); Bind().To(); + Bind().To(); // Bind some Layer Types to self as well in order to allow JSON.NET injection Bind().ToSelf(); Bind().ToSelf(); + Bind().ToSelf(); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorExtend.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorExtend.cs new file mode 100644 index 000000000..6ee78d885 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorExtend.cs @@ -0,0 +1,75 @@ +using System; +using Artemis.Profiles.Layers.Types.AmbientLight.Model; +using Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.AmbienceCreator +{ + public class AmbienceCreatorExtend : IAmbienceCreator + { + #region Methods + + public byte[] GetAmbience(byte[] pixels, int sourceWidth, int sourceHeight, + int targetWidth, int targetHeight, + AmbientLightPropertiesModel settings) + { + AvgColor[] colors = new AvgColor[targetWidth]; + for (int i = 0; i < colors.Length; i++) + colors[i] = new AvgColor(); + + int offsetLeft = settings.OffsetLeft + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Left) + ? pixels.DetectBlackBarLeft(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + int offsetRight = settings.OffsetRight + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Right) + ? pixels.DetectBlackBarRight(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + int offsetTop = settings.OffsetTop + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Top) + ? pixels.DetectBlackBarTop(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + int offsetBottom = settings.OffsetBottom + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Bottom) + ? pixels.DetectBlackBarBottom(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + + int effectiveSourceWidth = sourceWidth - offsetLeft - offsetRight; + int effectiveSourceHeight = sourceHeight - offsetTop - offsetBottom; + + int relevantSourceHeight = (int)Math.Round(effectiveSourceHeight * (settings.MirroredAmount / 100.0)); + int relevantOffsetTop = sourceHeight - offsetBottom - relevantSourceHeight; + + double widthPixels = effectiveSourceWidth / (double)targetWidth; + double heightPixels = relevantSourceHeight / (double)targetHeight; + + if (widthPixels <= 0 || heightPixels <= 0 || (relevantSourceHeight + relevantOffsetTop > sourceHeight) || effectiveSourceWidth > sourceWidth) + return colors.ToBGRArray(); + + int increment = Math.Max(1, Math.Min(20, settings.Downsampling)); + for (int y = 0; y < relevantSourceHeight; y += increment) + { + int targetWidthIndex = 0; + double widthCounter = widthPixels; + + for (int x = 0; x < effectiveSourceWidth; x += increment) + { + if (x >= widthCounter) + { + widthCounter += widthPixels; + targetWidthIndex++; + } + + int colorsOffset = targetWidthIndex; + int sourceOffset = ((((relevantOffsetTop + y) * sourceWidth) + offsetLeft + x) * 4); + + AvgColor color = colors[colorsOffset]; + color.AddB(pixels[sourceOffset]); + color.AddG(pixels[sourceOffset + 1]); + color.AddR(pixels[sourceOffset + 2]); + } + } + + colors = colors.Flip(targetWidth, settings.FlipMode); + colors = colors.ExtendHeight(targetHeight); + return colors.ToBGRArray(); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorMirror.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorMirror.cs new file mode 100644 index 000000000..3d4569eed --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorMirror.cs @@ -0,0 +1,83 @@ +using System; +using Artemis.Profiles.Layers.Types.AmbientLight.Model; +using Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.AmbienceCreator +{ + public class AmbienceCreatorMirror : IAmbienceCreator + { + #region Methods + + public byte[] GetAmbience(byte[] pixels, int sourceWidth, int sourceHeight, + int targetWidth, int targetHeight, + AmbientLightPropertiesModel settings) + { + AvgColor[] colors = new AvgColor[targetWidth * targetHeight]; + for (int i = 0; i < colors.Length; i++) + colors[i] = new AvgColor(); + + int offsetLeft = settings.OffsetLeft + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Left) + ? pixels.DetectBlackBarLeft(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + int offsetRight = settings.OffsetRight + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Right) + ? pixels.DetectBlackBarRight(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + int offsetTop = settings.OffsetTop + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Top) + ? pixels.DetectBlackBarTop(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + int offsetBottom = settings.OffsetBottom + (settings.BlackBarDetectionMode.HasFlag(BlackBarDetectionMode.Bottom) + ? pixels.DetectBlackBarBottom(sourceWidth, sourceHeight, settings.OffsetLeft, settings.OffsetRight, settings.OffsetTop, settings.OffsetBottom) + : 0); + + int effectiveSourceWidth = sourceWidth - offsetLeft - offsetRight; + int effectiveSourceHeight = sourceHeight - offsetTop - offsetBottom; + + int relevantSourceHeight = (int)Math.Round(effectiveSourceHeight * (settings.MirroredAmount / 100.0)); + int relevantOffsetTop = sourceHeight - offsetBottom - relevantSourceHeight; + + double widthPixels = effectiveSourceWidth / (double)targetWidth; + double heightPixels = relevantSourceHeight / (double)targetHeight; + + if (widthPixels <= 0 || heightPixels <= 0 || (relevantSourceHeight + relevantOffsetTop > sourceHeight) || effectiveSourceWidth > sourceWidth) + return colors.ToBGRArray(); + + int targetHeightIndex = 0; + double heightCounter = heightPixels; + + int increment = Math.Max(1, Math.Min(20, settings.Downsampling)); + for (int y = 0; y < relevantSourceHeight; y += increment) + { + if (y >= heightCounter) + { + heightCounter += heightPixels; + targetHeightIndex++; + } + + int targetWidthIndex = 0; + double widthCounter = widthPixels; + + for (int x = 0; x < effectiveSourceWidth; x += increment) + { + if (x >= widthCounter) + { + widthCounter += widthPixels; + targetWidthIndex++; + } + + int colorsOffset = (targetHeightIndex * targetWidth) + targetWidthIndex; + int sourceOffset = ((((relevantOffsetTop + y) * sourceWidth) + offsetLeft + x) * 4); + + AvgColor color = colors[colorsOffset]; + color.AddB(pixels[sourceOffset]); + color.AddG(pixels[sourceOffset + 1]); + color.AddR(pixels[sourceOffset + 2]); + } + } + + colors = colors.Flip(targetWidth, settings.FlipMode); + return colors.ToBGRArray(); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AvgColor.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AvgColor.cs new file mode 100644 index 000000000..9cef78a27 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AvgColor.cs @@ -0,0 +1,42 @@ +namespace Artemis.Profiles.Layers.Types.AmbientLight.AmbienceCreator +{ + public class AvgColor + { + #region Properties & Fields + + private int _rCount = 0; + private int _r = 0; + private int _gCount = 0; + private int _g = 0; + private int _bCount = 0; + private int _b = 0; + + public byte R => (byte)(_rCount > 0 ? (_r / _rCount) : 0); + public byte G => (byte)(_gCount > 0 ? (_g / _gCount) : 0); + public byte B => (byte)(_bCount > 0 ? (_b / _bCount) : 0); + + #endregion + + #region Methods + + public void AddR(byte r) + { + _r += r; + _rCount++; + } + + public void AddG(byte g) + { + _g += g; + _gCount++; + } + + public void AddB(byte b) + { + _b += b; + _bCount++; + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/IAmbienceCreator.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/IAmbienceCreator.cs new file mode 100644 index 000000000..df84941bd --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/IAmbienceCreator.cs @@ -0,0 +1,7 @@ +namespace Artemis.Profiles.Layers.Types.AmbientLight.AmbienceCreator +{ + public interface IAmbienceCreator + { + byte[] GetAmbience(byte[] pixels, int sourceWidth, int sourceHeight, int targetWidth, int targetHeight, AmbientLightPropertiesModel settings); + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesModel.cs new file mode 100644 index 000000000..5579ae504 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesModel.cs @@ -0,0 +1,42 @@ +using System.Windows.Media; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.AmbientLight.Model; +using Newtonsoft.Json; + +namespace Artemis.Profiles.Layers.Types.AmbientLight +{ + public class AmbientLightPropertiesModel : LayerPropertiesModel + { + #region Properties & Fields + + //HACK DarthAffe 30.10.2016: The 'normal' Brush-Property destoys the profile since Drawing-Brushes cannot be deserialized. + [JsonIgnore] + public Brush AmbientLightBrush { get; set; } + + public AmbienceCreatorType AmbienceCreatorType { get; set; } = AmbienceCreatorType.Mirror; + + public int OffsetLeft { get; set; } = 0; + public int OffsetRight { get; set; } = 0; + public int OffsetTop { get; set; } = 0; + public int OffsetBottom { get; set; } = 0; + + public int Downsampling { get; set; } = 2; + public double MirroredAmount { get; set; } = 10; + + public SmoothMode SmoothMode { get; set; } = SmoothMode.Low; + public BlackBarDetectionMode BlackBarDetectionMode { get; set; } = BlackBarDetectionMode.Bottom; + public FlipMode FlipMode { get; set; } = FlipMode.Vertical; + + #endregion + + #region Constructors + + public AmbientLightPropertiesModel(LayerPropertiesModel properties) + : base(properties) + { + Brush = new SolidColorBrush(Color.FromRgb(0, 0, 0)); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml new file mode 100644 index 000000000..78a0e2118 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml.cs new file mode 100644 index 000000000..f85eca5c2 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace Artemis.Profiles.Layers.Types.AmbientLight +{ + public partial class AmbientLightPropertiesView : UserControl + { + public AmbientLightPropertiesView() + { + InitializeComponent(); + } + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs new file mode 100644 index 000000000..4926ff2c4 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs @@ -0,0 +1,23 @@ +using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels.Profiles; + +namespace Artemis.Profiles.Layers.Types.AmbientLight +{ + public class AmbientLightPropertiesViewModel : LayerPropertiesViewModel + { + #region Constructors + + public AmbientLightPropertiesViewModel(LayerEditorViewModel editorVm) + : base(editorVm) + { } + + #endregion + + #region Methods + + public override void ApplyProperties() + { } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs new file mode 100644 index 000000000..6e75cd928 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs @@ -0,0 +1,110 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Artemis.Models.Interfaces; +using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Interfaces; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.AmbientLight.AmbienceCreator; +using Artemis.Profiles.Layers.Types.AmbientLight.Model; +using Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions; +using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing; +using Artemis.Properties; +using Artemis.Utilities; +using Artemis.ViewModels.Profiles; +using Newtonsoft.Json; + +namespace Artemis.Profiles.Layers.Types.AmbientLight +{ + public class AmbientLightType : ILayerType + { + #region Properties & Fields + + public string Name => "Keyboard - Ambient Light"; + public bool ShowInEdtor => true; + public DrawType DrawType => DrawType.Keyboard; + + [JsonIgnore] + private AmbienceCreatorType? _lastAmbienceCreatorType = null; + [JsonIgnore] + private IAmbienceCreator _lastAmbienceCreator; + + [JsonIgnore] + private byte[] _lastData; + + #endregion + + #region Methods + + public LayerPropertiesViewModel SetupViewModel(LayerEditorViewModel layerEditorViewModel, + LayerPropertiesViewModel layerPropertiesViewModel) + { + if (layerPropertiesViewModel is AmbientLightPropertiesViewModel) + return layerPropertiesViewModel; + return new AmbientLightPropertiesViewModel(layerEditorViewModel); + } + + public void SetupProperties(LayerModel layerModel) + { + if (layerModel.Properties is AmbientLightPropertiesModel) + return; + + layerModel.Properties = new AmbientLightPropertiesModel(layerModel.Properties); + } + + public void Update(LayerModel layerModel, IDataModel dataModel, bool isPreview = false) + { + AmbientLightPropertiesModel properties = layerModel?.Properties as AmbientLightPropertiesModel; + if (properties == null) return; + + int width = (int)Math.Round(properties.Width); + int height = (int)Math.Round(properties.Height); + + byte[] data = ScreenCaptureManager.GetLastScreenCapture(); + byte[] newData = GetAmbienceCreator(properties).GetAmbience(data, ScreenCaptureManager.LastCaptureWidth, ScreenCaptureManager.LastCaptureHeight, width, height, properties); + + _lastData = _lastData?.Blend(newData, properties.SmoothMode) ?? newData; + int stride = (width * ScreenCaptureManager.LastCapturePixelFormat.BitsPerPixel + 7) / 8; + properties.AmbientLightBrush = new DrawingBrush(new ImageDrawing + (BitmapSource.Create(width, height, 96, 96, ScreenCaptureManager.LastCapturePixelFormat, null, _lastData, stride), new Rect(0, 0, width, height))); + } + + public void Draw(LayerModel layer, DrawingContext c) + { + Rect rect = new Rect(layer.Properties.X * 4, + layer.Properties.Y * 4, + layer.Properties.Width * 4, + layer.Properties.Height * 4); + + c.DrawRectangle(((AmbientLightPropertiesModel)layer.Properties).AmbientLightBrush, null, rect); + } + + public ImageSource DrawThumbnail(LayerModel layer) + { + Rect thumbnailRect = new Rect(0, 0, 18, 18); + DrawingVisual visual = new DrawingVisual(); + using (DrawingContext c = visual.RenderOpen()) + c.DrawImage(ImageUtilities.BitmapToBitmapImage(Resources.ambilight), thumbnailRect); + + return new DrawingImage(visual.Drawing); + } + + private IAmbienceCreator GetAmbienceCreator(AmbientLightPropertiesModel properties) + { + if (_lastAmbienceCreatorType == properties.AmbienceCreatorType) + return _lastAmbienceCreator; + + _lastAmbienceCreatorType = properties.AmbienceCreatorType; + switch (properties.AmbienceCreatorType) + { + case AmbienceCreatorType.Mirror: return _lastAmbienceCreator = new AmbienceCreatorMirror(); + case AmbienceCreatorType.Extend: return _lastAmbienceCreator = new AmbienceCreatorExtend(); + default: throw new InvalidEnumArgumentException(); + } + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Helper/CheckboxEnumFlagHelper.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Helper/CheckboxEnumFlagHelper.cs new file mode 100644 index 000000000..6ba6a423c --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Helper/CheckboxEnumFlagHelper.cs @@ -0,0 +1,93 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Helper +{ + public class CheckboxEnumFlagHelper + { + #region DependencyProperties + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty FlagsProperty = DependencyProperty.RegisterAttached( + "Flags", typeof(Enum), typeof(CheckboxEnumFlagHelper), new FrameworkPropertyMetadata(default(Enum), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, FlagsChanged)); + + public static void SetFlags(DependencyObject element, Enum value) + { + element.SetValue(FlagsProperty, value); + } + + public static Enum GetFlags(DependencyObject element) + { + return (Enum)element.GetValue(FlagsProperty); + } + + public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached( + "Value", typeof(Enum), typeof(CheckboxEnumFlagHelper), new PropertyMetadata(default(Enum), ValueChanged)); + + public static void SetValue(DependencyObject element, Enum value) + { + element.SetValue(ValueProperty, value); + } + + public static Enum GetValue(DependencyObject element) + { + return (Enum)element.GetValue(ValueProperty); + } + + // ReSharper restore InconsistentNaming + #endregion + + #region Methods + + private static void FlagsChanged(DependencyObject dependencyObject, + DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + UpdateTarget(dependencyObject as CheckBox, dependencyPropertyChangedEventArgs.NewValue as Enum); + } + + private static void ValueChanged(DependencyObject dependencyObject, + DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + CheckBox checkbox = dependencyObject as CheckBox; + if (checkbox == null) return; + + checkbox.Checked -= UpdateSource; + checkbox.Unchecked -= UpdateSource; + + if (dependencyPropertyChangedEventArgs.NewValue != null) + { + checkbox.Checked += UpdateSource; + checkbox.Unchecked += UpdateSource; + } + + UpdateTarget(checkbox, GetFlags(checkbox)); + } + + private static void UpdateTarget(CheckBox checkbox, Enum flags) + { + if (checkbox == null) return; + + Enum value = GetValue(checkbox); + checkbox.IsChecked = value != null && (flags?.HasFlag(value) ?? false); + } + + private static void UpdateSource(object sender, RoutedEventArgs routedEventArgs) + { + CheckBox checkbox = sender as CheckBox; + if (checkbox == null) return; + + Enum flags = GetFlags(checkbox); + Enum value = GetValue(checkbox); + if (value == null) return; + + if (checkbox.IsChecked ?? false) + SetFlags(checkbox, flags == null ? value : flags.SetFlag(value, true, flags.GetType())); + else + SetFlags(checkbox, flags?.SetFlag(value, false, flags.GetType())); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/AmbienceCreatorType.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/AmbienceCreatorType.cs new file mode 100644 index 000000000..8f7f389e4 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/AmbienceCreatorType.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model +{ + public enum AmbienceCreatorType + { + [Description("Mirror")] + Mirror, + + [Description("Extend")] + Extend + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/BlackBarDetectionMode.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/BlackBarDetectionMode.cs new file mode 100644 index 000000000..31a3cfbfc --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/BlackBarDetectionMode.cs @@ -0,0 +1,14 @@ +using System; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model +{ + [Flags] + public enum BlackBarDetectionMode + { + None = 0, + Left = 1 << 0, + Right = 1 << 1, + Top = 1 << 2, + Bottom = 1 << 3, + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/AvgColorExtension.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/AvgColorExtension.cs new file mode 100644 index 000000000..449072211 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/AvgColorExtension.cs @@ -0,0 +1,74 @@ +using System; +using System.Linq; +using Artemis.Profiles.Layers.Types.AmbientLight.AmbienceCreator; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions +{ + public static class AvgColorExtension + { + #region Methods + + public static AvgColor[] Flip(this AvgColor[] colors, int width, FlipMode flipMode) + { + if (colors == null || width <= 0) return colors; + + if (flipMode.HasFlag(FlipMode.Vertical)) + return flipMode.HasFlag(FlipMode.Horizontal) ? colors.Reverse().ToArray() : colors.FlipVertical(width); + + if (flipMode.HasFlag(FlipMode.Horizontal)) + return colors.FlipHorizontal(width); + + return colors; + } + + public static AvgColor[] ExtendHeight(this AvgColor[] colors, int height) + { + AvgColor[] extended = new AvgColor[colors.Length * height]; + + for (int i = 0; i < height; i++) + Array.Copy(colors, 0, extended, i * colors.Length, colors.Length); + + return extended; + } + + public static AvgColor[] FlipVertical(this AvgColor[] colors, int width) + { + if (colors == null || width <= 0) return colors; + + AvgColor[] flipped = new AvgColor[colors.Length]; + for (int i = 0, j = colors.Length - width; i < colors.Length; i += width, j -= width) + for (int k = 0; k < width; ++k) + flipped[i + k] = colors[j + k]; + + return flipped; + } + + public static AvgColor[] FlipHorizontal(this AvgColor[] colors, int width) + { + if (colors == null || width <= 0) return colors; + + AvgColor[] flipped = new AvgColor[colors.Length]; + for (int i = 0; i < colors.Length; i += width) + for (int j = 0, k = width - 1; j < width; ++j, --k) + flipped[i + j] = colors[i + k]; + + return flipped; + } + + public static byte[] ToBGRArray(this AvgColor[] colors) + { + byte[] newData = new byte[colors.Length * 3]; + int counter = 0; + foreach (AvgColor color in colors) + { + newData[counter++] = color.B; + newData[counter++] = color.G; + newData[counter++] = color.R; + } + + return newData; + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/EnumExtension.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/EnumExtension.cs new file mode 100644 index 000000000..65f1ea6f9 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/EnumExtension.cs @@ -0,0 +1,26 @@ +using System; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions +{ + public static class EnumExtension + { + #region Methods + + public static Enum SetFlag(this Enum e, Enum value, bool set, Type t) + { + if (e == null || value == null || t == null) return e; + + int eValue = Convert.ToInt32(e); + int valueValue = Convert.ToInt32(value); + + if (set) + eValue |= valueValue; + else + eValue &= ~valueValue; + + return (Enum)Enum.ToObject(t, eValue); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/PixelDataExtension.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/PixelDataExtension.cs new file mode 100644 index 000000000..5fa442669 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/Extensions/PixelDataExtension.cs @@ -0,0 +1,111 @@ +using System; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions +{ + public static class PixelDataExtension + { + #region Methods + + public static int DetectBlackBarLeft(this byte[] pixels, int width, int height, int offsetLeft, int offsetRight, int offsetTop, int offsetBottom) + { + int bottomBorder = height - offsetBottom; + int rightBorder = width - offsetRight; + + int blackBarWidth = 0; + for (int x = rightBorder - 1; x >= offsetLeft; x--) + { + for (int y = offsetTop; y < bottomBorder; y++) + { + int offset = ((y * width) + x) * 4; + if (pixels[offset] > 15 || pixels[offset + 1] > 15 || pixels[offset + 2] > 15) + return blackBarWidth; + } + blackBarWidth++; + } + + return width; + } + + public static int DetectBlackBarRight(this byte[] pixels, int width, int height, int offsetLeft, int offsetRight, int offsetTop, int offsetBottom) + { + int bottomBorder = height - offsetBottom; + int rightBorder = width - offsetRight; + + int blackBarWidth = 0; + for (int x = offsetLeft; x < rightBorder; x++) + { + for (int y = offsetTop; y < bottomBorder; y++) + { + int offset = ((y * width) + x) * 4; + if (pixels[offset] > 15 || pixels[offset + 1] > 15 || pixels[offset + 2] > 15) + return blackBarWidth; + } + blackBarWidth++; + } + + return width; + } + + public static int DetectBlackBarTop(this byte[] pixels, int width, int height, int offsetLeft, int offsetRight, int offsetTop, int offsetBottom) + { + int bottomBorder = height - offsetBottom; + int rightBorder = width - offsetRight; + + int blackBarHeight = 0; + for (int y = offsetTop; y < bottomBorder; y++) + { + for (int x = offsetLeft; x < rightBorder; x++) + { + int offset = ((y * width) + x) * 4; + if (pixels[offset] > 15 || pixels[offset + 1] > 15 || pixels[offset + 2] > 15) + return blackBarHeight; + } + blackBarHeight++; + } + + return height; + } + + public static int DetectBlackBarBottom(this byte[] pixels, int width, int height, int offsetLeft, int offsetRight, int offsetTop, int offsetBottom) + { + int bottomBorder = height - offsetBottom; + int rightBorder = width - offsetRight; + + int blackBarHeight = 0; + for (int y = bottomBorder - 1; y >= offsetTop; y--) + { + for (int x = offsetLeft; x < rightBorder; x++) + { + int offset = ((y * width) + x) * 4; + if (pixels[offset] > 15 || pixels[offset + 1] > 15 || pixels[offset + 2] > 15) + return blackBarHeight; + } + blackBarHeight++; + } + + return height; + } + + public static byte[] Blend(this byte[] pixels, byte[] blendPixels, SmoothMode smoothMode) + { + if (smoothMode == SmoothMode.None || pixels.Length != blendPixels.Length) return blendPixels; + + double percentage = smoothMode == SmoothMode.Low? 0.25: (smoothMode == SmoothMode.Medium ? 0.075 : 0.025 /*high*/); + + byte[] blended = new byte[pixels.Length]; + + for (int i = 0; i < blended.Length; i++) + blended[i] = GetIntColor((blendPixels[i] / 255.0) * percentage + (pixels[i] / 255.0) * (1 - percentage)); + + return blended; + } + + private static byte GetIntColor(double d) + { + double calcF = Math.Max(0, Math.Min(1, d)); + return (byte)(calcF.Equals(1) ? 255 : calcF * 256); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/FlipMode.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/FlipMode.cs new file mode 100644 index 000000000..540617a5e --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/FlipMode.cs @@ -0,0 +1,12 @@ +using System; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model +{ + [Flags] + public enum FlipMode + { + None = 0, + Vertical = 1 << 0, + Horizontal = 1 << 1 + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/ScreenCaptureMode.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/ScreenCaptureMode.cs new file mode 100644 index 000000000..23213b13e --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/ScreenCaptureMode.cs @@ -0,0 +1,10 @@ +using System.ComponentModel; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model +{ + public enum ScreenCaptureMode + { + [Description("DirectX 9")] + DirectX9 + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/SmoothMode.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/SmoothMode.cs new file mode 100644 index 000000000..19c2bd3fd --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/Model/SmoothMode.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.Model +{ + public enum SmoothMode + { + [Description("None")] + None, + + [Description("Low")] + Low, + + [Description("Medium")] + Medium, + + [Description("High")] + High + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/DX9ScreenCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/DX9ScreenCapture.cs new file mode 100644 index 000000000..5833fda07 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/DX9ScreenCapture.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using System.Windows.Media; +using SharpDX; +using SharpDX.Direct3D9; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing +{ + public class DX9ScreenCapture : IScreenCapture + { + #region Properties & Fields + + private Device _device; + private Surface _surface; + private byte[] _buffer; + + public int Width { get; } + public int Height { get; } + public PixelFormat PixelFormat => PixelFormats.Bgr24; + + #endregion + + #region Constructors + + public DX9ScreenCapture() + { + Width = Screen.PrimaryScreen.Bounds.Width; + Height = Screen.PrimaryScreen.Bounds.Height; + + PresentParameters presentParams = new PresentParameters(Width, Height) + { + Windowed = true, + SwapEffect = SwapEffect.Discard + }; + + _device = new Device(new Direct3D(), 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, presentParams); + _surface = Surface.CreateOffscreenPlain(_device, Width, Height, Format.A8R8G8B8, Pool.Scratch); + _buffer = new byte[Width * Height * 4]; + } + + #endregion + + #region Methods + + public byte[] CaptureScreen() + { + _device.GetFrontBufferData(0, _surface); + + DataRectangle dr = _surface.LockRectangle(LockFlags.None); + Marshal.Copy(dr.DataPointer, _buffer, 0, _buffer.Length); + _surface.UnlockRectangle(); + + return _buffer; + } + + public void Dispose() + { + _device?.Dispose(); + _surface?.Dispose(); + } + + #endregion + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/IScreenCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/IScreenCapture.cs new file mode 100644 index 000000000..343d1c2ba --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/IScreenCapture.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows.Media; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing +{ + public interface IScreenCapture : IDisposable + { + int Width { get; } + int Height { get; } + PixelFormat PixelFormat { get; } + + /// + /// As Pixel-Data BGRA + /// + /// The Pixel-Data + byte[] CaptureScreen(); + } +} diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/ScreenCaptureManager.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/ScreenCaptureManager.cs new file mode 100644 index 000000000..92fe05ef5 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/ScreenCaptureManager.cs @@ -0,0 +1,116 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; +using System.Windows.Media; +using Artemis.Profiles.Layers.Types.AmbientLight.Model; + +namespace Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing +{ + public static class ScreenCaptureManager + { + #region Properties & Fields + + private static Thread _worker; + private static DateTime _lastCaptureAccess; + private static volatile byte[] _lastScreenCapture; + private static volatile bool _isRunning = false; + + private static ScreenCaptureMode? _lastScreenCaptureMode = null; + private static IScreenCapture _screenCapture; + + public static double StandByTime { get; set; } = 3; + public static double UpdateRate { get; set; } = 1.0 / 20.0; // DarthAffe 29.10.2016: I think 20 FPS should be enough as default + public static ScreenCaptureMode ScreenCaptureMode { get; set; } = ScreenCaptureMode.DirectX9; + + public static int LastCaptureWidth { get; private set; } + public static int LastCaptureHeight { get; private set; } + public static PixelFormat LastCapturePixelFormat { get; private set; } + + #endregion + + #region Methods + + private static IScreenCapture GetScreenCapture() + { + if (_lastScreenCaptureMode == ScreenCaptureMode && _screenCapture != null) + return _screenCapture; + + DisposeScreenCapture(); + + _lastScreenCaptureMode = ScreenCaptureMode; + switch (ScreenCaptureMode) + { + case ScreenCaptureMode.DirectX9: return _screenCapture = new DX9ScreenCapture(); + default: throw new InvalidEnumArgumentException(); + } + } + + private static void DisposeScreenCapture() + { + _screenCapture?.Dispose(); + _screenCapture = null; + _lastScreenCapture = null; + } + + private static void Update() + { + try + { + while ((DateTime.Now - _lastCaptureAccess).TotalSeconds < StandByTime) + { + DateTime lastCapture = DateTime.Now; + try + { + CaptureScreen(); + } + catch (Exception ex) + { + Debug.WriteLine("[CaptureLoop]: " + ex.Message); + } + + int sleep = (int)((UpdateRate - (DateTime.Now - lastCapture).TotalSeconds) * 1000); + if (sleep > 0) + Thread.Sleep(sleep); + } + } + finally + { + DisposeScreenCapture(); + _isRunning = false; + } + } + + private static void CaptureScreen() + { + IScreenCapture screenCapture = GetScreenCapture(); + + _lastScreenCapture = screenCapture.CaptureScreen(); + LastCaptureWidth = screenCapture.Width; + LastCaptureHeight = screenCapture.Height; + LastCapturePixelFormat = screenCapture.PixelFormat; + } + + private static void StartLoop() + { + if (_isRunning) return; + + // DarthAffe 31.10.2016: _lastScreenCapture should be always initialized! + CaptureScreen(); + + _isRunning = true; + _worker = new Thread(Update); + _worker.Start(); + } + + public static byte[] GetLastScreenCapture() + { + _lastCaptureAccess = DateTime.Now; + if (!_isRunning) + StartLoop(); + return _lastScreenCapture; + } + + #endregion + } +} diff --git a/Artemis/Artemis/Properties/Resources.Designer.cs b/Artemis/Artemis/Properties/Resources.Designer.cs index f9f421337..207ddd094 100644 --- a/Artemis/Artemis/Properties/Resources.Designer.cs +++ b/Artemis/Artemis/Properties/Resources.Designer.cs @@ -60,6 +60,16 @@ namespace Artemis.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ambilight { + get { + object obj = ResourceManager.GetObject("ambilight", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Artemis/Artemis/Properties/Resources.resx b/Artemis/Artemis/Properties/Resources.resx index 537017134..44eae2ca5 100644 --- a/Artemis/Artemis/Properties/Resources.resx +++ b/Artemis/Artemis/Properties/Resources.resx @@ -211,4 +211,7 @@ ..\Resources\lua-placeholder.lua;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\ambilight.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/Artemis/Artemis/Resources/ambilight.png b/Artemis/Artemis/Resources/ambilight.png new file mode 100644 index 000000000..7525f6acd Binary files /dev/null and b/Artemis/Artemis/Resources/ambilight.png differ diff --git a/Artemis/Artemis/Settings/GeneralSettings.cs b/Artemis/Artemis/Settings/GeneralSettings.cs index 189bb025f..b8cf81ee0 100644 --- a/Artemis/Artemis/Settings/GeneralSettings.cs +++ b/Artemis/Artemis/Settings/GeneralSettings.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.IO; using System.Windows; using Artemis.DAL; +using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing; using Artemis.Utilities; using Caliburn.Micro; using MahApps.Metro; @@ -59,6 +60,10 @@ namespace Artemis.Settings [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public string Theme { get; set; } + [DefaultValue(20)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int ScreenCaptureFPS { get; set; } + [DefaultValue("Info")] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] public string LogLevel { get; set; } @@ -71,6 +76,7 @@ namespace Artemis.Settings ApplyAutorun(); ApplyTheme(); ApplyGamestatePort(); + ApplyScreenCaptureFPS(); Logging.SetupLogging(LogLevel); } @@ -109,7 +115,7 @@ namespace Artemis.Settings { // Ignored, only happens when running from VS } - + } } @@ -138,5 +144,10 @@ namespace Artemis.Settings } }); } + + public void ApplyScreenCaptureFPS() + { + ScreenCaptureManager.UpdateRate = 1.0 / ScreenCaptureFPS; + } } } \ No newline at end of file diff --git a/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml b/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml index b61361692..8b50fe0dc 100644 --- a/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml +++ b/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml @@ -6,8 +6,7 @@ xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls" xmlns:cal="http://www.caliburnproject.org" mc:Ignorable="d" - d:DesignHeight="600" d:DesignWidth="300" - Width="270"> + d:DesignHeight="600" d:DesignWidth="300"> @@ -25,6 +24,7 @@ + @@ -89,27 +89,33 @@ Style="{StaticResource MahApps.Metro.Styles.ToggleSwitchButton.Win10}" HorizontalAlignment="Right" /> + +