diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj
index 6654080aa..fba35fc88 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,18 @@
+
+
+
+
+
+ AmbientLightPropertiesView.xaml
+
+
+
+
+
+
AudioPropertiesView.xaml
@@ -746,6 +766,10 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
MSBuild:Compile
Designer
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/AmbienceCreatorMirror.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorMirror.cs
new file mode 100644
index 000000000..93a447847
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorMirror.cs
@@ -0,0 +1,101 @@
+using System;
+
+namespace Artemis.Profiles.Layers.Types.AmbientLight.AmbienceCreator
+{
+ public class AmbienceCreatorMirror : IAmbienceCreator
+ {
+ #region Properties & Fields
+
+ #endregion
+
+ #region Constructors
+
+ #endregion
+
+ #region Methods
+
+ public byte[] GetAmbience(byte[] data, int sourceWidth, int sourceHeight, int targetWidth, int targetHeight)
+ {
+ int heightPixelCount = (int)Math.Round(sourceHeight * 0.1);
+ int sourceHeightOffset = sourceHeight - heightPixelCount;
+
+ AvgColor[] avgData = new AvgColor[targetWidth * targetHeight];
+ double widthPixels = (sourceWidth / (double)targetWidth);
+ double heightPixels = (heightPixelCount / (double)targetHeight);
+ int targetHeightIndex = 0;
+ double heightCounter = heightPixels;
+
+ for (int y = 0; y < heightPixelCount; y += 2)
+ {
+ if (y >= heightCounter)
+ {
+ heightCounter += heightPixels;
+ targetHeightIndex++;
+ }
+
+ int targetWidthIndex = 0;
+ double widthCounter = widthPixels;
+
+ for (int x = 0; x < sourceWidth; x += 2)
+ {
+ if (x >= widthCounter)
+ {
+ widthCounter += widthPixels;
+ targetWidthIndex++;
+ }
+
+ int newOffset = (targetHeightIndex * targetWidth) + targetWidthIndex;
+ int offset = ((((sourceHeightOffset + y) * sourceWidth) + x) * 4);
+
+ if (avgData[newOffset] == null)
+ avgData[newOffset] = new AvgColor();
+
+ AvgColor avgDataObject = avgData[newOffset];
+
+ avgDataObject.AddB(data[offset]);
+ avgDataObject.AddG(data[offset + 1]);
+ avgDataObject.AddR(data[offset + 2]);
+ }
+ }
+
+ avgData = FlipVertical(avgData, targetWidth);
+ return ToByteArray(avgData, targetWidth, targetHeight);
+ }
+
+ private byte[] ToByteArray(AvgColor[] colors, int width, int height)
+ {
+ byte[] newData = new byte[width * height * 3];
+ int counter = 0;
+ foreach (AvgColor color in colors)
+ {
+ newData[counter++] = color.B;
+ newData[counter++] = color.G;
+ newData[counter++] = color.R;
+ }
+
+ return newData;
+ }
+
+ private T[] FlipVertical(T[] data, int width)
+ {
+ T[] flipped = new T[data.Length];
+ for (int i = 0, j = data.Length - width; i < data.Length; i += width, j -= width)
+ for (int k = 0; k < width; ++k)
+ flipped[i + k] = data[j + k];
+
+ return flipped;
+ }
+
+ private T[] FlipHorizontal(T[] data, int width)
+ {
+ T[] flipped = new T[data.Length];
+ for (int i = 0; i < data.Length; i += width)
+ for (int j = 0, k = width - 1; j < width; ++j, --k)
+ flipped[i + j] = data[i + k];
+
+ return flipped;
+ }
+
+ #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..d7d85a162
--- /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)(_r / _rCount);
+ public byte G => (byte)(_g / _gCount);
+ public byte B => (byte)(_b / _bCount);
+
+ #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..d36d6c8ae
--- /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[] data, int sourceWidth, int sourceHeight, int targetWidth, int targetHeight);
+ }
+}
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..b561b0e1f
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesModel.cs
@@ -0,0 +1,27 @@
+using System.Windows.Media;
+using Artemis.Profiles.Layers.Models;
+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; }
+
+ #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..31cf9ccf7
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
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..10972a327
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs
@@ -0,0 +1,27 @@
+using Artemis.Profiles.Layers.Abstract;
+using Artemis.ViewModels.Profiles;
+
+namespace Artemis.Profiles.Layers.Types.AmbientLight
+{
+ public class AmbientLightPropertiesViewModel : LayerPropertiesViewModel
+ {
+ #region Properties & Fields
+
+ #endregion
+
+ #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..c96dca32d
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs
@@ -0,0 +1,88 @@
+using System;
+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.ScreenCapturing;
+using Artemis.ViewModels.Profiles;
+
+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;
+
+ private IAmbienceCreator _lastAmbienceCreator;
+ 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)
+ {
+ int width = (int)Math.Round(layerModel.Properties.Width);
+ int height = (int)Math.Round(layerModel.Properties.Height);
+
+ byte[] data = ScreenCaptureManager.GetLastScreenCapture();
+ _lastData = GetAmbienceCreator().GetAmbience(data, ScreenCaptureManager.LastCaptureWidth, ScreenCaptureManager.LastCaptureHeight, width, height);
+
+ int stride = (width * ScreenCaptureManager.LastCapturePixelFormat.BitsPerPixel + 7) / 8;
+ ((AmbientLightPropertiesModel)layerModel.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)
+ {
+ //TODO DarthAffe 30.10.2016: Add a real thumbnail
+ Rect thumbnailRect = new Rect(0, 0, 18, 18);
+ DrawingVisual visual = new DrawingVisual();
+ using (DrawingContext c = visual.RenderOpen())
+ c.DrawRectangle(new SolidColorBrush(Colors.Magenta), new Pen(new SolidColorBrush(Colors.DarkMagenta), 2), thumbnailRect);
+
+ return new DrawingImage(visual.Drawing);
+ }
+
+ private IAmbienceCreator GetAmbienceCreator()
+ {
+ //TODO DarthAffe 30.10.2016: Create from settings
+ return _lastAmbienceCreator ?? (_lastAmbienceCreator = new AmbienceCreatorMirror());
+ }
+
+ #endregion
+ }
+}
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..103ecf41a
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/DX9ScreenCapture.cs
@@ -0,0 +1,64 @@
+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;
+
+ 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);
+ }
+
+ #endregion
+
+ #region Methods
+
+ public byte[] CaptureScreen()
+ {
+ using (Surface s = Surface.CreateOffscreenPlain(_device, Width, Height, Format.A8R8G8B8, Pool.Scratch))
+ {
+ _device.GetFrontBufferData(0, s);
+ DataRectangle dr = s.LockRectangle(LockFlags.None);
+
+ byte[] buffer = new byte[Width * Height * 4];
+ Marshal.Copy(dr.DataPointer, buffer, 0, buffer.Length);
+
+ s.UnlockRectangle();
+ return buffer;
+ }
+ }
+
+ public void Dispose()
+ {
+ _device?.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..de63faf41
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/ScreenCaptureManager.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Windows.Media;
+
+namespace Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing
+{
+ public static class ScreenCaptureManager
+ {
+ #region Properties & Fields
+
+ // ReSharper disable once InconsistentNaming
+ private static readonly IScreenCapture _screenCapture;
+
+ private static Thread _worker;
+ private static DateTime _lastCaptureAccess;
+ private static volatile byte[] _lastScreenCapture;
+ private static volatile bool _isRunning = false;
+
+ public static double StandByTime { get; set; } = 3;
+ public static double UpdateRate { get; set; } = 1f / 20f; // DarthAffe 29.10.2016: I think 20 FPS should be enough as default
+
+ public static int LastCaptureWidth { get; private set; }
+ public static int LastCaptureHeight { get; private set; }
+ public static PixelFormat LastCapturePixelFormat { get; private set; }
+
+ #endregion
+
+ #region Constructors
+
+ static ScreenCaptureManager()
+ {
+ _screenCapture = new DX9ScreenCapture();
+ }
+
+ #endregion
+
+ #region Methods
+
+ private static void Update()
+ {
+ 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);
+ }
+
+ _isRunning = false;
+ }
+
+ private static void CaptureScreen()
+ {
+ _lastScreenCapture = _screenCapture.CaptureScreen();
+ LastCaptureWidth = _screenCapture.Width;
+ LastCaptureHeight = _screenCapture.Height;
+ LastCapturePixelFormat = _screenCapture.PixelFormat;
+ }
+
+ private static void StartLoop()
+ {
+ if (_isRunning) return;
+
+ _isRunning = true;
+ _worker = new Thread(Update);
+ _worker.Start();
+ }
+
+ public static byte[] GetLastScreenCapture()
+ {
+ _lastCaptureAccess = DateTime.Now;
+ if (!_isRunning)
+ {
+ // DarthAffe 29.10.2016: Make sure, that _lastScreenCapture is newer returned without data.
+ CaptureScreen();
+ StartLoop();
+ }
+ return _lastScreenCapture;
+ }
+
+ #endregion
+ }
+}
diff --git a/Artemis/Artemis/packages.config b/Artemis/Artemis/packages.config
index 4792a14cc..3df870d76 100644
--- a/Artemis/Artemis/packages.config
+++ b/Artemis/Artemis/packages.config
@@ -24,6 +24,8 @@
+
+