1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-13 01:58:30 +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,16 +7,21 @@
<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="CustomData">
<xsd:complexType>
<xsd:sequence>
<xsd:any />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="Leds">
<xsd:complexType>
<xsd:sequence>
@ -28,37 +33,19 @@
<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>
</xsd:element>
</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:complexType>

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.
> **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/).
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).
## 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();
```
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.)
## Adding prerelease packages using NuGet ##
This is the easiest and therefore preferred way to include RGB.NET in your project.
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.
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).
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();
```
### .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.
The basic setup is now complete and you can start setting up your rendering.
### 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);
```
### 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.
2. Create a rainbow-gradient.
```csharp
RainbowGradient rainbow = new RainbowGradient();
```
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
3. Add a decorator to the gradient to make it move. (Decorators are
```csharp
rainbow.AddDecorator(new MoveGradientDecorator(surface));
```
### Example usage of RGB.NET
[![Example video](https://img.youtube.com/vi/JLRa0Wv4qso/0.jpg)](http://www.youtube.com/watch?v=JLRa0Wv4qso)
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);
```
#### Example Projects
[https://github.com/DarthAffe/KeyboardAudioVisualizer](https://github.com/DarthAffe/KeyboardAudioVisualizer)
[https://github.com/DarthAffe/RGBSyncPlus](https://github.com/DarthAffe/RGBSyncPlus)
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
{
#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()}]";
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(Color color, object obj)
public virtual bool Equals(in Color color, object? obj)
{
if (!(obj is Color)) return false;
if (obj is not Color color2) 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);
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(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;
}
}
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="color">The <see cref="Color"/> to blend.</param>
public virtual Color Blend(Color baseColor, Color blendColor)
/// <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;
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));
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
{
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,55 +5,50 @@
using System;
using System.Diagnostics;
namespace RGB.NET.Core
{
/// <inheritdoc />
namespace RGB.NET.Core;
/// <summary>
/// Represents an ARGB (alpha, red, green, blue) color.
/// </summary>
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
public struct Color
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 Color Transparent => new Color(0, 0, 0, 0);
public static ref readonly Color Transparent => ref TRANSPARENT;
#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;
}
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 double A { get; }
public readonly float A;
/// <summary>
/// Gets the red component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public double R { get; }
public readonly float R;
/// <summary>
/// Gets the green component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public double G { get; }
public readonly float G;
/// <summary>
/// Gets the blue component value of this <see cref="Color"/> as percentage in the range [0..1].
/// </summary>
public double B { get; }
public readonly float B;
#endregion
@ -112,8 +107,8 @@ namespace RGB.NET.Core
/// <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)
public Color(float r, float g, float b)
: this(1.0f, r, g, b)
{ }
/// <summary>
@ -123,7 +118,7 @@ namespace RGB.NET.Core
/// <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)
public Color(float a, byte r, byte g, byte b)
: this(a, r.GetPercentageFromByteValue(), g.GetPercentageFromByteValue(), b.GetPercentageFromByteValue())
{ }
@ -134,7 +129,7 @@ namespace RGB.NET.Core
/// <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)
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))
{ }
@ -145,7 +140,7 @@ namespace RGB.NET.Core
/// <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)
public Color(int a, float r, float g, float b)
: this((byte)a.Clamp(0, byte.MaxValue), r, g, b)
{ }
@ -156,7 +151,7 @@ namespace RGB.NET.Core
/// <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)
public Color(byte a, float r, float g, float b)
: this(a.GetPercentageFromByteValue(), r, g, b)
{ }
@ -167,7 +162,7 @@ namespace RGB.NET.Core
/// <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)
public Color(float a, float r, float g, float b)
{
A = a.Clamp(0, 1);
R = r.Clamp(0, 1);
@ -180,7 +175,7 @@ namespace RGB.NET.Core
/// 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)
public Color(in Color color)
: this(color.A, color.R, color.G, color.B)
{ }
@ -199,19 +194,20 @@ namespace RGB.NET.Core
/// </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);
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(Color color) => Behavior.Blend(this, color);
public Color Blend(in Color color) => Behavior.Blend(this, color);
#endregion
@ -223,7 +219,7 @@ namespace RGB.NET.Core
/// <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);
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.
@ -231,7 +227,7 @@ namespace RGB.NET.Core
/// <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);
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.
@ -239,50 +235,49 @@ namespace RGB.NET.Core
/// <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);
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 Color(components.r, components.g, components.b);
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 Color(components.a, components.r, components.g, components.b);
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 Color(components.r, components.g, components.b);
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 Color(components.a, components.r, components.g, components.b);
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((double r, double g, double b) components) => new Color(components.r, components.g, components.b);
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((double a, double r, double g, double b) components) => new Color(components.a, components.r, components.g, components.b);
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,8 +2,11 @@
// 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
{
#region Getter
@ -11,23 +14,23 @@ namespace RGB.NET.Core
/// <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;
/// <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"></param>
/// <returns></returns>
public static double GetSaturation(this Color color) => color.GetHSV().saturation;
/// <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"></param>
/// <returns></returns>
public static double GetValue(this Color color) => color.GetHSV().value;
/// <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"/>.
@ -35,9 +38,9 @@ namespace RGB.NET.Core
/// 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)
/// <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
@ -45,67 +48,72 @@ namespace RGB.NET.Core
#region Manipulation
/// <summary>
/// Adds the given HSV values to this color.
/// 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 Color color, double hue = 0, double saturation = 0, double value = 0)
public static Color AddHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue + hue, cSaturation + saturation, cValue + value);
}
/// <summary>
/// Subtracts the given HSV values to this color.
/// 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 Color color, double hue = 0, double saturation = 0, double value = 0)
public static Color SubtractHSV(this in Color color, float hue = 0, float saturation = 0, float value = 0)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue - hue, cSaturation - saturation, cValue - value);
}
/// <summary>
/// Multiplies the given HSV values to this color.
/// 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 Color color, double hue = 1, double saturation = 1, double value = 1)
public static Color MultiplyHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue * hue, cSaturation * saturation, cValue * value);
}
/// <summary>
/// Divides the given HSV values to this color.
/// 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 Color color, double hue = 1, double saturation = 1, double value = 1)
public static Color DivideHSV(this in Color color, float hue = 1, float saturation = 1, float value = 1)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, cHue / hue, cSaturation / saturation, cValue / value);
}
/// <summary>
/// Sets the given hue value of this color.
/// 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 Color color, double? hue = null, double? saturation = null, double? value = null)
public static Color SetHSV(this in Color color, float? hue = null, float? saturation = null, float? value = null)
{
(double cHue, double cSaturation, double cValue) = color.GetHSV();
(float cHue, float cSaturation, float cValue) = color.GetHSV();
return Create(color.A, hue ?? cHue, saturation ?? cSaturation, value ?? cValue);
}
@ -120,8 +128,8 @@ namespace RGB.NET.Core
/// <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);
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.
@ -131,8 +139,8 @@ namespace RGB.NET.Core
/// <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);
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.
@ -142,8 +150,8 @@ namespace RGB.NET.Core
/// <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);
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.
@ -153,9 +161,9 @@ namespace RGB.NET.Core
/// <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)
public static Color Create(float a, float hue, float saturation, float value)
{
(double r, double g, double b) = CalculateRGBFromHSV(hue, saturation, value);
(float r, float g, float b) = CalculateRGBFromHSV(hue, saturation, value);
return new Color(a, r, g, b);
}
@ -163,33 +171,33 @@ namespace RGB.NET.Core
#region Helper
private static (double h, double s, double v) CaclulateHSVFromRGB(double r, double g, double b)
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);
double min = Math.Min(Math.Min(r, g), b);
double max = Math.Max(Math.Max(r, g), b);
float min = Math.Min(Math.Min(r, g), b);
float max = Math.Max(Math.Max(r, g), b);
double hue;
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.0 + ((b - r) / (max - min));
hue = 2.0f + ((b - r) / (max - min));
else // b is max
hue = 4.0 + ((r - g) / (max - min));
hue = 4.0f + ((r - g) / (max - min));
hue = hue * 60.0;
hue *= 60.0f;
hue = hue.Wrap(0, 360);
double saturation = max.EqualsInTolerance(0) ? 0 : 1.0 - (min / max);
double value = Math.Max(r, Math.Max(g, b));
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 (double r, double g, double b) CalculateRGBFromHSV(double h, double s, double v)
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);
@ -198,30 +206,23 @@ namespace RGB.NET.Core
if (s <= 0.0)
return (v, v, v);
double hh = h / 60.0;
float hh = h / 60.0f;
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)));
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)));
switch (i)
return i switch
{
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);
}
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,8 +2,11 @@
// 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
{
#region Getter
@ -11,45 +14,45 @@ namespace RGB.NET.Core
/// <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();
/// <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"></param>
/// <returns></returns>
public static byte GetR(this Color color) => color.R.GetByteValueFromPercentage();
/// <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"></param>
/// <returns></returns>
public static byte GetG(this Color color) => color.G.GetByteValueFromPercentage();
/// <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"></param>
/// <returns></returns>
public static byte GetB(this Color color) => color.B.GetByteValueFromPercentage();
/// <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"></param>
/// <returns></returns>
public static (byte a, byte r, byte g, byte b) GetRGBBytes(this Color color)
/// <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"></param>
/// <returns></returns>
public static (double a, double r, double g, double b) GetRGB(this Color color)
/// <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
@ -59,172 +62,189 @@ namespace RGB.NET.Core
#region Add
/// <summary>
/// Adds the given RGB values to this color.
/// 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 Color color, int r = 0, int g = 0, int b = 0)
=> new Color(color.A, color.GetR() + r, color.GetG() + g, color.GetB() + b);
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 given RGB-percent values to this color.
/// 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 Color color, double r = 0, double g = 0, double b = 0)
=> new Color(color.A, color.R + r, color.G + g, color.B + b);
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 given alpha value to this color.
/// 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 Color color, int a)
=> new Color(color.GetA() + a, color.R, color.G, color.B);
public static Color AddA(this in Color color, int a)
=> new(color.GetA() + a, color.R, color.G, color.B);
/// <summary>
/// Adds the given alpha-percent value to this color.
/// 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 Color color, double a)
=> new Color(color.A + a, color.R, color.G, color.B);
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 given RGB values to this color.
/// 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 Color color, int r = 0, int g = 0, int b = 0)
=> new Color(color.A, color.GetR() - r, color.GetG() - g, color.GetB() - b);
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 given RGB values to this color.
/// 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 Color color, double r = 0, double g = 0, double b = 0)
=> new Color(color.A, color.R - r, color.G - g, color.B - b);
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 given alpha value to this color.
/// 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 Color color, int a)
=> new Color(color.GetA() - a, color.R, color.G, color.B);
public static Color SubtractA(this in Color color, int a)
=> new(color.GetA() - a, color.R, color.G, color.B);
/// <summary>
/// Subtracts the given alpha-percent value to this color.
/// Subtracts the specified alpha-percent value to this color.
/// </summary>
/// <param name="a">The alpha value to subtract.</param>
/// <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 Color color, double aPercent)
=> new Color(color.A - aPercent, color.R, color.G, color.B);
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 given RGB values to this color.
/// 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 Color color, double r = 1, double g = 1, double b = 1)
=> new Color(color.A, color.R * r, color.G * g, color.B * b);
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 given alpha value to this color.
/// 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 Color color, double a)
=> new Color(color.A * a, color.R, color.G, color.B);
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 given RGB values to this color.
/// 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 Color color, double r = 1, double g = 1, double b = 1)
=> new Color(color.A, color.R / r, color.G / g, color.B / b);
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 given alpha value to this color.
/// 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 Color color, double a)
=> new Color(color.A / a, color.R, color.G, color.B);
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 given RGB value of this color.
/// 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 Color color, byte? r = null, byte? g = null, byte? b = null)
=> new Color(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
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 given RGB value of this color.
/// 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 Color color, int? r = null, int? g = null, int? b = null)
=> new Color(color.A, r ?? color.GetR(), g ?? color.GetG(), b ?? color.GetB());
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 given RGB value of this color.
/// 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 Color color, double? r = null, double? g = null, double? b = null)
=> new Color(color.A, r ?? color.R, g ?? color.G, b ?? color.B);
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 given alpha value of this color.
/// 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 Color color, int a) => new Color(a, color.R, color.G, color.B);
public static Color SetA(this in Color color, int a) => new(a, color.R, color.G, color.B);
/// <summary>
/// Sets the given alpha value of this color.
/// 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 Color color, double a) => new Color(a, color.R, color.G, color.B);
public static Color SetA(this in Color color, float a) => new(a, color.R, color.G, color.B);
#endregion
@ -236,13 +256,13 @@ namespace RGB.NET.Core
/// 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());
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 Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetA(), color.GetR(), color.GetG(), color.GetB());
public static string AsARGBHexString(this in Color color, bool leadingHash = true) => (leadingHash ? "#" : "") + ConversionHelper.ToHex(color.GetA(), color.GetR(), color.GetG(), color.GetB());
#endregion
@ -258,18 +278,18 @@ namespace RGB.NET.Core
if ((hexString == null) || (hexString.Length < 6))
throw new ArgumentException("Invalid hex string", nameof(hexString));
if (hexString[0] == '#')
hexString = hexString.Substring(1);
ReadOnlySpan<char> span = hexString.AsSpan();
if (span[0] == '#')
span = span[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]);
throw new ArgumentException("Invalid hex string", nameof(hexString));
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>
/// Applies the <see cref="IColorCorrection"/> to the given <see cref="Color"/>.
/// Applies the <see cref="IColorCorrection"/> to the specified <see cref="Color"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/> to correct.</param>
Color ApplyTo(Color color);
}
void ApplyTo(ref Color color);
}

