1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-12 17:48:31 +00:00

Merge pull request #287 from DarthAffe/Development

v1.0
This commit is contained in:
DarthAffe 2023-01-06 23:30:44 +01:00 committed by GitHub
commit be3f2bfa29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
541 changed files with 39776 additions and 29172 deletions

61
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: RGB.NET-CI
on:
push:
branches: [ Development ]
paths:
- '**.cs'
- '**.csproj'
- '**.yml'
jobs:
build:
runs-on: windows-2022
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 7.0.x
- name: Git Semantic Version
id: versioning
uses: PaulHatch/semantic-version@v4.0.3
with:
short_tags: false
format: "${major}.${minor}.${patch}-prerelease.${increment}"
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release /p:Version=${{ steps.versioning.outputs.version }}
- name: Test
run: dotnet test --no-build --verbosity normal --configuration Release
- name: Upload a Build Artifact NET5
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET5
path: bin/net5.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET6
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET6
path: bin/net6.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET7
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET7
path: bin/net7.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload Nuget Build Artifact
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-Nugets
path: bin/*nupkg
if-no-files-found: error
- name: Nuget Push
run: dotnet nuget push **\*.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json

24
.github/workflows/pr_verify.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: PR-Verify
on:
pull_request:
branches: [ master, Development ]
jobs:
build:
runs-on: windows-2022
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 7.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release /p:Version=0.0.0
- name: Test
run: dotnet test --no-build --verbosity normal --configuration Release

66
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: RGB.NET-Release
on:
push:
branches: [ master ]
paths:
- '**.cs'
- '**.csproj'
- '**.yml'
jobs:
build:
runs-on: windows-2022
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 7.0.x
- name: Git Semantic Version
id: versioning
uses: PaulHatch/semantic-version@v4.0.3
with:
short_tags: false
format: "${major}.${minor}.${patch}"
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release /p:Version=${{ steps.versioning.outputs.version }}
- name: Test
run: dotnet test --no-build --verbosity normal --configuration Release
- name: Upload a Build Artifact NET5
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET5
path: bin/net5.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET6
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET6
path: bin/net6.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET7
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET7
path: bin/net7.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload Nuget Build Artifact
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-Nugets
path: bin/*nupkg
if-no-files-found: error
- name: Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.versioning.outputs.version_tag }}
generate_release_notes: true
files: bin/net7.0/RGB.NET.*.dll
- name: Nuget Push
run: dotnet nuget push **\*.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json

View File

@ -7,17 +7,22 @@
<xsd:element name="Description" type="xsd:string" />
<xsd:element name="Author" type="xsd:string" />
<xsd:element name="Type" type="xsd:string" />
<xsd:element name="Lighting" type="xsd:string" />
<xsd:element name="Vendor" type="xsd:string" />
<xsd:element name="Model" type="xsd:string" />
<xsd:element name="Shape" type="xsd:string" />
<xsd:element name="Width" type="xsd:double" />
<xsd:element name="Height" type="xsd:double" />
<xsd:element name="ImageBasePath" type="xsd:string" />
<xsd:element name="DeviceImage" type="xsd:string" />
<xsd:element name="LedUnitWidth" type="xsd:double" />
<xsd:element name="LedUnitHeight" type="xsd:double" />
<xsd:element name="Leds">
<xsd:element name="CustomData">
<xsd:complexType>
<xsd:sequence>
<xsd:any />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="Leds">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="unbounded" name="Led">
@ -28,6 +33,13 @@
<xsd:element name="Y" type="xsd:string" />
<xsd:element name="Width" type="xsd:string" />
<xsd:element name="Height" type="xsd:string" />
<xsd:element name="CustomData">
<xsd:complexType>
<xsd:sequence>
<xsd:any />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="Id" type="xsd:string" use="required" />
</xsd:complexType>
@ -35,32 +47,7 @@
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="LedImageLayouts">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="unbounded" name="LedImageLayout">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="LedImages">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="unbounded" name="LedImage">
<xsd:complexType>
<xsd:attribute name="Id" type="xsd:string" use="required" />
<xsd:attribute name="Image" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="Layout" type="xsd:string" />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xs:schema>

View File

@ -1,30 +1,87 @@
# RGB.NET
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/DarthAffe/RGB.NET?style=for-the-badge)](https://github.com/DarthAffe/RGB.NET/releases)
[![Nuget](https://img.shields.io/nuget/v/RGB.NET.Core?style=for-the-badge)](https://www.nuget.org/packages?q=rgb.net)
[![GitHub](https://img.shields.io/github/license/DarthAffe/RGB.NET?style=for-the-badge)](https://github.com/DarthAffe/RGB.NET/blob/master/LICENSE)
[![GitHub Repo stars](https://img.shields.io/github/stars/DarthAffe/RGB.NET?style=for-the-badge)](https://github.com/DarthAffe/RGB.NET/stargazers)
[![Discord](https://img.shields.io/discord/366163308941934592?logo=discord&logoColor=white&style=for-the-badge)](https://discord.gg/9kytURv)
This project aims to unify the use of various RGB-devices.
**It is currently under heavy development and will have breaking changes in the future!** Right now a lot of devices aren't working as expected and there are bugs/unfinished features. Please think about that when you consider using the library in this early stage.
If you want to help with layouting/testing devices or if you need support using the library feel free to join the [RGB.NET discord-channel](https://discord.gg/9kytURv).
> **IMPORTANT NOTE**
This is a library to integrate RGB-devices into your own application. It does not contain any executables!
If you're looking for a full blown software solution to manage your RGB-devices, take a look at [Artemis](https://artemis-rgb.com/).
## Getting Started
### Setup
1. Add the [RGB.NET.Core](https://www.nuget.org/packages/RGB.NET.Core) and [Devices](https://www.nuget.org/packages?q=rgb.net.Devices)-Nugets for all devices you want to use.
2. For some of the vendors SDK-libraries are needed. Check the contained Readmes for more information in that case.
3. Create a new `RGBSurface`.
```csharp
RGBSurface surface = new RGBSurface();
```
## Adding prerelease packages using NuGet ##
This is the easiest and therefore preferred way to include RGB.NET in your project.
4. Initialize the providers for all devices you want to use and add the devices to the surface. For example:
```csharp
CorsairDeviceProvider.Instance.Initialize(throwExceptions: true);
surface.Attach(CorsairDeviceProvider.Instance.Devices);
```
The `Initialize`-method allows to load only devices of specific types by setting a filter and for debugging purposes allows to enable exception throwing. (By default they are catched and provided through the `Exception`-event.)
You can also use the `Load`-Extension on the surface.
```csharp
surface.Load(CorsairDeviceProvider.Instance);
```
> While most device-providers are implemented in a way that supports fast loading like this some may have a different loading procedures. (For example the `WS281XDeviceProvider` requires device-definitions before loading.)
Since there aren't any release-packages right now you'll have to use the CI-feed from [http://nuget.arge.be](http://nuget.arge.be).
You can include it either by adding ```http://nuget.arge.be/v3/index.json``` to your Visual Studio package sources or by adding this [NuGet.Config](https://github.com/DarthAffe/RGB.NET/tree/master/Documentation/NuGet.Config) to your project (at the same level as your solution).
5. Add an update-trigger. In most cases the TimerUpdateTrigger is preferable, but you can also implement your own to fit your needs.
```csharp
surface.RegisterUpdateTrigger(new TimerUpdateTrigger());
```
> If you want to trigger updates manually the `ManualUpdateTrigger` should be used.
### .NET 4.5 Support ###
At the end of the year with the release of .NET 5 the support for old .NET-Framwork versions will be droppped!
It's not recommended to use RGB.NET in projects targeting .NET 4.x that aren't planned to be moved to Core/.NET 5 in the future.
6. *This step is optional but recommended.* For rendering the location of each LED on the surface can be important. Since not all SDKs provide useful layout-information you might want to add Layouts to your devices. (TODO: add wiki article for this)
Same goes for the location of the device on the surface. If you don't care about the exact location of the devices you can use:
```csharp
surface.AlignDevices();
```
The basic setup is now complete and you can start setting up your rendering.
### Device-Layouts
To be able to have devices with correct LED-locations and sizes they need to be layouted. Pre-created layouts can be found at https://github.com/DarthAffe/RGB.NET-Resources.
### Basic Rendering
As an example we'll add a moving rainbow over all devices on the surface.
1. Create a led-group containing all leds on the surface (all devices)
```csharp
ILedGroup allLeds = new ListLedGroup(surface, surface.Leds);
```
If you plan to create layouts for your own devices check out https://github.com/DarthAffe/RGB.NET/wiki/Creating-Layouts first. There's also a layout-editor which strongly simplifies most of the work: https://github.com/SpoinkyNL/RGB.NET-Layout-Editor
2. Create a rainbow-gradient.
```csharp
RainbowGradient rainbow = new RainbowGradient();
```
### Example usage of RGB.NET
[![Example video](https://img.youtube.com/vi/JLRa0Wv4qso/0.jpg)](http://www.youtube.com/watch?v=JLRa0Wv4qso)
3. Add a decorator to the gradient to make it move. (Decorators are
```csharp
rainbow.AddDecorator(new MoveGradientDecorator(surface));
```
#### Example Projects
[https://github.com/DarthAffe/KeyboardAudioVisualizer](https://github.com/DarthAffe/KeyboardAudioVisualizer)
[https://github.com/DarthAffe/RGBSyncPlus](https://github.com/DarthAffe/RGBSyncPlus)
4. Create a texture (the size - in this example 10, 10 - is not important here since the gradient shoukd be stretched anyway)
```csharp
ITexture texture = new ConicalGradientTexture(new Size(10, 10), rainbow);
```
5. Add a brush rendering the texture to the led-group
```csharp
allLeds.Brush = new TextureBrush(texture);
```
### Full example
```csharp
RGBSurface surface = new RGBSurface();
surface.Load(CorsairDeviceProvider.Instance);
surface.AlignDevices();
surface.RegisterUpdateTrigger(new TimerUpdateTrigger());
ILedGroup allLeds = new ListLedGroup(surface, surface.Leds);
RainbowGradient rainbow = new RainbowGradient();
rainbow.AddDecorator(new MoveGradientDecorator(surface));
ITexture texture = new ConicalGradientTexture(new Size(10, 10), rainbow);
allLeds.Brush = new TextureBrush(texture);
```

View File

@ -1,119 +0,0 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable MemberCanBeProtected.Global
// ReSharper disable ReturnTypeCanBeEnumerable.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
// ReSharper disable UnusedMember.Global
using System;
using RGB.NET.Brushes.Gradients;
using RGB.NET.Core;
namespace RGB.NET.Brushes
{
/// <inheritdoc cref="AbstractBrush" />
/// <inheritdoc cref="IGradientBrush" />
/// <summary>
/// Represents a brush drawing a conical gradient.
/// </summary>
public class ConicalGradientBrush : AbstractBrush, IGradientBrush
{
#region Properties & Fields
private float _origin = (float)Math.Atan2(-1, 0);
/// <summary>
/// Gets or sets the origin (radian-angle) this <see cref="ConicalGradientBrush"/> is drawn to. (default: -π/2)
/// </summary>
public float Origin
{
get => _origin;
set => SetProperty(ref _origin, value);
}
private Point _center = new Point(0.5, 0.5);
/// <summary>
/// Gets or sets the center <see cref="Point"/> (as percentage in the range [0..1]) of the <see cref="IGradient"/> drawn by this <see cref="ConicalGradientBrush"/>. (default: 0.5, 0.5)
/// </summary>
public Point Center
{
get => _center;
set => SetProperty(ref _center, value);
}
private IGradient _gradient;
/// <inheritdoc />
/// <summary>
/// Gets or sets the gradient drawn by the brush. If null it will default to full transparent.
/// </summary>
public IGradient Gradient
{
get => _gradient;
set => SetProperty(ref _gradient, value);
}
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.ConicalGradientBrush" /> class.
/// </summary>
public ConicalGradientBrush()
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.ConicalGradientBrush" /> class.
/// </summary>
/// <param name="gradient">The <see cref="T:RGB.NET.Brushes.Gradients.IGradient" /> drawn by this <see cref="T:RGB.NET.Brushes.ConicalGradientBrush" />.</param>
public ConicalGradientBrush(IGradient gradient)
{
this.Gradient = gradient;
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.ConicalGradientBrush" /> class.
/// </summary>
/// <param name="center">The center <see cref="T:RGB.NET.Core.Point" /> (as percentage in the range [0..1]).</param>
/// <param name="gradient">The <see cref="T:RGB.NET.Brushes.Gradients.IGradient" /> drawn by this <see cref="T:RGB.NET.Brushes.ConicalGradientBrush" />.</param>
public ConicalGradientBrush(Point center, IGradient gradient)
{
this.Center = center;
this.Gradient = gradient;
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.ConicalGradientBrush" /> class.
/// </summary>
/// <param name="center">The center <see cref="T:RGB.NET.Core.Point" /> (as percentage in the range [0..1]).</param>
/// <param name="origin">The origin (radian-angle) the <see cref="T:RGB.NET.Core.IBrush" /> is drawn to.</param>
/// <param name="gradient">The <see cref="T:RGB.NET.Brushes.Gradients.IGradient" /> drawn by this <see cref="T:RGB.NET.Brushes.ConicalGradientBrush" />.</param>
public ConicalGradientBrush(Point center, float origin, IGradient gradient)
{
this.Center = center;
this.Origin = origin;
this.Gradient = gradient;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget)
{
double centerX = rectangle.Size.Width * Center.X;
double centerY = rectangle.Size.Height * Center.Y;
double angle = Math.Atan2(renderTarget.Point.Y - centerY, renderTarget.Point.X - centerX) - Origin;
if (angle < 0) angle += Math.PI * 2;
double offset = angle / (Math.PI * 2);
return Gradient.GetColor(offset);
}
#endregion
}
}

View File

@ -1,17 +0,0 @@
using RGB.NET.Brushes.Gradients;
using RGB.NET.Core;
namespace RGB.NET.Brushes
{
/// <inheritdoc />
/// <summary>
/// Represents a basic gradient-brush.
/// </summary>
public interface IGradientBrush : IBrush
{
/// <summary>
/// Gets the <see cref="IGradient"/> used by this <see cref="IGradientBrush"/>.
/// </summary>
IGradient Gradient { get; }
}
}

View File

@ -1,109 +0,0 @@
// ReSharper disable CollectionNeverUpdated.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable MemberCanBeProtected.Global
// ReSharper disable ReturnTypeCanBeEnumerable.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
// ReSharper disable UnusedMember.Global
using RGB.NET.Brushes.Gradients;
using RGB.NET.Brushes.Helper;
using RGB.NET.Core;
namespace RGB.NET.Brushes
{
/// <inheritdoc cref="AbstractBrush" />
/// <inheritdoc cref="IGradientBrush" />
/// <summary>
/// Represents a brush drawing a linear gradient.
/// </summary>
public class LinearGradientBrush : AbstractBrush, IGradientBrush
{
#region Properties & Fields
private Point _startPoint = new Point(0, 0.5);
/// <summary>
/// Gets or sets the start <see cref="Point"/> (as percentage in the range [0..1]) of the <see cref="IGradient"/> drawn by this <see cref="LinearGradientBrush"/>. (default: 0.0, 0.5)
/// </summary>
public Point StartPoint
{
get => _startPoint;
set => SetProperty(ref _startPoint, value);
}
private Point _endPoint = new Point(1, 0.5);
/// <summary>
/// Gets or sets the end <see cref="Point"/> (as percentage in the range [0..1]) of the <see cref="IGradient"/> drawn by this <see cref="LinearGradientBrush"/>. (default: 1.0, 0.5)
/// </summary>
public Point EndPoint
{
get => _endPoint;
set => SetProperty(ref _endPoint, value);
}
private IGradient _gradient;
/// <inheritdoc />
public IGradient Gradient
{
get => _gradient;
set => SetProperty(ref _gradient, value);
}
#endregion
#region Constructor
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.LinearGradientBrush" /> class.
/// </summary>
public LinearGradientBrush()
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.LinearGradientBrush" /> class.
/// </summary>
/// <param name="gradient">The <see cref="T:RGB.NET.Brushes.Gradients.IGradient" /> drawn by this <see cref="T:RGB.NET.Brushes.LinearGradientBrush" />.</param>
public LinearGradientBrush(IGradient gradient)
{
this.Gradient = gradient;
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.LinearGradientBrush" /> class.
/// </summary>
/// <param name="startPoint">The start <see cref="T:RGB.NET.Core.Point" /> (as percentage in the range [0..1]).</param>
/// <param name="endPoint">The end <see cref="T:RGB.NET.Core.Point" /> (as percentage in the range [0..1]).</param>
/// <param name="gradient">The <see cref="T:RGB.NET.Brushes.Gradients.IGradient" /> drawn by this <see cref="T:RGB.NET.Brushes.LinearGradientBrush" />.</param>
public LinearGradientBrush(Point startPoint, Point endPoint, IGradient gradient)
{
this.StartPoint = startPoint;
this.EndPoint = endPoint;
this.Gradient = gradient;
}
#endregion
#region Methods
/// <inheritdoc />
/// <summary>
/// Gets the color at an specific point assuming the brush is drawn into the given rectangle.
/// </summary>
/// <param name="rectangle">The rectangle in which the brush should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the color should be taken.</param>
/// <returns>The color at the specified point.</returns>
protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget)
{
if (Gradient == null) return Color.Transparent;
Point startPoint = new Point(StartPoint.X * rectangle.Size.Width, StartPoint.Y * rectangle.Size.Height);
Point endPoint = new Point(EndPoint.X * rectangle.Size.Width, EndPoint.Y * rectangle.Size.Height);
double offset = GradientHelper.CalculateLinearGradientOffset(startPoint, endPoint, renderTarget.Point);
return Gradient.GetColor(offset);
}
#endregion
}
}

View File

@ -1,97 +0,0 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
// ReSharper disable UnusedMember.Global
using System;
using RGB.NET.Brushes.Gradients;
using RGB.NET.Brushes.Helper;
using RGB.NET.Core;
namespace RGB.NET.Brushes
{
/// <inheritdoc cref="AbstractBrush" />
/// <inheritdoc cref="IGradientBrush" />
/// <summary>
/// Represents a brush drawing a radial gradient around a center point.
/// </summary>
public class RadialGradientBrush : AbstractBrush, IGradientBrush
{
#region Properties & Fields
private Point _center = new Point(0.5, 0.5);
/// <summary>
/// Gets or sets the center <see cref="Point"/> (as percentage in the range [0..1]) around which the <see cref="RadialGradientBrush"/> should be drawn. (default: 0.5, 0.5)
/// </summary>
public Point Center
{
get => _center;
set => SetProperty(ref _center, value);
}
private IGradient _gradient;
/// <inheritdoc />
public IGradient Gradient
{
get => _gradient;
set => SetProperty(ref _gradient, value);
}
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.RadialGradientBrush" /> class.
/// </summary>
public RadialGradientBrush()
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.RadialGradientBrush" /> class.
/// </summary>
/// <param name="gradient">The gradient drawn by the brush.</param>
public RadialGradientBrush(IGradient gradient)
{
this.Gradient = gradient;
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.RadialGradientBrush" /> class.
/// </summary>
/// <param name="center">The center point (as percentage in the range [0..1]).</param>
/// <param name="gradient">The gradient drawn by the brush.</param>
public RadialGradientBrush(Point center, IGradient gradient)
{
this.Center = center;
this.Gradient = gradient;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget)
{
if (Gradient == null) return Color.Transparent;
Point centerPoint = new Point(rectangle.Location.X + (rectangle.Size.Width * Center.X), rectangle.Location.Y + (rectangle.Size.Height * Center.Y));
// Calculate the distance to the farthest point from the center as reference (this has to be a corner)
// ReSharper disable once RedundantCast - never trust this ...
double refDistance = Math.Max(Math.Max(Math.Max(GradientHelper.CalculateDistance(rectangle.Location, centerPoint),
GradientHelper.CalculateDistance(new Point(rectangle.Location.X + rectangle.Size.Width, rectangle.Location.Y), centerPoint)),
GradientHelper.CalculateDistance(new Point(rectangle.Location.X, rectangle.Location.Y + rectangle.Size.Height), centerPoint)),
GradientHelper.CalculateDistance(new Point(rectangle.Location.X + rectangle.Size.Width, rectangle.Location.Y + rectangle.Size.Height), centerPoint));
double distance = GradientHelper.CalculateDistance(renderTarget.Point, centerPoint);
double offset = distance / refDistance;
return Gradient.GetColor(offset);
}
#endregion
}
}

View File

@ -1,65 +0,0 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using RGB.NET.Core;
namespace RGB.NET.Brushes
{
/// <inheritdoc />
/// <summary>
/// Represents a brush drawing only a single color.
/// </summary>
public class SolidColorBrush : AbstractBrush
{
#region Properties & Fields
private Color _color;
/// <summary>
/// Gets or sets the <see cref="Color"/> drawn by this <see cref="SolidColorBrush"/>.
/// </summary>
public Color Color
{
get => _color;
set => SetProperty(ref _color, value);
}
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.SolidColorBrush" /> class.
/// </summary>
/// <param name="color">The <see cref="P:RGB.NET.Brushes.SolidColorBrush.Color" /> drawn by this <see cref="T:RGB.NET.Brushes.SolidColorBrush" />.</param>
public SolidColorBrush(Color color)
{
this.Color = color;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget) => Color;
#endregion
#region Operators
/// <summary>
/// Converts a <see cref="Color" /> to a <see cref="SolidColorBrush" />.
/// </summary>
/// <param name="color">The <see cref="Color"/> to convert.</param>
public static explicit operator SolidColorBrush(Color color) => new SolidColorBrush(color);
/// <summary>
/// Converts a <see cref="SolidColorBrush" /> to a <see cref="Color" />.
/// </summary>
/// <param name="brush">The <see cref="Color"/> to convert.</param>
public static implicit operator Color(SolidColorBrush brush) => brush.Color;
#endregion
}
}

View File

@ -1,11 +0,0 @@
using RGB.NET.Core;
namespace RGB.NET.Brushes
{
/// <inheritdoc />
/// <summary>
/// Represents a basic decorator decorating a <see cref="T:RGB.NET.Brushes.Gradients.IGradient" />.
/// </summary>
public interface IGradientDecorator : IDecorator
{ }
}

View File

@ -1,150 +0,0 @@
// ReSharper disable MemberCanBeProtected.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using RGB.NET.Core;
namespace RGB.NET.Brushes.Gradients
{
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="IGradient" />
/// <summary>
/// Represents a basic gradient.
/// </summary>
public abstract class AbstractGradient : AbstractDecoratable<IGradientDecorator>, IGradient
{
#region Properties & Fields
/// <summary>
/// Gets a list of the stops used by this <see cref="AbstractGradient"/>.
/// </summary>
public ObservableCollection<GradientStop> GradientStops { get; } = new ObservableCollection<GradientStop>();
private bool _wrapGradient;
/// <summary>
/// Gets or sets if the Gradient wraps around if there isn't a second stop to take.
/// Example: There is a stop at offset 0.0, 0.5 and 0.75.
/// Without wrapping offset 1.0 will be calculated the same as 0.75; with wrapping it would be the same as 0.0.
/// </summary>
public bool WrapGradient
{
get => _wrapGradient;
set => SetProperty(ref _wrapGradient, value);
}
#endregion
#region Events
/// <inheritdoc />
public event EventHandler GradientChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractGradient"/> class.
/// </summary>
protected AbstractGradient()
{
GradientStops.CollectionChanged += GradientCollectionChanged;
PropertyChanged += (sender, args) => OnGradientChanged();
}
/// <summary>
/// Initializes a new instance of the <see cref="AbstractGradient"/> class.
/// </summary>
/// <param name="gradientStops">The stops with which the gradient should be initialized.</param>
protected AbstractGradient(params GradientStop[] gradientStops)
{
GradientStops.CollectionChanged += GradientCollectionChanged;
PropertyChanged += (sender, args) => OnGradientChanged();
foreach (GradientStop gradientStop in gradientStops)
GradientStops.Add(gradientStop);
}
/// <summary>
/// Initializes a new instance of the <see cref="AbstractGradient"/> class.
/// </summary>
/// <param name="wrapGradient">Specifies whether the gradient should wrapp or not (see <see cref="WrapGradient"/> for an example of what this means).</param>
/// <param name="gradientStops">The stops with which the gradient should be initialized.</param>
protected AbstractGradient(bool wrapGradient, params GradientStop[] gradientStops)
{
this.WrapGradient = wrapGradient;
GradientStops.CollectionChanged += GradientCollectionChanged;
PropertyChanged += (sender, args) => OnGradientChanged();
foreach (GradientStop gradientStop in gradientStops)
GradientStops.Add(gradientStop);
}
#endregion
#region Methods
/// <summary>
/// Clips the offset and ensures, that it is inside the bounds of the stop list.
/// </summary>
/// <param name="offset"></param>
/// <returns></returns>
protected double ClipOffset(double offset)
{
double max = GradientStops.Max(n => n.Offset);
if (offset > max)
return max;
double min = GradientStops.Min(n => n.Offset);
return offset < min ? min : offset;
}
/// <inheritdoc />
public abstract Color GetColor(double offset);
/// <inheritdoc />
public virtual void Move(double offset)
{
offset /= 360.0;
foreach (GradientStop gradientStop in GradientStops)
gradientStop.Offset += offset;
while (GradientStops.All(x => x.Offset > 1))
foreach (GradientStop gradientStop in GradientStops)
gradientStop.Offset -= 1;
while (GradientStops.All(x => x.Offset < 0))
foreach (GradientStop gradientStop in GradientStops)
gradientStop.Offset += 1;
}
/// <summary>
/// Should be called to indicate that the gradient was changed.
/// </summary>
protected void OnGradientChanged() => GradientChanged?.Invoke(this, null);
private void GradientCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (GradientStop gradientStop in e.OldItems)
gradientStop.PropertyChanged -= GradientStopChanged;
if (e.NewItems != null)
foreach (GradientStop gradientStop in e.NewItems)
gradientStop.PropertyChanged += GradientStopChanged;
OnGradientChanged();
}
private void GradientStopChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) => OnGradientChanged();
#endregion
}
}

View File

@ -1,52 +0,0 @@
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
// ReSharper disable MemberCanBePrivate.Global
using RGB.NET.Core;
namespace RGB.NET.Brushes.Gradients
{
/// <summary>
/// Represents a stop on a gradient.
/// </summary>
public class GradientStop : AbstractBindable
{
#region Properties & Fields
private double _offset;
/// <summary>
/// Gets or sets the percentage offset to place this <see cref="GradientStop"/>. This should be inside the range of [0..1] but it's not necessary.
/// </summary>
public double Offset
{
get => _offset;
set => SetProperty(ref _offset, value);
}
private Color _color;
/// <summary>
/// Gets or sets the <see cref="Color"/> of this <see cref="GradientStop"/>.
/// </summary>
public Color Color
{
get => _color;
set => SetProperty(ref _color, value);
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="GradientStop"/> class.
/// </summary>
/// <param name="offset">The percentage offset to place this <see cref="GradientStop"/>.</param>
/// <param name="color">The <see cref="Color"/> of the <see cref="GradientStop"/>.</param>
public GradientStop(double offset, Color color)
{
this.Offset = offset;
this.Color = color;
}
#endregion
}
}

View File

@ -1,30 +0,0 @@
using System;
using RGB.NET.Core;
namespace RGB.NET.Brushes.Gradients
{
/// <inheritdoc />
/// <summary>
/// Represents a basic gradient.
/// </summary>
public interface IGradient : IDecoratable<IGradientDecorator>
{
/// <summary>
/// Occurs if the <see cref="IGradient"/> is changed.
/// </summary>
event EventHandler GradientChanged;
/// <summary>
/// Gets the <see cref="Color"/> of the <see cref="IGradient"/> on the specified offset.
/// </summary>
/// <param name="offset">The percentage offset to take the <see cref="Color"/> from.</param>
/// <returns>The <see cref="Color"/> at the specific offset.</returns>
Color GetColor(double offset);
/// <summary>
/// Moves the <see cref="IGradient"/> by the provided offset.
/// </summary>
/// <param name="offset">The offset the <see cref="IGradient"/> should be moved.</param>
void Move(double offset);
}
}

View File

@ -1,152 +0,0 @@
// ReSharper disable UnusedMember.Global
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using RGB.NET.Core;
namespace RGB.NET.Brushes.Gradients
{
/// <inheritdoc />
/// <summary>
/// Represents a linear interpolated gradient with n stops.
/// </summary>
public class LinearGradient : AbstractGradient
{
#region Properties & Fields
private bool _isOrderedGradientListDirty = true;
private LinkedList<GradientStop> _orderedGradientStops;
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.Gradients.LinearGradient" /> class.
/// </summary>
public LinearGradient()
{
Initialize();
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.Gradients.LinearGradient" /> class.
/// </summary>
/// <param name="gradientStops">The stops with which the gradient should be initialized.</param>
public LinearGradient(params GradientStop[] gradientStops)
: base(gradientStops)
{
Initialize();
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Brushes.Gradients.AbstractGradient" /> class.
/// </summary>
/// <param name="wrapGradient">Specifies whether the gradient should wrapp or not (see <see cref="P:RGB.NET.Brushes.Gradients.AbstractGradient.WrapGradient" /> for an example of what this means).</param>
/// <param name="gradientStops">The stops with which the gradient should be initialized.</param>
public LinearGradient(bool wrapGradient, params GradientStop[] gradientStops)
: base(wrapGradient, gradientStops)
{
Initialize();
}
#endregion
#region Methods
private void Initialize()
{
void OnGradientStopOnPropertyChanged(object sender, PropertyChangedEventArgs args) => _isOrderedGradientListDirty = true;
foreach (GradientStop gradientStop in GradientStops)
gradientStop.PropertyChanged += OnGradientStopOnPropertyChanged;
GradientStops.CollectionChanged += (sender, args) =>
{
if (args.OldItems != null)
foreach (GradientStop gradientStop in args.OldItems)
gradientStop.PropertyChanged -= OnGradientStopOnPropertyChanged;
if (args.NewItems != null)
foreach (GradientStop gradientStop in args.NewItems)
gradientStop.PropertyChanged += OnGradientStopOnPropertyChanged;
};
}
/// <inheritdoc />
/// <summary>
/// Gets the linear interpolated <see cref="T:RGB.NET.Core.Color" /> at the given offset.
/// </summary>
/// <param name="offset">The percentage offset to take the color from.</param>
/// <returns>The <see cref="T:RGB.NET.Core.Color" /> at the specific offset.</returns>
public override Color GetColor(double offset)
{
if (GradientStops.Count == 0) return Color.Transparent;
if (GradientStops.Count == 1) return GradientStops[0].Color;
if (_isOrderedGradientListDirty)
_orderedGradientStops = new LinkedList<GradientStop>(GradientStops.OrderBy(x => x.Offset));
(GradientStop gsBefore, GradientStop gsAfter) = GetEnclosingGradientStops(offset, _orderedGradientStops, WrapGradient);
double blendFactor = 0;
if (!gsBefore.Offset.Equals(gsAfter.Offset))
blendFactor = ((offset - gsBefore.Offset) / (gsAfter.Offset - gsBefore.Offset));
double colA = ((gsAfter.Color.A - gsBefore.Color.A) * blendFactor) + gsBefore.Color.A;
double colR = ((gsAfter.Color.R - gsBefore.Color.R) * blendFactor) + gsBefore.Color.R;
double colG = ((gsAfter.Color.G - gsBefore.Color.G) * blendFactor) + gsBefore.Color.G;
double colB = ((gsAfter.Color.B - gsBefore.Color.B) * blendFactor) + gsBefore.Color.B;
return new Color(colA, colR, colG, colB);
}
/// <summary>
/// Get the two <see cref="GradientStop"/>s encapsulating the given offset.
/// </summary>
/// <param name="offset">The reference offset.</param>
/// <param name="orderedStops">The ordered list of <see cref="GradientStop"/> to choose from.</param>
/// <param name="wrap">Bool indicating if the gradient should be wrapped or not.</param>
/// <returns></returns>
protected virtual (GradientStop gsBefore, GradientStop gsAfter) GetEnclosingGradientStops(double offset, LinkedList<GradientStop> orderedStops, bool wrap)
{
LinkedList<GradientStop> gradientStops = new LinkedList<GradientStop>(orderedStops);
if (wrap)
{
GradientStop gsBefore, gsAfter;
do
{
gsBefore = gradientStops.LastOrDefault(n => n.Offset <= offset);
if (gsBefore == null)
{
GradientStop lastStop = gradientStops.Last.Value;
gradientStops.AddFirst(new GradientStop(lastStop.Offset - 1, lastStop.Color));
gradientStops.RemoveLast();
}
gsAfter = gradientStops.FirstOrDefault(n => n.Offset >= offset);
if (gsAfter == null)
{
GradientStop firstStop = gradientStops.First.Value;
gradientStops.AddLast(new GradientStop(firstStop.Offset + 1, firstStop.Color));
gradientStops.RemoveFirst();
}
} while ((gsBefore == null) || (gsAfter == null));
return (gsBefore, gsAfter);
}
offset = ClipOffset(offset);
return (gradientStops.Last(n => n.Offset <= offset), gradientStops.First(n => n.Offset >= offset));
}
#endregion
}
}

View File

@ -1,108 +0,0 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System;
using RGB.NET.Core;
namespace RGB.NET.Brushes.Gradients
{
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="IGradient" />
/// <summary>
/// Represents a rainbow gradient which circles through all colors of the HUE-color-space.<br />
/// See <see href="http://upload.wikimedia.org/wikipedia/commons/a/ad/HueScale.svg" /> as reference.
/// </summary>
public class RainbowGradient : AbstractDecoratable<IGradientDecorator>, IGradient
{
#region Properties & Fields
private double _startHue;
/// <summary>
/// Gets or sets the hue (in degrees) to start from.
/// </summary>
public double StartHue
{
get => _startHue;
set => SetProperty(ref _startHue, value);
}
private double _endHue;
/// <summary>
/// Gets or sets the hue (in degrees) to end the with.
/// </summary>
public double EndHue
{
get => _endHue;
set => SetProperty(ref _endHue, value);
}
#endregion
#region Events
/// <inheritdoc />
public event EventHandler GradientChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RainbowGradient"/> class.
/// </summary>
/// <param name="startHue">The hue (in degrees) to start from (default: 0)</param>
/// <param name="endHue">The hue (in degrees) to end with (default: 360)</param>
public RainbowGradient(double startHue = 0, double endHue = 360)
{
this.StartHue = startHue;
this.EndHue = endHue;
PropertyChanged += (sender, args) => OnGradientChanged();
}
#endregion
#region Methods
/// <inheritdoc />
/// <summary>
/// Gets the color on the rainbow at the given offset.
/// </summary>
/// <param name="offset">The percentage offset to take the color from.</param>
/// <returns>The color at the specific offset.</returns>
public Color GetColor(double offset)
{
double range = EndHue - StartHue;
double hue = StartHue + (range * offset);
return HSVColor.Create(hue, 1, 1);
}
/// <inheritdoc />
public void Move(double offset)
{
// RainbowGradient is calculated inverse
offset *= -1;
StartHue += offset;
EndHue += offset;
while ((StartHue > 360) && (EndHue > 360))
{
StartHue -= 360;
EndHue -= 360;
}
while ((StartHue < -360) && (EndHue < -360))
{
StartHue += 360;
EndHue += 360;
}
}
/// <summary>
/// Should be called to indicate that the gradient was changed.
/// </summary>
protected void OnGradientChanged() => GradientChanged?.Invoke(this, null);
#endregion
}
}

View File

@ -1,81 +0,0 @@
// ReSharper disable MemberCanBePrivate.Global
using System;
using RGB.NET.Core;
namespace RGB.NET.Brushes.Helper
{
/// <summary>
/// Offers some extensions and helper-methods for gradient related things.
/// </summary>
public static class GradientHelper
{
#region Methods
// Based on https://web.archive.org/web/20170125201230/https://dotupdate.wordpress.com/2008/01/28/find-the-color-of-a-point-in-a-lineargradientbrush/
/// <summary>
/// Calculates the offset of an given <see cref="Point"/> on an gradient.
/// </summary>
/// <param name="startPoint">The start <see cref="Point"/> of the gradient.</param>
/// <param name="endPoint">The end <see cref="Point"/> of the gradient.</param>
/// <param name="point">The <see cref="Point"/> on the gradient to which the offset is calculated.</param>
/// <returns>The offset of the <see cref="Point"/> on the gradient.</returns>
public static double CalculateLinearGradientOffset(Point startPoint, Point endPoint, Point point)
{
Point intersectingPoint;
if (startPoint.Y.Equals(endPoint.Y)) // Horizontal case
intersectingPoint = new Point(point.X, startPoint.Y);
else if (startPoint.X.Equals(endPoint.X)) // Vertical case
intersectingPoint = new Point(startPoint.X, point.Y);
else // Diagonal case
{
double slope = (endPoint.Y - startPoint.Y) / (endPoint.X - startPoint.X);
double orthogonalSlope = -1 / slope;
double startYIntercept = startPoint.Y - (slope * startPoint.X);
double pointYIntercept = point.Y - (orthogonalSlope * point.X);
double intersectingPointX = (pointYIntercept - startYIntercept) / (slope - orthogonalSlope);
double intersectingPointY = (slope * intersectingPointX) + startYIntercept;
intersectingPoint = new Point(intersectingPointX, intersectingPointY);
}
// Calculate distances relative to the vector start
double intersectDistance = CalculateDistance(intersectingPoint, startPoint, endPoint);
double gradientLength = CalculateDistance(endPoint, startPoint, endPoint);
return intersectDistance / gradientLength;
}
// Based on https://web.archive.org/web/20170125201230/https://dotupdate.wordpress.com/2008/01/28/find-the-color-of-a-point-in-a-lineargradientbrush/
/// <summary>
/// Returns the signed magnitude of a <see cref="Point"/> on a vector.
/// </summary>
/// <param name="point">The <see cref="Point"/> on the vector of which the magnitude should be calculated.</param>
/// <param name="origin">The origin of the vector.</param>
/// <param name="direction">The direction of the vector.</param>
/// <returns>The signed magnitude of a <see cref="Point"/> on a vector.</returns>
public static double CalculateDistance(Point point, Point origin, Point direction)
{
double distance = CalculateDistance(point, origin);
return (((point.Y < origin.Y) && (direction.Y > origin.Y)) ||
((point.Y > origin.Y) && (direction.Y < origin.Y)) ||
((point.Y.Equals(origin.Y)) && (point.X < origin.X) && (direction.X > origin.X)) ||
((point.Y.Equals(origin.Y)) && (point.X > origin.X) && (direction.X < origin.X)))
? -distance : distance;
}
/// <summary>
/// Calculated the distance between two <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>The distance between the two <see cref="Point"/>.</returns>
public static double CalculateDistance(Point point1, Point point2) => Math.Sqrt(((point1.Y - point2.Y) * (point1.Y - point2.Y)) + ((point1.X - point2.X) * (point1.X - point2.X)));
#endregion
}
}

View File

@ -1,131 +0,0 @@
// ReSharper disable VirtualMemberNeverOverriden.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable VirtualMemberNeverOverridden.Global
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core
{
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="IBrush" />
/// <summary>
/// Represents a basic brush.
/// </summary>
public abstract class AbstractBrush : AbstractDecoratable<IBrushDecorator>, IBrush
{
#region Properties & Fields
/// <inheritdoc />
public bool IsEnabled { get; set; } = true;
/// <inheritdoc />
public BrushCalculationMode BrushCalculationMode { get; set; } = BrushCalculationMode.Relative;
/// <inheritdoc />
public double Brightness { get; set; }
/// <inheritdoc />
public double Opacity { get; set; }
/// <inheritdoc />
public IList<IColorCorrection> ColorCorrections { get; } = new List<IColorCorrection>();
/// <inheritdoc />
public Rectangle RenderedRectangle { get; protected set; }
/// <inheritdoc />
public Dictionary<BrushRenderTarget, Color> RenderedTargets { get; } = new Dictionary<BrushRenderTarget, Color>();
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractBrush"/> class.
/// </summary>
/// <param name="brightness">The overall percentage brightness of the brush. (default: 1.0)</param>
/// <param name="opacity">The overall percentage opacity of the brush. (default: 1.0)</param>
protected AbstractBrush(double brightness = 1, double opacity = 1)
{
this.Brightness = brightness;
this.Opacity = opacity;
}
#endregion
#region Methods
/// <inheritdoc />
public virtual void PerformRender(Rectangle rectangle, IEnumerable<BrushRenderTarget> renderTargets)
{
RenderedRectangle = rectangle;
RenderedTargets.Clear();
foreach (BrushRenderTarget renderTarget in renderTargets)
{
Color color = GetColorAtPoint(rectangle, renderTarget);
color = ApplyDecorators(rectangle, renderTarget, color);
RenderedTargets[renderTarget] = color;
}
}
/// <summary>
/// Applies all attached and enabled decorators to the brush.
/// </summary>
/// <param name="rectangle">The rectangle in which the brush should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the color should be taken.</param>
/// <param name="color">The <see cref="Color"/> to be modified.</param>
protected virtual Color ApplyDecorators(Rectangle rectangle, BrushRenderTarget renderTarget, Color color)
{
lock (Decorators)
foreach (IBrushDecorator decorator in Decorators)
if (decorator.IsEnabled)
color = decorator.ManipulateColor(rectangle, renderTarget, color);
return color;
}
/// <inheritdoc />
public virtual void PerformFinalize()
{
List<BrushRenderTarget> renderTargets = RenderedTargets.Keys.ToList();
foreach (BrushRenderTarget renderTarget in renderTargets)
RenderedTargets[renderTarget] = FinalizeColor(RenderedTargets[renderTarget]);
}
/// <summary>
/// Gets the color at an specific point assuming the brush is drawn into the given rectangle.
/// </summary>
/// <param name="rectangle">The rectangle in which the brush should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the color should be taken.</param>
/// <returns>The color at the specified point.</returns>
protected abstract Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget);
/// <summary>
/// Finalizes the color by appliing the overall brightness and opacity.<br/>
/// This method should always be the last call of a <see cref="GetColorAtPoint" /> implementation.
/// </summary>
/// <param name="color">The color to finalize.</param>
/// <returns>The finalized color.</returns>
protected virtual Color FinalizeColor(Color color)
{
if (ColorCorrections.Count > 0)
foreach (IColorCorrection colorCorrection in ColorCorrections)
color = colorCorrection.ApplyTo(color);
// Since we use HSV to calculate there is no way to make a color 'brighter' than 100%
// Be carefull with the naming: Since we use HSV the correct term is 'value' but outside we call it 'brightness'
// THIS IS NOT A HSB CALCULATION!!!
if (Brightness < 1)
color = color.MultiplyHSV(value: Brightness.Clamp(0, 1));
if (Opacity < 1)
color = color.MultiplyA(Opacity.Clamp(0, 1));
return color;
}
#endregion
}
}

View File

@ -1,20 +0,0 @@
// ReSharper disable UnusedMember.Global
namespace RGB.NET.Core
{
/// <summary>
/// Contains a list of all brush calculation modes.
/// </summary>
public enum BrushCalculationMode
{
/// <summary>
/// The calculation <see cref="Rectangle"/> for <see cref="IBrush"/> will be the rectangle around the <see cref="ILedGroup"/> the <see cref="IBrush"/> is applied to.
/// </summary>
Relative,
/// <summary>
/// The calculation <see cref="Rectangle"/> for <see cref="IBrush"/> will always be the rectangle completly containing all affected <see cref="IRGBDevice"/>.
/// </summary>
Absolute
}
}

View File

@ -1,47 +0,0 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace RGB.NET.Core
{
/// <summary>
/// Represents a single target of a brush render.
/// </summary>
public class BrushRenderTarget
{
#region Properties & Fields
/// <summary>
/// Gets the target-<see cref="Core.Led"/>.
/// </summary>
public Led Led { get; }
/// <summary>
/// Gets the <see cref="Core.Rectangle"/> representing the area to render the target-<see cref="Core.Led"/>.
/// </summary>
public Rectangle Rectangle { get; }
/// <summary>
/// Gets the <see cref="Core.Point"/> representing the position to render the target-<see cref="Core.Led"/>.
/// </summary>
public Point Point { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="BrushRenderTarget"/> class.
/// </summary>
/// <param name="led">The target-<see cref="Core.Led"/>.</param>
/// <param name="rectangle">The <see cref="Core.Rectangle"/> representing the area to render the target-<see cref="Core.Led"/>.</param>
public BrushRenderTarget(Led led, Rectangle rectangle)
{
this.Led = led;
this.Rectangle = rectangle;
this.Point = rectangle.Center;
}
#endregion
}
}

View File

@ -1,61 +0,0 @@
// ReSharper disable UnusedMemberInSuper.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable ReturnTypeCanBeEnumerable.Global
using System.Collections.Generic;
namespace RGB.NET.Core
{
/// <summary>
/// Represents a basic brush.
/// </summary>
public interface IBrush : IDecoratable<IBrushDecorator>
{
/// <summary>
/// Gets or sets if the <see cref="IBrush"/> is enabled and will be drawn on an update.
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets the calculation mode used for the rectangle/points used for color-selection in brushes.
/// </summary>
BrushCalculationMode BrushCalculationMode { get; set; }
/// <summary>
/// Gets or sets the overall percentage brightness of the <see cref="IBrush"/>.
/// </summary>
double Brightness { get; set; }
/// <summary>
/// Gets or sets the overall percentage opacity of the <see cref="IBrush"/>.
/// </summary>
double Opacity { get; set; }
/// <summary>
/// Gets a list of <see cref="IColorCorrection"/> used to correct the colors of the <see cref="IBrush"/>.
/// </summary>
IList<IColorCorrection> ColorCorrections { get; }
/// <summary>
/// Gets the <see cref="RenderedRectangle"/> used in the last render pass.
/// </summary>
Rectangle RenderedRectangle { get; }
/// <summary>
/// Gets a dictionary containing all <see cref="Color"/> for <see cref="BrushRenderTarget"/> calculated in the last render pass.
/// </summary>
Dictionary<BrushRenderTarget, Color> RenderedTargets { get; }
/// <summary>
/// Performs the render pass of the <see cref="IBrush"/> and calculates the raw <see cref="Color"/> for all requested <see cref="BrushRenderTarget"/>.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/> in which the brush should be drawn.</param>
/// <param name="renderTargets">The <see cref="BrushRenderTarget"/> (keys/points) of which the color should be calculated.</param>
void PerformRender(Rectangle rectangle, IEnumerable<BrushRenderTarget> renderTargets);
/// <summary>
/// Performs the finalize pass of the <see cref="IBrush"/> and calculates the final <see cref="ColorCorrections"/> for all previously calculated <see cref="BrushRenderTarget"/>.
/// </summary>
void PerformFinalize();
}
}

View File

@ -1,80 +1,62 @@
namespace RGB.NET.Core
using System;
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the default-behavior for the work with colors.
/// </summary>
public class DefaultColorBehavior : IColorBehavior
{
public class DefaultColorBehavior : IColorBehavior
#region Methods
/// <summary>
/// Converts the individual byte values of this <see cref="Color"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. For example "[A: 255, R: 255, G: 0, B: 0]".</returns>
public virtual string ToString(in Color color) => $"[A: {color.GetA()}, R: {color.GetR()}, G: {color.GetG()}, B: {color.GetB()}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The color to test.</param>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public virtual bool Equals(in Color color, object? obj)
{
#region Properties & Fields
private static DefaultColorBehavior _instance = new DefaultColorBehavior();
/// <summary>
/// Gets the singleton instance of <see cref="DefaultColorBehavior"/>.
/// </summary>
public static DefaultColorBehavior Instance { get; } = _instance;
#endregion
#region Constructors
private DefaultColorBehavior()
{ }
#endregion
#region Methods
/// <summary>
/// Converts the individual byte values of this <see cref="Color"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. For example "[A: 255, R: 255, G: 0, B: 0]".</returns>
public virtual string ToString(Color color) => $"[A: {color.GetA()}, R: {color.GetR()}, G: {color.GetG()}, B: {color.GetB()}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public virtual bool Equals(Color color, object obj)
{
if (!(obj is Color)) return false;
(double a, double r, double g, double b) = ((Color)obj).GetRGB();
return color.A.EqualsInTolerance(a) && color.R.EqualsInTolerance(r) && color.G.EqualsInTolerance(g) && color.B.EqualsInTolerance(b);
}
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
public virtual int GetHashCode(Color color)
{
unchecked
{
int hashCode = color.A.GetHashCode();
hashCode = (hashCode * 397) ^ color.R.GetHashCode();
hashCode = (hashCode * 397) ^ color.G.GetHashCode();
hashCode = (hashCode * 397) ^ color.B.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="color">The <see cref="Color"/> to blend.</param>
public virtual Color Blend(Color baseColor, Color blendColor)
{
if (blendColor.A.EqualsInTolerance(0)) return baseColor;
if (blendColor.A.EqualsInTolerance(1))
return blendColor;
double resultA = (1.0 - ((1.0 - blendColor.A) * (1.0 - baseColor.A)));
double resultR = (((blendColor.R * blendColor.A) / resultA) + ((baseColor.R * baseColor.A * (1.0 - blendColor.A)) / resultA));
double resultG = (((blendColor.G * blendColor.A) / resultA) + ((baseColor.G * baseColor.A * (1.0 - blendColor.A)) / resultA));
double resultB = (((blendColor.B * blendColor.A) / resultA) + ((baseColor.B * baseColor.A * (1.0 - blendColor.A)) / resultA));
return new Color(resultA, resultR, resultG, resultB);
}
#endregion
if (obj is not Color color2) return false;
return color.A.EqualsInTolerance(color2.A)
&& color.R.EqualsInTolerance(color2.R)
&& color.G.EqualsInTolerance(color2.G)
&& color.B.EqualsInTolerance(color2.B);
}
}
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
public virtual int GetHashCode(in Color color) => HashCode.Combine(color.A, color.R, color.G, color.B);
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="baseColor">The <see cref="Color"/> to to blend over.</param>
/// <param name="blendColor">The <see cref="Color"/> to blend.</param>
public virtual Color Blend(in Color baseColor, in Color blendColor)
{
if (blendColor.A.EqualsInTolerance(0)) return baseColor;
if (blendColor.A.EqualsInTolerance(1))
return blendColor;
float resultA = (1.0f - ((1.0f - blendColor.A) * (1.0f - baseColor.A)));
float resultR = (((blendColor.R * blendColor.A) / resultA) + ((baseColor.R * baseColor.A * (1.0f - blendColor.A)) / resultA));
float resultG = (((blendColor.G * blendColor.A) / resultA) + ((baseColor.G * baseColor.A * (1.0f - blendColor.A)) / resultA));
float resultB = (((blendColor.B * blendColor.A) / resultA) + ((baseColor.B * baseColor.A * (1.0f - blendColor.A)) / resultA));
return new Color(resultA, resultR, resultG, resultB);
}
#endregion
}

View File

@ -1,13 +1,35 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a behavior of a color for base operations.
/// </summary>
public interface IColorBehavior
{
public interface IColorBehavior
{
string ToString(Color color);
/// <summary>
/// Converts the specified <see cref="Color"/> to a string representation.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The string representation of the specified color.</returns>
string ToString(in Color color);
bool Equals(Color color, object obj);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The color to test.</param>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
bool Equals(in Color color, object? obj);
int GetHashCode(Color color);
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
int GetHashCode(in Color color);
Color Blend(Color baseColor, Color blendColor);
}
}
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="baseColor">The <see cref="Color"/> to to blend over.</param>
/// <param name="blendColor">The <see cref="Color"/> to blend.</param>
Color Blend(in Color baseColor, in Color blendColor);
}

View File

@ -5,284 +5,279 @@
using System;
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents an ARGB (alpha, red, green, blue) color.
/// </summary>
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
public readonly struct Color
{
#region Constants
private static readonly Color TRANSPARENT = new(0, 0, 0, 0);
/// <summary>
/// Gets an transparent color [A: 0, R: 0, G: 0, B: 0]
/// </summary>
public static ref readonly Color Transparent => ref TRANSPARENT;
#endregion
#region Properties & Fields
/// <summary>
/// Gets or sets the <see cref="IColorBehavior"/> used to perform operations on colors.
/// </summary>
public static IColorBehavior Behavior { get; set; } = new DefaultColorBehavior();
/// <summary>
/// Gets the alpha component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public readonly float A;
/// <summary>
/// Gets the red component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public readonly float R;
/// <summary>
/// Gets the green component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public readonly float G;
/// <summary>
/// Gets the blue component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public readonly float B;
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents an ARGB (alpha, red, green, blue) color.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
public struct Color
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(byte r, byte g, byte b)
: this(byte.MaxValue, r, g, b)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(int r, int g, int b)
: this((byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, byte r, byte g, byte b)
: this(a.GetPercentageFromByteValue(), r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, int r, int g, int b)
: this((byte)a.Clamp(0, byte.MaxValue), (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using RGB-percent values.
/// Alpha defaults to 1.0.
/// </summary>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float r, float g, float b)
: this(1.0f, r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, byte r, byte g, byte b)
: this(a, r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, int r, int g, int b)
: this(a, (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, float r, float g, float b)
: this((byte)a.Clamp(0, byte.MaxValue), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, float r, float g, float b)
: this(a.GetPercentageFromByteValue(), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(float a, float r, float g, float b)
{
#region Constants
/// <summary>
/// Gets an transparent color [A: 0, R: 0, G: 0, B: 0]
/// </summary>
public static Color Transparent => new Color(0, 0, 0, 0);
#endregion
#region Properties & Fields
private static IColorBehavior _behavior = DefaultColorBehavior.Instance;
/// <summary>
/// Gets or sets the <see cref="IColorBehavior"/> used to perform operations on colors.
/// </summary>
public static IColorBehavior Behavior
{
get => _behavior;
set => _behavior = value ?? DefaultColorBehavior.Instance;
}
/// <summary>
/// Gets the alpha component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public double A { get; }
/// <summary>
/// Gets the red component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public double R { get; }
/// <summary>
/// Gets the green component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public double G { get; }
/// <summary>
/// Gets the blue component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public double B { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(byte r, byte g, byte b)
: this(byte.MaxValue, r, g, b)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using RGB-Values.
/// Alpha defaults to 255.
/// </summary>
/// <param name="r">The red component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="g">The green component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
/// <param name="b">The blue component value of this <see cref="T:RGB.NET.Core.Color" />.</param>
public Color(int r, int g, int b)
: this((byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, byte r, byte g, byte b)
: this(a.GetPercentageFromByteValue(), r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, int r, int g, int b)
: this((byte)a.Clamp(0, byte.MaxValue), (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using RGB-percent values.
/// Alpha defaults to 1.0.
/// </summary>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(double r, double g, double b)
: this(1.0, r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(double a, byte r, byte g, byte b)
: this(a, r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(double a, int r, int g, int b)
: this(a, (byte)r.Clamp(0, byte.MaxValue), (byte)g.Clamp(0, byte.MaxValue), (byte)b.Clamp(0, byte.MaxValue))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(int a, double r, double g, double b)
: this((byte)a.Clamp(0, byte.MaxValue), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(byte a, double r, double g, double b)
: this(a.GetPercentageFromByteValue(), r, g, b)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct using ARGB-percent values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="r">The red component value of this <see cref="Color"/>.</param>
/// <param name="g">The green component value of this <see cref="Color"/>.</param>
/// <param name="b">The blue component value of this <see cref="Color"/>.</param>
public Color(double a, double r, double g, double b)
{
A = a.Clamp(0, 1);
R = r.Clamp(0, 1);
G = g.Clamp(0, 1);
B = b.Clamp(0, 1);
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct by cloning a existing <see cref="T:RGB.NET.Core.Color" />.
/// </summary>
/// <param name="color">The <see cref="T:RGB.NET.Core.Color" /> the values are copied from.</param>
public Color(Color color)
: this(color.A, color.R, color.G, color.B)
{ }
#endregion
#region Methods
/// <summary>
/// Gets a human-readable string, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. Default format: "[A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => Behavior.ToString(this);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj) => Behavior.Equals(this, obj);
/// <summary>
/// Returns a hash code for this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
public override int GetHashCode() => Behavior.GetHashCode(this);
/// <summary>
/// Blends a <see cref="Color"/> over this color, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/> to blend.</param>
public Color Blend(Color color) => Behavior.Blend(this, color);
#endregion
#region Operators
/// <summary>
/// Blends the provided colors as if <see cref="Blend"/> would've been called on <paramref name="color1" />.
/// </summary>
/// <param name="color1">The base color.</param>
/// <param name="color2">The color to blend.</param>
/// <returns>The blended color.</returns>
public static Color operator +(Color color1, Color color2) => color1.Blend(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(Color color1, Color color2) => color1.Equals(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(Color color1, Color color2) => !(color1 == color2);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte r, byte g, byte b) components) => new Color(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte a, byte r, byte g, byte b) components) => new Color(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int r, int g, int b) components) => new Color(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int a, int r, int g, int b) components) => new Color(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((double r, double g, double b) components) => new Color(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((double a, double r, double g, double b) components) => new Color(components.a, components.r, components.g, components.b);
#endregion
A = a.Clamp(0, 1);
R = r.Clamp(0, 1);
G = g.Clamp(0, 1);
B = b.Clamp(0, 1);
}
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct by cloning a existing <see cref="T:RGB.NET.Core.Color" />.
/// </summary>
/// <param name="color">The <see cref="T:RGB.NET.Core.Color" /> the values are copied from.</param>
public Color(in Color color)
: this(color.A, color.R, color.G, color.B)
{ }
#endregion
#region Methods
/// <summary>
/// Gets a human-readable string, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. Default format: "[A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => Behavior.ToString(this);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => Behavior.Equals(this, obj);
/// <summary>
/// Returns a hash code for this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
// ReSharper disable once NonReadonlyMemberInGetHashCode
public override int GetHashCode() => Behavior.GetHashCode(this);
/// <summary>
/// Blends a <see cref="Color"/> over this color, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/> to blend.</param>
public Color Blend(in Color color) => Behavior.Blend(this, color);
#endregion
#region Operators
/// <summary>
/// Blends the provided colors as if <see cref="Blend"/> would've been called on <paramref name="color1" />.
/// </summary>
/// <param name="color1">The base color.</param>
/// <param name="color2">The color to blend.</param>
/// <returns>The blended color.</returns>
public static Color operator +(in Color color1, in Color color2) => color1.Blend(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Color color1, in Color color2) => color1.Equals(color2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Color" /> are equal.
/// </summary>
/// <param name="color1">The first <see cref="Color" /> to compare.</param>
/// <param name="color2">The second <see cref="Color" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="color1" /> and <paramref name="color2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Color color1, in Color color2) => !(color1 == color2);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte r, byte g, byte b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((byte a, byte r, byte g, byte b) components) => new(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int r, int g, int b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((int a, int r, int g, int b) components) => new(components.a, components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((float r, float g, float b) components) => new(components.r, components.g, components.b);
/// <summary>
/// Converts a <see cref="ValueTuple"/> of ARGB-components to a <see cref="Color"/>.
/// </summary>
/// <param name="components">The <see cref="ValueTuple"/> containing the components.</param>
/// <returns>The color.</returns>
public static implicit operator Color((float a, float r, float g, float b) components) => new(components.a, components.r, components.g, components.b);
#endregion
}

View File

@ -2,226 +2,227 @@
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the HSV color space.
/// </summary>
public static class HSVColor
{
public static class HSVColor
#region Getter
/// <summary>
/// Gets the hue component value (HSV-color space) of this <see cref="Color"/> as degree in the range [0..360].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The hue component value of the color.</returns>
public static float GetHue(this in Color color) => color.GetHSV().hue;
/// <summary>
/// Gets the saturation component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The saturation component value of the color.</returns>
public static float GetSaturation(this in Color color) => color.GetHSV().saturation;
/// <summary>
/// Gets the value component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The value component value of the color.</returns>
public static float GetValue(this in Color color) => color.GetHSV().value;
/// <summary>
/// Gets the hue, saturation and value component values (HSV-color space) of this <see cref="Color"/>.
/// Hue as degree in the range [0..1].
/// Saturation in the range [0..1].
/// Value in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the hue, saturation and value component value of the color.</returns>
public static (float hue, float saturation, float value) GetHSV(this in Color color)
=> CaclulateHSVFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to add.</param>
/// <param name="saturation">The saturation value to add.</param>
/// <param name="value">The value value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
#region Getter
/// <summary>
/// Gets the hue component value (HSV-color space) of this <see cref="Color"/> as degree in the range [0..360].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static double GetHue(this Color color) => color.GetHSV().hue;
/// <summary>
/// Gets the saturation component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static double GetSaturation(this Color color) => color.GetHSV().saturation;
/// <summary>
/// Gets the value component value (HSV-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static double GetValue(this Color color) => color.GetHSV().value;
/// <summary>
/// Gets the hue, saturation and value component values (HSV-color space) of this <see cref="Color"/>.
/// Hue as degree in the range [0..1].
/// Saturation in the range [0..1].
/// Value in the range [0..1].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static (double hue, double saturation, double value) GetHSV(this Color color)
=> CaclulateHSVFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the given HSV values to this color.
/// </summary>
/// <param name="hue">The hue value to add.</param>
/// <param name="saturation">The saturation value to add.</param>
/// <param name="value">The value value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddHSV(this Color color, double hue = 0, double saturation = 0, double value = 0)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
return Create(color.A, cHue + hue, cSaturation + saturation, cValue + value);
}
/// <summary>
/// Subtracts the given HSV values to this color.
/// </summary>
/// <param name="hue">The hue value to subtract.</param>
/// <param name="saturation">The saturation value to subtract.</param>
/// <param name="value">The value value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractHSV(this Color color, double hue = 0, double saturation = 0, double value = 0)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
return Create(color.A, cHue - hue, cSaturation - saturation, cValue - value);
}
/// <summary>
/// Multiplies the given HSV values to this color.
/// </summary>
/// <param name="hue">The hue value to multiply.</param>
/// <param name="saturation">The saturation value to multiply.</param>
/// <param name="value">The value value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyHSV(this Color color, double hue = 1, double saturation = 1, double value = 1)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
return Create(color.A, cHue * hue, cSaturation * saturation, cValue * value);
}
/// <summary>
/// Divides the given HSV values to this color.
/// </summary>
/// <param name="hue">The hue value to divide.</param>
/// <param name="saturation">The saturation value to divide.</param>
/// <param name="value">The value value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideHSV(this Color color, double hue = 1, double saturation = 1, double value = 1)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
return Create(color.A, cHue / hue, cSaturation / saturation, cValue / value);
}
/// <summary>
/// Sets the given hue value of this color.
/// </summary>
/// <param name="hue">The hue value to set.</param>
/// <param name="saturation">The saturation value to set.</param>
/// <param name="value">The value value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetHSV(this Color color, double? hue = null, double? saturation = null, double? value = null)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
return Create(color.A, hue ?? cHue, saturation ?? cSaturation, value ?? cValue);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using HSV-Values.
/// </summary>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(double hue, double saturation, double value)
=> Create(1.0, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte a, double hue, double saturation, double value)
=> Create((double)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int a, double hue, double saturation, double value)
=> Create((double)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(double a, double hue, double saturation, double value)
{
(double r, double g, double b) = CalculateRGBFromHSV(hue, saturation, value);
return new Color(a, r, g, b);
}
#endregion
#region Helper
private static (double h, double s, double v) CaclulateHSVFromRGB(double r, double g, double b)
{
if (r.EqualsInTolerance(g) && g.EqualsInTolerance(b)) return (0, 0, r);
double min = Math.Min(Math.Min(r, g), b);
double max = Math.Max(Math.Max(r, g), b);
double hue;
if (max.EqualsInTolerance(min))
hue = 0;
else if (max.EqualsInTolerance(r)) // r is max
hue = (g - b) / (max - min);
else if (max.EqualsInTolerance(g)) // g is max
hue = 2.0 + ((b - r) / (max - min));
else // b is max
hue = 4.0 + ((r - g) / (max - min));
hue = hue * 60.0;
hue = hue.Wrap(0, 360);
double saturation = max.EqualsInTolerance(0) ? 0 : 1.0 - (min / max);
double value = Math.Max(r, Math.Max(g, b));
return (hue, saturation, value);
}
private static (double r, double g, double b) CalculateRGBFromHSV(double h, double s, double v)
{
h = h.Wrap(0, 360);
s = s.Clamp(0, 1);
v = v.Clamp(0, 1);
if (s <= 0.0)
return (v, v, v);
double hh = h / 60.0;
int i = (int)hh;
double ff = hh - i;
double p = v * (1.0 - s);
double q = v * (1.0 - (s * ff));
double t = v * (1.0 - (s * (1.0 - ff)));
switch (i)
{
case 0:
return (v, t, p);
case 1:
return (q, v, p);
case 2:
return (p, v, t);
case 3:
return (p, q, v);
case 4:
return (t, p, v);
default:
return (v, p, q);
}
}
#endregion
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue + hue, cSaturation + saturation, cValue + value);
}
}
/// <summary>
/// Subtracts the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to subtract.</param>
/// <param name="saturation">The saturation value to subtract.</param>
/// <param name="value">The value value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue - hue, cSaturation - saturation, cValue - value);
}
/// <summary>
/// Multiplies the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to multiply.</param>
/// <param name="saturation">The saturation value to multiply.</param>
/// <param name="value">The value value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue * hue, cSaturation * saturation, cValue * value);
}
/// <summary>
/// Divides the specified HSV values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to divide.</param>
/// <param name="saturation">The saturation value to divide.</param>
/// <param name="value">The value value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue / hue, cSaturation / saturation, cValue / value);
}
/// <summary>
/// Sets the specified hue value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="hue">The hue value to set.</param>
/// <param name="saturation">The saturation value to set.</param>
/// <param name="value">The value value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetHSV(this in Color color, float? hue = null, float? saturation = null, float? value = null)
{
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, hue ?? cHue, saturation ?? cSaturation, value ?? cValue);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using HSV-Values.
/// </summary>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float hue, float saturation, float value)
=> Create(1.0f, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte a, float hue, float saturation, float value)
=> Create((float)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int a, float hue, float saturation, float value)
=> Create((float)a / byte.MaxValue, hue, saturation, value);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using AHSV-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="hue">The hue component value of this <see cref="Color"/>.</param>
/// <param name="saturation">The saturation component value of this <see cref="Color"/>.</param>
/// <param name="value">The value component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float a, float hue, float saturation, float value)
{
(float r, float g, float b) = CalculateRGBFromHSV(hue, saturation, value);
return new Color(a, r, g, b);
}
#endregion
#region Helper
private static (float h, float s, float v) CaclulateHSVFromRGB(float r, float g, float b)
{
if (r.EqualsInTolerance(g) && g.EqualsInTolerance(b)) return (0, 0, r);
float min = Math.Min(Math.Min(r, g), b);
float max = Math.Max(Math.Max(r, g), b);
float hue;
if (max.EqualsInTolerance(min))
hue = 0;
else if (max.EqualsInTolerance(r)) // r is max
hue = (g - b) / (max - min);
else if (max.EqualsInTolerance(g)) // g is max
hue = 2.0f + ((b - r) / (max - min));
else // b is max
hue = 4.0f + ((r - g) / (max - min));
hue *= 60.0f;
hue = hue.Wrap(0, 360);
float saturation = max.EqualsInTolerance(0) ? 0 : 1.0f - (min / max);
float value = Math.Max(r, Math.Max(g, b));
return (hue, saturation, value);
}
private static (float r, float g, float b) CalculateRGBFromHSV(float h, float s, float v)
{
h = h.Wrap(0, 360);
s = s.Clamp(0, 1);
v = v.Clamp(0, 1);
if (s <= 0.0)
return (v, v, v);
float hh = h / 60.0f;
int i = (int)hh;
float ff = hh - i;
float p = v * (1.0f - s);
float q = v * (1.0f - (s * ff));
float t = v * (1.0f - (s * (1.0f - ff)));
return i switch
{
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
_ => (v, p, q)
};
}
#endregion
}

View File

@ -0,0 +1,211 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the Hcl color space.
/// </summary>
public static class HclColor
{
#region Getter
/// <summary>
/// Gets the H component value (Hcl-color space) of this <see cref="Color"/> in the range [0..360].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The H component value of the color. </returns>
public static float GetHclH(this in Color color) => color.GetHcl().h;
/// <summary>
/// Gets the c component value (Hcl-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The c component value of the color. </returns>
public static float GetHclC(this in Color color) => color.GetHcl().c;
/// <summary>
/// Gets the l component value (Hcl-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The l component value of the color. </returns>
public static float GetHclL(this in Color color) => color.GetHcl().l;
/// <summary>
/// Gets the H, c and l component values (Hcl-color space) of this <see cref="Color"/>.
/// H in the range [0..360].
/// c in the range [0..1].
/// l in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the H, c and l component value of the color.</returns>
public static (float h, float c, float l) GetHcl(this in Color color)
=> CalculateHclFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to add.</param>
/// <param name="c">The c value to add.</param>
/// <param name="l">The l value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddHcl(this in Color color, float h = 0, float c = 0, float l = 0)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH + h, cC + c, cL + l);
}
/// <summary>
/// Subtracts the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to subtract.</param>
/// <param name="c">The c value to subtract.</param>
/// <param name="l">The l value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractHcl(this in Color color, float h = 0, float c = 0, float l = 0)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH - h, cC - c, cL - l);
}
/// <summary>
/// Multiplies the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to multiply.</param>
/// <param name="c">The c value to multiply.</param>
/// <param name="l">The l value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyHcl(this in Color color, float h = 1, float c = 1, float l = 1)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH * h, cC * c, cL * l);
}
/// <summary>
/// Divides the specified Hcl values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to divide.</param>
/// <param name="c">The c value to divide.</param>
/// <param name="l">The l value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideHcl(this in Color color, float h = 1, float c = 1, float l = 1)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, cH / h, cC / c, cL / l);
}
/// <summary>
/// Sets the specified X value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="h">The H value to set.</param>
/// <param name="c">The c value to set.</param>
/// <param name="l">The l value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetHcl(this in Color color, float? h = null, float? c = null, float? l = null)
{
(float cH, float cC, float cL) = color.GetHcl();
return Create(color.A, h ?? cH, c ?? cC, l ?? cL);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using Hcl-Values.
/// </summary>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float h, float c, float l)
=> Create(1.0f, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte alpha, float h, float c, float l)
=> Create((float)alpha / byte.MaxValue, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int alpha, float h, float c, float l)
=> Create((float)alpha / byte.MaxValue, h, c, l);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Hcl-Values.
/// </summary>
/// <param name="alpha">The alphc component value of this <see cref="Color"/>.</param>
/// <param name="h">The H component value of this <see cref="Color"/>.</param>
/// <param name="c">The c component value of this <see cref="Color"/>.</param>
/// <param name="l">The l component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float alpha, float h, float c, float l)
{
(float r, float g, float b) = CalculateRGBFromHcl(h, c, l);
return new Color(alpha, r, g, b);
}
#endregion
#region Helper
private static (float h, float c, float l) CalculateHclFromRGB(float r, float g, float b)
{
const float RADIANS_DEGREES_CONVERSION = 180.0f / MathF.PI;
// ReSharper disable once InconsistentNaming - b is used above
(float l, float a, float _b) = LabColor.CalculateLabFromRGB(r, g, b);
float h, c;
if (r.EqualsInTolerance(g) && r.EqualsInTolerance(b)) //DarthAffe 26.02.2021: The cumulated rounding errors are big enough to cause problems in that case
{
h = 0;
c = 0;
}
else
{
h = MathF.Atan2(_b, a);
if (h >= 0) h *= RADIANS_DEGREES_CONVERSION;
else h = 360 - (-h * RADIANS_DEGREES_CONVERSION);
c = MathF.Sqrt((a * a) + (_b * _b));
}
return (h, c, l);
}
private static (float r, float g, float b) CalculateRGBFromHcl(float h, float c, float l)
{
const float DEGREES_RADIANS_CONVERSION = MathF.PI / 180.0f;
h *= DEGREES_RADIANS_CONVERSION;
float a = c * MathF.Cos(h);
float b = c * MathF.Sin(h);
return LabColor.CalculateRGBFromLab(l, a, b);
}
#endregion
}

View File

@ -0,0 +1,227 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the Lab color space.
/// </summary>
public static class LabColor
{
#region Getter
/// <summary>
/// Gets the L component value (Lab-color space) of this <see cref="Color"/> in the range [0..100].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The L component value of the color.</returns>
public static float GetLabL(this in Color color) => color.GetLab().l;
/// <summary>
/// Gets the a component value (Lab-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The a component value of the color.</returns>
public static float GetLabA(this in Color color) => color.GetLab().a;
/// <summary>
/// Gets the b component value (Lab-color space) of this <see cref="Color"/> in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The b component value of the color.</returns>
public static float GetLabB(this in Color color) => color.GetLab().b;
/// <summary>
/// Gets the L, a and b component values (Lab-color space) of this <see cref="Color"/>.
/// L in the range [0..100].
/// a in the range [0..1].
/// b in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the L, a and b component value of the color.</returns>
public static (float l, float a, float b) GetLab(this in Color color)
=> CalculateLabFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to add.</param>
/// <param name="a">The a value to add.</param>
/// <param name="b">The b value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddLab(this in Color color, float l = 0, float a = 0, float b = 0)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL + l, cA + a, cB + b);
}
/// <summary>
/// Subtracts the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to subtract.</param>
/// <param name="a">The a value to subtract.</param>
/// <param name="b">The b value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractLab(this in Color color, float l = 0, float a = 0, float b = 0)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL - l, cA - a, cB - b);
}
/// <summary>
/// Multiplies the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to multiply.</param>
/// <param name="a">The a value to multiply.</param>
/// <param name="b">The b value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyLab(this in Color color, float l = 1, float a = 1, float b = 1)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL * l, cA * a, cB * b);
}
/// <summary>
/// Divides the specified Lab values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to divide.</param>
/// <param name="a">The a value to divide.</param>
/// <param name="b">The b value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideLab(this in Color color, float l = 1, float a = 1, float b = 1)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, cL / l, cA / a, cB / b);
}
/// <summary>
/// Sets the specified X valueof this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="l">The L value to set.</param>
/// <param name="a">The a value to set.</param>
/// <param name="b">The b value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetLab(this in Color color, float? l = null, float? a = null, float? b = null)
{
(float cL, float cA, float cB) = color.GetLab();
return Create(color.A, l ?? cL, a ?? cA, b ?? cB);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using Lab-Values.
/// </summary>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float l, float a, float b)
=> Create(1.0f, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte alpha, float l, float a, float b)
=> Create((float)alpha / byte.MaxValue, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int alpha, float l, float a, float b)
=> Create((float)alpha / byte.MaxValue, l, a, b);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and Lab-Values.
/// </summary>
/// <param name="alpha">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="l">The L component value of this <see cref="Color"/>.</param>
/// <param name="a">The a component value of this <see cref="Color"/>.</param>
/// <param name="b">The b component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float alpha, float l, float a, float b)
{
// ReSharper disable once InconsistentNaming - b is used above
(float r, float g, float _b) = CalculateRGBFromLab(l, a, b);
return new Color(alpha, r, g, _b);
}
#endregion
#region Helper
internal static (float l, float a, float b) CalculateLabFromRGB(float r, float g, float b)
{
(float x, float y, float z) = XYZColor.CaclulateXYZFromRGB(r, g, b);
return CaclulateLabFromXYZ(x, y, z);
}
internal static (float r, float g, float b) CalculateRGBFromLab(float l, float a, float b)
{
(float x, float y, float z) = CalculateXYZFromLab(l, a, b);
return XYZColor.CalculateRGBFromXYZ(x, y, z);
}
private static (float l, float a, float b) CaclulateLabFromXYZ(float x, float y, float z)
{
const float ONETHRID = 1.0f / 3.0f;
const float FACTOR2 = 16.0f / 116.0f;
x /= 95.047f;
y /= 100.0f;
z /= 108.883f;
x = ((x > 0.008856f) ? (MathF.Pow(x, ONETHRID)) : ((7.787f * x) + FACTOR2));
y = ((y > 0.008856f) ? (MathF.Pow(y, ONETHRID)) : ((7.787f * y) + FACTOR2));
z = ((z > 0.008856f) ? (MathF.Pow(z, ONETHRID)) : ((7.787f * z) + FACTOR2));
float l = (116.0f * y) - 16.0f;
float a = 500.0f * (x - y);
float b = 200.0f * (y - z);
return (l, a, b);
}
private static (float x, float y, float z) CalculateXYZFromLab(float l, float a, float b)
{
const float FACTOR2 = 16.0f / 116.0f;
float y = (l + 16.0f) / 116.0f;
float x = (a / 500.0f) + y;
float z = y - (b / 200.0f);
float powX = MathF.Pow(x, 3.0f);
float powY = MathF.Pow(y, 3.0f);
float powZ = MathF.Pow(z, 3.0f);
x = ((powX > 0.008856f) ? (powX) : ((x - FACTOR2) / 7.787f));
y = ((powY > 0.008856f) ? (powY) : ((y - FACTOR2) / 7.787f));
z = ((powZ > 0.008856f) ? (powZ) : ((z - FACTOR2) / 7.787f));
return (x * 95.047f, y * 100.0f, z * 108.883f);
}
#endregion
}

View File

@ -2,274 +2,294 @@
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the RGB color space.
/// </summary>
public static class RGBColor
{
public static class RGBColor
#region Getter
/// <summary>
/// Gets the A component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The A component value of the color.</returns>
public static byte GetA(this in Color color) => color.A.GetByteValueFromPercentage();
/// <summary>
/// Gets the R component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The R component value of the color.</returns>
public static byte GetR(this in Color color) => color.R.GetByteValueFromPercentage();
/// <summary>
/// Gets the G component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The G component value of the color.</returns>
public static byte GetG(this in Color color) => color.G.GetByteValueFromPercentage();
/// <summary>
/// Gets the B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The B component value of the color.</returns>
public static byte GetB(this in Color color) => color.B.GetByteValueFromPercentage();
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the A, R, G and B component value of the color.</returns>
public static (byte a, byte r, byte g, byte b) GetRGBBytes(this in Color color)
=> (color.GetA(), color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the A, R, G and B component value of the color.</returns>
public static (float a, float r, float g, float b) GetRGB(this in Color color)
=> (color.A, color.R, color.G, color.B);
#endregion
#region Manipulation
#region Add
/// <summary>
/// Adds the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this in Color color, int r = 0, int g = 0, int b = 0)
=> new(color.A, color.GetR() + r, color.GetG() + g, color.GetB() + b);
/// <summary>
/// Adds the specified RGB-percent values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this in Color color, float r = 0, float g = 0, float b = 0)
=> new(color.A, color.R + r, color.G + g, color.B + b);
/// <summary>
/// Adds the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this in Color color, int a)
=> new(color.GetA() + a, color.R, color.G, color.B);
/// <summary>
/// Adds the specified alpha-percent value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this in Color color, float a)
=> new(color.A + a, color.R, color.G, color.B);
#endregion
#region Subtract
/// <summary>
/// Subtracts the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this in Color color, int r = 0, int g = 0, int b = 0)
=> new(color.A, color.GetR() - r, color.GetG() - g, color.GetB() - b);
/// <summary>
/// Subtracts the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this in Color color, float r = 0, float g = 0, float b = 0)
=> new(color.A, color.R - r, color.G - g, color.B - b);
/// <summary>
/// Subtracts the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this in Color color, int a)
=> new(color.GetA() - a, color.R, color.G, color.B);
/// <summary>
/// Subtracts the specified alpha-percent value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="aPercent">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this in Color color, float aPercent)
=> new(color.A - aPercent, color.R, color.G, color.B);
#endregion
#region Multiply
/// <summary>
/// Multiplies the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to multiply.</param>
/// <param name="g">The green value to multiply.</param>
/// <param name="b">The blue value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyRGB(this in Color color, float r = 1, float g = 1, float b = 1)
=> new(color.A, color.R * r, color.G * g, color.B * b);
/// <summary>
/// Multiplies the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyA(this in Color color, float a)
=> new(color.A * a, color.R, color.G, color.B);
#endregion
#region Divide
/// <summary>
/// Divides the specified RGB values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to divide.</param>
/// <param name="g">The green value to divide.</param>
/// <param name="b">The blue value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideRGB(this in Color color, float r = 1, float g = 1, float b = 1)
=> new(color.A, color.R / r, color.G / g, color.B / b);
/// <summary>
/// Divides the specified alpha value to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideA(this in Color color, float a)
=> new(color.A / a, color.R, color.G, color.B);
#endregion
#region Set
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, byte? r = null, byte? g = null, byte? b = null)
=> new(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, int? r = null, int? g = null, int? b = null)
=> new(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the specified RGB value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this in Color color, float? r = null, float? g = null, float? b = null)
=> new(color.A, r ?? color.R, g ?? color.G, b ?? color.B);
/// <summary>
/// Sets the specified alpha value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this in Color color, int a) => new(a, color.R, color.G, color.B);
/// <summary>
/// Sets the specified alpha value of this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this in Color color, float a) => new(a, color.R, color.G, color.B);
#endregion
#endregion
#region Conversion
/// <summary>
/// Gets the current color as a RGB-HEX-string.
/// </summary>
/// <returns>The RGB-HEX-string.</returns>
public static string AsRGBHexString(this in Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the current color as a ARGB-HEX-string.
/// </summary>
/// <returns>The ARGB-HEX-string.</returns>
public static string AsARGBHexString(this in Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetA(), color.GetR(), color.GetG(), color.GetB());
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using a HEX-string.
/// </summary>
/// <param name="hexString">The HEX-representation of the color.</param>
/// <returns>The color created from the HEX-string.</returns>
public static Color FromHexString(string hexString)
{
#region Getter
/// <summary>
/// Gets the A component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static byte GetA(this Color color) => color.A.GetByteValueFromPercentage();
/// <summary>
/// Gets the R component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static byte GetR(this Color color) => color.R.GetByteValueFromPercentage();
/// <summary>
/// Gets the G component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static byte GetG(this Color color) => color.G.GetByteValueFromPercentage();
/// <summary>
/// Gets the B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static byte GetB(this Color color) => color.B.GetByteValueFromPercentage();
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as byte in the range [0..255].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static (byte a, byte r, byte g, byte b) GetRGBBytes(this Color color)
=> (color.GetA(), color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the A, R, G and B component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static (double a, double r, double g, double b) GetRGB(this Color color)
=> (color.A, color.R, color.G, color.B);
#endregion
#region Manipulation
#region Add
/// <summary>
/// Adds the given RGB values to this color.
/// </summary>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this Color color, int r = 0, int g = 0, int b = 0)
=> new Color(color.A, color.GetR() + r, color.GetG() + g, color.GetB() + b);
/// <summary>
/// Adds the given RGB-percent values to this color.
/// </summary>
/// <param name="r">The red value to add.</param>
/// <param name="g">The green value to add.</param>
/// <param name="b">The blue value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddRGB(this Color color, double r = 0, double g = 0, double b = 0)
=> new Color(color.A, color.R + r, color.G + g, color.B + b);
/// <summary>
/// Adds the given alpha value to this color.
/// </summary>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this Color color, int a)
=> new Color(color.GetA() + a, color.R, color.G, color.B);
/// <summary>
/// Adds the given alpha-percent value to this color.
/// </summary>
/// <param name="a">The alpha value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddA(this Color color, double a)
=> new Color(color.A + a, color.R, color.G, color.B);
#endregion
#region Subtract
/// <summary>
/// Subtracts the given RGB values to this color.
/// </summary>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this Color color, int r = 0, int g = 0, int b = 0)
=> new Color(color.A, color.GetR() - r, color.GetG() - g, color.GetB() - b);
/// <summary>
/// Subtracts the given RGB values to this color.
/// </summary>
/// <param name="r">The red value to subtract.</param>
/// <param name="g">The green value to subtract.</param>
/// <param name="b">The blue value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractRGB(this Color color, double r = 0, double g = 0, double b = 0)
=> new Color(color.A, color.R - r, color.G - g, color.B - b);
/// <summary>
/// Subtracts the given alpha value to this color.
/// </summary>
/// <param name="a">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this Color color, int a)
=> new Color(color.GetA() - a, color.R, color.G, color.B);
/// <summary>
/// Subtracts the given alpha-percent value to this color.
/// </summary>
/// <param name="a">The alpha value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractA(this Color color, double aPercent)
=> new Color(color.A - aPercent, color.R, color.G, color.B);
#endregion
#region Multiply
/// <summary>
/// Multiplies the given RGB values to this color.
/// </summary>
/// <param name="r">The red value to multiply.</param>
/// <param name="g">The green value to multiply.</param>
/// <param name="b">The blue value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyRGB(this Color color, double r = 1, double g = 1, double b = 1)
=> new Color(color.A, color.R * r, color.G * g, color.B * b);
/// <summary>
/// Multiplies the given alpha value to this color.
/// </summary>
/// <param name="a">The alpha value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyA(this Color color, double a)
=> new Color(color.A * a, color.R, color.G, color.B);
#endregion
#region Divide
/// <summary>
/// Divides the given RGB values to this color.
/// </summary>
/// <param name="r">The red value to divide.</param>
/// <param name="g">The green value to divide.</param>
/// <param name="b">The blue value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideRGB(this Color color, double r = 1, double g = 1, double b = 1)
=> new Color(color.A, color.R / r, color.G / g, color.B / b);
/// <summary>
/// Divides the given alpha value to this color.
/// </summary>
/// <param name="a">The alpha value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideA(this Color color, double a)
=> new Color(color.A / a, color.R, color.G, color.B);
#endregion
#region Set
/// <summary>
/// Sets the given RGB value of this color.
/// </summary>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this Color color, byte? r = null, byte? g = null, byte? b = null)
=> new Color(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the given RGB value of this color.
/// </summary>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this Color color, int? r = null, int? g = null, int? b = null)
=> new Color(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
/// <summary>
/// Sets the given RGB value of this color.
/// </summary>
/// <param name="r">The red value to set.</param>
/// <param name="g">The green value to set.</param>
/// <param name="b">The blue value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetRGB(this Color color, double? r = null, double? g = null, double? b = null)
=> new Color(color.A, r ?? color.R, g ?? color.G, b ?? color.B);
/// <summary>
/// Sets the given alpha value of this color.
/// </summary>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this Color color, int a) => new Color(a, color.R, color.G, color.B);
/// <summary>
/// Sets the given alpha value of this color.
/// </summary>
/// <param name="a">The alpha value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetA(this Color color, double a) => new Color(a, color.R, color.G, color.B);
#endregion
#endregion
#region Conversion
/// <summary>
/// Gets the current color as a RGB-HEX-string.
/// </summary>
/// <returns>The RGB-HEX-string.</returns>
public static string AsRGBHexString(this Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetR(), color.GetG(), color.GetB());
/// <summary>
/// Gets the current color as a ARGB-HEX-string.
/// </summary>
/// <returns>The ARGB-HEX-string.</returns>
public static string AsARGBHexString(this Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetA(), color.GetR(), color.GetG(), color.GetB());
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using a HEX-string.
/// </summary>
/// <param name="hexString">The HEX-representation of the color.</param>
/// <returns>The color created from the HEX-string.</returns>
public static Color FromHexString(string hexString)
{
if ((hexString == null) || (hexString.Length < 6))
throw new ArgumentException("Invalid hex string", nameof(hexString));
if (hexString[0] == '#')
hexString = hexString.Substring(1);
byte[] data = ConversionHelper.HexToBytes(hexString);
if (data.Length == 3)
return new Color(data[0], data[1], data[2]);
if (data.Length == 4)
return new Color(data[0], data[1], data[2], data[3]);
if ((hexString == null) || (hexString.Length < 6))
throw new ArgumentException("Invalid hex string", nameof(hexString));
}
#endregion
ReadOnlySpan<char> span = hexString.AsSpan();
if (span[0] == '#')
span = span[1..];
byte[] data = ConversionHelper.HexToBytes(span);
return data.Length switch
{
3 => new Color(data[0], data[1], data[2]),
4 => new Color(data[0], data[1], data[2], data[3]),
_ => throw new ArgumentException($"Invalid hex string '{hexString}'", nameof(hexString))
};
}
}
#endregion
}

View File

@ -0,0 +1,207 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
namespace RGB.NET.Core;
/// <summary>
/// Contains helper-methods and extension for the <see cref="Color"/>-type to work in the XYZ color space.
/// </summary>
public static class XYZColor
{
#region Getter
/// <summary>
/// Gets the X component value (XYZ-color space) of this <see cref="Color"/> in the range [0..95.047].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The X component value of the color.</returns>
public static float GetX(this in Color color) => color.GetXYZ().x;
/// <summary>
/// Gets the Y component value (XYZ-color space) of this <see cref="Color"/> in the range [0..100].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The Y component value of the color.</returns>
public static float GetY(this in Color color) => color.GetXYZ().y;
/// <summary>
/// Gets the Z component value (XYZ-color space) of this <see cref="Color"/> in the range [0..108.883].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>The Z component value of the color.</returns>
public static float GetZ(this in Color color) => color.GetXYZ().z;
/// <summary>
/// Gets the X, Y and Z component values (XYZ-color space) of this <see cref="Color"/>.
/// X in the range [0..95.047].
/// Y in the range [0..100].
/// Z in the range [0..108.883].
/// </summary>
/// <param name="color">The color to get the value from.</param>
/// <returns>A tuple containing the X, Y and Z component value of the color.</returns>
public static (float x, float y, float z) GetXYZ(this in Color color)
=> CaclulateXYZFromRGB(color.R, color.G, color.B);
#endregion
#region Manipulation
/// <summary>
/// Adds the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to add.</param>
/// <param name="y">The Y value to add.</param>
/// <param name="z">The Z value to add.</param>
/// <returns>The new color after the modification.</returns>
public static Color AddXYZ(this in Color color, float x = 0, float y = 0, float z = 0)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX + x, cY + y, cZ + z);
}
/// <summary>
/// Subtracts the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to subtract.</param>
/// <param name="y">The Y value to subtract.</param>
/// <param name="z">The Z value to subtract.</param>
/// <returns>The new color after the modification.</returns>
public static Color SubtractXYZ(this in Color color, float x = 0, float y = 0, float z = 0)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX - x, cY - y, cZ - z);
}
/// <summary>
/// Multiplies the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to multiply.</param>
/// <param name="y">The Y value to multiply.</param>
/// <param name="z">The Z value to multiply.</param>
/// <returns>The new color after the modification.</returns>
public static Color MultiplyXYZ(this in Color color, float x = 1, float y = 1, float z = 1)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX * x, cY * y, cZ * z);
}
/// <summary>
/// Divides the specified XYZ values to this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to divide.</param>
/// <param name="y">The Y value to divide.</param>
/// <param name="z">The Z value to divide.</param>
/// <returns>The new color after the modification.</returns>
public static Color DivideXYZ(this in Color color, float x = 1, float y = 1, float z = 1)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, cX / x, cY / y, cZ / z);
}
/// <summary>
/// Sets the specified X valueof this color.
/// </summary>
/// <param name="color">The color to modify.</param>
/// <param name="x">The X value to set.</param>
/// <param name="y">The Y value to set.</param>
/// <param name="z">The Z value to set.</param>
/// <returns>The new color after the modification.</returns>
public static Color SetXYZ(this in Color color, float? x = null, float? y = null, float? z = null)
{
(float cX, float cY, float cZ) = color.GetXYZ();
return Create(color.A, x ?? cX, y ?? cY, z ?? cZ);
}
#endregion
#region Factory
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using XYZ-Values.
/// </summary>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float x, float y, float z)
=> Create(1.0f, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(byte a, float x, float y, float z)
=> Create((float)a / byte.MaxValue, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(int a, float x, float y, float z)
=> Create((float)a / byte.MaxValue, x, y, z);
/// <summary>
/// Creates a new instance of the <see cref="T:RGB.NET.Core.Color" /> struct using alpha and XYZ-Values.
/// </summary>
/// <param name="a">The alpha component value of this <see cref="Color"/>.</param>
/// <param name="x">The X component value of this <see cref="Color"/>.</param>
/// <param name="y">The Y component value of this <see cref="Color"/>.</param>
/// <param name="z">The Z component value of this <see cref="Color"/>.</param>
/// <returns>The color created from the values.</returns>
public static Color Create(float a, float x, float y, float z)
{
(float r, float g, float b) = CalculateRGBFromXYZ(x, y, z);
return new Color(a, r, g, b);
}
#endregion
#region Helper
internal static (float x, float y, float z) CaclulateXYZFromRGB(float r, float g, float b)
{
r = ((r > 0.04045f) ? MathF.Pow(((r + 0.055f) / 1.055f), 2.4f) : (r / 12.92f)) * 100.0f;
g = ((g > 0.04045f) ? MathF.Pow(((g + 0.055f) / 1.055f), 2.4f) : (g / 12.92f)) * 100.0f;
b = ((b > 0.04045f) ? MathF.Pow(((b + 0.055f) / 1.055f), 2.4f) : (b / 12.92f)) * 100.0f;
float x = (r * 0.4124f) + (g * 0.3576f) + (b * 0.1805f);
float y = (r * 0.2126f) + (g * 0.7152f) + (b * 0.0722f);
float z = (r * 0.0193f) + (g * 0.1192f) + (b * 0.9505f);
return (x, y, z);
}
internal static (float r, float g, float b) CalculateRGBFromXYZ(float x, float y, float z)
{
const float INVERSE_EXPONENT = 1.0f / 2.4f;
x /= 100.0f;
y /= 100.0f;
z /= 100.0f;
float r = (x * 3.2406f) + (y * -1.5372f) + (z * -0.4986f);
float g = (x * -0.9689f) + (y * 1.8758f) + (z * 0.0415f);
float b = (x * 0.0557f) + (y * -0.2040f) + (z * 1.0570f);
r = ((r > 0.0031308f) ? ((1.055f * (MathF.Pow(r, INVERSE_EXPONENT))) - 0.055f) : (12.92f * r));
g = ((g > 0.0031308f) ? ((1.055f * (MathF.Pow(g, INVERSE_EXPONENT))) - 0.055f) : (12.92f * g));
b = ((b > 0.0031308f) ? ((1.055f * (MathF.Pow(b, INVERSE_EXPONENT))) - 0.055f) : (12.92f * b));
return (r, g, b);
}
#endregion
}

View File

@ -1,16 +1,15 @@
// ReSharper disable UnusedMember.Global
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic color-correction.
/// </summary>
public interface IColorCorrection
{
/// <summary>
/// Represents a generic color-correction.
/// Applies the <see cref="IColorCorrection"/> to the specified <see cref="Color"/>.
/// </summary>
public interface IColorCorrection
{
/// <summary>
/// Applies the <see cref="IColorCorrection"/> to the given <see cref="Color"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/> to correct.</param>
Color ApplyTo(Color color);
}
}
/// <param name="color">The <see cref="Color"/> to correct.</param>
void ApplyTo(ref Color color);
}

View File

@ -2,64 +2,68 @@
using System.Collections.ObjectModel;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecoratable{T}" />
public abstract class AbstractDecoratable<T> : AbstractBindable, IDecoratable<T>
where T : IDecorator
{
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecoratable{T}" />
public abstract class AbstractDecoratable<T> : AbstractBindable, IDecoratable<T>
where T : IDecorator
#region Properties & Fields
private readonly List<T> _decorators = new();
/// <inheritdoc />
public IReadOnlyList<T> Decorators { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractDecoratable{T}"/> class.
/// </summary>
protected AbstractDecoratable()
{
#region Properties & Fields
private readonly List<T> _decorators = new List<T>();
/// <inheritdoc />
public IReadOnlyCollection<T> Decorators
{
get
{
lock (_decorators)
return new ReadOnlyCollection<T>(_decorators);
}
}
#endregion
#region Methods
/// <inheritdoc />
public void AddDecorator(T decorator)
{
lock (Decorators)
{
_decorators.Add(decorator);
_decorators.Sort((d1, d2) => d1.Order.CompareTo(d2.Order));
}
decorator.OnAttached(this);
}
/// <inheritdoc />
public void RemoveDecorator(T decorator)
{
lock (Decorators)
_decorators.Remove(decorator);
decorator.OnDetached(this);
}
/// <inheritdoc />
public void RemoveAllDecorators()
{
IEnumerable<T> decorators;
lock (Decorators)
decorators = Decorators.ToList();
foreach (T decorator in decorators)
RemoveDecorator(decorator);
}
#endregion
Decorators = new ReadOnlyCollection<T>(_decorators);
}
}
#endregion
#region Methods
/// <inheritdoc />
public void AddDecorator(T decorator)
{
lock (Decorators)
{
_decorators.Add(decorator);
_decorators.Sort((d1, d2) => d1.Order.CompareTo(d2.Order));
}
decorator.OnAttached(this);
}
/// <inheritdoc />
public void RemoveDecorator(T decorator)
{
lock (Decorators)
_decorators.Remove(decorator);
decorator.OnDetached(this);
}
/// <inheritdoc />
public void RemoveAllDecorators()
{
IEnumerable<T> decorators;
lock (Decorators)
decorators = Decorators.ToList();
foreach (T decorator in decorators)
RemoveDecorator(decorator);
}
#endregion
}

View File

@ -2,61 +2,60 @@
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecorator" />
public abstract class AbstractDecorator : AbstractBindable, IDecorator
{
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IDecorator" />
public abstract class AbstractDecorator : AbstractBindable, IDecorator
#region Properties & Fields
private bool _isEnabled = true;
/// <inheritdoc />
public bool IsEnabled
{
#region Properties & Fields
private bool _isEnabled = true;
/// <inheritdoc />
public bool IsEnabled
{
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
private int _order;
/// <inheritdoc />
public int Order
{
get => _order;
set => SetProperty(ref _order, value);
}
/// <summary>
/// Gets a readonly-list of all <see cref="IDecoratable"/> this decorator is attached to.
/// </summary>
protected List<IDecoratable> DecoratedObjects { get; } = new List<IDecoratable>();
#endregion
#region Methods
/// <inheritdoc />
public virtual void OnAttached(IDecoratable decoratable) => DecoratedObjects.Add(decoratable);
/// <inheritdoc />
public virtual void OnDetached(IDecoratable decoratable) => DecoratedObjects.Remove(decoratable);
/// <summary>
/// Detaches the decorator from all <see cref="IDecoratable"/> it is currently attached to.
/// </summary>
protected virtual void Detach()
{
List<IDecoratable> decoratables = new List<IDecoratable>(DecoratedObjects);
foreach (IDecoratable decoratable in decoratables)
{
IEnumerable<Type> types = decoratable.GetType().GetInterfaces().Where(t => t.IsGenericType
&& (t.Name == typeof(IDecoratable<>).Name)
&& t.GenericTypeArguments[0].IsInstanceOfType(this));
foreach (Type decoratableType in types)
decoratableType.GetMethod(nameof(IDecoratable<IDecorator>.RemoveDecorator))?.Invoke(decoratable, new object[] { this });
}
}
#endregion
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
}
private int _order;
/// <inheritdoc />
public int Order
{
get => _order;
set => SetProperty(ref _order, value);
}
/// <summary>
/// Gets a readonly-list of all <see cref="IDecoratable"/> this decorator is attached to.
/// </summary>
protected List<IDecoratable> DecoratedObjects { get; } = new();
#endregion
#region Methods
/// <inheritdoc />
public virtual void OnAttached(IDecoratable decoratable) => DecoratedObjects.Add(decoratable);
/// <inheritdoc />
public virtual void OnDetached(IDecoratable decoratable) => DecoratedObjects.Remove(decoratable);
/// <summary>
/// Detaches the decorator from all <see cref="IDecoratable"/> it is currently attached to.
/// </summary>
protected virtual void Detach()
{
List<IDecoratable> decoratables = new(DecoratedObjects);
foreach (IDecoratable decoratable in decoratables)
{
IEnumerable<Type> types = decoratable.GetType().GetInterfaces().Where(t => t.IsGenericType
&& (t.Name == typeof(IDecoratable<>).Name)
&& t.GenericTypeArguments[0].IsInstanceOfType(this));
foreach (Type decoratableType in types)
decoratableType.GetMethod(nameof(IDecoratable<IDecorator>.RemoveDecorator))?.Invoke(decoratable, new object[] { this });
}
}
#endregion
}

View File

@ -1,65 +1,71 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a basic decorator which is aware of the <see cref="E:RGB.NET.Core.RGBSurface.Updating" /> event.
/// </summary>
public abstract class AbstractUpdateAwareDecorator : AbstractDecorator
{
/// <inheritdoc />
#region Properties & Fields
/// <summary>
/// Represents a basic decorator which is aware of the <see cref="E:RGB.NET.Core.RGBSurface.Updating" /> event.
/// Gets the surface this decorator is attached to.
/// </summary>
public abstract class AbstractUpdateAwareDecorator : AbstractDecorator
protected RGBSurface Surface { get; }
/// <summary>
/// Gets or sets if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.
/// </summary>
protected bool UpdateIfDisabled { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractUpdateAwareDecorator"/> class.
/// </summary>
/// <param name="surface">The surface this decorator is attached to.</param>
/// <param name="updateIfDisabled">Bool indicating if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.</param>
protected AbstractUpdateAwareDecorator(RGBSurface surface, bool updateIfDisabled = false)
{
#region Properties & Fields
/// <summary>
/// Gets or sets if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.
/// </summary>
protected bool UpdateIfDisabled { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractUpdateAwareDecorator"/> class.
/// </summary>
/// <param name="updateIfDisabled">Bool indicating if the <see cref="AbstractUpdateAwareDecorator"/> should call <see cref="Update"/> even if the Decorator is disabled.</param>
protected AbstractUpdateAwareDecorator(bool updateIfDisabled = false)
{
this.UpdateIfDisabled = updateIfDisabled;
}
#endregion
#region Methods
/// <inheritdoc />
public override void OnAttached(IDecoratable decoratable)
{
if (DecoratedObjects.Count == 0)
RGBSurface.Instance.Updating += OnSurfaceUpdating;
base.OnAttached(decoratable);
}
/// <inheritdoc />
public override void OnDetached(IDecoratable decoratable)
{
base.OnDetached(decoratable);
if (DecoratedObjects.Count == 0)
RGBSurface.Instance.Updating -= OnSurfaceUpdating;
}
private void OnSurfaceUpdating(UpdatingEventArgs args)
{
if (IsEnabled || UpdateIfDisabled)
Update(args.DeltaTime);
}
/// <summary>
/// Updates this <see cref="AbstractUpdateAwareDecorator"/>.
/// </summary>
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
protected abstract void Update(double deltaTime);
#endregion
this.Surface = surface;
this.UpdateIfDisabled = updateIfDisabled;
}
}
#endregion
#region Methods
/// <inheritdoc />
public override void OnAttached(IDecoratable decoratable)
{
if (DecoratedObjects.Count == 0)
Surface.Updating += OnSurfaceUpdating;
base.OnAttached(decoratable);
}
/// <inheritdoc />
public override void OnDetached(IDecoratable decoratable)
{
base.OnDetached(decoratable);
if (DecoratedObjects.Count == 0)
Surface.Updating -= OnSurfaceUpdating;
}
private void OnSurfaceUpdating(UpdatingEventArgs args)
{
if (IsEnabled || UpdateIfDisabled)
Update(args.DeltaTime);
}
/// <summary>
/// Updates this <see cref="AbstractUpdateAwareDecorator"/>.
/// </summary>
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
protected abstract void Update(double deltaTime);
#endregion
}

View File

@ -1,17 +1,16 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a <see cref="T:RGB.NET.Core.IDecorator" /> decorating a <see cref="T:RGB.NET.Core.IBrush" />.
/// </summary>
public interface IBrushDecorator : IDecorator
{
/// <inheritdoc />
/// <summary>
/// Represents a <see cref="T:RGB.NET.Core.IDecorator" /> decorating a <see cref="T:RGB.NET.Core.IBrush" />.
/// Decorator-Method called by the <see cref="IBrush"/>.
/// </summary>
public interface IBrushDecorator : IDecorator
{
/// <summary>
/// Decorator-Method called by the <see cref="IBrush"/>.
/// </summary>
/// <param name="rectangle">The rectangle in which the <see cref="IBrush"/> should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the <see cref="Color"/> should be taken.</param>
/// <param name="color">The <see cref="Color"/> to be modified.</param>
Color ManipulateColor(Rectangle rectangle, BrushRenderTarget renderTarget, Color color);
}
}
/// <param name="rectangle">The rectangle in which the <see cref="IBrush"/> should be drawn.</param>
/// <param name="renderTarget">The target (key/point) from which the <see cref="Color"/> should be taken.</param>
/// <param name="color">The <see cref="Color"/> to be modified.</param>
void ManipulateColor(in Rectangle rectangle, in RenderTarget renderTarget, ref Color color);
}

View File

@ -1,42 +1,41 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic decoratable.
/// </summary>
public interface IDecoratable : INotifyPropertyChanged
{ }
/// <inheritdoc />
/// <summary>
/// Represents a basic decoratable for a specific type of <see cref="T:RGB.NET.Core.IDecorator" />
/// </summary>
/// <typeparam name="T">The type of decorators this decoratable can be decorated with.</typeparam>
public interface IDecoratable<T> : IDecoratable
where T : IDecorator
{
/// <summary>
/// Represents a basic decoratable.
/// Gets a readonly-list of all <see cref="IDecorator"/> attached to this <see cref="IDecoratable{T}"/>.
/// </summary>
public interface IDecoratable : INotifyPropertyChanged
{ }
IReadOnlyList<T> Decorators { get; }
/// <inheritdoc />
/// <summary>
/// Represents a basic decoratable for a specific type of <see cref="T:RGB.NET.Core.IDecorator" />
/// Adds an <see cref="IDecorator"/> to the <see cref="IDecoratable"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IDecoratable<T> : IDecoratable
where T : IDecorator
{
/// <summary>
/// Gets a readonly-list of all <see cref="IDecorator"/> attached to this <see cref="IDecoratable{T}"/>.
/// </summary>
IReadOnlyCollection<T> Decorators { get; }
/// <param name="decorator">The <see cref="IDecorator"/> to be added.</param>
void AddDecorator(T decorator);
/// <summary>
/// Adds an <see cref="IDecorator"/> to the <see cref="IDecoratable"/>.
/// </summary>
/// <param name="decorator">The <see cref="IDecorator"/> to be added.</param>
void AddDecorator(T decorator);
/// <summary>
/// Removes an <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
/// <param name="decorator">The <see cref="IDecorator"/> to be removed.</param>
void RemoveDecorator(T decorator);
/// <summary>
/// Removes an <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
/// <param name="decorator">The <see cref="IDecorator"/> to be removed.</param>
void RemoveDecorator(T decorator);
/// <summary>
/// Removes all <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
void RemoveAllDecorators();
}
}
/// <summary>
/// Removes all <see cref="IDecorator"/> from the <see cref="IDecoratable"/>.
/// </summary>
void RemoveAllDecorators();
}

View File

@ -1,39 +1,38 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic decorator.
/// </summary>
public interface IDecorator
{
#region Properties & Fields
/// <summary>
/// Represents a basic decorator.
/// Gets or sets if the <see cref="IDecorator"/> is enabled and will be used.
/// </summary>
public interface IDecorator
{
#region Properties & Fields
bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets if the <see cref="IDecorator"/> is enabled and will be used.
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// Gets or sets the order in which multiple decorators should be applied on the same object.
/// Higher orders are processed first.
/// </summary>
int Order { get; set; }
/// <summary>
/// Gets or sets the order in which multiple decorators should be applied on the same object.
/// Higher orders are processed first.
/// </summary>
int Order { get; set; }
#endregion
#endregion
#region Methods
#region Methods
/// <summary>
/// Attaches this <see cref="IDecorator"/> to the specified target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be attached to.</param>
void OnAttached(IDecoratable decoratable);
/// <summary>
/// Attaches this <see cref="IDecorator"/> to the given target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be attached to.</param>
void OnAttached(IDecoratable decoratable);
/// <summary>
/// Detaches this <see cref="IDecorator"/> from the specified target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be detached from.</param>
void OnDetached(IDecoratable decoratable);
/// <summary>
/// Detaches this <see cref="IDecorator"/> from the given target.
/// </summary>
/// <param name="decoratable">The object this <see cref="IDecorator"/> should be detached from.</param>
void OnDetached(IDecoratable decoratable);
#endregion
}
}
#endregion
}

View File

@ -1,9 +1,8 @@
namespace RGB.NET.Core
{
/// <inheritdoc />
/// <summary>
/// Represents a basic decorator decorating a <see cref="T:RGB.NET.Core.ILedGroup" />.
/// </summary>
public interface ILedGroupDecorator : IDecorator
{ }
}
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a basic decorator decorating a <see cref="T:RGB.NET.Core.ILedGroup" />.
/// </summary>
public interface ILedGroupDecorator : IDecorator
{ }

View File

@ -2,297 +2,237 @@
// ReSharper disable UnusedMember.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using RGB.NET.Core.Layout;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a generic RGB-device.
/// </summary>
public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDeviceInfo>
where TDeviceInfo : class, IRGBDeviceInfo
{
/// <inheritdoc cref="AbstractBindable" />
/// <inheritdoc cref="IRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a generic RGB-device.
/// </summary>
public abstract class AbstractRGBDevice<TDeviceInfo> : AbstractBindable, IRGBDevice<TDeviceInfo>
where TDeviceInfo : class, IRGBDeviceInfo
private RGBSurface? _surface;
#region Properties & Fields
RGBSurface? IRGBDevice.Surface
{
#region Properties & Fields
/// <inheritdoc />
public abstract TDeviceInfo DeviceInfo { get; }
/// <inheritdoc />
IRGBDeviceInfo IRGBDevice.DeviceInfo => DeviceInfo;
private Point _location = new Point(0, 0);
/// <inheritdoc />
public Point Location
get => _surface;
set
{
get => _location;
set
if (SetProperty(ref _surface, value))
{
if (SetProperty(ref _location, value))
UpdateActualData();
if (value == null) OnDetached();
else OnAttached();
}
}
private Size _size = Size.Invalid;
/// <inheritdoc />
public Size Size
{
get => _size;
protected set
{
if (SetProperty(ref _size, value))
UpdateActualData();
}
}
private Size _actualSize;
/// <inheritdoc />
public Size ActualSize
{
get => _actualSize;
private set => SetProperty(ref _actualSize, value);
}
private Rectangle _deviceRectangle;
/// <inheritdoc />
public Rectangle DeviceRectangle
{
get => _deviceRectangle;
private set => SetProperty(ref _deviceRectangle, value);
}
private Scale _scale = new Scale(1);
/// <inheritdoc />
public Scale Scale
{
get => _scale;
set
{
if (SetProperty(ref _scale, value))
UpdateActualData();
}
}
private Rotation _rotation = new Rotation(0);
/// <inheritdoc />
public Rotation Rotation
{
get => _rotation;
set
{
if (SetProperty(ref _rotation, value))
UpdateActualData();
}
}
/// <summary>
/// Gets or sets if the device needs to be flushed on every update.
/// </summary>
protected bool RequiresFlush { get; set; } = false;
/// <inheritdoc />
public DeviceUpdateMode UpdateMode { get; set; } = DeviceUpdateMode.Sync;
/// <summary>
/// Gets a dictionary containing all <see cref="Led"/> of the <see cref="IRGBDevice"/>.
/// </summary>
protected Dictionary<LedId, Led> LedMapping { get; } = new Dictionary<LedId, Led>();
/// <summary>
/// Gets a dictionary containing all <see cref="IRGBDeviceSpecialPart"/> associated with this <see cref="IRGBDevice"/>.
/// </summary>
protected Dictionary<Type, IRGBDeviceSpecialPart> SpecialDeviceParts { get; } = new Dictionary<Type, IRGBDeviceSpecialPart>();
#region Indexer
/// <inheritdoc />
Led IRGBDevice.this[LedId ledId] => LedMapping.TryGetValue(ledId, out Led led) ? led : null;
/// <inheritdoc />
Led IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.LedRectangle.Contains(location));
/// <inheritdoc />
IEnumerable<Led> IRGBDevice.this[Rectangle referenceRect, double minOverlayPercentage]
=> LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.LedRectangle) >= minOverlayPercentage);
#endregion
#endregion
#region Methods
private void UpdateActualData()
{
ActualSize = Size * Scale;
DeviceRectangle = new Rectangle(Location, new Rectangle(new Rectangle(Location, ActualSize).Rotate(Rotation)).Size);
}
/// <inheritdoc />
public virtual void Update(bool flushLeds = false)
{
// Device-specific updates
DeviceUpdate();
// Send LEDs to SDK
List<Led> ledsToUpdate = GetLedsToUpdate(flushLeds)?.ToList() ?? new List<Led>();
foreach (Led ledToUpdate in ledsToUpdate)
ledToUpdate.Update();
if (UpdateMode.HasFlag(DeviceUpdateMode.Sync))
UpdateLeds(ledsToUpdate);
}
protected virtual IEnumerable<Led> GetLedsToUpdate(bool flushLeds) => ((RequiresFlush || flushLeds) ? LedMapping.Values : LedMapping.Values.Where(x => x.IsDirty));
/// <inheritdoc />
public virtual void SyncBack()
{ }
/// <inheritdoc />
public virtual void Dispose()
{
try
{
SpecialDeviceParts.Clear();
LedMapping.Clear();
}
catch { /* this really shouldn't happen */ }
}
/// <summary>
/// Performs device specific updates.
/// </summary>
protected virtual void DeviceUpdate()
{ }
/// <summary>
/// Sends all the updated <see cref="Led"/> to the device.
/// </summary>
protected abstract void UpdateLeds(IEnumerable<Led> ledsToUpdate);
/// <summary>
/// Initializes the <see cref="Led"/> with the specified id.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> to initialize.</param>
/// <param name="ledRectangle">The <see cref="Rectangle"/> representing the position of the <see cref="Led"/> to initialize.</param>
/// <returns></returns>
[Obsolete("Use InitializeLed(LedId ledId, Point location, Size size) instead.")]
protected virtual Led InitializeLed(LedId ledId, Rectangle rectangle) => InitializeLed(ledId, rectangle.Location, rectangle.Size);
/// <summary>
/// Initializes the <see cref="Led"/> with the specified id.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> to initialize.</param>
/// <param name="location">The location of the <see cref="Led"/> to initialize.</param>
/// <param name="size">The size of the <see cref="Led"/> to initialize.</param>
/// <returns>The initialized led.</returns>
protected virtual Led InitializeLed(LedId ledId, Point location, Size size)
{
if ((ledId == LedId.Invalid) || LedMapping.ContainsKey(ledId)) return null;
Led led = new Led(this, ledId, location, size, CreateLedCustomData(ledId));
LedMapping.Add(ledId, led);
return led;
}
/// <summary>
/// Applies the give <see cref="Color"/> to the <see cref="Led"/> ignoring internal workflows regarding locks and update-requests.
/// This should be only used for syncbacks!
/// </summary>
/// <param name="led">The <see cref="Led"/> the <see cref="Color"/> should be aplied to.</param>
/// <param name="color">The <see cref="Color"/> to apply.</param>
protected virtual void SetLedColorWithoutRequest(Led led, Color color)
{
if (led == null) return;
led.InternalColor = color;
}
/// <summary>
/// Applies the given layout.
/// </summary>
/// <param name="layoutPath">The file containing the layout.</param>
/// <param name="imageLayout">The name of the layout used to get the images of the leds.</param>
/// <param name="createMissingLeds">If set to true a new led is initialized for every id in the layout if it doesn't already exist.</param>
protected virtual DeviceLayout ApplyLayoutFromFile(string layoutPath, string imageLayout, bool createMissingLeds = false)
{
DeviceLayout layout = DeviceLayout.Load(layoutPath);
if (layout != null)
{
string imageBasePath = string.IsNullOrWhiteSpace(layout.ImageBasePath) ? null : PathHelper.GetAbsolutePath(this, layout.ImageBasePath);
if ((imageBasePath != null) && !string.IsNullOrWhiteSpace(layout.DeviceImage) && (DeviceInfo != null))
DeviceInfo.Image = new Uri(Path.Combine(imageBasePath, layout.DeviceImage), UriKind.Absolute);
LedImageLayout ledImageLayout = layout.LedImageLayouts.FirstOrDefault(x => string.Equals(x.Layout, imageLayout, StringComparison.OrdinalIgnoreCase));
Size = new Size(layout.Width, layout.Height);
if (layout.Leds != null)
foreach (LedLayout layoutLed in layout.Leds)
{
if (Enum.TryParse(layoutLed.Id, true, out LedId ledId))
{
if (!LedMapping.TryGetValue(ledId, out Led led) && createMissingLeds)
led = InitializeLed(ledId, new Point(), new Size());
if (led != null)
{
led.Location = new Point(layoutLed.X, layoutLed.Y);
led.Size = new Size(layoutLed.Width, layoutLed.Height);
led.Shape = layoutLed.Shape;
led.ShapeData = layoutLed.ShapeData;
LedImage image = ledImageLayout?.LedImages.FirstOrDefault(x => x.Id == layoutLed.Id);
if ((imageBasePath != null) && !string.IsNullOrEmpty(image?.Image))
led.Image = new Uri(Path.Combine(imageBasePath, image.Image), UriKind.Absolute);
}
}
}
}
return layout;
}
/// <summary>
/// Creates provider-specific data associated with this <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/>.</param>
protected virtual object CreateLedCustomData(LedId ledId) => null;
/// <inheritdoc />
public void AddSpecialDevicePart<T>(T specialDevicePart)
where T : class, IRGBDeviceSpecialPart
=> SpecialDeviceParts[typeof(T)] = specialDevicePart;
/// <inheritdoc />
public T GetSpecialDevicePart<T>()
where T : class, IRGBDeviceSpecialPart
=> SpecialDeviceParts.TryGetValue(typeof(T), out IRGBDeviceSpecialPart devicePart) ? (T)devicePart : default;
#region Enumerator
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
public IEnumerator<Led> GetEnumerator() => LedMapping.Values.GetEnumerator();
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
#endregion
}
}
/// <inheritdoc />
public TDeviceInfo DeviceInfo { get; }
/// <inheritdoc />
IRGBDeviceInfo IRGBDevice.DeviceInfo => DeviceInfo;
/// <inheritdoc />
public IList<IColorCorrection> ColorCorrections { get; } = new List<IColorCorrection>();
/// <summary>
/// Gets or sets if the device needs to be flushed on every update.
/// </summary>
protected bool RequiresFlush { get; set; } = false;
/// <summary>
/// Gets a dictionary containing all <see cref="Led"/> of the <see cref="IRGBDevice"/>.
/// </summary>
protected Dictionary<LedId, Led> LedMapping { get; } = new();
/// <summary>
/// Gets the update queue used to update this device.
/// </summary>
protected IUpdateQueue UpdateQueue { get; }
#region Indexer
/// <inheritdoc />
Led? IRGBDevice.this[LedId ledId] => LedMapping.TryGetValue(ledId, out Led? led) ? led : null;
/// <inheritdoc />
Led? IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.Boundary.Contains(location));
/// <inheritdoc />
IEnumerable<Led> IRGBDevice.this[Rectangle referenceRect, double minOverlayPercentage]
=> LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.Boundary) >= minOverlayPercentage);
#endregion
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractRGBDevice{T}"/> class.
/// </summary>
/// <param name="deviceInfo">The device info of this device.</param>
/// <param name="updateQueue">The queue used to update this device.</param>
protected AbstractRGBDevice(TDeviceInfo deviceInfo, IUpdateQueue updateQueue)
{
this.DeviceInfo = deviceInfo;
this.UpdateQueue = updateQueue;
}
#endregion
#region Methods
/// <inheritdoc />
public virtual void Update(bool flushLeds = false)
{
// Device-specific updates
DeviceUpdate();
// Send LEDs to SDK
List<Led> ledsToUpdate = GetLedsToUpdate(flushLeds).ToList();
foreach (Led led in ledsToUpdate)
led.Update();
UpdateLeds(ledsToUpdate);
}
/// <summary>
/// Gets an enumerable of LEDs that are changed and requires an update.
/// </summary>
/// <param name="flushLeds">Forces all LEDs to be treated as dirty.</param>
/// <returns>The collection LEDs to update.</returns>
protected virtual IEnumerable<Led> GetLedsToUpdate(bool flushLeds) => ((RequiresFlush || flushLeds) ? LedMapping.Values : LedMapping.Values.Where(x => x.IsDirty)).Where(led => led.RequestedColor?.A > 0);
/// <summary>
/// Gets an enumerable of a custom data and color tuple for the specified leds.
/// </summary>
/// <remarks>
/// Applies all <see cref="ColorCorrections"/>.
/// if no <see cref="Led.CustomData"/> ist specified the <see cref="Led.Id"/> is used.
/// </remarks>
/// <param name="leds">The enumerable of leds to convert.</param>
/// <returns>The enumerable of custom data and color tuples for the specified leds.</returns>
protected virtual IEnumerable<(object key, Color color)> GetUpdateData(IEnumerable<Led> leds)
{
if (ColorCorrections.Count > 0)
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
foreach (IColorCorrection colorCorrection in ColorCorrections)
colorCorrection.ApplyTo(ref color);
yield return (key, color);
}
}
else
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
yield return (key, color);
}
}
}
/// <summary>
/// Sends all the updated <see cref="Led"/> to the device.
/// </summary>
protected virtual void UpdateLeds(IEnumerable<Led> ledsToUpdate) => UpdateQueue.SetData(GetUpdateData(ledsToUpdate));
/// <inheritdoc />
public virtual void Dispose()
{
try { UpdateQueue.Dispose(); } catch { /* :( */ }
try { LedMapping.Clear(); } catch { /* this really shouldn't happen */ }
IdGenerator.ResetCounter(GetType().Assembly);
}
/// <summary>
/// Performs device specific updates.
/// </summary>
protected virtual void DeviceUpdate()
{ }
/// <inheritdoc />
public virtual Led? AddLed(LedId ledId, in Point location, in Size size, object? customData = null)
{
if ((ledId == LedId.Invalid) || LedMapping.ContainsKey(ledId)) return null;
Led led = new(this, ledId, location, size, customData ?? GetLedCustomData(ledId));
LedMapping.Add(ledId, led);
return led;
}
/// <inheritdoc />
public virtual Led? RemoveLed(LedId ledId)
{
if (ledId == LedId.Invalid) return null;
if (!LedMapping.TryGetValue(ledId, out Led? led)) return null;
LedMapping.Remove(ledId);
return led;
}
/// <summary>
/// Gets the custom data associated with the specified LED.
/// </summary>
/// <param name="ledId">The id of the led.</param>
/// <returns>The custom data for the specified LED.</returns>
protected virtual object? GetLedCustomData(LedId ledId) => null;
/// <summary>
/// Called when the device is attached to a surface.
/// </summary>
/// <remarks>
/// When overriden base should be called to validate boundries.
/// </remarks>
protected virtual void OnAttached()
{
if (Location == Point.Invalid) Location = new Point(0, 0);
if (Size == Size.Invalid)
{
Rectangle ledRectangle = new(this.Select(x => x.Boundary));
Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
}
}
/// <summary>
/// Called when the device is detached from a surface.
/// </summary>
protected virtual void OnDetached() { }
#region Enumerator
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
public IEnumerator<Led> GetEnumerator() => LedMapping.Values.GetEnumerator();
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates over all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
/// <returns>An enumerator for all <see cref="T:RGB.NET.Core.Led" /> of the <see cref="T:RGB.NET.Core.IRGBDevice" />.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
#endregion
}

