From fc723d8e3837a51b8b713b0603e33e104f08f1b9 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 30 Oct 2016 23:03:43 +0100 Subject: [PATCH] Created first working version of an ambilight (WIP) --- Artemis/Artemis/Artemis.csproj | 24 +++++ .../InjectionModules/ProfileModules.cs | 3 + .../AmbienceCreator/AmbienceCreatorMirror.cs | 101 ++++++++++++++++++ .../AmbientLight/AmbienceCreator/AvgColor.cs | 42 ++++++++ .../AmbienceCreator/IAmbienceCreator.cs | 7 ++ .../AmbientLightPropertiesModel.cs | 27 +++++ .../AmbientLightPropertiesView.xaml | 11 ++ .../AmbientLightPropertiesView.xaml.cs | 12 +++ .../AmbientLightPropertiesViewModel.cs | 27 +++++ .../Types/AmbientLight/AmbientLightType.cs | 88 +++++++++++++++ .../ScreenCapturing/DX9ScreenCapture.cs | 64 +++++++++++ .../ScreenCapturing/IScreenCapture.cs | 18 ++++ .../ScreenCapturing/ScreenCaptureManager.cs | 93 ++++++++++++++++ Artemis/Artemis/packages.config | 2 + 14 files changed, 519 insertions(+) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AmbienceCreatorMirror.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/AvgColor.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbienceCreator/IAmbienceCreator.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesModel.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesView.xaml.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/DX9ScreenCapture.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/IScreenCapture.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/AmbientLight/ScreenCapturing/ScreenCaptureManager.cs 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 @@ + +