View File

@ -2,8 +2,8 @@
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>
@ -11,16 +11,21 @@ namespace RGB.NET.Core
{
#region Properties & Fields
private readonly List<T> _decorators = new List<T>();
private readonly List<T> _decorators = new();
/// <inheritdoc />
public IReadOnlyCollection<T> Decorators
public IReadOnlyList<T> Decorators { get; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractDecoratable{T}"/> class.
/// </summary>
protected AbstractDecoratable()
{
get
{
lock (_decorators)
return new ReadOnlyCollection<T>(_decorators);
}
Decorators = new ReadOnlyCollection<T>(_decorators);
}
#endregion
@ -62,4 +67,3 @@ namespace RGB.NET.Core
#endregion
}
}

View File

@ -2,8 +2,8 @@
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
@ -29,7 +29,7 @@ namespace RGB.NET.Core
/// <summary>
/// Gets a readonly-list of all <see cref="IDecoratable"/> this decorator is attached to.
/// </summary>
protected List<IDecoratable> DecoratedObjects { get; } = new List<IDecoratable>();
protected List<IDecoratable> DecoratedObjects { get; } = new();
#endregion
@ -46,7 +46,7 @@ namespace RGB.NET.Core
/// </summary>
protected virtual void Detach()
{
List<IDecoratable> decoratables = new List<IDecoratable>(DecoratedObjects);
List<IDecoratable> decoratables = new(DecoratedObjects);
foreach (IDecoratable decoratable in decoratables)
{
IEnumerable<Type> types = decoratable.GetType().GetInterfaces().Where(t => t.IsGenericType
@ -59,4 +59,3 @@ namespace RGB.NET.Core
#endregion
}
}

View File

@ -1,5 +1,5 @@
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.
@ -8,6 +8,11 @@
{
#region Properties & Fields
/// <summary>
/// Gets the surface this decorator is attached to.
/// </summary>
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>
@ -20,9 +25,11 @@
/// <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(bool updateIfDisabled = false)
protected AbstractUpdateAwareDecorator(RGBSurface surface, bool updateIfDisabled = false)
{
this.Surface = surface;
this.UpdateIfDisabled = updateIfDisabled;
}
@ -34,7 +41,7 @@
public override void OnAttached(IDecoratable decoratable)
{
if (DecoratedObjects.Count == 0)
RGBSurface.Instance.Updating += OnSurfaceUpdating;
Surface.Updating += OnSurfaceUpdating;
base.OnAttached(decoratable);
}
@ -45,7 +52,7 @@
base.OnDetached(decoratable);
if (DecoratedObjects.Count == 0)
RGBSurface.Instance.Updating -= OnSurfaceUpdating;
Surface.Updating -= OnSurfaceUpdating;
}
private void OnSurfaceUpdating(UpdatingEventArgs args)
@ -62,4 +69,3 @@
#endregion
}
}