View File

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace RGB.NET.Core;
/// <summary>
/// Represents the abstract base implementation for a <see cref="IRGBDeviceProvider"/>.
/// </summary>
public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
{
#region Properties & Fields
private readonly double _defaultUpdateRateHardLimit;
/// <inheritdoc />
public bool IsInitialized { get; protected set; }
/// <inheritdoc />
public bool ThrowsExceptions { get; protected set; }
/// <inheritdoc />
public virtual IEnumerable<IRGBDevice> Devices { get; protected set; } = Enumerable.Empty<IRGBDevice>();
/// <summary>
/// Gets the dictionary containing the registered update triggers.
/// Normally <see cref="UpdateTriggers"/> should be used to access them.
/// </summary>
protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = new();
/// <inheritdoc />
public IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)>(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList());
#endregion
#region Events
/// <inheritdoc />
public event EventHandler<ExceptionEventArgs>? Exception;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractRGBDeviceProvider" /> class.
/// </summary>
/// <param name="defaultUpdateRateHardLimit">The update rate hard limit all update triggers for this device provider are initialized with.</param>
protected AbstractRGBDeviceProvider(double defaultUpdateRateHardLimit = 0)
{
this._defaultUpdateRateHardLimit = defaultUpdateRateHardLimit;
}
#endregion
#region Methods
/// <inheritdoc />
public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false)
{
ThrowsExceptions = throwExceptions;
try
{
Reset();
InitializeSDK();
Devices = new ReadOnlyCollection<IRGBDevice>(GetLoadedDevices(loadFilter).ToList());
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Start();
IsInitialized = true;
}
catch (DeviceProviderException)
{
Reset();
throw;
}
catch (Exception ex)
{
Reset();
Throw(ex, true);
return false;
}
return true;
}
/// <summary>
/// Loads devices and returns a filtered list of them.
/// </summary>
/// <remarks>
/// The underlying loading of the devices happens in <see cref="LoadDevices"/>.
/// </remarks>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the device with.</param>
/// <returns>The filtered collection of loaded devices.</returns>
protected virtual IEnumerable<IRGBDevice> GetLoadedDevices(RGBDeviceType loadFilter)
{
List<IRGBDevice> devices = new();
foreach (IRGBDevice device in LoadDevices())
{
try
{
if (loadFilter.HasFlag(device.DeviceInfo.DeviceType))
devices.Add(device);
else
device.Dispose();
}
catch (Exception ex)
{
Throw(ex);
}
}
return devices;
}
/// <summary>
/// Initializes the underlying SDK.
/// </summary>
protected abstract void InitializeSDK();
/// <summary>
/// Loads all devices this device provider is capable of loading.
/// </summary>
/// <remarks>
/// Filtering happens in <see cref="GetLoadedDevices"/>.
/// </remarks>
/// <returns>A collection of loaded devices.</returns>
protected abstract IEnumerable<IRGBDevice> LoadDevices();
/// <summary>
/// Gets the <see cref="IDeviceUpdateTrigger"/> mapped to the specified id or a new one if the id wasn't requested before.
/// </summary>
/// <remarks>
/// The creation of the update trigger happens in <see cref="CreateUpdateTrigger"/>.
/// </remarks>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit to be set in the update trigger.</param>
/// <returns>The update trigger mapped to the specified id.</returns>
protected virtual IDeviceUpdateTrigger GetUpdateTrigger(int id = -1, double? updateRateHardLimit = null)
{
if (!UpdateTriggerMapping.TryGetValue(id, out IDeviceUpdateTrigger? updaeTrigger))
UpdateTriggerMapping[id] = (updaeTrigger = CreateUpdateTrigger(id, updateRateHardLimit ?? _defaultUpdateRateHardLimit));
return updaeTrigger;
}
/// <summary>
/// Creates a update trigger with the specified id and the specified update rate hard limit.
/// </summary>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit tobe set in the update trigger.</param>
/// <returns>The newly created update trigger.</returns>
protected virtual IDeviceUpdateTrigger CreateUpdateTrigger(int id, double updateRateHardLimit) => new DeviceUpdateTrigger(updateRateHardLimit);
/// <summary>
/// Resets the device provider and disposes all devices and update triggers.
/// </summary>
protected virtual void Reset()
{
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Dispose();
foreach (IRGBDevice device in Devices)
device.Dispose();
Devices = Enumerable.Empty<IRGBDevice>();
UpdateTriggerMapping.Clear();
IsInitialized = false;
}
/// <summary>
/// Triggers the <see cref="Exception"/>-event and throws the specified exception if <see cref="ThrowsExceptions"/> is true and it is not overriden in the event.
/// </summary>
/// <param name="ex">The exception to throw.</param>
/// <param name="isCritical">Indicates if the exception is critical for device provider to work correctly.</param>
protected virtual void Throw(Exception ex, bool isCritical = false)
{
ExceptionEventArgs args = new(ex, isCritical, ThrowsExceptions);
try { OnException(args); } catch { /* we don't want to throw due to bad event handlers */ }
if (args.Throw)
throw new DeviceProviderException(ex, isCritical);
}
/// <summary>
/// Throws the <see cref="Exception"/> event.
/// </summary>
/// <param name="args">The parameters passed to the event.</param>
protected virtual void OnException(ExceptionEventArgs args) => Exception?.Invoke(this, args);
/// <inheritdoc />
public virtual void Dispose()
{
Reset();
GC.SuppressFinalize(this);
}
#endregion
}