View File

@ -1,5 +1,5 @@
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" />.
@ -12,6 +12,5 @@
/// <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);
}
void ManipulateColor(in Rectangle rectangle, in RenderTarget renderTarget, ref Color color);
}

View File

@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace RGB.NET.Core
{
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic decoratable.
/// </summary>
@ -13,14 +13,14 @@ namespace RGB.NET.Core
/// <summary>
/// Represents a basic decoratable for a specific type of <see cref="T:RGB.NET.Core.IDecorator" />
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="T">The type of decorators this decoratable can be decorated with.</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; }
IReadOnlyList<T> Decorators { get; }
/// <summary>
/// Adds an <see cref="IDecorator"/> to the <see cref="IDecoratable"/>.
@ -39,4 +39,3 @@ namespace RGB.NET.Core
/// </summary>
void RemoveAllDecorators();
}
}

View File

@ -1,5 +1,5 @@
namespace RGB.NET.Core
{
namespace RGB.NET.Core;
/// <summary>
/// Represents a basic decorator.
/// </summary>
@ -23,17 +23,16 @@
#region Methods
/// <summary>
/// Attaches this <see cref="IDecorator"/> to the given target.
/// 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>
/// Detaches this <see cref="IDecorator"/> from the given target.
/// 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);
#endregion
}
}