View File

@ -1,32 +0,0 @@
using System;
namespace RGB.NET.Core
{
/// <summary>
/// Contains a list of different device device update modes.
/// </summary>
[Flags]
public enum DeviceUpdateMode
{
/// <summary>
/// Represents nothing.
/// </summary>
None = 0,
/// <summary>
/// Represents a mode which updates the leds of the device.
/// </summary>
Sync = 1 << 0,
/// <summary>
/// Represents a mode which reads the color of the leds of the device.
/// This isn't supported by all devices!
/// </summary>
SyncBack = 1 << 1,
/// <summary>
/// Represents all update modes.
/// </summary>
NoUpdate = 1 << 0xFF
}
}

View File

@ -1,129 +1,98 @@
using System;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="IEnumerable{Led}" />
/// <inheritdoc cref="IBindable" />
/// <inheritdoc cref="IDisposable" />
/// <summary>
/// Represents a generic RGB-device.
/// </summary>
public interface IRGBDevice : IEnumerable<Led>, IPlaceable, IBindable, IDisposable
{
/// <inheritdoc cref="IEnumerable{T}" />
/// <inheritdoc cref="IBindable" />
/// <inheritdoc cref="IDisposable" />
#region Properties
/// <summary>
/// Represents a generic RGB-device.
/// Gets the surface this device is attached to.
/// </summary>
public interface IRGBDevice : IEnumerable<Led>, IBindable, IDisposable
{
#region Properties
RGBSurface? Surface { get; internal set; }
/// <summary>
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
IRGBDeviceInfo DeviceInfo { get; }
/// <summary>
/// Gets or sets the location of the <see cref="IRGBDevice"/>.
/// </summary>
Point Location { get; set; }
/// <summary>
/// Gets the <see cref="Size"/> of the <see cref="IRGBDevice"/>.
/// </summary>
Size Size { get; }
/// <summary>
/// Gets the actual <see cref="Size"/> of the <see cref="IRGBDevice"/>.
/// This includes the <see cref="Scale"/>.
/// </summary>
Size ActualSize { get; }
/// <summary>
/// Gets a <see cref="Rectangle"/> representing the logical location of the <see cref="DeviceRectangle"/> relative to the <see cref="RGBSurface"/>.
/// </summary>
Rectangle DeviceRectangle { get; }
/// <summary>
/// Gets or sets the scale of the <see cref="IRGBDevice"/>.
/// </summary>
Scale Scale { get; set; }
/// <summary>
/// Gets or sets the rotation of the <see cref="IRGBDevice"/>.
/// </summary>
Rotation Rotation { get; set; }
/// <summary>
/// Gets or sets the <see cref="DeviceUpdateMode"/> of the <see cref="IRGBDevice"/>.
/// </summary>
DeviceUpdateMode UpdateMode { get; set; }
#endregion
#region Indexer
/// <summary>
/// Gets the <see cref="Led"/> with the specified <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> of the <see cref="Led"/> to get.</param>
/// <returns>The <see cref="Led"/> with the specified <see cref="LedId"/> or null if no <see cref="Led"/> is found.</returns>
Led this[LedId ledId] { get; }
/// <summary>
/// Gets the <see cref="Led" /> at the given physical location.
/// </summary>
/// <param name="location">The <see cref="Point"/> to get the location from.</param>
/// <returns>The <see cref="Led"/> at the given <see cref="Point"/> or null if no location is found.</returns>
Led this[Point location] { get; }
/// <summary>
/// Gets a list of <see cref="Led" /> inside the given <see cref="Rectangle"/>.
/// </summary>
/// <param name="referenceRect">The <see cref="Rectangle"/> to check.</param>
/// <param name="minOverlayPercentage">The minimal percentage overlay a <see cref="Led"/> must have with the <see cref="Rectangle" /> to be taken into the list.</param>
/// <returns></returns>
IEnumerable<Led> this[Rectangle referenceRect, double minOverlayPercentage = 0.5] { get; }
#endregion
#region Methods
/// <summary>
/// Perform an update for all dirty <see cref="Led"/>, or all <see cref="Led"/> if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/> (including clean ones) should be updated.</param>
void Update(bool flushLeds = false);
/// <summary>
/// Synchronizes the internal state of the device to the real (physical) state.
/// This isn't supported by all devices! Check <see cref="IRGBDeviceInfo.SupportsSyncBack"/> to see if it's supported or not.
/// </summary>
void SyncBack();
/// <summary>
/// Adds the given <see cref="IRGBDeviceSpecialPart"/> to the device.
/// This will override existing <see cref="IRGBDeviceSpecialPart"/> of the same type.
/// </summary>
/// <param name="specialDevicePart">The <see cref="IRGBDeviceSpecialPart"/> to add.</param>
/// <typeparam name="T">The generic typeof of the <see cref="IRGBDeviceSpecialPart"/> to add.</typeparam>
void AddSpecialDevicePart<T>(T specialDevicePart) where T : class, IRGBDeviceSpecialPart;
/// <summary>
/// Gets the requested <see cref="IRGBDeviceSpecialPart"/> if available on this <see cref="IRGBDevice"/>.
/// </summary>
/// <typeparam name="T">The generic type of the requested <see cref="IRGBDeviceSpecialPart"/>.</typeparam>
/// <returns>The requested <see cref="IRGBDeviceSpecialPart"/> or null if not available in this <see cref="IRGBDevice"/>.</returns>
T GetSpecialDevicePart<T>() where T : class, IRGBDeviceSpecialPart;
#endregion
}
/// <inheritdoc />
/// <summary>
/// Represents a generic RGB-device with an known device-info type.
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
public interface IRGBDevice<out TDeviceInfo> : IRGBDevice
where TDeviceInfo : IRGBDeviceInfo
{
/// <summary>
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
new TDeviceInfo DeviceInfo { get; }
}
IRGBDeviceInfo DeviceInfo { get; }
/// <summary>
/// Gets a list of color corrections applied to this device.
/// </summary>
IList<IColorCorrection> ColorCorrections { get; }
#endregion
#region Indexer
/// <summary>
/// Gets the <see cref="Led"/> with the specified <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> of the <see cref="Led"/> to get.</param>
/// <returns>The <see cref="Led"/> with the specified <see cref="LedId"/> or null if no <see cref="Led"/> is found.</returns>
Led? this[LedId ledId] { get; }
/// <summary>
/// Gets the <see cref="Led" /> at the specified physical location.
/// </summary>
/// <param name="location">The <see cref="Point"/> to get the location from.</param>
/// <returns>The <see cref="Led"/> at the specified <see cref="Point"/> or null if no location is found.</returns>
Led? this[Point location] { get; }
/// <summary>
/// Gets a list of <see cref="Led" /> inside the specified <see cref="Rectangle"/>.
/// </summary>
/// <param name="referenceRect">The <see cref="Rectangle"/> to check.</param>
/// <param name="minOverlayPercentage">The minimal percentage overlay a <see cref="Led"/> must have with the <see cref="Rectangle" /> to be taken into the list.</param>
/// <returns>A enumerable of leds inside the specified rectangle.</returns>
IEnumerable<Led> this[Rectangle referenceRect, double minOverlayPercentage = 0.5] { get; }
#endregion
#region Methods
/// <summary>
/// Perform an update for all dirty <see cref="Led"/>, or all <see cref="Led"/> if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/> (including clean ones) should be updated.</param>
void Update(bool flushLeds = false);
/// <summary>
/// Adds a led to the device.
/// </summary>
/// <param name="ledId">The id of the led.</param>
/// <param name="location">The location of the led on the device.</param>
/// <param name="size">The size of the led.</param>
/// <param name="customData">Custom data saved on the led.</param>
/// <returns>The newly added led or <c>null</c> if a led with this id is already added.</returns>
Led? AddLed(LedId ledId, in Point location, in Size size, object? customData = null);
/// <summary>
/// Removes the led with the specified id from the device.
/// </summary>
/// <param name="ledId">The id of the led to remove.</param>
/// <returns>The removed led or <c>null</c> if there was no led with the specified id.</returns>
Led? RemoveLed(LedId ledId);
#endregion
}
/// <inheritdoc />
/// <summary>
/// Represents a generic RGB-device with an known device-info type.
/// </summary>
public interface IRGBDevice<out TDeviceInfo> : IRGBDevice
where TDeviceInfo : IRGBDeviceInfo
{
/// <summary>
/// Gets generic information about the <see cref="IRGBDevice"/>.
/// </summary>
new TDeviceInfo DeviceInfo { get; }
}

View File

@ -1,49 +1,36 @@
using System;
namespace RGB.NET.Core;
namespace RGB.NET.Core
/// <summary>
/// Represents a generic information for a <see cref="IRGBDevice"/>
/// </summary>
public interface IRGBDeviceInfo
{
#region Properties & Fields
/// <summary>
/// Represents a generic information for a <see cref="IRGBDevice"/>
/// Gets the <see cref="RGBDeviceType"/> of the <see cref="IRGBDevice"/>.
/// </summary>
public interface IRGBDeviceInfo
{
#region Properties & Fields
RGBDeviceType DeviceType { get; }
/// <summary>
/// Gets the <see cref="RGBDeviceType"/> of the <see cref="IRGBDevice"/>.
/// </summary>
RGBDeviceType DeviceType { get; }
/// <summary>
/// Unique name of the <see cref="IRGBDevice"/>.
/// </summary>
string DeviceName { get; }
/// <summary>
/// Unique name of the <see cref="IRGBDevice"/>.
/// </summary>
string DeviceName { get; }
/// <summary>
/// Gets the manufacturer-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Manufacturer { get; }
/// <summary>
/// Gets the manufacturer-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Manufacturer { get; }
/// <summary>
/// Gets the model-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Model { get; }
/// <summary>
/// Gets the model-name of the <see cref="IRGBDevice"/>.
/// </summary>
string Model { get; }
/// <summary>
/// Gets custom metadata added to the layout.
/// </summary>
object? LayoutMetadata { get; set; }
/// <summary>
/// Gets the lighting capability of the <see cref="IRGBDevice"/>
/// </summary>
RGBDeviceLighting Lighting { get; }
/// <summary>
/// Gets a bool indicating, if the <see cref="IRGBDevice"/> supports SynBacks or not.
/// </summary>
bool SupportsSyncBack { get; }
/// <summary>
/// Gets the URI of an image of the <see cref="IRGBDevice"/> or null if there is no image.
/// </summary>
Uri Image { get; set; }
#endregion
}
}
#endregion
}