View File

@ -1,9 +1,8 @@
namespace RGB.NET.Core
{
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,137 +2,94 @@
// 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> : AbstractBindable, IRGBDevice<TDeviceInfo>
public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDeviceInfo>
where TDeviceInfo : class, IRGBDeviceInfo
{
private RGBSurface? _surface;
#region Properties & Fields
RGBSurface? IRGBDevice.Surface
{
get => _surface;
set
{
if (SetProperty(ref _surface, value))
{
if (value == null) OnDetached();
else OnAttached();
}
}
}
/// <inheritdoc />
public abstract TDeviceInfo DeviceInfo { get; }
public TDeviceInfo DeviceInfo { get; }
/// <inheritdoc />
IRGBDeviceInfo IRGBDevice.DeviceInfo => DeviceInfo;
private Point _location = new Point(0, 0);
/// <inheritdoc />
public Point Location
{
get => _location;
set
{
if (SetProperty(ref _location, value))
UpdateActualData();
}
}
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();
}
}
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;
/// <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>();
protected Dictionary<LedId, Led> LedMapping { get; } = new();
/// <summary>
/// Gets a dictionary containing all <see cref="IRGBDeviceSpecialPart"/> associated with this <see cref="IRGBDevice"/>.
/// Gets the update queue used to update this device.
/// </summary>
protected Dictionary<Type, IRGBDeviceSpecialPart> SpecialDeviceParts { get; } = new Dictionary<Type, IRGBDeviceSpecialPart>();
protected IUpdateQueue UpdateQueue { get; }
#region Indexer
/// <inheritdoc />
Led IRGBDevice.this[LedId ledId] => LedMapping.TryGetValue(ledId, out Led led) ? led : null;
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));
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.LedRectangle) >= 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
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)
{
@ -140,29 +97,69 @@ namespace RGB.NET.Core
DeviceUpdate();
// Send LEDs to SDK
List<Led> ledsToUpdate = GetLedsToUpdate(flushLeds)?.ToList() ?? new List<Led>();
foreach (Led ledToUpdate in ledsToUpdate)
ledToUpdate.Update();
List<Led> ledsToUpdate = GetLedsToUpdate(flushLeds).ToList();
foreach (Led led in ledsToUpdate)
led.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));
/// <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);
/// <inheritdoc />
public virtual void SyncBack()
{ }
/// <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
{
SpecialDeviceParts.Clear();
LedMapping.Clear();
}
catch { /* this really shouldn't happen */ }
try { UpdateQueue.Dispose(); } catch { /* :( */ }
try { LedMapping.Clear(); } catch { /* this really shouldn't happen */ }
IdGenerator.ResetCounter(GetType().Assembly);
}
/// <summary>
@ -171,109 +168,53 @@ namespace RGB.NET.Core
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)
/// <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 Led(this, ledId, location, size, CreateLedCustomData(ledId));
Led led = new(this, ledId, location, size, customData ?? GetLedCustomData(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;
public virtual Led? RemoveLed(LedId ledId)
{
if (ledId == LedId.Invalid) return null;
if (!LedMapping.TryGetValue(ledId, out Led? led)) return null;
/// <inheritdoc />
public T GetSpecialDevicePart<T>()
where T : class, IRGBDeviceSpecialPart
=> SpecialDeviceParts.TryGetValue(typeof(T), out IRGBDeviceSpecialPart devicePart) ? (T)devicePart : default;
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
@ -295,4 +236,3 @@ namespace RGB.NET.Core
#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,58 +1,32 @@
using System;
using System.Collections.Generic;
namespace RGB.NET.Core
{
/// <inheritdoc cref="IEnumerable{T}" />
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>, IBindable, IDisposable
public interface IRGBDevice : IEnumerable<Led>, IPlaceable, IBindable, IDisposable
{
#region Properties
/// <summary>
/// Gets the surface this device is attached to.
/// </summary>
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"/>.
/// Gets a list of color corrections applied to this device.
/// </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; }
IList<IColorCorrection> ColorCorrections { get; }
#endregion
@ -63,21 +37,21 @@ namespace RGB.NET.Core
/// </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; }
Led? this[LedId ledId] { get; }
/// <summary>
/// Gets the <see cref="Led" /> at the given physical location.
/// 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 given <see cref="Point"/> or null if no location is found.</returns>
Led this[Point location] { get; }
/// <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 given <see cref="Rectangle"/>.
/// 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></returns>
/// <returns>A enumerable of leds inside the specified rectangle.</returns>
IEnumerable<Led> this[Rectangle referenceRect, double minOverlayPercentage = 0.5] { get; }
#endregion
@ -91,25 +65,21 @@ namespace RGB.NET.Core
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.
/// Adds a led to the device.
/// </summary>
void SyncBack();
/// <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>
/// Adds the given <see cref="IRGBDeviceSpecialPart"/> to the device.
/// This will override existing <see cref="IRGBDeviceSpecialPart"/> of the same type.
/// Removes the led with the specified id from the device.
/// </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;
/// <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
}
@ -126,4 +96,3 @@ namespace RGB.NET.Core
/// </summary>
new TDeviceInfo DeviceInfo { get; }
}
}

View File

@ -1,7 +1,5 @@
using System;
namespace RGB.NET.Core;
namespace RGB.NET.Core
{
/// <summary>
/// Represents a generic information for a <see cref="IRGBDevice"/>
/// </summary>
@ -30,20 +28,9 @@ namespace RGB.NET.Core
string Model { get; }
/// <summary>
/// Gets the lighting capability of the <see cref="IRGBDevice"/>
/// Gets custom metadata added to the layout.
/// </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; }
object? LayoutMetadata { get; set; }
#endregion
}
}

View File

@ -1,8 +1,10 @@
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>
@ -16,33 +18,40 @@ namespace RGB.NET.Core
bool IsInitialized { get; }
/// <summary>
/// Gets a list of <see cref="IRGBDevice"/> loaded by this <see cref="IRGBDeviceProvider"/>.
/// Indicates if exceptions in the device provider are thrown or silently ignored.
/// </summary>
bool ThrowsExceptions { 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.
/// Gets a collection <see cref="IDeviceUpdateTrigger"/> registered to this device provider.
/// </summary>
bool HasExclusiveAccess { get; }
IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers { get; }
#endregion
#region Events
/// <summary>
/// Occurs when an exception is thrown in the device provider.
/// </summary>
event EventHandler<ExceptionEventArgs>? Exception;
#endregion
#region Methods
/// <summary>
/// Initializes the <see cref="IRGBDeviceProvider"/> if not already happened or reloads it if it is already initialized.
/// Initializes the device provider and loads available devices.
/// </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>
/// Resets all handled <see cref="IRGBDevice"/> back top default.
/// </summary>
void ResetDevices();
/// <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,7 +1,7 @@
using System;
namespace RGB.NET.Core
{
namespace RGB.NET.Core;
/// <summary>
/// Contains a list of different types of device.
/// </summary>
@ -83,6 +83,16 @@ namespace RGB.NET.Core
/// </summary>
Cooler = 1 << 13,
/// <summary>
/// Represents a monitor.
/// </summary>
Monitor = 1 << 14,
/// <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>
@ -93,4 +103,3 @@ namespace RGB.NET.Core
/// </summary>
All = ~None
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
namespace RGB.NET.Core
{
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 keyboard-device
/// Represents a generic keyboard-device.
/// </summary>
public interface IKeyboard : IRGBDevice
{ }
{
/// <summary>
/// Gets the device information assiciated with this device.
/// </summary>
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
{
namespace RGB.NET.Core;
/// <summary>
/// Represents a keypad-device
/// </summary>
public interface IKeypad : IRGBDevice
{ }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@
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.
@ -18,6 +18,16 @@ namespace RGB.NET.Core
/// </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
@ -27,11 +37,14 @@ namespace RGB.NET.Core
/// 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)
/// <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)
{
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,10 +2,9 @@
// 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.
@ -17,7 +16,7 @@ namespace RGB.NET.Core
/// <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; }
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"/>.
@ -25,9 +24,14 @@ namespace RGB.NET.Core
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"/>.
/// 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 DeviceLocationChanged { get; }
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
@ -39,14 +43,24 @@ namespace RGB.NET.Core
/// </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)
/// <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)
{
this.Devices = devices;
this.DeviceAdded = deviceAdded;
this.DeviceLocationChanged = deviceLocationChanged;
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
{
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,8 +3,8 @@
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.
@ -21,7 +21,7 @@ namespace RGB.NET.Core
/// <summary>
/// Gets the trigger causing this update.
/// </summary>
public IUpdateTrigger Trigger { get; }
public IUpdateTrigger? Trigger { get; }
/// <summary>
/// Gets the custom-data provided by the trigger for this update.
@ -39,7 +39,7 @@ namespace RGB.NET.Core
/// <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)
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, CustomUpdateData customData)
{
this.DeltaTime = deltaTime;
this.Trigger = trigger;
@ -48,4 +48,3 @@ namespace RGB.NET.Core
#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,7 +1,7 @@
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" />.
@ -16,10 +16,9 @@ namespace RGB.NET.Core
/// </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)
public RGBDeviceException(string message, Exception? innerException = null)
: base(message, innerException)
{ }
#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,19 +1,22 @@
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
{
#region Methods
/// <summary>
/// Calculates the distance between the two given colors using the redmean algorithm.
/// 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></returns>
public static double DistanceTo(this Color color1, Color color2)
/// <returns>The redmean distance between the two specified colors.</returns>
public static double DistanceTo(this in Color color1, in Color color2)
{
(_, byte r1, byte g1, byte b1) = color1.GetRGBBytes();
(_, byte r2, byte g2, byte b2) = color2.GetRGBBytes();
@ -27,4 +30,3 @@ namespace RGB.NET.Core
#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,19 +1,19 @@
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 doubles
/// Offers some extensions and helper-methods for the work with floats.
/// </summary>
public static class DoubleExtensions
public static class FloatExtensions
{
#region Constants
/// <summary>
/// Defines the precision RGB.NET processes floating point comparisons in.
/// </summary>
public const double TOLERANCE = 1E-10;
public const float TOLERANCE = 1E-7f;
#endregion
@ -26,7 +26,7 @@ namespace RGB.NET.Core
/// <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;
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.
@ -36,7 +36,7 @@ namespace RGB.NET.Core
/// <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)
public static float Clamp(this float value, float min, float max)
{
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
if (value < min) return min;
@ -70,9 +70,9 @@ namespace RGB.NET.Core
/// <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)
public static float Wrap(this float value, float min, float max)
{
double range = max - min;
float range = max - min;
while (value >= max)
value -= range;
@ -83,19 +83,28 @@ namespace RGB.NET.Core
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 double percentage)
public static byte GetByteValueFromPercentage(this float percentage)
{
if (double.IsNaN(percentage)) return 0;
if (float.IsNaN(percentage)) return 0;
percentage = percentage.Clamp(0, 1.0);
return (byte)(percentage >= 1.0 ? 255 : percentage * 256.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 double GetPercentageFromByteValue(this byte value)
=> ((double)value) / byte.MaxValue;
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
{
#region Methods
/// <summary>
/// Moves the specified <see cref="Point"/> by the given amount.
/// 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 Point point, double x = 0, double y = 0) => new Point(point.X + x, point.Y + y);
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 given amuont around the given origin.
/// 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 Point point, Rotation rotation, Point origin = new Point())
public static Point Rotate(this in Point point, in Rotation rotation, in Point origin = new())
{
double sin = Math.Sin(rotation.Radians);
double cos = Math.Cos(rotation.Radians);
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
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); ;
float x = point.X - origin.X;
float y = point.Y - origin.Y;
x = (x * cos) - (y * sin);
y = (x * sin) + (y * cos);
return new Point(x + origin.X, y + origin.Y);
}
#endregion
}
}

View File

@ -1,65 +1,69 @@
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
{
#region Methods
/// <summary>
/// Sets the <see cref="Rectangle.Location"/> of the given rectangle.
/// 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 Rectangle rect, Point location) => new Rectangle(location, rect.Size);
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 given rectangle.
/// 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 Rectangle rect, double x) => new Rectangle(new Point(x, rect.Location.Y), rect.Size);
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 given rectangle.
/// 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 Rectangle rect, double y) => new Rectangle(new Point(rect.Location.X, y), rect.Size);
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 given rectangle.
/// 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 Rectangle rect, Size size) => new Rectangle(rect.Location, size);
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 given rectangle.
/// 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 Rectangle rect, double width) => new Rectangle(rect.Location, new Size(width, rect.Size.Height));
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 given rectangle.
/// 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 Rectangle rect, double height) => new Rectangle(rect.Location, new Size(rect.Size.Width, height));
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 double CalculateIntersectPercentage(this Rectangle rect, Rectangle intersectingRect)
public static float CalculateIntersectPercentage(this in Rectangle rect, in Rectangle intersectingRect)
{
if (rect.IsEmpty || intersectingRect.IsEmpty) return 0;
@ -70,15 +74,16 @@ namespace RGB.NET.Core
/// <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>
/// <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 Rectangle rect, Rectangle intersectingRectangle)
public static Rectangle CalculateIntersection(this in Rectangle rect, in 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);
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);
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);
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);
@ -89,46 +94,49 @@ namespace RGB.NET.Core
/// <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 given point; otherwise <c>false</c>.</returns>
public static bool Contains(this Rectangle rect, Point point) => rect.Contains(point.X, point.Y);
/// <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 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))
/// <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 <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))
/// <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 given amount.
/// 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 Rectangle rect, Point point) => rect.Translate(point.X, point.Y);
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 given amount.
/// 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 Rectangle rect, double x = 0, double y = 0) => new Rectangle(rect.Location.Translate(x, y), rect.Size);
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 given amuont around the given origin.
/// 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:
@ -141,17 +149,17 @@ namespace RGB.NET.Core
/// <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())
public static Point[] Rotate(this in Rectangle rect, in Rotation rotation, in Point origin = new())
{
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
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
};
double sin = Math.Sin(rotation.Radians);
double cos = Math.Cos(rotation.Radians);
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);
for (int i = 0; i < points.Length; i++)
{
@ -166,4 +174,3 @@ namespace RGB.NET.Core
#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,7 +1,8 @@
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>
@ -11,8 +12,13 @@ namespace RGB.NET.Core
{
#region Properties & Fields
RGBSurface? ILedGroup.Surface { get; set; }
/// <inheritdoc cref="ILedGroup.Surface" />
public RGBSurface? Surface => ((ILedGroup)this).Surface;
/// <inheritdoc />
public IBrush Brush { get; set; }
public IBrush? Brush { get; set; }
/// <inheritdoc />
public int ZIndex { get; set; } = 0;
@ -24,28 +30,32 @@ namespace RGB.NET.Core
/// <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)
protected AbstractLedGroup(RGBSurface? attachTo)
{
if (autoAttach)
RGBSurface.Instance.AttachLedGroup(this);
attachTo?.Attach(this);
}
#endregion
#region Methods
/// <inheritdoc />
public abstract IList<Led> GetLeds();
/// <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()
{ }
public virtual void OnAttach() { }
/// <inheritdoc />
public virtual void OnDetach()
{ }
public virtual void OnDetach() { }
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<Led> GetEnumerator() => GetLeds().GetEnumerator();
#endregion
}
}

View File

@ -3,30 +3,33 @@
using System.Collections.Generic;
namespace RGB.NET.Core
{
/// <inheritdoc />
namespace RGB.NET.Core;
/// <summary>
/// Represents a generic ledgroup.
/// </summary>
public interface ILedGroup : IDecoratable<ILedGroupDecorator>
public interface ILedGroup : IDecoratable<ILedGroupDecorator>, IEnumerable<Led>
{
/// <summary>
/// Gets the surface this group is attached to or <c>null</c> if it is not attached to any surface.
/// </summary>
RGBSurface? Surface { get; internal set; }
/// <summary>
/// Gets a bool indicating if the group is attached to a surface.
/// </summary>
bool IsAttached => Surface != null;
/// <summary>
/// Gets or sets the <see cref="IBrush"/> which should be drawn over this <see cref="ILedGroup"/>.
/// </summary>
IBrush Brush { get; set; }
IBrush? Brush { get; 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 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>
/// Called when the <see cref="ILedGroup"/> is attached to the <see cref="RGBSurface"/>.
/// </summary>
@ -37,4 +40,3 @@ namespace RGB.NET.Core
/// </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,5 +1,7 @@
namespace RGB.NET.Core
{
using System;
namespace RGB.NET.Core;
/// <summary>
/// Contains helper methods for converting things.
/// </summary>
@ -35,10 +37,10 @@
/// </summary>
/// <param name="hexString">The HEX-string to convert.</param>
/// <returns>The correspondending byte array.</returns>
public static byte[] HexToBytes(string hexString)
public static byte[] HexToBytes(ReadOnlySpan<char> hexString)
{
if ((hexString.Length == 0) || ((hexString.Length % 2) != 0))
return new byte[0];
return Array.Empty<byte>();
byte[] buffer = new byte[hexString.Length / 2];
for (int bx = 0, sx = 0; bx < buffer.Length; ++bx, ++sx)
@ -57,4 +59,3 @@
#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,17 +1,15 @@
// 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 : AbstractBindable
public class Led : Placeable
{
#region Properties & Fields
@ -35,96 +33,30 @@ namespace RGB.NET.Core
set => SetProperty(ref _shape, value);
}
private string _shapeData;
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
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;
private Rectangle _absoluteBoundary;
/// <summary>
/// Gets a rectangle representing the logical location of the <see cref="Led"/> on the <see cref="RGBSurface"/>.
/// </summary>
public Rectangle AbsoluteLedRectangle
public Rectangle AbsoluteBoundary
{
get => _absoluteLedRectangle;
private set => SetProperty(ref _absoluteLedRectangle, value);
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 != InternalColor);
public bool IsDirty => RequestedColor.HasValue && (RequestedColor != Color);
private Color? _requestedColor;
/// <summary>
@ -151,46 +83,23 @@ namespace RGB.NET.Core
{
get => _color;
set
{
if (!IsLocked)
{
if (RequestedColor.HasValue)
RequestedColor += value;
RequestedColor = RequestedColor.Value + value;
else
RequestedColor = value;
}
RequestedColor = _color + 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; }
public object? CustomData { get; }
/// <summary>
/// Gets or sets some custom metadata of this led.
/// </summary>
public object? LayoutMetadata { get; set; }
#endregion
@ -204,56 +113,26 @@ namespace RGB.NET.Core
/// <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)
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;
device.PropertyChanged += DevicePropertyChanged;
}
#endregion
#region Methods
private void DevicePropertyChanged(object sender, PropertyChangedEventArgs e)
/// <inheritdoc />
protected override void UpdateActualPlaceableData()
{
if ((e.PropertyName == nameof(IRGBDevice.Location)))
UpdateAbsoluteData();
else if (e.PropertyName == nameof(IRGBDevice.DeviceRectangle))
{
UpdateActualData();
UpdateAbsoluteData();
}
}
base.UpdateActualPlaceableData();
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);
AbsoluteBoundary = Boundary.Translate(Device.Location);
}
/// <summary>
@ -276,19 +155,6 @@ namespace RGB.NET.Core
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
@ -297,14 +163,13 @@ namespace RGB.NET.Core
/// 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;
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?.LedRectangle ?? new Rectangle();
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,8 +1,8 @@
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.
@ -14,24 +14,21 @@ namespace RGB.NET.Core
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
#endregion
#region Methods
/// <summary>
/// Checks if the property already matches the desirec value or needs to be updated.
/// 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></returns>
/// <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)
{
return !Equals(storage, value);
}
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.
@ -42,13 +39,13 @@ namespace RGB.NET.Core
/// <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)
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
{
if (!this.RequiresUpdate(ref storage, value)) return false;
if (!RequiresUpdate(ref storage, value)) return false;
storage = value;
// ReSharper disable once ExplicitCallerInfoArgument
this.OnPropertyChanged(propertyName);
OnPropertyChanged(propertyName);
return true;
}
@ -57,11 +54,8 @@ namespace RGB.NET.Core
/// </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));
}
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
{
}
}

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,22 +1,24 @@
// 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 struct Point
public readonly struct Point
{
#region Constants
private static readonly Point INVALID = new(float.NaN, float.NaN);
/// <summary>
/// Gets a [NaN,NaN]-Point.
/// </summary>
public static Point Invalid => new Point(double.NaN, double.NaN);
public static ref readonly Point Invalid => ref INVALID;
#endregion
@ -25,12 +27,12 @@ namespace RGB.NET.Core
/// <summary>
/// Gets the X-position of this <see cref="Point"/>.
/// </summary>
public double X { get; }
public float X { get; }
/// <summary>
/// Gets the Y-position of this <see cref="Point"/>.
/// </summary>
public double Y { get; }
public float Y { get; }
#endregion
@ -41,7 +43,7 @@ namespace RGB.NET.Core
/// </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)
public Point(float x, float y)
{
this.X = x;
this.Y = y;
@ -62,28 +64,19 @@ namespace RGB.NET.Core
/// </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)
public override bool Equals(object? obj)
{
if (!(obj is Point)) return false;
if (obj is not Point comparePoint) 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));
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()
{
unchecked
{
int hashCode = X.GetHashCode();
hashCode = (hashCode * 397) ^ Y.GetHashCode();
return hashCode;
}
}
public override int GetHashCode() => HashCode.Combine(X, Y);
#endregion
@ -95,7 +88,7 @@ namespace RGB.NET.Core
/// <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);
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.
@ -103,7 +96,7 @@ namespace RGB.NET.Core
/// <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);
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"/>.
@ -111,7 +104,7 @@ namespace RGB.NET.Core
/// <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);
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"/>.
@ -119,7 +112,7 @@ namespace RGB.NET.Core
/// <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);
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"/>.
@ -127,7 +120,7 @@ namespace RGB.NET.Core
/// <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);
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"/>.
@ -135,7 +128,7 @@ namespace RGB.NET.Core
/// <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);
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"/>.
@ -143,7 +136,7 @@ namespace RGB.NET.Core
/// <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)
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);
@ -155,8 +148,7 @@ namespace RGB.NET.Core
/// <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);
public static Point operator *(in Point point, in Scale scale) => new(point.X * scale.Horizontal, point.Y * scale.Vertical);
#endregion
}
}

View File

@ -6,13 +6,13 @@ 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 struct Rectangle
public readonly struct Rectangle
{
#region Properties & Fields
@ -35,7 +35,7 @@ namespace RGB.NET.Core
/// 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);
public bool IsEmpty => (Size.Width <= FloatExtensions.TOLERANCE) || (Size.Height <= FloatExtensions.TOLERANCE);
#endregion
@ -49,19 +49,19 @@ namespace RGB.NET.Core
/// <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)
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 given <see cref="Core.Size"/>.
/// 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 given <see cref="Point"/> and <see cref="Core.Size"/>.
/// 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>
@ -69,33 +69,33 @@ namespace RGB.NET.Core
{
this.Location = location;
this.Size = size;
Center = new Point(Location.X + (Size.Width / 2.0), Location.Y + (Size.Height / 2.0));
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 given array of <see cref="T:RGB.NET.Core.Rectangle" />.
/// 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>
/// <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"/>.
/// 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>
/// <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;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
if (rectangles != null)
foreach (Rectangle rectangle in rectangles)
{
hasPoint = true;
@ -108,35 +108,34 @@ namespace RGB.NET.Core
(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));
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 given array of <see cref="T:RGB.NET.Core.Point" />.
/// 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>
/// <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" />.
/// 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>
/// <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;
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
if (points != null)
foreach (Point point in points)
{
hasPoint = true;
@ -150,19 +149,19 @@ namespace RGB.NET.Core
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0), Location.Y + (Size.Height / 2.0));
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(Point point1, Point point2)
private static (Point location, Size size) InitializeFromPoints(in Point point1, in 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;
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));
}
@ -178,9 +177,9 @@ namespace RGB.NET.Core
/// </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)
public override bool Equals(object? obj)
{
if (!(obj is Rectangle compareRect))
if (obj is not Rectangle compareRect)
return false;
if (GetType() != compareRect.GetType())
@ -213,7 +212,7 @@ namespace RGB.NET.Core
/// <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);
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.
@ -221,8 +220,22 @@ namespace RGB.NET.Core
/// <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);
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