View File

@ -1,48 +1,57 @@
using System;
// ReSharper disable EventNeverSubscribedTo.Global
using System;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic device provider.
/// </summary>
public interface IRGBDeviceProvider : IDisposable
{
#region Properties & Fields
/// <summary>
/// Represents a generic device provider.
/// Indicates if the used SDK is initialized and ready to use.
/// </summary>
public interface IRGBDeviceProvider : IDisposable
{
#region Properties & Fields
bool IsInitialized { get; }
/// <summary>
/// Indicates if the used SDK is initialized and ready to use.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Indicates if exceptions in the device provider are thrown or silently ignored.
/// </summary>
bool ThrowsExceptions { get; }
/// <summary>
/// Gets a list of <see cref="IRGBDevice"/> loaded by this <see cref="IRGBDeviceProvider"/>.
/// </summary>
IEnumerable<IRGBDevice> Devices { get; }
/// <summary>
/// Gets a collection of <see cref="IRGBDevice"/> loaded by this <see cref="IRGBDeviceProvider"/>.
/// </summary>
IEnumerable<IRGBDevice> Devices { get; }
/// <summary>
/// Gets whether the application has exclusive access to devices or not.
/// </summary>
bool HasExclusiveAccess { get; }
/// <summary>
/// Gets a collection <see cref="IDeviceUpdateTrigger"/> registered to this device provider.
/// </summary>
IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers { get; }
#endregion
#endregion
#region Methods
#region Events
/// <summary>
/// Initializes the <see cref="IRGBDeviceProvider"/> if not already happened or reloads it if it is already initialized.
/// </summary>
/// <param name="loadFilter">Specifies which types of devices to load.</param>
/// <param name="exclusiveAccessIfPossible">Specifies whether the application should request exclusive access of possible or not.</param>
/// <param name="throwExceptions">Specifies whether exception during the initialization sequence should be thrown or not.</param>
/// <returns></returns>
bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool exclusiveAccessIfPossible = false, bool throwExceptions = false);
/// <summary>
/// Occurs when an exception is thrown in the device provider.
/// </summary>
event EventHandler<ExceptionEventArgs>? Exception;
/// <summary>
/// Resets all handled <see cref="IRGBDevice"/> back top default.
/// </summary>
void ResetDevices();
#endregion
#endregion
}
}
#region Methods
/// <summary>
/// Initializes the device provider and loads available devices.
/// </summary>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the devices to load.</param>
/// <param name="throwExceptions">Specifies if exceptions should be thrown or silently be ignored.</param>
/// <returns><c>true</c> if the initialization was successful; <c>false</c> otherwise.</returns>
bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false);
#endregion
}

View File

@ -1,20 +0,0 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a generic device provider loaded used to dynamically load devices into an application.
/// This class should always provide an empty public constructor!
/// </summary>
public interface IRGBDeviceProviderLoader
{
/// <summary>
/// Indicates if the returned device-provider needs some specific initialization before use.
/// </summary>
bool RequiresInitialization { get; }
/// <summary>
/// Gets the device-provider.
/// </summary>
/// <returns>The device-provider.</returns>
IRGBDeviceProvider GetDeviceProvider();
}
}

View File

@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace RGB.NET.Core
{
/// <inheritdoc />
/// <summary>
/// Represents a special part of a <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
public interface IRGBDeviceSpecialPart : IEnumerable<Led>
{ }
}

View File

@ -0,0 +1,17 @@
// ReSharper disable InconsistentNaming
#pragma warning disable 1591
namespace RGB.NET.Core;
/// <summary>
/// Contains a list of available keyboard layout types.
/// </summary>
public enum KeyboardLayoutType
{
Unknown = 0,
ANSI = 1,
ISO = 2,
JIS = 3,
ABNT = 4,
KS = 5
}

View File

@ -1,151 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Xml.Serialization;
namespace RGB.NET.Core.Layout
{
/// <summary>
/// Represents the serializable layout of a <see cref="IRGBDevice"/>.
/// </summary>
[Serializable]
[XmlRoot("Device")]
public class DeviceLayout
{
#region Properties & Fields
/// <summary>
/// Gets or sets the name of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the description of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Description")]
public string Description { get; set; }
/// <summary>
/// Gets or sets the <see cref="RGBDeviceType"/> of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Type")]
public RGBDeviceType Type { get; set; }
/// <summary>
/// Gets or sets the <see cref="RGBDeviceLighting"/> of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Lighting")]
public RGBDeviceLighting Lighting { get; set; }
/// <summary>
/// Gets or sets the vendor of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Vendor")]
public string Vendor { get; set; }
/// <summary>
/// Gets or sets the model of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Model")]
public string Model { get; set; }
/// <summary>
/// Gets or sets the <see cref="Core.Shape"/> of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Shape")]
[DefaultValue(Shape.Rectangle)]
public Shape Shape { get; set; } = Shape.Rectangle;
/// <summary>
/// Gets or sets the width of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Width")]
public double Width { get; set; }
/// <summary>
/// Gets or sets the height of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlElement("Height")]
public double Height { get; set; }
/// <summary>
/// Gets or sets the width of one 'unit' used for the calculation of led positions and sizes.
/// </summary>
[XmlElement("LedUnitWidth")]
[DefaultValue(19.0)]
public double LedUnitWidth { get; set; } = 19.0;
/// <summary>
/// Gets or sets the height of one 'unit' used for the calculation of led positions and sizes.
/// </summary>
[XmlElement("LedUnitHeight")]
[DefaultValue(19.0)]
public double LedUnitHeight { get; set; } = 19.0;
/// <summary>
/// The path images for this device are collected in.
/// </summary>
[XmlElement("ImageBasePath")]
public string ImageBasePath { get; set; }
/// <summary>
/// The image file for this device.
/// </summary>
[XmlElement("DeviceImage")]
public string DeviceImage { get; set; }
/// <summary>
/// Gets or sets a list of <see cref="LedLayout"/> representing all the <see cref="Led"/> of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlArray("Leds")]
public List<LedLayout> Leds { get; set; } = new List<LedLayout>();
/// <summary>
/// Gets or sets a list of <see cref="LedImageLayout"/> representing the layouts for the images of all the <see cref="Led"/> of the <see cref="DeviceLayout"/>.
/// </summary>
[XmlArray("LedImageLayouts")]
public List<LedImageLayout> LedImageLayouts { get; set; } = new List<LedImageLayout>();
#endregion
#region Methods
/// <summary>
/// Creates a new <see cref="DeviceLayout"/> from the given xml.
/// </summary>
/// <param name="path">The path to the xml file.</param>
/// <returns>The deserialized <see cref="DeviceLayout"/>.</returns>
public static DeviceLayout Load(string path)
{
if (!File.Exists(path)) return null;
try
{
XmlSerializer serializer = new XmlSerializer(typeof(DeviceLayout));
using (StreamReader reader = new StreamReader(path))
{
DeviceLayout layout = serializer.Deserialize(reader) as DeviceLayout;
if (layout?.Leds != null)
{
LedLayout lastLed = null;
foreach (LedLayout led in layout.Leds)
{
led.CalculateValues(layout, lastLed);
lastLed = led;
}
}
return layout;
}
}
catch
{
return null;
}
}
#endregion
}
}

View File

@ -1,25 +0,0 @@
using System;
using System.Xml.Serialization;
namespace RGB.NET.Core.Layout
{
/// <summary>
/// Represents the serializable image-data of a specific <see cref="Led"/>.
/// </summary>
[Serializable]
[XmlRoot("LedImage")]
public class LedImage
{
/// <summary>
/// Gets or sets the Id of the <see cref="LedImage"/>.
/// </summary>
[XmlAttribute("Id")]
public string Id { get; set; }
/// <summary>
/// Gets or sets the image of the <see cref="LedImage"/>.
/// </summary>
[XmlAttribute("Image")]
public string Image { get; set; }
}
}

View File

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;
namespace RGB.NET.Core.Layout
{
/// <summary>
/// Represents the serializable collection of <see cref="LedImage"/> for a specific layout.
/// </summary>
[Serializable]
[XmlRoot("LedImageLayout")]
public class LedImageLayout
{
/// <summary>
/// Gets or sets the layout of the <see cref="LedImage"/>.
/// </summary>
[XmlAttribute("Layout")]
[DefaultValue(null)]
public string Layout { get; set; }
/// <summary>
/// Gets or sets a list of <see cref="LedImage"/> representing the images of all the <see cref="Led"/> of the represented layout.
/// </summary>
[XmlArray("LedImages")]
public List<LedImage> LedImages { get; set; } = new List<LedImage>();
}
}

View File

@ -1,182 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Xml.Serialization;
namespace RGB.NET.Core.Layout
{
/// <summary>
/// Represents the serializable layout of a <see cref="Led"/>.
/// </summary>
[Serializable]
[XmlType("Led")]
public class LedLayout
{
#region Properties & Fields
/// <summary>
/// Gets or sets the Id of the <see cref="LedLayout"/>.
/// </summary>
[XmlAttribute("Id")]
public string Id { get; set; }
/// <summary>
/// Gets or sets the descriptive <see cref="RGB.NET.Core.Shape"/> of the <see cref="LedLayout"/>.
/// This property is for XML-serialization only and should not be directly accessed.
/// </summary>
[XmlElement("Shape")]
[DefaultValue("Rectangle")]
public string DescriptiveShape { get; set; } = "Rectangle";
/// <summary>
/// Gets or sets the descriptive x-position of the <see cref="LedLayout"/>.
/// This property is for XML-serialization only and should not be directly accessed.
/// </summary>
[XmlElement("X")]
[DefaultValue("+")]
public string DescriptiveX { get; set; } = "+";
/// <summary>
/// Gets or sets the descriptive y-position of the <see cref="LedLayout"/>.
/// This property is for XML-serialization only and should not be directly accessed.
/// </summary>
[XmlElement("Y")]
[DefaultValue("=")]
public string DescriptiveY { get; set; } = "=";
/// <summary>
/// Gets or sets the descriptive width of the <see cref="LedLayout"/>.
/// This property is for XML-serialization only and should not be directly accessed.
/// </summary>
[XmlElement("Width")]
[DefaultValue("1.0")]
public string DescriptiveWidth { get; set; } = "1.0";
/// <summary>
/// Gets or sets the descriptive height of the <see cref="LedLayout"/>.
/// This property is for XML-serialization only and should not be directly accessed.
/// </summary>
[XmlElement("Height")]
[DefaultValue("1.0")]
public string DescriptiveHeight { get; set; } = "1.0";
/// <summary>
/// Gets or sets the <see cref="RGB.NET.Core.Shape"/> of the <see cref="LedLayout"/>.
/// </summary>
[XmlIgnore]
public Shape Shape { get; set; }
/// <summary>
/// Gets or sets the vecor-data representing a custom-shape of the <see cref="LedLayout"/>.
/// </summary>
[XmlIgnore]
public string ShapeData { get; set; }
/// <summary>
/// Gets or sets the x-position of the <see cref="LedLayout"/>.
/// </summary>
[XmlIgnore]
public double X { get; private set; }
/// <summary>
/// Gets or sets the y-position of the <see cref="LedLayout"/>.
/// </summary>
[XmlIgnore]
public double Y { get; private set; }
/// <summary>
/// Gets or sets the width of the <see cref="LedLayout"/>.
/// </summary>
[XmlIgnore]
public double Width { get; private set; }
/// <summary>
/// Gets or sets the height of the <see cref="LedLayout"/>.
/// </summary>
[XmlIgnore]
public double Height { get; private set; }
#endregion
#region Methods
/// <summary>
/// Calculates the position- and size-data from the respective descriptive values.
/// </summary>
/// <param name="device">The <see cref="DeviceLayout"/> this <see cref="LedLayout"/> belongs to.</param>
/// <param name="lastLed">The <see cref="LedLayout"/> previously calculated.</param>
public void CalculateValues(DeviceLayout device, LedLayout lastLed)
{
if (!Enum.TryParse(DescriptiveShape, true, out Shape shape))
{
shape = Shape.Custom;
ShapeData = DescriptiveShape;
}
Shape = shape;
Width = GetSizeValue(DescriptiveWidth, device.LedUnitWidth);
Height = GetSizeValue(DescriptiveHeight, device.LedUnitHeight);
X = GetLocationValue(DescriptiveX, lastLed?.X ?? 0, Width, lastLed?.Width ?? 0);
Y = GetLocationValue(DescriptiveY, lastLed?.Y ?? 0, Height, lastLed?.Height ?? 0);
}
private double GetLocationValue(string value, double lastValue, double currentSize, double lastSize)
{
try
{
if (string.IsNullOrWhiteSpace(value)) return 0;
value = value.Replace(" ", string.Empty);
if (string.Equals(value, "=", StringComparison.Ordinal))
return lastValue;
if (string.Equals(value, "+", StringComparison.Ordinal))
return lastValue + lastSize;
if (value.StartsWith("+", StringComparison.Ordinal))
return lastValue + lastSize + double.Parse(value.Substring(1), CultureInfo.InvariantCulture);
if (string.Equals(value, "-", StringComparison.Ordinal))
return lastValue - currentSize;
if (value.StartsWith("-", StringComparison.Ordinal))
return lastValue - currentSize - double.Parse(value.Substring(1), CultureInfo.InvariantCulture);
if (string.Equals(value, "~", StringComparison.Ordinal))
return (lastValue + lastSize) - currentSize;
if (value.StartsWith("~", StringComparison.Ordinal))
return (lastValue + lastSize) - currentSize - double.Parse(value.Substring(1), CultureInfo.InvariantCulture);
return double.Parse(value, CultureInfo.InvariantCulture);
}
catch
{
return 0;
}
}
private double GetSizeValue(string value, double unitSize)
{
try
{
if (string.IsNullOrWhiteSpace(value)) return 0;
value = value.Replace(" ", string.Empty);
if (value.EndsWith("mm", StringComparison.OrdinalIgnoreCase))
return double.Parse(value.Substring(0, value.Length - 2), CultureInfo.InvariantCulture);
return unitSize * double.Parse(value, CultureInfo.InvariantCulture);
}
catch
{
return 0;
}
}
#endregion
}
}

View File

@ -1,25 +0,0 @@
using RGB.NET.Core.Layout;
namespace RGB.NET.Core
{
/// <summary>
/// Contains a list of different lightning-modes used by <see cref="DeviceLayout"/>.
/// </summary>
public enum RGBDeviceLighting
{
/// <summary>
/// The <see cref="IRGBDevice"/> doesn't support lighting,
/// </summary>
None = 0,
/// <summary>
/// The <see cref="IRGBDevice"/> supports per-key-lightning.
/// </summary>
Key = 1,
/// <summary>
/// The <see cref="IRGBDevice"/> supports per-device-lightning.
/// </summary>
Device = 2,
}
}

View File

@ -1,96 +1,105 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Contains a list of different types of device.
/// </summary>
[Flags]
public enum RGBDeviceType
{
/// <summary>
/// Contains a list of different types of device.
/// Represents nothing.
/// </summary>
[Flags]
public enum RGBDeviceType
{
/// <summary>
/// Represents nothing.
/// </summary>
None = 0,
None = 0,
/// <summary>
/// Represents a keyboard.
/// </summary>
Keyboard = 1 << 0,
/// <summary>
/// Represents a keyboard.
/// </summary>
Keyboard = 1 << 0,
/// <summary>
/// Represents a mouse.
/// </summary>
Mouse = 1 << 1,
/// <summary>
/// Represents a mouse.
/// </summary>
Mouse = 1 << 1,
/// <summary>
/// Represents a headset.
/// </summary>
Headset = 1 << 2,
/// <summary>
/// Represents a headset.
/// </summary>
Headset = 1 << 2,
/// <summary>
/// Represents a mousepad.
/// </summary>
Mousepad = 1 << 3,
/// <summary>
/// Represents a mousepad.
/// </summary>
Mousepad = 1 << 3,
/// <summary>
/// Represents a LED-stipe.
/// </summary>
LedStripe = 1 << 4,
/// <summary>
/// Represents a LED-stipe.
/// </summary>
LedStripe = 1 << 4,
/// <summary>
/// Represents a LED-matrix.
/// </summary>
LedMatrix = 1 << 5,
/// <summary>
/// Represents a LED-matrix.
/// </summary>
LedMatrix = 1 << 5,
/// <summary>
/// Represents a Mainboard.
/// </summary>
Mainboard = 1 << 6,
/// <summary>
/// Represents a Mainboard.
/// </summary>
Mainboard = 1 << 6,
/// <summary>
/// Represents a Graphics card.
/// </summary>
GraphicsCard = 1 << 7,
/// <summary>
/// Represents a Graphics card.
/// </summary>
GraphicsCard = 1 << 7,
/// <summary>
/// Represents a DRAM-bank.
/// </summary>
DRAM = 1 << 8,
/// <summary>
/// Represents a DRAM-bank.
/// </summary>
DRAM = 1 << 8,
/// <summary>
/// Represents a headset stand.
/// </summary>
HeadsetStand = 1 << 9,
/// <summary>
/// Represents a headset stand.
/// </summary>
HeadsetStand = 1 << 9,
/// <summary>
/// Represents a keypad.
/// </summary>
Keypad = 1 << 10,
/// <summary>
/// Represents a keypad.
/// </summary>
Keypad = 1 << 10,
/// <summary>
/// Represents a fan.
/// </summary>
Fan = 1 << 11,
/// <summary>
/// Represents a fan.
/// </summary>
Fan = 1 << 11,
/// <summary>
/// Represents a speaker
/// </summary>
Speaker = 1 << 12,
/// <summary>
/// Represents a speaker
/// </summary>
Speaker = 1 << 12,
/// <summary>
/// Represents a cooler.
/// </summary>
Cooler = 1 << 13,
/// <summary>
/// Represents a cooler.
/// </summary>
Cooler = 1 << 13,
/// <summary>
/// Represents a device where the type is not known or not present in the list.
/// </summary>
Unknown = 1 << 31,
/// <summary>
/// Represents a monitor.
/// </summary>
Monitor = 1 << 14,
/// <summary>
/// Represents all devices.
/// </summary>
All = ~None
}
}
/// <summary>
/// Represents a generic led-controller.
/// </summary>
LedController = 1 << 15,
/// <summary>
/// Represents a device where the type is not known or not present in the list.
/// </summary>
Unknown = 1 << 31,
/// <summary>
/// Represents all devices.
/// </summary>
All = ~None
}

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a cooler-device
/// </summary>
public interface ICooler : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a cooler-device
/// </summary>
public interface ICooler : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a DRAM-device
/// </summary>
public interface IDRAM : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a DRAM-device
/// </summary>
public interface IDRAM : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// represents a fan-device
/// </summary>
public interface IFan : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// represents a fan-device
/// </summary>
public interface IFan : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a graphics-card-device
/// </summary>
public interface IGraphicsCard : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a graphics-card-device
/// </summary>
public interface IGraphicsCard : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a headset-device
/// </summary>
public interface IHeadset : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a headset-device
/// </summary>
public interface IHeadset : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a headset-stand-device
/// </summary>
public interface IHeadsetStand : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a headset-stand-device
/// </summary>
public interface IHeadsetStand : IRGBDevice
{ }

View File

@ -1,8 +1,23 @@
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic keyboard-device.
/// </summary>
public interface IKeyboard : IRGBDevice
{
/// <summary>
/// Represents a keyboard-device
/// Gets the device information assiciated with this device.
/// </summary>
public interface IKeyboard : IRGBDevice
{ }
new IKeyboardDeviceInfo DeviceInfo { get; }
}
/// <summary>
/// Represents a generic keyboard device information.
/// </summary>
public interface IKeyboardDeviceInfo : IRGBDeviceInfo
{
/// <summary>
/// Gets the <see cref="KeyboardLayoutType"/> of the keyboard.
/// </summary>
KeyboardLayoutType Layout { get; }
}

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a keypad-device
/// </summary>
public interface IKeypad : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a keypad-device
/// </summary>
public interface IKeypad : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a led-matrix-device
/// </summary>
public interface ILedMatrix : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a led-matrix-device
/// </summary>
public interface ILedMatrix : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a led-stripe-device
/// </summary>
public interface ILedStripe : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a led-stripe-device
/// </summary>
public interface ILedStripe : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a mainboard-device
/// </summary>
public interface IMainboard : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a mainboard-device
/// </summary>
public interface IMainboard : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a mouse-device
/// </summary>
public interface IMouse : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a mouse-device
/// </summary>
public interface IMouse : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a mousepad-device
/// </summary>
public interface IMousepad : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a mousepad-device
/// </summary>
public interface IMousepad : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a speaker-device
/// </summary>
public interface ISpeaker : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a speaker-device
/// </summary>
public interface ISpeaker : IRGBDevice
{ }

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
/// <summary>
/// Represents a device with unkown or not specified type.
/// </summary>
public interface IUnknownDevice : IRGBDevice
{ }
}
namespace RGB.NET.Core;
/// <summary>
/// Represents a device with unkown or not specified type.
/// </summary>
public interface IUnknownDevice : IRGBDevice
{ }

View File

@ -3,35 +3,48 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Exception" />-event.
/// </summary>
public class ExceptionEventArgs : EventArgs
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="System.Exception"/> which is responsible for the event-call.
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Gets a bool indicating if the exception is critical for the thrower.
/// </summary>
public bool IsCritical { get; }
/// <summary>
/// Gets or sets if the exception should be thrown after the event is handled.
/// </summary>
public bool Throw { get; set; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Exception" />-event.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.ExceptionEventArgs" /> class.
/// </summary>
public class ExceptionEventArgs : EventArgs
/// <param name="exception">The <see cref="T:System.Exception" /> which is responsible for the event-call.</param>
/// <param name="isCritical">Indicates if the exception is critical for the thrower.</param>
/// <param name="throw">Indicates if the exception should be thrown after the event is handled.</param>
public ExceptionEventArgs(Exception exception, bool isCritical = false, bool @throw = false)
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="System.Exception"/> which is responsible for the event-call.
/// </summary>
public Exception Exception { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.ExceptionEventArgs" /> class.
/// </summary>
/// <param name="exception">The <see cref="T:System.Exception" /> which is responsible for the event-call.</param>
public ExceptionEventArgs(Exception exception)
{
this.Exception = exception;
}
#endregion
this.Exception = exception;
this.IsCritical = isCritical;
this.Throw = @throw;
}
}
#endregion
}

View File

@ -1,64 +0,0 @@
using System;
namespace RGB.NET.Core
{
public class ResolvePathEventArgs : EventArgs
{
#region Properties & Fields
/// <summary>
/// Gets the filename used to resolve the path.
/// This has to be checked for null since it'S possible that only <see cref="FileName"/> is used.
/// Also check <see cref="RelativePath "/> before use.
/// </summary>
public string RelativePart { get; }
/// <summary>
/// Gets the filename used to resolve the path.
/// This has to be checked for null since it'S possible that only <see cref="RelativePart"/> is used.
/// Also check <see cref="RelativePath "/> before use.
/// </summary>
public string FileName { get; }
/// <summary>
/// Gets the relative path used to resolve the path.
/// If this is set <see cref="RelativePart" /> and <see cref="FileName" /> are unused.
/// </summary>
public string RelativePath { get; }
/// <summary>
/// Gets or sets the resolved path.
/// </summary>
public string FinalPath { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Corer.ResolvePathEventArgs" /> class.
/// </summary>
/// <param name="relativePart">The filename used to resolve the path.</param>
/// <param name="fileName">The filename used to resolve the path.</param>
/// <param name="finalPath">The relative part used to resolve the path.</param>
public ResolvePathEventArgs(string relativePart, string fileName, string finalPath)
{
this.RelativePart = relativePart;
this.FileName = fileName;
this.FinalPath = finalPath;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Corer.ResolvePathEventArgs" /> class.
/// </summary>
/// <param name="relativePath">The relative path used to resolve the path.</param>
/// <param name="finalPath">The relative part used to resolve the path.</param>
public ResolvePathEventArgs(string relativePath, string finalPath)
{
this.RelativePath = relativePath;
this.FinalPath = finalPath;
}
#endregion
}
}

View File

@ -2,51 +2,65 @@
// ReSharper disable UnusedAutoPropertyAccessor.Global
using System;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.SurfaceLayoutChanged" />-event.
/// </summary>
public class SurfaceLayoutChangedEventArgs : EventArgs
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="IRGBDevice"/> that caused the change. Returns null if the change isn't caused by a <see cref="IRGBDevice"/>.
/// </summary>
public IRGBDevice? Devices { get; }
/// <summary>
/// Gets a value indicating if the event is caused by the addition of a new <see cref="IRGBDevice"/> to the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceAdded { get; }
/// <summary>
/// Gets a value indicating if the event is caused by the removal of a <see cref="IRGBDevice"/> to the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceRemoved { get; }
/// <summary>
/// Gets a value indicating if the event is caused by a changed location or size of one of the <see cref="IRGBDevice"/> on the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceChanged { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.SurfaceLayoutChanged" />-event.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.SurfaceLayoutChangedEventArgs" /> class.
/// </summary>
public class SurfaceLayoutChangedEventArgs : EventArgs
/// <param name="devices">The <see cref="T:RGB.NET.Core.IRGBDevice" /> that caused the change.</param>
/// <param name="deviceAdded">A value indicating if the event is caused by the addition of a new <see cref="T:RGB.NET.Core.IRGBDevice" /> to the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
/// <param name="deviceRemoved">A value indicating if the event is caused by the removal of a <see cref="T:RGB.NET.Core.IRGBDevice" /> from the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
/// <param name="deviceChanged">A value indicating if the event is caused by a change to a <see cref="T:RGB.NET.Core.IRGBDevice" /> on the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
private SurfaceLayoutChangedEventArgs(IRGBDevice? devices, bool deviceAdded, bool deviceRemoved, bool deviceChanged)
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="IRGBDevice"/> that caused the change. Returns null if the change isn't caused by a <see cref="IRGBDevice"/>.
/// </summary>
public IEnumerable<IRGBDevice> Devices { get; }
/// <summary>
/// Gets a value indicating if the event is caused by the addition of a new <see cref="IRGBDevice"/> to the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceAdded { get; }
/// <summary>
/// Gets a value indicating if the event is caused by a changed location of one of the devices on the <see cref="RGBSurface"/>.
/// </summary>
public bool DeviceLocationChanged { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.SurfaceLayoutChangedEventArgs" /> class.
/// </summary>
/// <param name="devices">The <see cref="T:RGB.NET.Core.IRGBDevice" /> that caused the change.</param>
/// <param name="deviceAdded">A value indicating if the event is caused by the addition of a new <see cref="T:RGB.NET.Core.IRGBDevice" /> to the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
/// <param name="deviceLocationChanged">A value indicating if the event is caused by a changed location of one of the devices on the <see cref="T:RGB.NET.Core.RGBSurface" />.</param>
public SurfaceLayoutChangedEventArgs(IEnumerable<IRGBDevice> devices, bool deviceAdded, bool deviceLocationChanged)
{
this.Devices = devices;
this.DeviceAdded = deviceAdded;
this.DeviceLocationChanged = deviceLocationChanged;
}
#endregion
this.Devices = devices;
this.DeviceAdded = deviceAdded;
this.DeviceRemoved = deviceRemoved;
this.DeviceChanged = deviceChanged;
}
}
#endregion
#region Factory
internal static SurfaceLayoutChangedEventArgs FromAddedDevice(IRGBDevice device) => new(device, true, false, false);
internal static SurfaceLayoutChangedEventArgs FromRemovedDevice(IRGBDevice device) => new(device, false, true, false);
internal static SurfaceLayoutChangedEventArgs FromChangedDevice(IRGBDevice device) => new(device, false, false, true);
internal static SurfaceLayoutChangedEventArgs Misc() => new(null, false, false, false);
#endregion
}

View File

@ -1,11 +1,10 @@
using System;
namespace RGB.NET.Core
{
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updated" />-event.
/// </summary>
public class UpdatedEventArgs : EventArgs
{ }
}
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updated" />-event.
/// </summary>
public class UpdatedEventArgs : EventArgs
{ }

View File

@ -3,49 +3,48 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updating" />-event.
/// </summary>
public class UpdatingEventArgs : EventArgs
{
#region Properties & Fields
/// <summary>
/// Gets the elapsed time (in seconds) since the last update.
/// </summary>
public double DeltaTime { get; }
/// <summary>
/// Gets the trigger causing this update.
/// </summary>
public IUpdateTrigger? Trigger { get; }
/// <summary>
/// Gets the custom-data provided by the trigger for this update.
/// </summary>
public CustomUpdateData CustomData { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updating" />-event.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.UpdatingEventArgs" /> class.
/// </summary>
public class UpdatingEventArgs : EventArgs
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
/// <param name="trigger">The trigger causing this update.</param>
/// <param name="customData">The custom-data provided by the trigger for this update.</param>
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, CustomUpdateData customData)
{
#region Properties & Fields
/// <summary>
/// Gets the elapsed time (in seconds) since the last update.
/// </summary>
public double DeltaTime { get; }
/// <summary>
/// Gets the trigger causing this update.
/// </summary>
public IUpdateTrigger Trigger { get; }
/// <summary>
/// Gets the custom-data provided by the trigger for this update.
/// </summary>
public CustomUpdateData CustomData { get; }
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.UpdatingEventArgs" /> class.
/// </summary>
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
/// <param name="trigger">The trigger causing this update.</param>
/// <param name="customData">The custom-data provided by the trigger for this update.</param>
public UpdatingEventArgs(double deltaTime, IUpdateTrigger trigger, CustomUpdateData customData)
{
this.DeltaTime = deltaTime;
this.Trigger = trigger;
this.CustomData = customData;
}
#endregion
this.DeltaTime = deltaTime;
this.Trigger = trigger;
this.CustomData = customData;
}
}
#endregion
}

View File

@ -0,0 +1,34 @@
using System;
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by a <see cref="IRGBDeviceProvider" />.
/// </summary>
public class DeviceProviderException : ApplicationException
{
#region Properties & Fields
/// <summary>
/// Gets a bool indicating if the exception is critical and shouldn't be ingored.
/// </summary>
public bool IsCritical { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DeviceProviderException" /> class.
/// </summary>
/// <param name="innerException">The exception that is the casue of the current exception or null if this exception was thrown on purpose.</param>
/// <param name="isCritical">A value indicating if the exception is critical and shouldn't be ignored.</param>
public DeviceProviderException(Exception? innerException, bool isCritical)
: base(innerException?.Message, innerException)
{
this.IsCritical = isCritical;
}
#endregion
}

View File

@ -1,25 +1,24 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by an <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
public class RGBDeviceException : ApplicationException
{
#region Constructors
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by an <see cref="T:RGB.NET.Core.IRGBDevice" />.
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.RGBDeviceException" /> class.
/// </summary>
public class RGBDeviceException : ApplicationException
{
#region Constructors
/// <param name="message">The message which describes the reason of throwing this exception.</param>
/// <param name="innerException">Optional inner exception, which lead to this exception.</param>
public RGBDeviceException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.RGBDeviceException" /> class.
/// </summary>
/// <param name="message">The message which describes the reason of throwing this exception.</param>
/// <param name="innerException">Optional inner exception, which lead to this exception.</param>
public RGBDeviceException(string message, Exception innerException = null)
: base(message, innerException)
{ }
#endregion
}
}
#endregion
}

View File

@ -0,0 +1,24 @@
using System;
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents an exception thrown by an <see cref="T:RGB.NET.Core.RGBSurface" />.
/// </summary>
public class RGBSurfaceException : ApplicationException
{
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.RGBSurfaceException" /> class.
/// </summary>
/// <param name="message">The message which describes the reason of throwing this exception.</param>
/// <param name="innerException">Optional inner exception, which lead to this exception.</param>
public RGBSurfaceException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
#endregion
}

View File

@ -1,30 +1,32 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for <see cref="Color"/> related things.
/// </summary>
public static class ColorExtensions
{
public static class ColorExtensions
#region Methods
/// <summary>
/// Calculates the distance between the two specified colors using the redmean algorithm.
/// For more infos check https://www.compuphase.com/cmetric.htm
/// </summary>
/// <param name="color1">The start color of the distance calculation.</param>
/// <param name="color2">The end color fot the distance calculation.</param>
/// <returns>The redmean distance between the two specified colors.</returns>
public static double DistanceTo(this in Color color1, in Color color2)
{
#region Methods
(_, byte r1, byte g1, byte b1) = color1.GetRGBBytes();
(_, byte r2, byte g2, byte b2) = color2.GetRGBBytes();
/// <summary>
/// Calculates the distance between the two given colors using the redmean algorithm.
/// For more infos check https://www.compuphase.com/cmetric.htm
/// </summary>
/// <param name="color1">The start color of the distance calculation.</param>
/// <param name="color2">The end color fot the distance calculation.</param>
/// <returns></returns>
public static double DistanceTo(this Color color1, Color color2)
{
(_, byte r1, byte g1, byte b1) = color1.GetRGBBytes();
(_, byte r2, byte g2, byte b2) = color2.GetRGBBytes();
long rmean = (r1 + r2) / 2;
long r = r1 - r2;
long g = g1 - g2;
long b = b1 - b2;
return Math.Sqrt((((512 + rmean) * r * r) >> 8) + (4 * g * g) + (((767 - rmean) * b * b) >> 8));
}
#endregion
long rmean = (r1 + r2) / 2;
long r = r1 - r2;
long g = g1 - g2;
long b = b1 - b2;
return Math.Sqrt((((512 + rmean) * r * r) >> 8) + (4 * g * g) + (((767 - rmean) * b * b) >> 8));
}
}
#endregion
}

View File

@ -0,0 +1,55 @@
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions for easier use of <see cref="CustomUpdateData"/>.
/// </summary>
public static class CustomUpdateDataExtension
{
/// <summary>
/// Sets the <see cref="CustomUpdateDataIndex.FLUSH_LEDS"/>-Parameter to the given value.
/// </summary>
/// <param name="customUpdateData">The update-data to modify.</param>
/// <param name="value">The value to set.</param>
/// <returns>The modified update-data.</returns>
public static CustomUpdateData FlushLeds(this CustomUpdateData customUpdateData, bool value = true)
{
customUpdateData[CustomUpdateDataIndex.FLUSH_LEDS] = value;
return customUpdateData;
}
/// <summary>
/// Sets the <see cref="CustomUpdateDataIndex.RENDER"/>-Parameter to the given value.
/// </summary>
/// <param name="customUpdateData">The update-data to modify.</param>
/// <param name="value">The value to set.</param>
/// <returns>The modified update-data.</returns>
public static CustomUpdateData Render(this CustomUpdateData customUpdateData, bool value = true)
{
customUpdateData[CustomUpdateDataIndex.RENDER] = value;
return customUpdateData;
}
/// <summary>
/// Sets the <see cref="CustomUpdateDataIndex.UPDATE_DEVICES"/>-Parameter to the given value.
/// </summary>
/// <param name="customUpdateData">The update-data to modify.</param>
/// <param name="value">The value to set.</param>
/// <returns>The modified update-data.</returns>
public static CustomUpdateData UpdateDevices(this CustomUpdateData customUpdateData, bool value = true)
{
customUpdateData[CustomUpdateDataIndex.UPDATE_DEVICES] = value;
return customUpdateData;
}
/// <summary>
/// Sets the <see cref="CustomUpdateDataIndex.HEARTBEAT"/>-Parameter to the given value.
/// </summary>
/// <param name="customUpdateData">The update-data to modify.</param>
/// <param name="value">The value to set.</param>
/// <returns>The modified update-data.</returns>
public static CustomUpdateData Heartbeat(this CustomUpdateData customUpdateData, bool value = true)
{
customUpdateData[CustomUpdateDataIndex.HEARTBEAT] = value;
return customUpdateData;
}
}

View File

@ -1,101 +1,110 @@
using System;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for the work with floats.
/// </summary>
public static class FloatExtensions
{
#region Constants
/// <summary>
/// Offers some extensions and helper-methods for the work with doubles
/// Defines the precision RGB.NET processes floating point comparisons in.
/// </summary>
public static class DoubleExtensions
public const float TOLERANCE = 1E-7f;
#endregion
#region Methods
/// <summary>
/// Checks if two values are equal respecting the <see cref="TOLERANCE"/>.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The first value to compare.</param>
/// <returns><c>true</c> if the difference is smaller than the <see cref="TOLERANCE"/>; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsInTolerance(this float value1, float value2) => Math.Abs(value1 - value2) < TOLERANCE;
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Clamp(this float value, float min, float max)
{
#region Constants
/// <summary>
/// Defines the precision RGB.NET processes floating point comparisons in.
/// </summary>
public const double TOLERANCE = 1E-10;
#endregion
#region Methods
/// <summary>
/// Checks if two values are equal respecting the <see cref="TOLERANCE"/>.
/// </summary>
/// <param name="value1">The first value to compare.</param>
/// <param name="value2">The first value to compare.</param>
/// <returns><c>true</c> if the difference is smaller than the <see cref="TOLERANCE"/>; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsInTolerance(this double value1, double value2) => Math.Abs(value1 - value2) < TOLERANCE;
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Clamp(this double value, double min, double max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Clamp(this int value, int min, int max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
/// <summary>
/// Enforces the provided value to be in the specified range by wrapping it around the edges if it exceeds them.
/// </summary>
/// <param name="value">The value to wrap.</param>
/// <param name="min">The lower value of the range the value is wrapped into.</param>
/// <param name="max">The higher value of the range the value is wrapped into.</param>
/// <returns>The wrapped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Wrap(this double value, double min, double max)
{
double range = max - min;
while (value >= max)
value -= range;
while (value < min)
value += range;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteValueFromPercentage(this double percentage)
{
if (double.IsNaN(percentage)) return 0;
percentage = percentage.Clamp(0, 1.0);
return (byte)(percentage >= 1.0 ? 255 : percentage * 256.0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double GetPercentageFromByteValue(this byte value)
=> ((double)value) / byte.MaxValue;
#endregion
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
}
/// <summary>
/// Clamps the provided value to be bigger or equal min and smaller or equal max.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The lower value of the range the value is clamped to.</param>
/// <param name="max">The higher value of the range the value is clamped to.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Clamp(this int value, int min, int max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
if (value > max) return max;
return value;
// ReSharper restore ConvertIfStatementToReturnStatement
}
/// <summary>
/// Enforces the provided value to be in the specified range by wrapping it around the edges if it exceeds them.
/// </summary>
/// <param name="value">The value to wrap.</param>
/// <param name="min">The lower value of the range the value is wrapped into.</param>
/// <param name="max">The higher value of the range the value is wrapped into.</param>
/// <returns>The wrapped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Wrap(this float value, float min, float max)
{
float range = max - min;
while (value >= max)
value -= range;
while (value < min)
value += range;
return value;
}
/// <summary>
/// Converts a normalized float value in the range [0..1] to a byte [0..255].
/// </summary>
/// <param name="percentage">The normalized float value to convert.</param>
/// <returns>The byte value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetByteValueFromPercentage(this float percentage)
{
if (float.IsNaN(percentage)) return 0;
percentage = percentage.Clamp(0, 1.0f);
return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f);
}
/// <summary>
/// Converts a byte value [0..255] to a normalized float value in the range [0..1].
/// </summary>
/// <param name="value">The byte value to convert.</param>
/// <returns>The normalized float value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetPercentageFromByteValue(this byte value)
=> value == 255 ? 1.0f : (value / 256.0f);
#endregion
}

View File

@ -1,37 +1,43 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for <see cref="Point"/> related things.
/// </summary>
public static class PointExtensions
{
public static class PointExtensions
#region Methods
/// <summary>
/// Moves the specified <see cref="Point"/> by the specified amount.
/// </summary>
/// <param name="point">The <see cref="Point"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The new location of the point.</returns>
public static Point Translate(this in Point point, float x = 0, float y = 0) => new(point.X + x, point.Y + y);
/// <summary>
/// Rotates the specified <see cref="Point"/> by the specified amuont around the specified origin.
/// </summary>
/// <param name="point">The <see cref="Point"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>The new location of the point.</returns>
public static Point Rotate(this in Point point, in Rotation rotation, in Point origin = new())
{
#region Methods
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
/// <summary>
/// Moves the specified <see cref="Point"/> by the given amount.
/// </summary>
/// <param name="point">The <see cref="Point"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The new location of the point.</returns>
public static Point Translate(this Point point, double x = 0, double y = 0) => new Point(point.X + x, point.Y + y);
float x = point.X - origin.X;
float y = point.Y - origin.Y;
/// <summary>
/// Rotates the specified <see cref="Point"/> by the given amuont around the given origin.
/// </summary>
/// <param name="point">The <see cref="Point"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>The new location of the point.</returns>
public static Point Rotate(this Point point, Rotation rotation, Point origin = new Point())
{
double sin = Math.Sin(rotation.Radians);
double cos = Math.Cos(rotation.Radians);
x = (x * cos) - (y * sin);
y = (x * sin) + (y * cos);
point = new Point(point.X - origin.X, point.Y - origin.Y);
point = new Point((point.X * cos) - (point.Y * sin), (point.X * sin) + (point.Y * cos));
return new Point(point.X + origin.X, point.Y + origin.Y); ;
}
#endregion
return new Point(x + origin.X, y + origin.Y);
}
}
#endregion
}

View File

@ -1,169 +1,176 @@
using System;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for the work with rectangles.
/// </summary>
public static class RectangleExtensions
{
public static class RectangleExtensions
#region Methods
/// <summary>
/// Sets the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="location">The new location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetLocation(this in Rectangle rect, in Point location) => new(location, rect.Size);
/// <summary>
/// Sets the <see cref="Point.X"/> of the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="x">The new x-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetX(this in Rectangle rect, float x) => new(new Point(x, rect.Location.Y), rect.Size);
/// <summary>
/// Sets the <see cref="Point.Y"/> of the <see cref="Rectangle.Location"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="y">The new y-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetY(this in Rectangle rect, float y) => new(new Point(rect.Location.X, y), rect.Size);
/// <summary>
/// Sets the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="size">The new size of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetSize(this in Rectangle rect, in Size size) => new(rect.Location, size);
/// <summary>
/// Sets the <see cref="Size.Width"/> of the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="width">The new width of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetWidth(this in Rectangle rect, float width) => new(rect.Location, new Size(width, rect.Size.Height));
/// <summary>
/// Sets the <see cref="Size.Height"/> of the <see cref="Rectangle.Size"/> of the specified rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="height">The new height of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetHeight(this in Rectangle rect, float height) => new(rect.Location, new Size(rect.Size.Width, height));
/// <summary>
/// Calculates the percentage of intersection of a rectangle.
/// </summary>
/// <param name="rect">The rectangle to calculate the intersection for.</param>
/// <param name="intersectingRect">The intersecting rectangle.</param>
/// <returns>The percentage of intersection.</returns>
public static float CalculateIntersectPercentage(this in Rectangle rect, in Rectangle intersectingRect)
{
#region Methods
if (rect.IsEmpty || intersectingRect.IsEmpty) return 0;
/// <summary>
/// Sets the <see cref="Rectangle.Location"/> of the given rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="location">The new location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetLocation(this Rectangle rect, Point location) => new Rectangle(location, rect.Size);
/// <summary>
/// Sets the <see cref="Point.X"/> of the <see cref="Rectangle.Location"/> of the given rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="x">The new x-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetX(this Rectangle rect, double x) => new Rectangle(new Point(x, rect.Location.Y), rect.Size);
/// <summary>
/// Sets the <see cref="Point.Y"/> of the <see cref="Rectangle.Location"/> of the given rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="y">The new y-location of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetY(this Rectangle rect, double y) => new Rectangle(new Point(rect.Location.X, y), rect.Size);
/// <summary>
/// Sets the <see cref="Rectangle.Size"/> of the given rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="size">The new size of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetSize(this Rectangle rect, Size size) => new Rectangle(rect.Location, size);
/// <summary>
/// Sets the <see cref="Size.Width"/> of the <see cref="Rectangle.Size"/> of the given rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="width">The new width of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetWidth(this Rectangle rect, double width) => new Rectangle(rect.Location, new Size(width, rect.Size.Height));
/// <summary>
/// Sets the <see cref="Size.Height"/> of the <see cref="Rectangle.Size"/> of the given rectangle.
/// </summary>
/// <param name="rect">The rectangle to modify.</param>
/// <param name="height">The new height of the rectangle.</param>
/// <returns>The modified <see cref="Rectangle"/>.</returns>
public static Rectangle SetHeight(this Rectangle rect, double height) => new Rectangle(rect.Location, new Size(rect.Size.Width, height));
/// <summary>
/// Calculates the percentage of intersection of a rectangle.
/// </summary>
/// <param name="intersectingRect">The intersecting rectangle.</param>
/// <returns>The percentage of intersection.</returns>
public static double CalculateIntersectPercentage(this Rectangle rect, Rectangle intersectingRect)
{
if (rect.IsEmpty || intersectingRect.IsEmpty) return 0;
Rectangle intersection = rect.CalculateIntersection(intersectingRect);
return (intersection.Size.Width * intersection.Size.Height) / (rect.Size.Width * rect.Size.Height);
}
/// <summary>
/// Calculates the <see cref="Rectangle"/> representing the intersection of this <see cref="Rectangle"/> and the one provided as parameter.
/// </summary>
/// <param name="intersectingRectangle">The intersecting <see cref="Rectangle"/></param>
/// <returns>A new <see cref="Rectangle"/> representing the intersection this <see cref="Rectangle"/> and the one provided as parameter.</returns>
public static Rectangle CalculateIntersection(this Rectangle rect, Rectangle intersectingRectangle)
{
double x1 = Math.Max(rect.Location.X, intersectingRectangle.Location.X);
double x2 = Math.Min(rect.Location.X + rect.Size.Width, intersectingRectangle.Location.X + intersectingRectangle.Size.Width);
double y1 = Math.Max(rect.Location.Y, intersectingRectangle.Location.Y);
double y2 = Math.Min(rect.Location.Y + rect.Size.Height, intersectingRectangle.Location.Y + intersectingRectangle.Size.Height);
if ((x2 >= x1) && (y2 >= y1))
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
return new Rectangle();
}
/// <summary>
/// Determines if the specified <see cref="Point"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the given point; otherwise <c>false</c>.</returns>
public static bool Contains(this Rectangle rect, Point point) => rect.Contains(point.X, point.Y);
/// <summary>
/// Determines if the specified location is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="x">The X-location to test.</param>
/// <param name="y">The Y-location to test.</param>
/// <returns><c>true</c> if the rectangle contains the given coordinates; otherwise <c>false</c>.</returns>
public static bool Contains(this Rectangle rect, double x, double y) => (rect.Location.X <= x) && (x < (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= y) && (y < (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Determines if the specified <see cref="Rectangle"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the given rect; otherwise <c>false</c>.</returns>
public static bool Contains(this Rectangle rect, Rectangle rect2) => (rect.Location.X <= rect2.Location.X) && ((rect2.Location.X + rect2.Size.Width) <= (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= rect2.Location.Y) && ((rect2.Location.Y + rect2.Size.Height) <= (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the given amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="point">The amount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this Rectangle rect, Point point) => rect.Translate(point.X, point.Y);
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the given amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this Rectangle rect, double x = 0, double y = 0) => new Rectangle(rect.Location.Translate(x, y), rect.Size);
/// <summary>
/// Rotates the specified <see cref="Rectangle"/> by the given amuont around the given origin.
/// </summary>
/// <remarks>
/// The returned array of <see cref="Point"/> is filled with the new locations of the rectangle clockwise starting from the top left:
/// [0] = top left
/// [1] = top right
/// [2] = bottom right
/// [3] = bottom left
/// </remarks>
/// <param name="rect">The <see cref="Rectangle"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>A array of <see cref="Point"/> containing the new locations of the corners of the original rectangle.</returns>
public static Point[] Rotate(this Rectangle rect, Rotation rotation, Point origin = new Point())
{
Point[] points = {
rect.Location, // top left
new Point(rect.Location.X + rect.Size.Width, rect.Location.Y), // top right
new Point(rect.Location.X + rect.Size.Width, rect.Location.Y + rect.Size.Height), // bottom right
new Point(rect.Location.X, rect.Location.Y + rect.Size.Height), // bottom right
};
double sin = Math.Sin(rotation.Radians);
double cos = Math.Cos(rotation.Radians);
for (int i = 0; i < points.Length; i++)
{
Point point = points[i];
point = new Point(point.X - origin.X, point.Y - origin.Y);
point = new Point((point.X * cos) - (point.Y * sin), (point.X * sin) + (point.Y * cos));
points[i] = new Point(point.X + origin.X, point.Y + origin.Y);
}
return points;
}
#endregion
Rectangle intersection = rect.CalculateIntersection(intersectingRect);
return (intersection.Size.Width * intersection.Size.Height) / (rect.Size.Width * rect.Size.Height);
}
}
/// <summary>
/// Calculates the <see cref="Rectangle"/> representing the intersection of this <see cref="Rectangle"/> and the one provided as parameter.
/// </summary>
/// <param name="rect">The rectangle to calculate the intersection for.</param>
/// <param name="intersectingRectangle">The intersecting <see cref="Rectangle"/>.</param>
/// <returns>A new <see cref="Rectangle"/> representing the intersection this <see cref="Rectangle"/> and the one provided as parameter.</returns>
public static Rectangle CalculateIntersection(this in Rectangle rect, in Rectangle intersectingRectangle)
{
float x1 = Math.Max(rect.Location.X, intersectingRectangle.Location.X);
float x2 = Math.Min(rect.Location.X + rect.Size.Width, intersectingRectangle.Location.X + intersectingRectangle.Size.Width);
float y1 = Math.Max(rect.Location.Y, intersectingRectangle.Location.Y);
float y2 = Math.Min(rect.Location.Y + rect.Size.Height, intersectingRectangle.Location.Y + intersectingRectangle.Size.Height);
if ((x2 >= x1) && (y2 >= y1))
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
return new Rectangle();
}
/// <summary>
/// Determines if the specified <see cref="Point"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="point">The <see cref="Point"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified point; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, in Point point) => rect.Contains(point.X, point.Y);
/// <summary>
/// Determines if the specified location is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="x">The X-location to test.</param>
/// <param name="y">The Y-location to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified coordinates; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, float x, float y) => (rect.Location.X <= x) && (x < (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= y) && (y < (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Determines if the specified <see cref="Rectangle"/> is contained within this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The containing rectangle.</param>
/// <param name="rect2">The <see cref="Rectangle"/> to test.</param>
/// <returns><c>true</c> if the rectangle contains the specified rect; otherwise <c>false</c>.</returns>
public static bool Contains(this in Rectangle rect, in Rectangle rect2) => (rect.Location.X <= rect2.Location.X) && ((rect2.Location.X + rect2.Size.Width) <= (rect.Location.X + rect.Size.Width))
&& (rect.Location.Y <= rect2.Location.Y) && ((rect2.Location.Y + rect2.Size.Height) <= (rect.Location.Y + rect.Size.Height));
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="point">The amount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this in Rectangle rect, in Point point) => rect.Translate(point.X, point.Y);
/// <summary>
/// Moves the specified <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="rect">The <see cref="Rectangle"/> to move.</param>
/// <param name="x">The x-ammount to move.</param>
/// <param name="y">The y-ammount to move.</param>
/// <returns>The moved rectangle.</returns>
public static Rectangle Translate(this in Rectangle rect, float x = 0, float y = 0) => new(rect.Location.Translate(x, y), rect.Size);
/// <summary>
/// Rotates the specified <see cref="Rectangle"/> by the specified amuont around the specified origin.
/// </summary>
/// <remarks>
/// The returned array of <see cref="Point"/> is filled with the new locations of the rectangle clockwise starting from the top left:
/// [0] = top left
/// [1] = top right
/// [2] = bottom right
/// [3] = bottom left
/// </remarks>
/// <param name="rect">The <see cref="Rectangle"/> to rotate.</param>
/// <param name="rotation">The rotation.</param>
/// <param name="origin">The origin to rotate around. [0,0] if not set.</param>
/// <returns>A array of <see cref="Point"/> containing the new locations of the corners of the original rectangle.</returns>
public static Point[] Rotate(this in Rectangle rect, in Rotation rotation, in Point origin = new())
{
Point[] points = {
rect.Location, // top left
new(rect.Location.X + rect.Size.Width, rect.Location.Y), // top right
new(rect.Location.X + rect.Size.Width, rect.Location.Y + rect.Size.Height), // bottom right
new(rect.Location.X, rect.Location.Y + rect.Size.Height), // bottom right
};
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
for (int i = 0; i < points.Length; i++)
{
Point point = points[i];
point = new Point(point.X - origin.X, point.Y - origin.Y);
point = new Point((point.X * cos) - (point.Y * sin), (point.X * sin) + (point.Y * cos));
points[i] = new Point(point.X + origin.X, point.Y + origin.Y);
}
return points;
}
#endregion
}

View File

@ -0,0 +1,84 @@
// ReSharper disable UnusedMember.Global
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for the work with the surface.
/// </summary>
public static class SurfaceExtensions
{
#region Methods
/// <summary>
/// Initializes the specifiec device provider and attaches all devices.
/// </summary>
/// <param name="surface">The surface to attach the devices to.</param>
/// <param name="deviceProvider">The device provider to load.</param>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the devices to load.</param>
/// <param name="throwExceptions">Specifies if exceptions should be thrown or silently be ignored.</param>
public static void Load(this RGBSurface surface, IRGBDeviceProvider deviceProvider, RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false)
{
if (!deviceProvider.IsInitialized)
deviceProvider.Initialize(loadFilter, throwExceptions);
surface.Attach(deviceProvider.Devices);
}
/// <summary>
/// Attaches the specified devices to the surface.
/// </summary>
/// <param name="surface">The surface the devices are attached to.</param>
/// <param name="devices">The devices to attach.</param>
public static void Attach(this RGBSurface surface, IEnumerable<IRGBDevice> devices)
{
foreach (IRGBDevice device in devices)
surface.Attach(device);
}
/// <summary>
/// Detaches the specified devices from the surface.
/// </summary>
/// <param name="surface">The surface the devices are detached from.</param>
/// <param name="devices">The devices to detach.</param>
public static void Detach(this RGBSurface surface, IEnumerable<IRGBDevice> devices)
{
foreach (IRGBDevice device in devices)
surface.Detach(device);
}
/// <summary>
/// Gets all devices of a specific type.
/// </summary>
/// <typeparam name="T">The type of devices to get.</typeparam>
/// <returns>A collection of devices with the specified type.</returns>
public static IEnumerable<T> GetDevices<T>(this RGBSurface surface)
where T : class
=> surface.Devices.Where(x => x is T).Cast<T>();
/// <summary>
/// Gets all devices of the specified <see cref="RGBDeviceType"/>.
/// </summary>
/// <param name="surface">The surface to get the devices from.</param>
/// <param name="deviceType">The <see cref="RGBDeviceType"/> of the devices to get.</param>
/// <returns>A collection of devices matching the specified <see cref="RGBDeviceType"/>.</returns>
public static IEnumerable<IRGBDevice> GetDevices(this RGBSurface surface, RGBDeviceType deviceType)
=> surface.Devices.Where(d => deviceType.HasFlag(d.DeviceInfo.DeviceType));
/// <summary>
/// Automatically aligns all devices to prevent overlaps.
/// </summary>
public static void AlignDevices(this RGBSurface surface)
{
float posX = 0;
foreach (IRGBDevice device in surface.Devices)
{
device.Location += new Point(posX, 0);
posX += device.ActualSize.Width + 1;
}
}
#endregion
}

View File

@ -1,51 +1,61 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="ILedGroup" />
/// <summary>
/// Represents a generic <see cref="T:RGB.NET.Core.AbstractLedGroup" />.
/// </summary>
public abstract class AbstractLedGroup : AbstractDecoratable<ILedGroupDecorator>, ILedGroup
{
/// <inheritdoc cref="AbstractDecoratable{T}" />
/// <inheritdoc cref="ILedGroup" />
#region Properties & Fields
RGBSurface? ILedGroup.Surface { get; set; }
/// <inheritdoc cref="ILedGroup.Surface" />
public RGBSurface? Surface => ((ILedGroup)this).Surface;
/// <inheritdoc />
public IBrush? Brush { get; set; }
/// <inheritdoc />
public int ZIndex { get; set; } = 0;
#endregion
#region Constructors
/// <summary>
/// Represents a generic <see cref="T:RGB.NET.Core.AbstractLedGroup" />.
/// Initializes a new instance of the <see cref="AbstractLedGroup"/> class.
/// </summary>
public abstract class AbstractLedGroup : AbstractDecoratable<ILedGroupDecorator>, ILedGroup
protected AbstractLedGroup(RGBSurface? attachTo)
{
#region Properties & Fields
/// <inheritdoc />
public IBrush Brush { get; set; }
/// <inheritdoc />
public int ZIndex { get; set; } = 0;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractLedGroup"/> class.
/// </summary>
/// <param name="autoAttach">Specifies whether this <see cref="AbstractLedGroup"/> should be automatically attached or not.</param>
protected AbstractLedGroup(bool autoAttach)
{
if (autoAttach)
RGBSurface.Instance.AttachLedGroup(this);
}
#endregion
#region Methods
/// <inheritdoc />
public abstract IList<Led> GetLeds();
/// <inheritdoc />
public virtual void OnAttach()
{ }
/// <inheritdoc />
public virtual void OnDetach()
{ }
#endregion
attachTo?.Attach(this);
}
}
#endregion
#region Methods
/// <summary>
/// Gets a enumerable containing all leds in this group.
/// </summary>
/// <returns>A enumerable containing all leds of this group.</returns>
protected abstract IEnumerable<Led> GetLeds();
/// <inheritdoc />
public virtual void OnAttach() { }
/// <inheritdoc />
public virtual void OnDetach() { }
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<Led> GetEnumerator() => GetLeds().GetEnumerator();
#endregion
}

View File

@ -3,38 +3,40 @@
using System.Collections.Generic;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic ledgroup.
/// </summary>
public interface ILedGroup : IDecoratable<ILedGroupDecorator>, IEnumerable<Led>
{
/// <inheritdoc />
/// <summary>
/// Represents a generic ledgroup.
/// Gets the surface this group is attached to or <c>null</c> if it is not attached to any surface.
/// </summary>
public interface ILedGroup : IDecoratable<ILedGroupDecorator>
{
/// <summary>
/// Gets or sets the <see cref="IBrush"/> which should be drawn over this <see cref="ILedGroup"/>.
/// </summary>
IBrush Brush { get; set; }
RGBSurface? Surface { get; internal set; }
/// <summary>
/// Gets or sets the z-index of this <see cref="ILedGroup"/> to allow ordering them before drawing. (lowest first) (default: 0)
/// </summary>
int ZIndex { get; set; }
/// <summary>
/// Gets a bool indicating if the group is attached to a surface.
/// </summary>
bool IsAttached => Surface != null;
/// <summary>
/// Gets a list containing all <see cref="Led"/> of this <see cref="ILedGroup"/>.
/// </summary>
/// <returns>The list containing all <see cref="Led"/> of this <see cref="ILedGroup"/>.</returns>
IList<Led> GetLeds();
/// <summary>
/// Gets or sets the <see cref="IBrush"/> which should be drawn over this <see cref="ILedGroup"/>.
/// </summary>
IBrush? Brush { get; set; }
/// <summary>
/// Called when the <see cref="ILedGroup"/> is attached to the <see cref="RGBSurface"/>.
/// </summary>
void OnAttach();
/// <summary>
/// Gets or sets the z-index of this <see cref="ILedGroup"/> to allow ordering them before drawing. (lowest first) (default: 0)
/// </summary>
int ZIndex { get; set; }
/// <summary>
/// Called when the <see cref="ILedGroup"/> is detached from the <see cref="RGBSurface"/>.
/// </summary>
void OnDetach();
}
}
/// <summary>
/// Called when the <see cref="ILedGroup"/> is attached to the <see cref="RGBSurface"/>.
/// </summary>
void OnAttach();
/// <summary>
/// Called when the <see cref="ILedGroup"/> is detached from the <see cref="RGBSurface"/>.
/// </summary>
void OnDetach();
}

View File

@ -0,0 +1,57 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
namespace RGB.NET.Core;
/// <summary>
/// Offers some extensions and helper-methods for <see cref="ILedGroup"/> related things.
/// </summary>
public static class LedGroupExtension
{
/// <summary>
/// Converts the specified <see cref="ILedGroup" /> to a <see cref="ListLedGroup" />.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup" /> to convert.</param>
/// <returns>The converted <see cref="ListLedGroup" />.</returns>
public static ListLedGroup ToListLedGroup(this ILedGroup ledGroup)
{
// ReSharper disable once InvertIf
if (ledGroup is not ListLedGroup listLedGroup)
{
if (ledGroup.IsAttached)
ledGroup.Detach();
listLedGroup = new ListLedGroup(ledGroup.Surface, ledGroup) { Brush = ledGroup.Brush, ZIndex = ledGroup.ZIndex };
}
return listLedGroup;
}
/// <summary>
/// Returns a new <see cref="ListLedGroup" /> which contains all <see cref="Led"/> from the specified <see cref="ILedGroup"/> excluding the specified ones.
/// </summary>
/// <param name="ledGroup">The base <see cref="ILedGroup"/>.</param>
/// <param name="ledIds">The <see cref="Led"/> to exclude.</param>
/// <returns>The new <see cref="ListLedGroup" />.</returns>
public static ListLedGroup Exclude(this ILedGroup ledGroup, params Led[] ledIds)
{
ListLedGroup listLedGroup = ledGroup.ToListLedGroup();
foreach (Led led in ledIds)
listLedGroup.RemoveLed(led);
return listLedGroup;
}
// ReSharper disable once UnusedMethodReturnValue.Global
/// <summary>
/// Attaches the specified <see cref="ILedGroup"/> to the <see cref="RGBSurface"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <param name="surface">The <see cref="RGBSurface"/> to attach this group to.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be attached; otherwise, <c>false</c>.</returns>
public static bool Attach(this ILedGroup ledGroup, RGBSurface surface) => surface.Attach(ledGroup);
/// <summary>
/// Detaches the specified <see cref="ILedGroup"/> from the <see cref="RGBSurface"/>.
/// </summary>
/// <param name="ledGroup">The <see cref="ILedGroup"/> to attach.</param>
/// <returns><c>true</c> if the <see cref="ILedGroup"/> could be detached; otherwise, <c>false</c>.</returns>
public static bool Detach(this ILedGroup ledGroup) => ledGroup.Surface?.Detach(ledGroup) ?? false;
}

View File

@ -0,0 +1,132 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System.Collections.Generic;
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a ledgroup containing arbitrary <see cref="T:RGB.NET.Core.Led" />.
/// </summary>
public class ListLedGroup : AbstractLedGroup
{
#region Properties & Fields
/// <summary>
/// Gets the list containing the <see cref="Led"/> of this <see cref="ListLedGroup"/>.
/// </summary>
protected IList<Led> GroupLeds { get; } = new List<Led>();
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
public ListLedGroup(RGBSurface? surface)
: base(surface)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
/// <param name="leds">The initial <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Groups.ListLedGroup" />.</param>
public ListLedGroup(RGBSurface? surface, IEnumerable<Led> leds)
: base(surface)
{
AddLeds(leds);
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Groups.ListLedGroup" /> class.
/// </summary>
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
/// <param name="leds">The initial <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Groups.ListLedGroup" />.</param>
public ListLedGroup(RGBSurface? surface, params Led[] leds)
: base(surface)
{
AddLeds(leds);
}
#endregion
#region Methods
/// <summary>
/// Adds the specified LED(s) to this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The LED(s) to add.</param>
public void AddLed(params Led[] leds) => AddLeds(leds);
/// <summary>
/// Adds the specified <see cref="Led"/> to this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The <see cref="Led"/> to add.</param>
public void AddLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
foreach (Led led in leds)
if (!ContainsLed(led))
GroupLeds.Add(led);
}
/// <summary>
/// Removes the specified LED(s) from this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The LED(s) to remove.</param>
public void RemoveLed(params Led[] leds) => RemoveLeds(leds);
/// <summary>
/// Removes the specified <see cref="Led"/> from this <see cref="ListLedGroup"/>.
/// </summary>
/// <param name="leds">The <see cref="Led"/> to remove.</param>
public void RemoveLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
foreach (Led led in leds)
GroupLeds.Remove(led);
}
/// <summary>
/// Checks if a specified LED is contained by this ledgroup.
/// </summary>
/// <param name="led">The LED which should be checked.</param>
/// <returns><c>true</c> if the LED is contained by this ledgroup; otherwise, <c>false</c>.</returns>
public bool ContainsLed(Led led)
{
lock (GroupLeds)
return GroupLeds.Contains(led);
}
/// <summary>
/// Merges the <see cref="Led"/> from the specified ledgroup in this ledgroup.
/// </summary>
/// <param name="groupToMerge">The ledgroup to merge.</param>
public void MergeLeds(ILedGroup groupToMerge)
{
lock (GroupLeds)
foreach (Led led in groupToMerge)
if (!GroupLeds.Contains(led))
GroupLeds.Add(led);
}
/// <inheritdoc />
/// <summary>
/// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns>
protected override IEnumerable<Led> GetLeds()
{
lock (GroupLeds)
return new List<Led>(GroupLeds);
}
#endregion
}

View File

@ -1,60 +1,61 @@
namespace RGB.NET.Core
using System;
namespace RGB.NET.Core;
/// <summary>
/// Contains helper methods for converting things.
/// </summary>
public static class ConversionHelper
{
#region Methods
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Contains helper methods for converting things.
/// Converts an array of bytes to a HEX-representation.
/// </summary>
public static class ConversionHelper
/// <param name="bytes">The array of bytes.</param>
/// <returns>The HEX-representation of the provided bytes.</returns>
public static string ToHex(params byte[] bytes)
{
#region Methods
char[] c = new char[bytes.Length * 2];
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Converts an array of bytes to a HEX-representation.
/// </summary>
/// <param name="bytes">The array of bytes.</param>
/// <returns>The HEX-representation of the provided bytes.</returns>
public static string ToHex(params byte[] bytes)
for (int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx)
{
char[] c = new char[bytes.Length * 2];
byte b = ((byte)(bytes[bx] >> 4));
c[cx] = (char)(b > 9 ? b + 0x37 : b + 0x30);
for (int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx)
{
byte b = ((byte)(bytes[bx] >> 4));
c[cx] = (char)(b > 9 ? b + 0x37: b + 0x30);
b = ((byte)(bytes[bx] & 0x0F));
c[++cx] = (char)(b > 9 ? b + 0x37: b + 0x30);
}
return new string(c);
b = ((byte)(bytes[bx] & 0x0F));
c[++cx] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Converts the HEX-representation of a byte array to that array.
/// </summary>
/// <param name="hexString">The HEX-string to convert.</param>
/// <returns>The correspondending byte array.</returns>
public static byte[] HexToBytes(string hexString)
{
if ((hexString.Length == 0) || ((hexString.Length % 2) != 0))
return new byte[0];
byte[] buffer = new byte[hexString.Length / 2];
for (int bx = 0, sx = 0; bx < buffer.Length; ++bx, ++sx)
{
// Convert first half of byte
char c = hexString[sx];
buffer[bx] = (byte)((c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0')) << 4);
// Convert second half of byte
c = hexString[++sx];
buffer[bx] |= (byte)(c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0'));
}
return buffer;
}
#endregion
return new string(c);
}
}
// Source: https://web.archive.org/web/20180224104425/https://stackoverflow.com/questions/623104/byte-to-hex-string/3974535
/// <summary>
/// Converts the HEX-representation of a byte array to that array.
/// </summary>
/// <param name="hexString">The HEX-string to convert.</param>
/// <returns>The correspondending byte array.</returns>
public static byte[] HexToBytes(ReadOnlySpan<char> hexString)
{
if ((hexString.Length == 0) || ((hexString.Length % 2) != 0))
return Array.Empty<byte>();
byte[] buffer = new byte[hexString.Length / 2];
for (int bx = 0, sx = 0; bx < buffer.Length; ++bx, ++sx)
{
// Convert first half of byte
char c = hexString[sx];
buffer[bx] = (byte)((c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0')) << 4);
// Convert second half of byte
c = hexString[++sx];
buffer[bx] |= (byte)(c > '9' ? (c > 'Z' ? ((c - 'a') + 10) : ((c - 'A') + 10)) : (c - '0'));
}
return buffer;
}
#endregion
}

View File

@ -1,44 +0,0 @@
using System;
using System.Globalization;
using System.Runtime.InteropServices;
namespace RGB.NET.Core
{
/// <summary>
/// Offers some helper-methods for culture related things.
/// </summary>
public static class CultureHelper
{
#region DLLImports
[DllImport("user32.dll")]
private static extern IntPtr GetKeyboardLayout(uint thread);
#endregion
#region Constructors
#endregion
#region Methods
/// <summary>
/// Gets the current keyboard-layout from the OS.
/// </summary>
/// <returns>The current keyboard-layout</returns>
public static CultureInfo GetCurrentCulture()
{
try
{
int keyboardLayout = GetKeyboardLayout(0).ToInt32() & 0xFFFF;
return new CultureInfo(keyboardLayout);
}
catch
{
return new CultureInfo(1033); // en-US on error.
}
}
#endregion
}
}

View File

@ -0,0 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core;
/// <summary>
/// Offsers some helper methods for device creation.
/// </summary>
public static class DeviceHelper
{
#region Methods
/// <summary>
/// Creates a unique device name from a manufacturer and model name.
/// </summary>
/// <remarks>
/// The id is made unique based on the assembly calling this method.
/// </remarks>
/// <param name="manufacturer">The manufacturer of the device.</param>
/// <param name="model">The model of the device.</param>
/// <returns>The unique identifier for this device.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static string CreateDeviceName(string manufacturer, string model) => IdGenerator.MakeUnique(Assembly.GetCallingAssembly(), $"{manufacturer} {model}");
#endregion
}

View File

@ -1,83 +0,0 @@
using System;
using System.IO;
using System.Reflection;
namespace RGB.NET.Core
{
/// <summary>
/// Offers some helper-methods for file-path related things.
/// </summary>
public static class PathHelper
{
#region Events
/// <summary>
/// Occurs when a path is resolving.
/// </summary>
public static event EventHandler<ResolvePathEventArgs> ResolvingAbsolutePath;
#endregion
#region Methods
/// <summary>
/// Returns an absolute path created from an relative path relatvie to the location of the executung assembly.
/// </summary>
/// <param name="relativePath">The relative part of the path to convert.</param>
/// <returns>The absolute path.</returns>
public static string GetAbsolutePath(string relativePath) => GetAbsolutePath((object)null, relativePath);
/// <summary>
/// Returns an absolute path created from an relative path relatvie to the location of the executung assembly.
/// </summary>
/// <param name="relativePath">The relative part of the path to convert.</param>
/// <param name="fileName">The file name of the path to convert.</param>
/// <returns>The absolute path.</returns>
public static string GetAbsolutePath(string relativePath, string fileName) => GetAbsolutePath(null, relativePath, fileName);
/// <summary>
/// Returns an absolute path created from an relative path relatvie to the location of the executung assembly.
/// </summary>
/// <param name="sender">The requester of this path. (Used for better control when using the event to override this behavior.)</param>
/// <param name="relativePath">The relative path to convert.</param>
/// <param name="fileName">The file name of the path to convert.</param>
/// <returns>The absolute path.</returns>
public static string GetAbsolutePath(object sender, string relativePath, string fileName)
{
string relativePart = Path.Combine(relativePath, fileName);
string assemblyLocation = Assembly.GetEntryAssembly()?.Location;
if (assemblyLocation == null) return relativePart;
string directoryName = Path.GetDirectoryName(assemblyLocation);
string path = directoryName == null ? null : Path.Combine(directoryName, relativePart);
ResolvePathEventArgs args = new ResolvePathEventArgs(relativePath, fileName, path);
ResolvingAbsolutePath?.Invoke(sender, args);
return args.FinalPath;
}
/// <summary>
/// Returns an absolute path created from an relative path relatvie to the location of the executung assembly.
/// </summary>
/// <param name="sender">The requester of this path. (Used for better control when using the event to override this behavior.)</param>
/// <param name="relativePath">The relative path to convert.</param>
/// <returns>The absolute path.</returns>
public static string GetAbsolutePath(object sender, string relativePath)
{
string assemblyLocation = Assembly.GetEntryAssembly()?.Location;
if (assemblyLocation == null) return relativePath;
string directoryName = Path.GetDirectoryName(assemblyLocation);
string path = directoryName == null ? null : Path.Combine(directoryName, relativePath);
ResolvePathEventArgs args = new ResolvePathEventArgs(relativePath, path);
ResolvingAbsolutePath?.Invoke(sender, args);
return args.FinalPath;
}
#endregion
}
}

View File

@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace RGB.NET.Core;
/// <summary>
/// Offers some helper methods for timed operations.
/// </summary>
public static class TimerHelper
{
#region DLL-Imports
[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
private static extern void TimeBeginPeriod(int t);
[DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
private static extern void TimeEndPeriod(int t);
#endregion
#region Properties & Fields
private static readonly object HIGH_RESOLUTION_TIMER_LOCK = new();
private static bool _areHighResolutionTimersEnabled = false;
private static bool _useHighResolutionTimers = true;
/// <summary>
/// Gets or sets if High Resolution Timers should be used.
/// </summary>
public static bool UseHighResolutionTimers
{
get => _useHighResolutionTimers;
set
{
lock (HIGH_RESOLUTION_TIMER_LOCK)
{
_useHighResolutionTimers = value;
CheckHighResolutionTimerUsage();
}
}
}
// ReSharper disable once InconsistentNaming
private static readonly HashSet<HighResolutionTimerDisposable> _timerLeases = new();
#endregion
#region Methods
/// <summary>
/// Executes the provided action and blocks if needed until the the <see param="targetExecuteTime"/> has passed.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <param name="targetExecuteTime">The time in ms this method should block. default: 0</param>
/// <returns>The time in ms spent executing the <see param="action"/>.</returns>
public static double Execute(Action action, double targetExecuteTime = 0)
{
long preUpdateTicks = Stopwatch.GetTimestamp();
action();
double updateTime = GetElapsedTime(preUpdateTicks);
if (targetExecuteTime > 0)
{
int sleep = (int)(targetExecuteTime - updateTime);
if (sleep > 0)
Thread.Sleep(sleep);
}
return updateTime;
}
/// <summary>
/// Calculates the elapsed time in ms from the provided timestamp until now.
/// </summary>
/// <param name="initialTimestamp">The initial timestamp to calculate the time from.</param>
/// <returns>The elapsed time in ms.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double GetElapsedTime(long initialTimestamp) => ((Stopwatch.GetTimestamp() - initialTimestamp) / (Stopwatch.Frequency / 1000.0));
/// <summary>
/// Requests to use to use High Resolution Timers if enabled.
/// IMPORTANT: Always dispose the returned disposable if High Resolution Timers are no longer needed for the caller.
/// </summary>
/// <returns>A disposable to remove the request.</returns>
public static IDisposable RequestHighResolutionTimer()
{
HighResolutionTimerDisposable timerLease = new();
lock (HIGH_RESOLUTION_TIMER_LOCK)
{
_timerLeases.Add(timerLease);
CheckHighResolutionTimerUsage();
}
return timerLease;
}
private static void CheckHighResolutionTimerUsage()
{
if (UseHighResolutionTimers && (_timerLeases.Count > 0))
EnableHighResolutionTimers();
else
DisableHighResolutionTimers();
}
private static void EnableHighResolutionTimers()
{
lock (HIGH_RESOLUTION_TIMER_LOCK)
{
if (_areHighResolutionTimersEnabled) return;
// DarthAffe 06.05.2022: Linux should use 1ms timers by default
if (OperatingSystem.IsWindows())
TimeBeginPeriod(1);
_areHighResolutionTimersEnabled = true;
}
}
private static void DisableHighResolutionTimers()
{
lock (HIGH_RESOLUTION_TIMER_LOCK)
{
if (!_areHighResolutionTimersEnabled) return;
if (OperatingSystem.IsWindows())
TimeEndPeriod(1);
_areHighResolutionTimersEnabled = false;
}
}
/// <summary>
/// Disposes all open High Resolution Timer Requests.
/// This should be called once when exiting the application to make sure nothing remains open and the application correctly unregisters itself on OS level.
/// Shouldn't be needed if everything is disposed, but better safe then sorry.
/// </summary>
public static void DisposeAllHighResolutionTimerRequests()
{
List<HighResolutionTimerDisposable> timerLeases = new(_timerLeases);
foreach (HighResolutionTimerDisposable timer in timerLeases)
timer.Dispose();
}
#endregion
private class HighResolutionTimerDisposable : IDisposable
{
#region Properties & Fields
private bool _isDisposed = false;
#endregion
#region Methods
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
lock (HIGH_RESOLUTION_TIMER_LOCK)
{
_timerLeases.Remove(this);
CheckHighResolutionTimerUsage();
}
}
#endregion
}
}

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core;
/// <summary>
/// Offers some methods to create and handle unique identifiers.
/// </summary>
public static class IdGenerator
{
#region Properties & Fields
// ReSharper disable InconsistentNaming
private static readonly HashSet<string> _registeredIds = new();
private static readonly Dictionary<Assembly, Dictionary<string, string>> _idMappings = new();
private static readonly Dictionary<Assembly, Dictionary<string, int>> _counter = new();
// ReSharper restore InconsistentNaming
#endregion
#region Methods
/// <summary>
/// Makes the specified id unique based on the calling assembly by adding a counter if needed.
/// </summary>
/// <param name="id">The id to make unique.</param>
/// <returns>The unique id.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static string MakeUnique(string id) => MakeUnique(Assembly.GetCallingAssembly(), id);
internal static string MakeUnique(Assembly callingAssembly, string id)
{
if (!_idMappings.TryGetValue(callingAssembly, out Dictionary<string, string>? idMapping))
{
_idMappings.Add(callingAssembly, idMapping = new Dictionary<string, string>());
_counter.Add(callingAssembly, new Dictionary<string, int>());
}
Dictionary<string, int> counterMapping = _counter[callingAssembly];
if (!idMapping.TryGetValue(id, out string? mappedId))
{
mappedId = id;
int mappingCounter = 1;
while (_registeredIds.Contains(mappedId))
mappedId = $"{id} ({++mappingCounter})";
_registeredIds.Add(mappedId);
idMapping.Add(id, mappedId);
}
if (!counterMapping.ContainsKey(mappedId))
counterMapping.Add(mappedId, 0);
int counter = ++counterMapping[mappedId];
return counter <= 1 ? mappedId : $"{mappedId} ({counter})";
}
/// <summary>
/// Resets the counter used to create unique ids.
/// All previous generated ids are not garantueed to stay unique if this is called!
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ResetCounter() => ResetCounter(Assembly.GetCallingAssembly());
internal static void ResetCounter(Assembly callingAssembly)
{
if (_counter.TryGetValue(callingAssembly, out Dictionary<string, int>? counter))
counter.Clear();
}
#endregion
}

View File

@ -1,310 +1,175 @@
// ReSharper disable MemberCanBePrivate.Global
using System;
using System.ComponentModel;
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a single LED of a RGB-device.
/// </summary>
[DebuggerDisplay("{Id} {Color}")]
public class Led : Placeable
{
/// <inheritdoc />
#region Properties & Fields
/// <summary>
/// Represents a single LED of a RGB-device.
/// Gets the <see cref="IRGBDevice"/> this <see cref="Led"/> is associated with.
/// </summary>
[DebuggerDisplay("{Id} {Color}")]
public class Led : AbstractBindable
public IRGBDevice Device { get; }
/// <summary>
/// Gets the <see cref="LedId"/> of the <see cref="Led" />.
/// </summary>
public LedId Id { get; }
private Shape _shape = Shape.Rectangle;
/// <summary>
/// Gets or sets the <see cref="Core.Shape"/> of the <see cref="Led"/>.
/// </summary>
public Shape Shape
{
#region Properties & Fields
/// <summary>
/// Gets the <see cref="IRGBDevice"/> this <see cref="Led"/> is associated with.
/// </summary>
public IRGBDevice Device { get; }
/// <summary>
/// Gets the <see cref="LedId"/> of the <see cref="Led" />.
/// </summary>
public LedId Id { get; }
private Shape _shape = Shape.Rectangle;
/// <summary>
/// Gets or sets the <see cref="Core.Shape"/> of the <see cref="Led"/>.
/// </summary>
public Shape Shape
{
get => _shape;
set => SetProperty(ref _shape, value);
}
private string _shapeData;
/// <summary>
/// Gets or sets the data used for by the <see cref="Core.Shape.Custom"/>-<see cref="Core.Shape"/>.
/// </summary>
public string ShapeData
{
get => _shapeData;
set => SetProperty(ref _shapeData, value);
}
private Point _location;
/// <summary>
/// Gets or sets the relative location of the <see cref="Led"/>.
/// </summary>
public Point Location
{
get => _location;
set
{
if (SetProperty(ref _location, value))
{
UpdateActualData();
UpdateAbsoluteData();
}
}
}
private Size _size;
/// <summary>
/// Gets or sets the size of the <see cref="Led"/>.
/// </summary>
public Size Size
{
get => _size;
set
{
if (SetProperty(ref _size, value))
{
UpdateActualData();
UpdateAbsoluteData();
}
}
}
private Point _actualLocation;
/// <summary>
/// Gets the actual location of the <see cref="Led"/>.
/// This includes device-scaling and rotation.
/// </summary>
public Point ActualLocation
{
get => _actualLocation;
private set => SetProperty(ref _actualLocation, value);
}
private Size _actualSize;
/// <summary>
/// Gets the actual size of the <see cref="Led"/>.
/// This includes device-scaling.
/// </summary>
public Size ActualSize
{
get => _actualSize;
private set => SetProperty(ref _actualSize, value);
}
private Rectangle _ledRectangle;
/// <summary>
/// Gets a rectangle representing the logical location of the <see cref="Led"/> relative to the <see cref="Device"/>.
/// </summary>
public Rectangle LedRectangle
{
get => _ledRectangle;
private set => SetProperty(ref _ledRectangle, value);
}
private Rectangle _absoluteLedRectangle;
/// <summary>
/// Gets a rectangle representing the logical location of the <see cref="Led"/> on the <see cref="RGBSurface"/>.
/// </summary>
public Rectangle AbsoluteLedRectangle
{
get => _absoluteLedRectangle;
private set => SetProperty(ref _absoluteLedRectangle, value);
}
/// <summary>
/// Indicates whether the <see cref="Led" /> is about to change it's color.
/// </summary>
public bool IsDirty => RequestedColor.HasValue && (RequestedColor != InternalColor);
private Color? _requestedColor;
/// <summary>
/// Gets a copy of the <see cref="Core.Color"/> the LED should be set to on the next update.
/// Null if there is no update-request for the next update.
/// </summary>
public Color? RequestedColor
{
get => _requestedColor;
private set
{
SetProperty(ref _requestedColor, value);
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(IsDirty));
}
}
private Color _color = Color.Transparent;
/// <summary>
/// Gets the current <see cref="Core.Color"/> of the <see cref="Led"/>. Sets the <see cref="RequestedColor" /> for the next update.
/// </summary>
public Color Color
{
get => _color;
set
{
if (!IsLocked)
{
if (RequestedColor.HasValue)
RequestedColor += value;
else
RequestedColor = value;
}
}
}
/// <summary>
/// Gets or set the <see cref="Color"/> ignoring all workflows regarding locks and update-requests. />
/// </summary>
internal Color InternalColor
{
get => _color;
set => SetProperty(ref _color, value);
}
private bool _isLocked;
/// <summary>
/// Gets or sets if the color of this LED can be changed.
/// </summary>
public bool IsLocked
{
get => _isLocked;
set => SetProperty(ref _isLocked, value);
}
/// <summary>
/// Gets the URI of an image of the <see cref="Led"/> or null if there is no image.
/// </summary>
public Uri Image { get; set; }
/// <summary>
/// Gets the provider-specific data associated with this led.
/// </summary>
public object CustomData { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Led"/> class.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> the <see cref="Led"/> is associated with.</param>
/// <param name="id">The <see cref="LedId"/> of the <see cref="Led"/>.</param>
/// <param name="location">The physical location of the <see cref="Led"/> relative to the <see cref="Device"/>.</param>
/// <param name="size">The size of the <see cref="Led"/>.</param>
/// <param name="customData">The provider-specific data associated with this led.</param>
internal Led(IRGBDevice device, LedId id, Point location, Size size, object customData = null)
{
this.Device = device;
this.Id = id;
this.Location = location;
this.Size = size;
this.CustomData = customData;
device.PropertyChanged += DevicePropertyChanged;
}
#endregion
#region Methods
private void DevicePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if ((e.PropertyName == nameof(IRGBDevice.Location)))
UpdateAbsoluteData();
else if (e.PropertyName == nameof(IRGBDevice.DeviceRectangle))
{
UpdateActualData();
UpdateAbsoluteData();
}
}
private void UpdateActualData()
{
ActualSize = Size * Device.Scale;
Point actualLocation = (Location * Device.Scale);
Rectangle ledRectangle = new Rectangle(Location * Device.Scale, Size * Device.Scale);
if (Device.Rotation.IsRotated)
{
Point deviceCenter = new Rectangle(Device.ActualSize).Center;
Point actualDeviceCenter = new Rectangle(Device.DeviceRectangle.Size).Center;
Point centerOffset = new Point(actualDeviceCenter.X - deviceCenter.X, actualDeviceCenter.Y - deviceCenter.Y);
actualLocation = actualLocation.Rotate(Device.Rotation, new Rectangle(Device.ActualSize).Center) + centerOffset;
ledRectangle = new Rectangle(ledRectangle.Rotate(Device.Rotation, new Rectangle(Device.ActualSize).Center)).Translate(centerOffset);
}
ActualLocation = actualLocation;
LedRectangle = ledRectangle;
}
private void UpdateAbsoluteData()
{
AbsoluteLedRectangle = LedRectangle.Translate(Device.Location);
}
/// <summary>
/// Converts the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/>. For example "Enter [A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => $"{Id} {Color}";
/// <summary>
/// Updates the <see cref="Led"/> to the requested <see cref="Core.Color"/>.
/// </summary>
internal void Update()
{
if (!RequestedColor.HasValue) return;
_color = RequestedColor.Value;
RequestedColor = null;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(Color));
}
/// <summary>
/// Resets the <see cref="Led"/> back to default.
/// </summary>
internal void Reset()
{
_color = Color.Transparent;
RequestedColor = null;
IsLocked = false;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(Color));
}
#endregion
#region Operators
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Core.Color" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Color(Led led) => led?.Color ?? Color.Transparent;
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Rectangle" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Rectangle(Led led) => led?.LedRectangle ?? new Rectangle();
#endregion
get => _shape;
set => SetProperty(ref _shape, value);
}
}
private string? _shapeData;
/// <summary>
/// Gets or sets the data used for by the <see cref="Core.Shape.Custom"/>-<see cref="Core.Shape"/>.
/// </summary>
public string? ShapeData
{
get => _shapeData;
set => SetProperty(ref _shapeData, value);
}
private Rectangle _absoluteBoundary;
/// <summary>
/// Gets a rectangle representing the logical location of the <see cref="Led"/> on the <see cref="RGBSurface"/>.
/// </summary>
public Rectangle AbsoluteBoundary
{
get => _absoluteBoundary;
private set => SetProperty(ref _absoluteBoundary, value);
}
/// <summary>
/// Indicates whether the <see cref="Led" /> is about to change it's color.
/// </summary>
public bool IsDirty => RequestedColor.HasValue && (RequestedColor != Color);
private Color? _requestedColor;
/// <summary>
/// Gets a copy of the <see cref="Core.Color"/> the LED should be set to on the next update.
/// Null if there is no update-request for the next update.
/// </summary>
public Color? RequestedColor
{
get => _requestedColor;
private set
{
SetProperty(ref _requestedColor, value);
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(IsDirty));
}
}
private Color _color = Color.Transparent;
/// <summary>
/// Gets the current <see cref="Core.Color"/> of the <see cref="Led"/>. Sets the <see cref="RequestedColor" /> for the next update.
/// </summary>
public Color Color
{
get => _color;
set
{
if (RequestedColor.HasValue)
RequestedColor = RequestedColor.Value + value;
else
RequestedColor = _color + value;
}
}
/// <summary>
/// Gets the provider-specific data associated with this led.
/// </summary>
public object? CustomData { get; }
/// <summary>
/// Gets or sets some custom metadata of this led.
/// </summary>
public object? LayoutMetadata { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Led"/> class.
/// </summary>
/// <param name="device">The <see cref="IRGBDevice"/> the <see cref="Led"/> is associated with.</param>
/// <param name="id">The <see cref="LedId"/> of the <see cref="Led"/>.</param>
/// <param name="location">The physical location of the <see cref="Led"/> relative to the <see cref="Device"/>.</param>
/// <param name="size">The size of the <see cref="Led"/>.</param>
/// <param name="customData">The provider-specific data associated with this led.</param>
internal Led(IRGBDevice device, LedId id, Point location, Size size, object? customData = null)
: base(device)
{
this.Device = device;
this.Id = id;
this.Location = location;
this.Size = size;
this.CustomData = customData;
}
#endregion
#region Methods
/// <inheritdoc />
protected override void UpdateActualPlaceableData()
{
base.UpdateActualPlaceableData();
AbsoluteBoundary = Boundary.Translate(Device.Location);
}
/// <summary>
/// Converts the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Id"/> and the <see cref="Color"/> of this <see cref="Led"/>. For example "Enter [A: 255, R: 255, G: 0, B: 0]".</returns>
public override string ToString() => $"{Id} {Color}";
/// <summary>
/// Updates the <see cref="Led"/> to the requested <see cref="Core.Color"/>.
/// </summary>
internal void Update()
{
if (!RequestedColor.HasValue) return;
_color = RequestedColor.Value;
RequestedColor = null;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(Color));
}
#endregion
#region Operators
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Core.Color" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Color(Led led) => led.Color;
/// <summary>
/// Converts a <see cref="Led" /> to a <see cref="Rectangle" />.
/// </summary>
/// <param name="led">The <see cref="Led"/> to convert.</param>
public static implicit operator Rectangle(Led led) => led.Boundary;
#endregion
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,148 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core;
/// <summary>
/// Represents a mapping from <see cref="LedId"/> to a custom identifier.
/// </summary>
/// <typeparam name="T">The identifier the <see cref="LedId"/> is mapped to.</typeparam>
public class LedMapping<T> : IEnumerable<(LedId ledId, T mapping)>
where T : notnull
{
#region Properties & Fields
private readonly Dictionary<LedId, T> _mapping = new();
private readonly Dictionary<T, LedId> _reverseMapping = new();
/// <summary>
/// Gets the number of entries in this mapping.
/// </summary>
public int Count => _mapping.Count;
/// <summary>
/// Gets a collection of all mapped ledids.
/// </summary>
public ICollection<LedId> LedIds => _mapping.Keys;
/// <summary>
/// Gets a collection of all mapped custom identifiers.
/// </summary>
public ICollection<T> Mappings => _reverseMapping.Keys;
#endregion
#region Indexer
/// <summary>
/// Gets the custom identifier mapped to the specified <see cref="LedId"/>.
/// </summary>
/// <param name="ledId">The led id to get the mapped identifier.</param>
/// <returns>The mapped ifentifier.</returns>
public T this[LedId ledId]
{
get => _mapping[ledId];
set
{
_mapping[ledId] = value;
_reverseMapping[value] = ledId;
}
}
/// <summary>
/// Gets the <see cref="LedId"/> mapped to the specified custom identifier.
/// </summary>
/// <param name="mapping">The custom identifier to get the mapped led id.</param>
/// <returns>The led id.</returns>
public LedId this[T mapping]
{
get => _reverseMapping[mapping];
set => this[value] = mapping;
}
#endregion
#region Methods
/// <summary>
/// Adds a new entry to the mapping.
/// </summary>
/// <param name="ledId">The <see cref="LedId"/> to map.</param>
/// <param name="mapping">The custom identifier to map.</param>
public void Add(LedId ledId, T mapping)
{
_mapping.Add(ledId, mapping);
_reverseMapping.Add(mapping, ledId);
}
/// <summary>
/// Checks if the specified <see cref="LedId"/> is mapped.
/// </summary>
/// <param name="ledId">The led id to check.</param>
/// <returns><c>true</c> if the led id is mapped; otherwise <c>false</c>.</returns>
public bool Contains(LedId ledId) => _mapping.ContainsKey(ledId);
/// <summary>
/// Checks if the specified custom identifier is mapped.
/// </summary>
/// <param name="mapping">The custom identifier to check.</param>
/// <returns><c>true</c> if the led id is mapped; otherwise <c>false</c>.</returns>
public bool Contains(T mapping) => _reverseMapping.ContainsKey(mapping);
/// <summary>
/// Gets the custom identifier mapped to the specified led id.
/// </summary>
/// <param name="ledId">The led id to get the custom identifier for.</param>
/// <param name="mapping">Contains the mapped custom identifier or null if there is no mapping for the specified led id.</param>
/// <returns><c>true</c> if there was a custom identifier for the specified led id; otherwise <c>false</c>.</returns>
public bool TryGetValue(LedId ledId, out T? mapping) => _mapping.TryGetValue(ledId, out mapping);
/// <summary>
/// Gets the led id mapped to the specified custom identifier.
/// </summary>
/// <param name="mapping">The custom identifier to get the led id for.</param>
/// <param name="ledId">Contains the mapped led id or null if there is no mapping for the specified led id.</param>
/// <returns><c>true</c> if there was a led id for the specified custom identifier; otherwise <c>false</c>.</returns>
public bool TryGetValue(T mapping, out LedId ledId) => _reverseMapping.TryGetValue(mapping, out ledId);
/// <summary>
/// Removes the specified led id and the mapped custom identifier.
/// </summary>
/// <param name="ledId">The led id to remove.</param>
/// <returns><c>true</c> if there was a mapping for the led id to remove; otherwise <c>false</c>.</returns>
public bool Remove(LedId ledId)
{
if (_mapping.TryGetValue(ledId, out T? mapping))
_reverseMapping.Remove(mapping);
return _mapping.Remove(ledId);
}
/// <summary>
/// Removes the specified custom identifier and the mapped led id.
/// </summary>
/// <param name="mapping">The custom identifier to remove.</param>
/// <returns><c>true</c> if there was a mapping for the custom identifier to remove; otherwise <c>false</c>.</returns>
public bool Remove(T mapping)
{
if (_reverseMapping.TryGetValue(mapping, out LedId ledId))
_mapping.Remove(ledId);
return _reverseMapping.Remove(mapping);
}
/// <summary>
/// Removes all registered mappings.
/// </summary>
public void Clear()
{
_mapping.Clear();
_reverseMapping.Clear();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<(LedId ledId, T mapping)> GetEnumerator() => _mapping.Select(x => (x.Key, x.Value)).GetEnumerator();
#endregion
}

View File

@ -1,67 +1,61 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <inheritdoc />
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// </summary>
public abstract class AbstractBindable : IBindable
{
/// <inheritdoc />
#region Events
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// Occurs when a property value changes.
/// </summary>
public abstract class AbstractBindable : IBindable
public event PropertyChangedEventHandler? PropertyChanged;
#endregion
#region Methods
/// <summary>
/// Checks if the property already matches the desired value or needs to be updated.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <returns><c>true</c> if the value needs to be updated; otherweise <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool RequiresUpdate<T>(ref T storage, T value) => !Equals(storage, value);
/// <summary>
/// Checks if the property already matches the desired value and updates it if not.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
/// <returns><c>true</c> if the value was changed, <c>false</c> if the existing value matched the desired value.</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
{
#region Events
if (!RequiresUpdate(ref storage, value)) return false;
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
/// <summary>
/// Checks if the property already matches the desirec value or needs to be updated.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool RequiresUpdate<T>(ref T storage, T value)
{
return !Equals(storage, value);
}
/// <summary>
/// Checks if the property already matches the desired value and updates it if not.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
/// <returns><c>true</c> if the value was changed, <c>false</c> if the existing value matched the desired value.</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!this.RequiresUpdate(ref storage, value)) return false;
storage = value;
// ReSharper disable once ExplicitCallerInfoArgument
this.OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Triggers the <see cref="PropertyChanged"/>-event when a a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
storage = value;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propertyName);
return true;
}
}
/// <summary>
/// Triggers the <see cref="PropertyChanged"/>-event when a a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
}

View File

@ -1,11 +1,10 @@
using System.ComponentModel;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// </summary>
public interface IBindable : INotifyPropertyChanged
{
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// </summary>
public interface IBindable : INotifyPropertyChanged
{
}
}
}

View File

@ -0,0 +1,92 @@
// ReSharper disable EventNeverSubscribedTo.Global
using System;
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic placeable element.
/// </summary>
public interface IPlaceable
{
#region Properties & Fields
/// <summary>
/// Gets or sets the location of the <see cref="IPlaceable"/>.
/// </summary>
Point Location { get; set; }
/// <summary>
/// Gets the size of the <see cref="IPlaceable"/>.
/// </summary>
Size Size { get; set; }
/// <summary>
/// Gets or sets the scale of the <see cref="IPlaceable"/>.
/// </summary>
Scale Scale { get; set; }
/// <summary>
/// Gets or sets the rotation of the <see cref="IPlaceable"/>.
/// </summary>
Rotation Rotation { get; set; }
/// <summary>
/// Gets the actual location of the <see cref="IPlaceable"/>.
/// This includes the <see cref="Rotation"/>.
/// </summary>
Point ActualLocation { get; }
/// <summary>
/// Gets the actual <see cref="Size"/> of the <see cref="IPlaceable"/>.
/// This includes the <see cref="Scale"/>.
/// </summary>
Size ActualSize { get; }
/// <summary>
/// Gets a rectangle containing the whole <see cref="IPlaceable"/>.
/// This includes <see cref="Location"/>, <see cref="Size"/>, <see cref="Scale"/> and <see cref="Rotation"/>.
/// </summary>
Rectangle Boundary { get; }
#endregion
#region Events
/// <summary>
/// Occurs when the <see cref="Location"/> property was changed.
/// </summary>
event EventHandler<EventArgs> LocationChanged;
/// <summary>
/// Occurs when the <see cref="Size"/> property was changed.
/// </summary>
event EventHandler<EventArgs> SizeChanged;
/// <summary>
/// Occurs when the <see cref="Scale"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ScaleChanged;
/// <summary>
/// Occurs when the <see cref="Rotation"/> property was changed.
/// </summary>
event EventHandler<EventArgs> RotationChanged;
/// <summary>
/// Occurs when the <see cref="ActualLocation"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ActualLocationChanged;
/// <summary>
/// Occurs when the <see cref="ActualSize"/> property was changed.
/// </summary>
event EventHandler<EventArgs> ActualSizeChanged;
/// <summary>
/// Occurs when the <see cref="Boundary"/> property was changed.
/// </summary>
event EventHandler<EventArgs> BoundaryChanged;
#endregion
}

View File

@ -0,0 +1,261 @@
using System;
namespace RGB.NET.Core;
/// <summary>
/// Represents a placeable element.
/// </summary>
public class Placeable : AbstractBindable, IPlaceable
{
#region Properties & Fields
/// <summary>
/// Gets the parent this placeable is placed in.
/// </summary>
protected IPlaceable? Parent { get; }
private Point _location = Point.Invalid;
/// <inheritdoc />
public Point Location
{
get => _location;
set
{
if (SetProperty(ref _location, value))
OnLocationChanged();
}
}
private Size _size = Size.Invalid;
/// <inheritdoc />
public Size Size
{
get => _size;
set
{
if (SetProperty(ref _size, value))
OnSizeChanged();
}
}
private Scale _scale = new(1);
/// <inheritdoc />
public Scale Scale
{
get => _scale;
set
{
if (SetProperty(ref _scale, value))
OnScaleChanged();
}
}
private Rotation _rotation = new(0);
/// <inheritdoc />
public Rotation Rotation
{
get => _rotation;
set
{
if (SetProperty(ref _rotation, value))
OnRotationChanged();
}
}
private Point _actualLocation = Point.Invalid;
/// <inheritdoc />
public Point ActualLocation
{
get => _actualLocation;
private set
{
if (SetProperty(ref _actualLocation, value))
OnActualLocationChanged();
}
}
private Size _actualSize = Size.Invalid;
/// <inheritdoc />
public Size ActualSize
{
get => _actualSize;
private set
{
if (SetProperty(ref _actualSize, value))
OnActualSizeChanged();
}
}
private Rectangle _boundary = new(Point.Invalid, Point.Invalid);
/// <inheritdoc />
public Rectangle Boundary
{
get => _boundary;
private set
{
if (SetProperty(ref _boundary, value))
OnBoundaryChanged();
}
}
#endregion
#region Events
/// <inheritdoc />
public event EventHandler<EventArgs>? LocationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? SizeChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ScaleChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? RotationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ActualLocationChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? ActualSizeChanged;
/// <inheritdoc />
public event EventHandler<EventArgs>? BoundaryChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
public Placeable() { }
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="parent">The parent this placeable is placed in.</param>
public Placeable(IPlaceable parent)
{
this.Parent = parent;
Parent.BoundaryChanged += (_, _) => UpdateActualPlaceableData();
}
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="location">The location of this placeable.</param>
/// <param name="size">The size of this placeable.</param>
public Placeable(Point location, Size size)
{
this.Location = location;
this.Size = size;
}
/// <summary>
/// Initializes a new instance of the <see cref="Placeable" /> class.
/// </summary>
/// <param name="parent">The parent placeable this placeable is placed in.</param>
/// <param name="location">The location of this placeable.</param>
/// <param name="size">The size of this placeable.</param>
public Placeable(IPlaceable parent, Point location, Size size)
{
this.Parent = parent;
this.Location = location;
this.Size = size;
Parent.BoundaryChanged += (_, _) => UpdateActualPlaceableData();
}
#endregion
#region Methods
/// <summary>
/// Updates the <see cref="ActualSize"/>, <see cref="ActualLocation"/> and <see cref="Boundary"/> based on the <see cref="Size"/>, <see cref="Scale"/> and <see cref="Rotation"/>.
/// </summary>
protected virtual void UpdateActualPlaceableData()
{
if (Parent != null)
{
Size actualSize = Size * Parent.Scale;
Point actualLocation = (Location * Parent.Scale);
Rectangle boundary = new(actualLocation, actualSize);
if (Parent.Rotation.IsRotated)
{
Point parentCenter = new Rectangle(Parent.ActualSize).Center;
Point actualParentCenter = new Rectangle(Parent.Boundary.Size).Center;
Point centerOffset = new(actualParentCenter.X - parentCenter.X, actualParentCenter.Y - parentCenter.Y);
actualLocation = actualLocation.Rotate(Parent.Rotation, new Rectangle(Parent.ActualSize).Center) + centerOffset;
boundary = new Rectangle(boundary.Rotate(Parent.Rotation, new Rectangle(Parent.ActualSize).Center)).Translate(centerOffset);
}
ActualLocation = actualLocation;
ActualSize = actualSize;
Boundary = boundary;
}
else
{
ActualLocation = Location;
ActualSize = Size * Scale;
Boundary = new Rectangle(Location, new Rectangle(new Rectangle(Location, ActualSize).Rotate(Rotation)).Size);
}
}
/// <summary>
/// Called when the <see cref="Location"/> property was changed.
/// </summary>
protected virtual void OnLocationChanged()
{
LocationChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Size"/> property was changed.
/// </summary>
protected virtual void OnSizeChanged()
{
SizeChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Scale"/> property was changed.
/// </summary>
protected virtual void OnScaleChanged()
{
ScaleChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="Rotation"/> property was changed.
/// </summary>
protected virtual void OnRotationChanged()
{
RotationChanged?.Invoke(this, new EventArgs());
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualLocationChanged() => ActualLocationChanged?.Invoke(this, new EventArgs());
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualSizeChanged() => ActualSizeChanged?.Invoke(this, new EventArgs());
/// <summary>
/// Called when the <see cref="Boundary"/> property was changed.
/// </summary>
protected virtual void OnBoundaryChanged() => BoundaryChanged?.Invoke(this, new EventArgs());
#endregion
}

View File

@ -1,162 +1,154 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Diagnostics;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a point consisting of a X- and a Y-position.
/// </summary>
[DebuggerDisplay("[X: {X}, Y: {Y}]")]
public readonly struct Point
{
#region Constants
private static readonly Point INVALID = new(float.NaN, float.NaN);
/// <summary>
/// Represents a point consisting of a X- and a Y-position.
/// Gets a [NaN,NaN]-Point.
/// </summary>
[DebuggerDisplay("[X: {X}, Y: {Y}]")]
public struct Point
public static ref readonly Point Invalid => ref INVALID;
#endregion
#region Properties & Fields
/// <summary>
/// Gets the X-position of this <see cref="Point"/>.
/// </summary>
public float X { get; }
/// <summary>
/// Gets the Y-position of this <see cref="Point"/>.
/// </summary>
public float Y { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> class using the provided values.
/// </summary>
/// <param name="x">The value used for the X-position.</param>
/// <param name="y">The value used for the Y-position.</param>
public Point(float x, float y)
{
#region Constants
/// <summary>
/// Gets a [NaN,NaN]-Point.
/// </summary>
public static Point Invalid => new Point(double.NaN, double.NaN);
#endregion
#region Properties & Fields
/// <summary>
/// Gets the X-position of this <see cref="Point"/>.
/// </summary>
public double X { get; }
/// <summary>
/// Gets the Y-position of this <see cref="Point"/>.
/// </summary>
public double Y { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> class using the provided values.
/// </summary>
/// <param name="x">The value used for the X-position.</param>
/// <param name="y">The value used for the Y-position.</param>
public Point(double x, double y)
{
this.X = x;
this.Y = y;
}
#endregion
#region Methods
/// <summary>
/// Converts the <see cref="X"/>- and <see cref="Y"/>-position of this <see cref="Point"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="X"/> and <see cref="Y"/> of this <see cref="Point"/>. For example "[X: 100, Y: 20]".</returns>
public override string ToString() => $"[X: {X}, Y: {Y}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Point" /> and is equivalent to this <see cref="Point" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Point" /> equivalent to this <see cref="Point" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
if (!(obj is Point)) return false;
Point comparePoint = (Point)obj;
return ((double.IsNaN(X) && double.IsNaN(comparePoint.X)) || X.EqualsInTolerance(comparePoint.X))
&& ((double.IsNaN(Y) && double.IsNaN(comparePoint.Y)) || Y.EqualsInTolerance(comparePoint.Y));
}
/// <summary>
/// Returns a hash code for this <see cref="Point" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Point" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = X.GetHashCode();
hashCode = (hashCode * 397) ^ Y.GetHashCode();
return hashCode;
}
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(Point point1, Point point2) => point1.Equals(point2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(Point point1, Point point2) => !(point1 == point2);
/// <summary>
/// Returns a new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.</returns>
public static Point operator +(Point point1, Point point2) => new Point(point1.X + point2.X, point1.Y + point2.Y);
/// <summary>
/// Returns a new <see cref="Rectangle"/> created from the provided <see cref="Point"/> and <see cref="Size"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/> of the rectangle.</param>
/// <param name="size">The <see cref="Size"/> of the rectangle.</param>
/// <returns>The rectangle created from the provided <see cref="Point"/> and <see cref="Size"/>.</returns>
public static Rectangle operator +(Point point, Size size) => new Rectangle(point, size);
/// <summary>
/// Returns a new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.</returns>
public static Point operator -(Point point1, Point point2) => new Point(point1.X - point2.X, point1.Y - point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.</returns>
public static Point operator *(Point point1, Point point2) => new Point(point1.X * point2.X, point1.Y * point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.</returns>
public static Point operator /(Point point1, Point point2)
{
if (point2.X.EqualsInTolerance(0) || point2.Y.EqualsInTolerance(0)) return Invalid;
return new Point(point1.X / point2.X, point1.Y / point2.Y);
}
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/>.</param>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.</returns>
public static Point operator *(Point point, Scale scale) => new Point(point.X * scale.Horizontal, point.Y * scale.Vertical);
#endregion
this.X = x;
this.Y = y;
}
}
#endregion
#region Methods
/// <summary>
/// Converts the <see cref="X"/>- and <see cref="Y"/>-position of this <see cref="Point"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="X"/> and <see cref="Y"/> of this <see cref="Point"/>. For example "[X: 100, Y: 20]".</returns>
public override string ToString() => $"[X: {X}, Y: {Y}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Point" /> and is equivalent to this <see cref="Point" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Point" /> equivalent to this <see cref="Point" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Point comparePoint) return false;
return ((float.IsNaN(X) && float.IsNaN(comparePoint.X)) || X.EqualsInTolerance(comparePoint.X))
&& ((float.IsNaN(Y) && float.IsNaN(comparePoint.Y)) || Y.EqualsInTolerance(comparePoint.Y));
}
/// <summary>
/// Returns a hash code for this <see cref="Point" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Point" />.</returns>
public override int GetHashCode() => HashCode.Combine(X, Y);
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Point point1, in Point point2) => point1.Equals(point2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Point" /> are equal.
/// </summary>
/// <param name="point1">The first <see cref="Point" /> to compare.</param>
/// <param name="point2">The second <see cref="Point" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="point1" /> and <paramref name="point2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Point point1, in Point point2) => !(point1 == point2);
/// <summary>
/// Returns a new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the addition of the two provided <see cref="Point"/>.</returns>
public static Point operator +(in Point point1, in Point point2) => new(point1.X + point2.X, point1.Y + point2.Y);
/// <summary>
/// Returns a new <see cref="Rectangle"/> created from the provided <see cref="Point"/> and <see cref="Size"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/> of the rectangle.</param>
/// <param name="size">The <see cref="Size"/> of the rectangle.</param>
/// <returns>The rectangle created from the provided <see cref="Point"/> and <see cref="Size"/>.</returns>
public static Rectangle operator +(in Point point, in Size size) => new(point, size);
/// <summary>
/// Returns a new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the subtraction of the two provided <see cref="Point"/>.</returns>
public static Point operator -(in Point point1, in Point point2) => new(point1.X - point2.X, point1.Y - point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the two provided <see cref="Point"/>.</returns>
public static Point operator *(in Point point1, in Point point2) => new(point1.X * point2.X, point1.Y * point2.Y);
/// <summary>
/// Returns a new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.
/// </summary>
/// <param name="point1">The first <see cref="Point"/>.</param>
/// <param name="point2">The second <see cref="Point"/>.</param>
/// <returns>A new <see cref="Point"/> representing the division of the two provided <see cref="Point"/>.</returns>
public static Point operator /(in Point point1, in Point point2)
{
if (point2.X.EqualsInTolerance(0) || point2.Y.EqualsInTolerance(0)) return Invalid;
return new Point(point1.X / point2.X, point1.Y / point2.Y);
}
/// <summary>
/// Returns a new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.
/// </summary>
/// <param name="point">The <see cref="Point"/>.</param>
/// <param name="scale">The <see cref="Scale"/>.</param>
/// <returns>A new <see cref="Point"/> representing the multiplication of the <see cref="Point"/> and the provided <see cref="Scale"/>.</returns>
public static Point operator *(in Point point, in Scale scale) => new(point.X * scale.Horizontal, point.Y * scale.Vertical);
#endregion
}

View File

@ -6,223 +6,236 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace RGB.NET.Core
namespace RGB.NET.Core;
/// <summary>
/// Represents a rectangle defined by it's position and it's size.
/// </summary>
[DebuggerDisplay("[Location: {Location}, Size: {Size}]")]
public readonly struct Rectangle
{
#region Properties & Fields
/// <summary>
/// Represents a rectangle defined by it's position and it's size.
/// Gets the <see cref="Point"/> representing the top-left corner of the <see cref="Rectangle"/>.
/// </summary>
[DebuggerDisplay("[Location: {Location}, Size: {Size}]")]
public struct Rectangle
public Point Location { get; }
/// <summary>
/// Gets the <see cref="Size"/> of the <see cref="Rectangle"/>.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets a new <see cref="Point"/> representing the center-point of the <see cref="Rectangle"/>.
/// </summary>
public Point Center { get; }
/// <summary>
/// Gets a bool indicating if both, the width and the height of the rectangle is greater than zero.
/// <c>True</c> if the rectangle has a width or a height of zero; otherwise, <c>false</c>.
/// </summary>
public bool IsEmpty => (Size.Width <= FloatExtensions.TOLERANCE) || (Size.Height <= FloatExtensions.TOLERANCE);
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the provided values for <see cref="P:RGB.NET.Core.Rectangle.Location" /> ans <see cref="P:RGB.NET.Core.Rectangle.Size" />.
/// </summary>
/// <param name="x">The x-value of the <see cref="T:RGB.NET.Core.Location" /> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="y">The y-value of the <see cref="T:RGB.NET.Core.Location" />-position of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="width">The width of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="height">The height of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(float x, float y, float width, float height)
: this(new Point(x, y), new Size(width, height))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the <see cref="Location"/>(0,0) and the specified <see cref="Core.Size"/>.
/// </summary>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Size size) : this(new Point(), size)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the specified <see cref="Point"/> and <see cref="Core.Size"/>.
/// </summary>
/// <param name="location">The location of this of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Point location, Size size)
{
#region Properties & Fields
this.Location = location;
this.Size = size;
/// <summary>
/// Gets the <see cref="Point"/> representing the top-left corner of the <see cref="Rectangle"/>.
/// </summary>
public Point Location { get; }
/// <summary>
/// Gets the <see cref="Size"/> of the <see cref="Rectangle"/>.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets a new <see cref="Point"/> representing the center-point of the <see cref="Rectangle"/>.
/// </summary>
public Point Center { get; }
/// <summary>
/// Gets a bool indicating if both, the width and the height of the rectangle is greater than zero.
/// <c>True</c> if the rectangle has a width or a height of zero; otherwise, <c>false</c>.
/// </summary>
public bool IsEmpty => (Size.Width <= DoubleExtensions.TOLERANCE) || (Size.Height <= DoubleExtensions.TOLERANCE);
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the provided values for <see cref="P:RGB.NET.Core.Rectangle.Location" /> ans <see cref="P:RGB.NET.Core.Rectangle.Size" />.
/// </summary>
/// <param name="x">The x-value of the <see cref="T:RGB.NET.Core.Location" /> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="y">The y-value of the <see cref="T:RGB.NET.Core.Location" />-position of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="width">The width of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="height">The height of the <see cref="T:RGB.NET.Core.Size"/> of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(double x, double y, double width, double height)
: this(new Point(x, y), new Size(width, height))
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the <see cref="Location"/>(0,0) and the given <see cref="Core.Size"/>.
/// </summary>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Size size) : this(new Point(), size)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the given <see cref="Point"/> and <see cref="Core.Size"/>.
/// </summary>
/// <param name="location">The location of this of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Point location, Size size)
{
this.Location = location;
this.Size = size;
Center = new Point(Location.X + (Size.Width / 2.0), Location.Y + (Size.Height / 2.0));
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the given array of <see cref="T:RGB.NET.Core.Rectangle" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The array of <see cref="T:RGB.NET.Core.Rectangle" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /></param>
public Rectangle(params Rectangle[] rectangles)
: this(rectangles.AsEnumerable())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the given list of <see cref="Rectangle"/>.
/// The <see cref="Location"/> and <see cref="Size"/> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The list of <see cref="Rectangle"/> used to calculate the <see cref="Location"/> and <see cref="Size"/></param>
public Rectangle(IEnumerable<Rectangle> rectangles)
{
bool hasPoint = false;
double posX = double.MaxValue;
double posY = double.MaxValue;
double posX2 = double.MinValue;
double posY2 = double.MinValue;
if (rectangles != null)
foreach (Rectangle rectangle in rectangles)
{
hasPoint = true;
posX = Math.Min(posX, rectangle.Location.X);
posY = Math.Min(posY, rectangle.Location.Y);
posX2 = Math.Max(posX2, rectangle.Location.X + rectangle.Size.Width);
posY2 = Math.Max(posY2, rectangle.Location.Y + rectangle.Size.Height);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0), Location.Y + (Size.Height / 2.0));
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the given array of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The array of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /></param>
public Rectangle(params Point[] points)
: this(points.AsEnumerable())
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the given list of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The list of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /></param>
public Rectangle(IEnumerable<Point> points)
: this()
{
bool hasPoint = false;
double posX = double.MaxValue;
double posY = double.MaxValue;
double posX2 = double.MinValue;
double posY2 = double.MinValue;
if (points != null)
foreach (Point point in points)
{
hasPoint = true;
posX = Math.Min(posX, point.X);
posY = Math.Min(posY, point.Y);
posX2 = Math.Max(posX2, point.X);
posY2 = Math.Max(posY2, point.Y);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0), Location.Y + (Size.Height / 2.0));
}
#endregion
#region Methods
private static (Point location, Size size) InitializeFromPoints(Point point1, Point point2)
{
double posX = Math.Min(point1.X, point2.X);
double posY = Math.Min(point1.Y, point2.Y);
double width = Math.Max(point1.X, point2.X) - posX;
double height = Math.Max(point1.Y, point2.Y) - posY;
return (new Point(posX, posY), new Size(width, height));
}
/// <summary>
/// Converts the <see cref="Location"/>- and <see cref="Size"/>-position of this <see cref="Rectangle"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Location"/> and <see cref="Size"/> of this <see cref="Rectangle"/>. For example "[Location: [X: 100, Y: 10], Size: [Width: 20, Height: [40]]".</returns>
public override string ToString() => $"[Location: {Location}, Size: {Size}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Rectangle" /> and is equivalent to this <see cref="Rectangle" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Rectangle" /> equivalent to this <see cref="Rectangle" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
if (!(obj is Rectangle compareRect))
return false;
if (GetType() != compareRect.GetType())
return false;
return (Location == compareRect.Location) && (Size == compareRect.Size);
}
/// <summary>
/// Returns a hash code for this <see cref="Rectangle" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Rectangle" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Location.GetHashCode();
hashCode = (hashCode * 397) ^ Size.GetHashCode();
return hashCode;
}
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(Rectangle rectangle1, Rectangle rectangle2) => rectangle1.Equals(rectangle2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(Rectangle rectangle1, Rectangle rectangle2) => !(rectangle1 == rectangle2);
#endregion
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified array of <see cref="T:RGB.NET.Core.Rectangle" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The array of <see cref="T:RGB.NET.Core.Rectangle" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(params Rectangle[] rectangles)
: this(rectangles.AsEnumerable())
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the specified list of <see cref="Rectangle"/>.
/// The <see cref="Location"/> and <see cref="Size"/> is calculated to completely contain all rectangles provided as parameters.
/// </summary>
/// <param name="rectangles">The list of <see cref="Rectangle"/> used to calculate the <see cref="Location"/> and <see cref="Size"/>.</param>
public Rectangle(IEnumerable<Rectangle> rectangles)
{
bool hasPoint = false;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
foreach (Rectangle rectangle in rectangles)
{
hasPoint = true;
posX = Math.Min(posX, rectangle.Location.X);
posY = Math.Min(posY, rectangle.Location.Y);
posX2 = Math.Max(posX2, rectangle.Location.X + rectangle.Size.Width);
posY2 = Math.Max(posY2, rectangle.Location.Y + rectangle.Size.Height);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified array of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The array of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(params Point[] points)
: this(points.AsEnumerable())
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified list of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The list of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(IEnumerable<Point> points)
: this()
{
bool hasPoint = false;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
foreach (Point point in points)
{
hasPoint = true;
posX = Math.Min(posX, point.X);
posY = Math.Min(posY, point.Y);
posX2 = Math.Max(posX2, point.X);
posY2 = Math.Max(posY2, point.Y);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
#endregion
#region Methods
private static (Point location, Size size) InitializeFromPoints(in Point point1, in Point point2)
{
float posX = Math.Min(point1.X, point2.X);
float posY = Math.Min(point1.Y, point2.Y);
float width = Math.Max(point1.X, point2.X) - posX;
float height = Math.Max(point1.Y, point2.Y) - posY;
return (new Point(posX, posY), new Size(width, height));
}
/// <summary>
/// Converts the <see cref="Location"/>- and <see cref="Size"/>-position of this <see cref="Rectangle"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Location"/> and <see cref="Size"/> of this <see cref="Rectangle"/>. For example "[Location: [X: 100, Y: 10], Size: [Width: 20, Height: [40]]".</returns>
public override string ToString() => $"[Location: {Location}, Size: {Size}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Rectangle" /> and is equivalent to this <see cref="Rectangle" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Rectangle" /> equivalent to this <see cref="Rectangle" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Rectangle compareRect)
return false;
if (GetType() != compareRect.GetType())
return false;
return (Location == compareRect.Location) && (Size == compareRect.Size);
}
/// <summary>
/// Returns a hash code for this <see cref="Rectangle" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Rectangle" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Location.GetHashCode();
hashCode = (hashCode * 397) ^ Size.GetHashCode();
return hashCode;
}
}
#endregion
#region Operators
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(in Rectangle rectangle1, in Rectangle rectangle2) => rectangle1.Equals(rectangle2);
/// <summary>
/// Returns a value that indicates whether two specified <see cref="Rectangle" /> are equal.
/// </summary>
/// <param name="rectangle1">The first <see cref="Rectangle" /> to compare.</param>
/// <param name="rectangle2">The second <see cref="Rectangle" /> to compare.</param>
/// <returns><c>true</c> if <paramref name="rectangle1" /> and <paramref name="rectangle2" /> are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(in Rectangle rectangle1, in Rectangle rectangle2) => !(rectangle1 == rectangle2);
// DarthAffe 20.02.2021: Used for normalization
/// <summary>
/// Returns a <see cref="Rectangle"/> normalized to the specified reference.
/// </summary>
/// <param name="rectangle1">The rectangle to nromalize.</param>
/// <param name="rectangle2">The reference used for normalization.</param>
/// <returns>A normalized rectangle.</returns>
public static Rectangle operator /(in Rectangle rectangle1, in Rectangle rectangle2)
{
float x = rectangle1.Location.X / (rectangle2.Size.Width - rectangle2.Location.X);
float y = rectangle1.Location.Y / (rectangle2.Size.Height - rectangle2.Location.Y);
Size size = rectangle1.Size / rectangle2.Size;
return new Rectangle(new Point(x, y), size);
}
#endregion
}

Some files were not shown because too many files have changed in this diff Show More