Browse Source

Merge pull request #144 from JimBobSquarePants/feature/icc

WIP ICC Support
pull/220/head
James Jackson-South 9 years ago
committed by GitHub
parent
commit
023cce36ae
  1. 256
      src/ImageSharp.Drawing/DrawImage.cs
  2. 151
      src/ImageSharp/ColorSpaces/CieLab.cs
  3. 247
      src/ImageSharp/ColorSpaces/CieLch.cs
  4. 247
      src/ImageSharp/ColorSpaces/CieLchuv.cs
  5. 229
      src/ImageSharp/ColorSpaces/CieLuv.cs
  6. 161
      src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs
  7. 179
      src/ImageSharp/ColorSpaces/CieXyy.cs
  8. 90
      src/ImageSharp/ColorSpaces/CieXyz.cs
  9. 92
      src/ImageSharp/ColorSpaces/Cmyk.cs
  10. 24
      src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs
  11. 198
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs
  12. 205
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs
  13. 192
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs
  14. 192
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs
  15. 202
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs
  16. 197
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs
  17. 253
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs
  18. 198
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs
  19. 198
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs
  20. 198
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs
  21. 189
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs
  22. 209
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs
  23. 184
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs
  24. 193
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs
  25. 198
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs
  26. 104
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs
  27. 27
      src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs
  28. 22
      src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs
  29. 50
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
  30. 67
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
  31. 34
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs
  32. 42
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs
  33. 34
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs
  34. 42
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs
  35. 80
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs
  36. 102
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs
  37. 53
      src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs
  38. 53
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs
  39. 159
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs
  40. 130
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs
  41. 51
      src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs
  42. 75
      src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs
  43. 37
      src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs
  44. 85
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
  45. 101
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
  46. 52
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs
  47. 48
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs
  48. 36
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs
  49. 69
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs
  50. 52
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs
  51. 30
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs
  52. 107
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs
  53. 33
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs
  54. 32
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs
  55. 30
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs
  56. 107
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs
  57. 34
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs
  58. 57
      src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs
  59. 75
      src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs
  60. 109
      src/ImageSharp/ColorSpaces/Hsl.cs
  61. 58
      src/ImageSharp/ColorSpaces/Hsv.cs
  62. 223
      src/ImageSharp/ColorSpaces/HunterLab.cs
  63. 4
      src/ImageSharp/ColorSpaces/IAlmostEquatable.cs
  64. 20
      src/ImageSharp/ColorSpaces/IColorVector.cs
  65. 37
      src/ImageSharp/ColorSpaces/ICompanding.cs
  66. 34
      src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs
  67. 71
      src/ImageSharp/ColorSpaces/Illuminants.cs
  68. 213
      src/ImageSharp/ColorSpaces/LinearRgb.cs
  69. 180
      src/ImageSharp/ColorSpaces/Lms.cs
  70. 233
      src/ImageSharp/ColorSpaces/Rgb.cs
  71. 117
      src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs
  72. 89
      src/ImageSharp/ColorSpaces/YCbCr.cs
  73. 167
      src/ImageSharp/Colors/Spaces/Bgra32.cs
  74. 13
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  75. 155
      src/ImageSharp/Common/Helpers/MathF.cs
  76. 5
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  77. 65
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  78. 88
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  79. 39
      src/ImageSharp/MetaData/ImageMetaData.cs
  80. 45
      src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs
  81. 95
      src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs
  82. 60
      src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs
  83. 150
      src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs
  84. 87
      src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs
  85. 51
      src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs
  86. 221
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs
  87. 173
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs
  88. 65
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
  89. 87
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs
  90. 183
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs
  91. 178
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
  92. 875
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
  93. 106
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
  94. 179
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs
  95. 128
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
  96. 162
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
  97. 80
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs
  98. 137
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs
  99. 248
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs
  100. 1003
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs

256
src/ImageSharp.Drawing/DrawImage.cs

@ -1,129 +1,129 @@
// <copyright file="DrawImage.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using Drawing.Processors;
using ImageSharp.PixelFormats;
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, Size size, Point location, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, options), source.Bounds);
return source;
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, float percent)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
return DrawImage(source, image, default(Size), default(Point), options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="blender">The blending mode.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
options.BlenderMode = blender;
return DrawImage(source, image, default(Size), default(Point), options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="options">The options, including the blending type and belnding amount.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return DrawImage(source, image, default(Size), default(Point), options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, float percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
return source.DrawImage(image, size, location, options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="blender">The type of bending to apply.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlenderMode = blender;
options.BlendPercentage = percent;
return source.DrawImage(image, size, location, options);
}
}
// <copyright file="DrawImage.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using Drawing.Processors;
using ImageSharp.PixelFormats;
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, Size size, Point location, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, options), source.Bounds);
return source;
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, float percent)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
return DrawImage(source, image, default(Size), default(Point), options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="blender">The blending mode.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
options.BlenderMode = blender;
return DrawImage(source, image, default(Size), default(Point), options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="options">The options, including the blending type and belnding amount.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return DrawImage(source, image, default(Size), default(Point), options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, float percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
return source.DrawImage(image, size, location, options);
}
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="blender">The type of bending to apply.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlenderMode = blender;
options.BlendPercentage = percent;
return source.DrawImage(image, size, location, options);
}
}
}

151
src/ImageSharp/Colors/Spaces/CieLab.cs → src/ImageSharp/ColorSpaces/CieLab.cs

@ -3,33 +3,29 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using ImageSharp.PixelFormats;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an CIE LAB 1976 color.
/// Represents a CIE L*a*b* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
/// </summary>
public struct CieLab : IEquatable<CieLab>, IAlmostEquatable<CieLab, float>
internal struct CieLab : IColorVector, IEquatable<CieLab>, IAlmostEquatable<CieLab, float>
{
/// <summary>
/// Represents a <see cref="CieLab"/> that has L, A, B values set to zero.
/// D50 standard illuminant.
/// Used when reference white is not specified explicitly.
/// </summary>
public static readonly CieLab Empty = default(CieLab);
public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
/// <summary>
/// Min range used for clamping
/// </summary>
private static readonly Vector3 VectorMin = new Vector3(0, -100, -100);
/// <summary>
/// Max range used for clamping
/// Represents a <see cref="CieLab"/> that has L, A, B values set to zero.
/// </summary>
private static readonly Vector3 VectorMax = new Vector3(100);
public static readonly CieLab Empty = default(CieLab);
/// <summary>
/// The backing vector for SIMD support.
@ -42,29 +38,88 @@ namespace ImageSharp.Colors.Spaces
/// <param name="l">The lightness dimension.</param>
/// <param name="a">The a (green - magenta) component.</param>
/// <param name="b">The b (blue - yellow) component.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLab(float l, float a, float b)
: this(new Vector3(l, a, b), DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="a">The a (green - magenta) component.</param>
/// <param name="b">The b (blue - yellow) component.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLab(float l, float a, float b, CieXyz whitePoint)
: this(new Vector3(l, a, b), whitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, a, b components.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLab(Vector3 vector)
: this(vector, DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, a, b components.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLab(Vector3 vector, CieXyz whitePoint)
: this()
{
this.backingVector = Vector3.Clamp(new Vector3(l, a, b), VectorMin, VectorMax);
this.backingVector = vector;
this.WhitePoint = whitePoint;
}
/// <summary>
/// Gets the reference white point of this color
/// </summary>
public CieXyz WhitePoint
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the lightness dimension.
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
/// </summary>
public float L => this.backingVector.X;
public float L
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the a color component.
/// <remarks>Negative is green, positive magenta.</remarks>
/// <remarks>A value ranging from -100 to 100. Negative is green, positive magenta.</remarks>
/// </summary>
public float A => this.backingVector.Y;
public float A
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the b color component.
/// <remarks>Negative is blue, positive is yellow</remarks>
/// <remarks>A value ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
/// </summary>
public float B => this.backingVector.Z;
public float B
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="CieLab"/> is empty.
@ -72,39 +127,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="CieLab"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Rgba32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CieLab"/>.
/// </returns>
public static implicit operator CieLab(Rgba32 color)
/// <inheritdoc />
public Vector3 Vector
{
// First convert to CIE XYZ
Vector4 vector = color.ToVector4().Expand();
float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F);
float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F);
float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F);
// Now to LAB
x /= 0.95047F;
// y /= 1F;
z /= 1.08883F;
x = x > 0.008856F ? MathF.Pow(x, 0.3333333F) : ((903.3F * x) + 16F) / 116F;
y = y > 0.008856F ? MathF.Pow(y, 0.3333333F) : ((903.3F * y) + 16F) / 116F;
z = z > 0.008856F ? MathF.Pow(z, 0.3333333F) : ((903.3F * z) + 16F) / 116F;
float l = MathF.Max(0, (116F * y) - 16F);
float a = 500F * (x - y);
float b = 200F * (y - z);
return new CieLab(l, a, b);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
@ -119,6 +146,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieLab left, CieLab right)
{
return left.Equals(right);
@ -136,6 +164,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLab left, CieLab right)
{
return !left.Equals(right);
@ -144,7 +173,12 @@ namespace ImageSharp.Colors.Spaces
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
unchecked
{
int hashCode = this.WhitePoint.GetHashCode();
hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
return hashCode;
}
}
/// <inheritdoc/>
@ -159,6 +193,7 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieLab)
@ -170,19 +205,23 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieLab other)
{
return this.backingVector.Equals(other.backingVector);
return this.backingVector.Equals(other.backingVector)
&& this.WhitePoint.Equals(other.WhitePoint);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieLab other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
return this.WhitePoint.Equals(other.WhitePoint)
&& result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}
}

247
src/ImageSharp/ColorSpaces/CieLch.cs

@ -0,0 +1,247 @@
// <copyright file="CieLch.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC"/>
/// </summary>
internal struct CieLch : IColorVector, IEquatable<CieLch>, IAlmostEquatable<CieLch, float>
{
/// <summary>
/// D50 standard illuminant.
/// Used when reference white is not specified explicitly.
/// </summary>
public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
/// <summary>
/// Represents a <see cref="CieLch"/> that has L, C, H values set to zero.
/// </summary>
public static readonly CieLch Empty = default(CieLch);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="c">The chroma, relative saturation.</param>
/// <param name="h">The hue in degrees.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLch(float l, float c, float h)
: this(new Vector3(l, c, h), DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="c">The chroma, relative saturation.</param>
/// <param name="h">The hue in degrees.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLch(float l, float c, float h, CieXyz whitePoint)
: this(new Vector3(l, c, h), whitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, c, h components.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLch(Vector3 vector)
: this(vector, DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, c, h components.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLch(Vector3 vector, CieXyz whitePoint)
: this()
{
this.backingVector = vector;
this.WhitePoint = whitePoint;
}
/// <summary>
/// Gets the reference white point of this color
/// </summary>
public CieXyz WhitePoint
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the lightness dimension.
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
/// </summary>
public float L
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the a chroma component.
/// <remarks>A value ranging from 0 to 100.</remarks>
/// </summary>
public float C
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the h° hue component in degrees.
/// <remarks>A value ranging from 0 to 360.</remarks>
/// </summary>
public float H
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="CieLch"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Compares two <see cref="CieLch"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLch"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLch"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieLch left, CieLch right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieLch"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="CieLch"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLch"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLch left, CieLch right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.WhitePoint.GetHashCode();
hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
return hashCode;
}
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieLch [Empty]";
}
return $"CieLch [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieLch)
{
return this.Equals((CieLch)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieLch other)
{
return this.backingVector.Equals(other.backingVector)
&& this.WhitePoint.Equals(other.WhitePoint);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieLch other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return this.WhitePoint.Equals(other.WhitePoint)
&& result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
/// <summary>
/// Computes the saturation of the color (chroma normalized by lightness)
/// </summary>
/// <remarks>
/// A value ranging from 0 to 100.
/// </remarks>
/// <returns>The <see cref="float"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Saturation()
{
float result = 100 * (this.C / this.L);
if (float.IsNaN(result))
{
return 0;
}
return result;
}
}
}

247
src/ImageSharp/ColorSpaces/CieLchuv.cs

@ -0,0 +1,247 @@
// <copyright file="CieLchuv.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CieLchuv_or_CIEHLC"/>
/// </summary>
internal struct CieLchuv : IColorVector, IEquatable<CieLchuv>, IAlmostEquatable<CieLchuv, float>
{
/// <summary>
/// D50 standard illuminant.
/// Used when reference white is not specified explicitly.
/// </summary>
public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
/// <summary>
/// Represents a <see cref="CieLchuv"/> that has L, C, H values set to zero.
/// </summary>
public static readonly CieLchuv Empty = default(CieLchuv);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieLchuv"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="c">The chroma, relative saturation.</param>
/// <param name="h">The hue in degrees.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLchuv(float l, float c, float h)
: this(new Vector3(l, c, h), DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLchuv"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="c">The chroma, relative saturation.</param>
/// <param name="h">The hue in degrees.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLchuv(float l, float c, float h, CieXyz whitePoint)
: this(new Vector3(l, c, h), whitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLchuv"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, c, h components.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLchuv(Vector3 vector)
: this(vector, DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLchuv"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, c, h components.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLchuv(Vector3 vector, CieXyz whitePoint)
: this()
{
this.backingVector = vector;
this.WhitePoint = whitePoint;
}
/// <summary>
/// Gets the reference white point of this color
/// </summary>
public CieXyz WhitePoint
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the lightness dimension.
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
/// </summary>
public float L
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the a chroma component.
/// <remarks>A value ranging from 0 to 100.</remarks>
/// </summary>
public float C
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the h° hue component in degrees.
/// <remarks>A value ranging from 0 to 360.</remarks>
/// </summary>
public float H
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="CieLchuv"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Compares two <see cref="CieLchuv"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLchuv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLchuv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieLchuv left, CieLchuv right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieLchuv"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="CieLchuv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLchuv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLchuv left, CieLchuv right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.WhitePoint.GetHashCode();
hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
return hashCode;
}
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieLchuv [Empty]";
}
return $"CieLchuv [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieLchuv)
{
return this.Equals((CieLchuv)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieLchuv other)
{
return this.backingVector.Equals(other.backingVector)
&& this.WhitePoint.Equals(other.WhitePoint);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieLchuv other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return this.WhitePoint.Equals(other.WhitePoint)
&& result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
/// <summary>
/// Computes the saturation of the color (chroma normalized by lightness)
/// </summary>
/// <remarks>
/// A value ranging from 0 to 100.
/// </remarks>
/// <returns>The <see cref="float"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Saturation()
{
float result = 100 * (this.C / this.L);
if (float.IsNaN(result))
{
return 0;
}
return result;
}
}
}

229
src/ImageSharp/ColorSpaces/CieLuv.cs

@ -0,0 +1,229 @@
// <copyright file="CieLuv.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// The CIE 1976 (L*, u*, v*) color space, commonly known by its abbreviation CIELUV, is a color space adopted by the International
/// Commission on Illumination (CIE) in 1976, as a simple-to-compute transformation of the 1931 CIE XYZ color space, but which
/// attempted perceptual uniformity
/// <see href="https://en.wikipedia.org/wiki/CIELUV"/>
/// </summary>
internal struct CieLuv : IColorVector, IEquatable<CieLuv>, IAlmostEquatable<CieLuv, float>
{
/// <summary>
/// D65 standard illuminant.
/// Used when reference white is not specified explicitly.
/// </summary>
public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
/// <summary>
/// Represents a <see cref="CieLuv"/> that has L, U, and V values set to zero.
/// </summary>
public static readonly CieLuv Empty = default(CieLuv);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="u">The blue-yellow chromaticity coordinate of the given whitepoint.</param>
/// <param name="v">The red-green chromaticity coordinate of the given whitepoint.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLuv(float l, float u, float v)
: this(new Vector3(l, u, v), DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="u">The blue-yellow chromaticity coordinate of the given whitepoint.</param>
/// <param name="v">The red-green chromaticity coordinate of the given whitepoint.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLuv(float l, float u, float v, CieXyz whitePoint)
: this(new Vector3(l, u, v), whitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, u, v components.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLuv(Vector3 vector)
: this(vector, DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, u, v components.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLuv(Vector3 vector, CieXyz whitePoint)
: this()
{
this.backingVector = vector;
this.WhitePoint = whitePoint;
}
/// <summary>
/// Gets the reference white point of this color
/// </summary>
public CieXyz WhitePoint
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets the lightness dimension
/// <remarks>A value usually ranging between 0 and 100.</remarks>
/// </summary>
public float L
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the blue-yellow chromaticity coordinate of the given whitepoint.
/// <remarks>A value usually ranging between -100 and 100.</remarks>
/// </summary>
public float U
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the red-green chromaticity coordinate of the given whitepoint.
/// <remarks>A value usually ranging between -100 and 100.</remarks>
/// </summary>
public float V
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="CieLuv"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Compares two <see cref="CieLuv"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLuv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLuv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieLuv left, CieLuv right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieLuv"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLuv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLuv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLuv left, CieLuv right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.WhitePoint.GetHashCode();
hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
return hashCode;
}
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieLuv [ Empty ]";
}
return $"CieLuv [ L={this.L:#0.##}, U={this.U:#0.##}, V={this.V:#0.##} ]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieLuv)
{
return this.Equals((CieLuv)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieLuv other)
{
return this.backingVector.Equals(other.backingVector)
&& this.WhitePoint.Equals(other.WhitePoint);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieLuv other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return this.WhitePoint.Equals(other.WhitePoint)
&& result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

161
src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs

@ -0,0 +1,161 @@
// <copyright file="CieXyChromaticityCoordinates.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents the coordinates of CIEXY chromaticity space
/// </summary>
internal struct CieXyChromaticityCoordinates : IEquatable<CieXyChromaticityCoordinates>, IAlmostEquatable<CieXyChromaticityCoordinates, float>
{
/// <summary>
/// Represents a <see cref="CieXyChromaticityCoordinates"/> that has X, Y values set to zero.
/// </summary>
public static readonly CieXyChromaticityCoordinates Empty = default(CieXyChromaticityCoordinates);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector2 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieXyChromaticityCoordinates"/> struct.
/// </summary>
/// <param name="x">Chromaticity coordinate x (usually from 0 to 1)</param>
/// <param name="y">Chromaticity coordinate y (usually from 0 to 1)</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyChromaticityCoordinates(float x, float y)
: this(new Vector2(x, y))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyChromaticityCoordinates"/> struct.
/// </summary>
/// <param name="vector">The vector containing the XY Chromaticity coordinates</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyChromaticityCoordinates(Vector2 vector)
{
this.backingVector = vector;
}
/// <summary>
/// Gets the chromaticity X-coordinate.
/// </summary>
/// <remarks>
/// Ranges usually from 0 to 1.
/// </remarks>
public float X
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the chromaticity Y-coordinate
/// </summary>
/// <remarks>
/// Ranges usually from 0 to 1.
/// </remarks>
public float Y
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets a value indicating whether this <see cref="CieXyChromaticityCoordinates"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Compares two <see cref="CieXyChromaticityCoordinates"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieXyChromaticityCoordinates"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyChromaticityCoordinates"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieXyChromaticityCoordinates"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="CieXyChromaticityCoordinates"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyChromaticityCoordinates"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right)
{
return !left.Equals(right);
}
/// <inheritdoc />
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieXyChromaticityCoordinates [Empty]";
}
return $"CieXyChromaticityCoordinates [ X={this.X:#0.##}, Y={this.Y:#0.##}]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieXyChromaticityCoordinates)
{
return this.Equals((CieXyChromaticityCoordinates)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyChromaticityCoordinates other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieXyChromaticityCoordinates other, float precision)
{
var result = Vector2.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision;
}
}
}

179
src/ImageSharp/ColorSpaces/CieXyy.cs

@ -0,0 +1,179 @@
// <copyright file="CieXyy.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an CIE xyY 1931 color
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space"/>
/// </summary>
internal struct CieXyy : IColorVector, IEquatable<CieXyy>, IAlmostEquatable<CieXyy, float>
{
/// <summary>
/// Represents a <see cref="CieXyy"/> that has X, Y, and Y values set to zero.
/// </summary>
public static readonly CieXyy Empty = default(CieXyy);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieXyy"/> struct.
/// </summary>
/// <param name="x">The x chroma component.</param>
/// <param name="y">The y chroma component.</param>
/// <param name="yl">The y luminance component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyy(float x, float y, float yl)
: this(new Vector3(x, y, yl))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyy"/> struct.
/// </summary>
/// <param name="vector">The vector representing the x, y, Y components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyy(Vector3 vector)
: this()
{
// Not clamping as documentation about this space seems to indicate "usual" ranges
this.backingVector = vector;
}
/// <summary>
/// Gets the X chrominance component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float X
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the Y chrominance component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float Y
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float Yl
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="CieXyy"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Compares two <see cref="CieXyy"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieXyy"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyy"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieXyy left, CieXyy right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieXyy"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="CieXyy"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyy"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieXyy left, CieXyy right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieXyy [ Empty ]";
}
return $"CieXyy [ X={this.X:#0.##}, Y={this.Y:#0.##}, Yl={this.Yl:#0.##} ]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieXyy)
{
return this.Equals((CieXyy)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyy other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieXyy other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

90
src/ImageSharp/Colors/Spaces/CieXyz.cs → src/ImageSharp/ColorSpaces/CieXyz.cs

@ -3,21 +3,21 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using ImageSharp.PixelFormats;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an CIE 1931 color
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space"/>
/// Represents an CIE XYZ 1931 color
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#Definition_of_the_CIE_XYZ_color_space"/>
/// </summary>
public struct CieXyz : IEquatable<CieXyz>, IAlmostEquatable<CieXyz, float>
internal struct CieXyz : IColorVector, IEquatable<CieXyz>, IAlmostEquatable<CieXyz, float>
{
/// <summary>
/// Represents a <see cref="CieXyz"/> that has Y, Cb, and Cr values set to zero.
/// Represents a <see cref="CieXyz"/> that has X, Y, and Z values set to zero.
/// </summary>
public static readonly CieXyz Empty = default(CieXyz);
@ -32,30 +32,53 @@ namespace ImageSharp.Colors.Spaces
/// <param name="x">X is a mix (a linear combination) of cone response curves chosen to be nonnegative</param>
/// <param name="y">The y luminance component.</param>
/// <param name="z">Z is quasi-equal to blue stimulation, or the S cone of the human eye.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz(float x, float y, float z)
: this(new Vector3(x, y, z))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyz"/> struct.
/// </summary>
/// <param name="vector">The vector representing the x, y, z components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz(Vector3 vector)
: this()
{
// Not clamping as documentation about this space seems to indicate "usual" ranges
this.backingVector = new Vector3(x, y, z);
this.backingVector = vector;
}
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 380 and 780.</remarks>
/// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float X => this.backingVector.X;
public float X
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 380 and 780.</remarks>
/// Gets the Y luminance component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float Y => this.backingVector.Y;
public float Y
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the Cr chroma component.
/// <remarks>A value ranging between 380 and 780.</remarks>
/// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float Z => this.backingVector.Z;
public float Z
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="CieXyz"/> is empty.
@ -63,29 +86,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="CieXyz"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Rgba32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CieXyz"/>.
/// </returns>
public static implicit operator CieXyz(Rgba32 color)
/// <inheritdoc />
public Vector3 Vector
{
Vector4 vector = color.ToVector4().Expand();
float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F);
float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F);
float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F);
x *= 100F;
y *= 100F;
z *= 100F;
return new CieXyz(x, y, z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
@ -100,6 +105,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieXyz left, CieXyz right)
{
return left.Equals(right);
@ -117,6 +123,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieXyz left, CieXyz right)
{
return !left.Equals(right);
@ -140,6 +147,7 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieXyz)
@ -151,19 +159,21 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyz other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieXyz other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}
}

92
src/ImageSharp/Colors/Spaces/Cmyk.cs → src/ImageSharp/ColorSpaces/Cmyk.cs

@ -3,33 +3,23 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using ImageSharp.PixelFormats;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
/// </summary>
public struct Cmyk : IEquatable<Cmyk>, IAlmostEquatable<Cmyk, float>
internal struct Cmyk : IEquatable<Cmyk>, IAlmostEquatable<Cmyk, float>
{
/// <summary>
/// Represents a <see cref="Cmyk"/> that has C, M, Y, and K values set to zero.
/// </summary>
public static readonly Cmyk Empty = default(Cmyk);
/// <summary>
/// Min range used for clamping
/// </summary>
private static readonly Vector4 VectorMin = Vector4.Zero;
/// <summary>
/// Max range used for clamping
/// </summary>
private static readonly Vector4 VectorMax = Vector4.One;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
@ -42,35 +32,62 @@ namespace ImageSharp.Colors.Spaces
/// <param name="m">The magenta component.</param>
/// <param name="y">The yellow component.</param>
/// <param name="k">The keyline black component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Cmyk(float c, float m, float y, float k)
: this(new Vector4(c, m, y, k))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// </summary>
/// <param name="vector">The vector representing the c, m, y, k components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Cmyk(Vector4 vector)
: this()
{
this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), VectorMin, VectorMax);
this.backingVector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One);
}
/// <summary>
/// Gets the cyan color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float C => this.backingVector.X;
public float C
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the magenta color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float M => this.backingVector.Y;
public float M
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the yellow color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Y => this.backingVector.Z;
public float Y
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets the keyline black color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float K => this.backingVector.W;
public float K
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.W;
}
/// <summary>
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
@ -78,36 +95,6 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="Cmyk"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Cmyk"/>.
/// </returns>
public static implicit operator Cmyk(Rgba32 color)
{
float c = 1f - (color.R / 255F);
float m = 1f - (color.G / 255F);
float y = 1f - (color.B / 255F);
float k = MathF.Min(c, MathF.Min(m, y));
if (MathF.Abs(k - 1.0f) <= Constants.Epsilon)
{
return new Cmyk(0, 0, 0, 1);
}
c = (c - k) / (1 - k);
m = (m - k) / (1 - k);
y = (y - k) / (1 - k);
return new Cmyk(c, m, y, k);
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects for equality.
/// </summary>
@ -120,6 +107,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Cmyk left, Cmyk right)
{
return left.Equals(right);
@ -137,6 +125,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Cmyk left, Cmyk right)
{
return !left.Equals(right);
@ -160,6 +149,7 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Cmyk)
@ -171,15 +161,17 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Cmyk other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Cmyk other, float precision)
{
Vector4 result = Vector4.Abs(this.backingVector - other.backingVector);
var result = Vector4.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
@ -187,4 +179,4 @@ namespace ImageSharp.Colors.Spaces
&& result.W <= precision;
}
}
}
}

24
src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs

@ -0,0 +1,24 @@
// <copyright file="CieConstants.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
/// <summary>
/// Constants use for Cie conversion calculations
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html"/>
/// </summary>
internal static class CieConstants
{
/// <summary>
/// 216F / 24389F
/// </summary>
public const float Epsilon = 0.008856452F;
/// <summary>
/// 24389F / 27F
/// </summary>
public const float Kappa = 903.2963F;
}
}

198
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs

@ -0,0 +1,198 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using System;
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
/// <content>
/// Performs chromatic adaptation on the various color spaces.
/// </content>
internal partial class ColorSpaceConverter
{
/// <summary>
/// Performs chromatic adaptation of given <see cref="CieXyz"/> color.
/// Target white point is <see cref="WhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <param name="sourceWhitePoint">The white point to adapt for</param>
/// <returns>The adapted color</returns>
public CieXyz Adapt(CieXyz color, CieXyz sourceWhitePoint)
{
Guard.NotNull(color, nameof(color));
Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
if (!this.IsChromaticAdaptationPerformed)
{
throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
}
return this.ChromaticAdaptation.Transform(color, sourceWhitePoint, this.WhitePoint);
}
/// <summary>
/// Adapts <see cref="CieLab"/> color from the source white point to white point set in <see cref="TargetLabWhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
public CieLab Adapt(CieLab color)
{
Guard.NotNull(color, nameof(color));
if (!this.IsChromaticAdaptationPerformed)
{
throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
}
if (color.WhitePoint.Equals(this.TargetLabWhitePoint))
{
return color;
}
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Adapts <see cref="CieLch"/> color from the source white point to white point set in <see cref="TargetLabWhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
public CieLch Adapt(CieLch color)
{
Guard.NotNull(color, nameof(color));
if (!this.IsChromaticAdaptationPerformed)
{
throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
}
if (color.WhitePoint.Equals(this.TargetLabWhitePoint))
{
return color;
}
CieLab labColor = this.ToCieLab(color);
return this.ToCieLch(labColor);
}
/// <summary>
/// Adapts <see cref="CieLchuv"/> color from the source white point to white point set in <see cref="TargetLabWhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
public CieLchuv Adapt(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
if (!this.IsChromaticAdaptationPerformed)
{
throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
}
if (color.WhitePoint.Equals(this.TargetLabWhitePoint))
{
return color;
}
CieLuv luvColor = this.ToCieLuv(color);
return this.ToCieLchuv(luvColor);
}
/// <summary>
/// Adapts <see cref="CieLuv"/> color from the source white point to white point set in <see cref="TargetLuvWhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
public CieLuv Adapt(CieLuv color)
{
Guard.NotNull(color, nameof(color));
if (!this.IsChromaticAdaptationPerformed)
{
throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
}
if (color.WhitePoint.Equals(this.TargetLuvWhitePoint))
{
return color;
}
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Adapts <see cref="HunterLab"/> color from the source white point to white point set in <see cref="TargetHunterLabWhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
public HunterLab Adapt(HunterLab color)
{
Guard.NotNull(color, nameof(color));
if (!this.IsChromaticAdaptationPerformed)
{
throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
}
if (color.WhitePoint.Equals(this.TargetHunterLabWhitePoint))
{
return color;
}
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Adapts a <see cref="LinearRgb"/> color from the source working space to working space set in <see cref="TargetRgbWorkingSpace"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
public LinearRgb Adapt(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
if (!this.IsChromaticAdaptationPerformed)
{
throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
}
if (color.WorkingSpace.Equals(this.TargetRgbWorkingSpace))
{
return color;
}
// Conversion to XYZ
LinearRgbToCieXyzConverter converterToXYZ = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace);
CieXyz unadapted = converterToXYZ.Convert(color);
// Adaptation
CieXyz adapted = this.ChromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint);
// Conversion back to RGB
CieXyzToLinearRgbConverter converterToRGB = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace);
return converterToRGB.Convert(adapted);
}
/// <summary>
/// Adapts an <see cref="Rgb"/> color from the source working space to working space set in <see cref="TargetRgbWorkingSpace"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
public Rgb Adapt(Rgb color)
{
Guard.NotNull(color, nameof(color));
LinearRgb linearInput = this.ToLinearRgb(color);
LinearRgb linearOutput = this.Adapt(linearInput);
return this.ToRgb(linearOutput);
}
}
}

205
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs

@ -0,0 +1,205 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLab;
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLch;
/// <content>
/// Allows conversion to <see cref="CieLab"/>.
/// </content>
internal partial class ColorSpaceConverter
{
/// <summary>
/// The converter for converting between CieLch to CieLab.
/// </summary>
private static readonly CieLchToCieLabConverter CieLchToCieLabConverter = new CieLchToCieLabConverter();
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(CieLch color)
{
Guard.NotNull(color, nameof(color));
// Conversion (perserving white point)
CieLab unadapted = CieLchToCieLabConverter.Convert(color);
if (!this.IsChromaticAdaptationPerformed)
{
return unadapted;
}
// Adaptation
return this.Adapt(unadapted);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(CieLuv color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(CieXyy color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed
? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint)
: color;
// Conversion
CieXyzToCieLabConverter converter = new CieXyzToCieLabConverter(this.TargetLabWhitePoint);
return converter.Convert(adapted);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(Cmyk color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(Hsl color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(Hsv color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(HunterLab color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(Lms color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(Rgb color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLab ToCieLab(YCbCr color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
}
}

192
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs

@ -0,0 +1,192 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLch;
/// <content>
/// Allows conversion to <see cref="CieLch"/>.
/// </content>
internal partial class ColorSpaceConverter
{
/// <summary>
/// The converter for converting between CieLab to CieLch.
/// </summary>
private static readonly CieLabToCieLchConverter CieLabToCieLchConverter = new CieLabToCieLchConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(CieLab color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieLab adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color;
// Conversion
return CieLabToCieLchConverter.Convert(adapted);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(CieLuv color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(CieXyy color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(CieXyz color)
{
Guard.NotNull(color, nameof(color));
CieLab labColor = this.ToCieLab(color);
return this.ToCieLch(labColor);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(Cmyk color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(Hsl color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(Hsv color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(HunterLab color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(Lms color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(Rgb color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(YCbCr color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLch(xyzColor);
}
}
}

192
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs

@ -0,0 +1,192 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv;
/// <content>
/// Allows conversion to <see cref="CieLchuv"/>.
/// </content>
internal partial class ColorSpaceConverter
{
/// <summary>
/// The converter for converting between CieLab to CieLchuv.
/// </summary>
private static readonly CieLuvToCieLchuvConverter CieLuvToCieLchuvConverter = new CieLuvToCieLchuvConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(CieLab color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(CieLch color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(CieLuv color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieLuv adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color;
// Conversion
return CieLuvToCieLchuvConverter.Convert(adapted);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(CieXyy color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(CieXyz color)
{
Guard.NotNull(color, nameof(color));
CieLab labColor = this.ToCieLab(color);
return this.ToCieLchuv(labColor);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(Cmyk color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(Hsl color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(Hsv color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(HunterLab color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(Lms color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(Rgb color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="CieLchuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLchuv"/></returns>
public CieLchuv ToCieLchuv(YCbCr color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
}
}
}

202
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs

@ -0,0 +1,202 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv;
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv;
/// <content>
/// Allows conversion to <see cref="CieLuv"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly CieLchuvToCieLuvConverter CieLchuvToCieLuvConverter = new CieLchuvToCieLuvConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
public CieLuv ToCieLuv(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
// Conversion (perserving white point)
CieLuv unadapted = CieLchuvToCieLuvConverter.Convert(color);
if (!this.IsChromaticAdaptationPerformed)
{
return unadapted;
}
// Adaptation
return this.Adapt(unadapted);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed
? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint)
: color;
// Conversion
var converter = new CieXyzToCieLuvConverter(this.TargetLuvWhitePoint);
return converter.Convert(adapted);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(Hsl color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(Hsv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(Rgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="CieLuv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLuv"/></returns>
public CieLuv ToCieLuv(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieLuv(xyzColor);
}
}
}

197
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs

@ -0,0 +1,197 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces.Conversion.Implementation.CieXyy;
/// <content>
/// Allows conversion to <see cref="CieXyy"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly CieXyzAndCieXyyConverter CieXyzAndCieXyyConverter = new CieXyzAndCieXyyConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(CieXyz color)
{
Guard.NotNull(color, nameof(color));
return CieXyzAndCieXyyConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(Hsl color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(Hsv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(Rgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="CieXyy"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyy"/></returns>
public CieXyy ToCieXyy(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCieXyy(xyzColor);
}
}
}

253
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs

@ -0,0 +1,253 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLab;
using ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv;
using ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab;
using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
/// <content>
/// Allows conversion to <see cref="CieXyz"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter();
private static readonly CieLuvToCieXyzConverter CieLuvToCieXyzConverter = new CieLuvToCieXyzConverter();
private static readonly HunterLabToCieXyzConverter HunterLabToCieXyzConverter = new HunterLabToCieXyzConverter();
private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter;
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(CieLab color)
{
Guard.NotNull(color, nameof(color));
// Conversion
CieXyz unadapted = CieLabToCieXyzConverter.Convert(color);
// Adaptation
CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
? unadapted
: this.Adapt(unadapted, color.WhitePoint);
return adapted;
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(CieLch color)
{
Guard.NotNull(color, nameof(color));
// Conversion to Lab
CieLab labColor = CieLchToCieLabConverter.Convert(color);
// Conversion to XYZ (incl. adaptation)
return this.ToCieXyz(labColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
// Conversion to Luv
CieLuv luvColor = CieLchuvToCieLuvConverter.Convert(color);
// Conversion to XYZ (incl. adaptation)
return this.ToCieXyz(luvColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(CieLuv color)
{
Guard.NotNull(color, nameof(color));
// Conversion
CieXyz unadapted = CieLuvToCieXyzConverter.Convert(color);
// Adaptation
CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
? unadapted
: this.Adapt(unadapted, color.WhitePoint);
return adapted;
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(CieXyy color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return CieXyzAndCieXyyConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(Cmyk color)
{
Guard.NotNull(color, nameof(color));
// Conversion
var rgb = this.ToRgb(color);
return this.ToCieXyz(rgb);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(Hsl color)
{
Guard.NotNull(color, nameof(color));
// Conversion
var rgb = this.ToRgb(color);
return this.ToCieXyz(rgb);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(Hsv color)
{
Guard.NotNull(color, nameof(color));
// Conversion
var rgb = this.ToRgb(color);
return this.ToCieXyz(rgb);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(HunterLab color)
{
Guard.NotNull(color, nameof(color));
// Conversion
CieXyz unadapted = HunterLabToCieXyzConverter.Convert(color);
// Adaptation
CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
? unadapted
: this.Adapt(unadapted, color.WhitePoint);
return adapted;
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
LinearRgbToCieXyzConverter converter = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace);
CieXyz unadapted = converter.Convert(color);
// Adaptation
return color.WorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
? unadapted
: this.Adapt(unadapted, color.WorkingSpace.WhitePoint);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(Lms color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return this.cachedCieXyzAndLmsConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(Rgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
LinearRgb linear = RgbToLinearRgbConverter.Convert(color);
return this.ToCieXyz(linear);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(YCbCr color)
{
Guard.NotNull(color, nameof(color));
// Conversion
var rgb = this.ToRgb(color);
return this.ToCieXyz(rgb);
}
/// <summary>
/// Gets the correct converter for the given rgb working space.
/// </summary>
/// <param name="workingSpace">The source working space</param>
/// <returns>The <see cref="LinearRgbToCieXyzConverter"/></returns>
private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace)
{
if (this.linearRgbToCieXyzConverter != null && this.linearRgbToCieXyzConverter.SourceWorkingSpace.Equals(workingSpace))
{
return this.linearRgbToCieXyzConverter;
}
return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace);
}
}
}

198
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs

@ -0,0 +1,198 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.Cmyk;
/// <content>
/// Allows conversion to <see cref="Cmyk"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly CmykAndRgbConverter CmykAndRgbConverter = new CmykAndRgbConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCmyk(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCmyk(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCmyk(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCmyk(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCmyk(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(CieXyz color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return CmykAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(Hsl color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return CmykAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(Hsv color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return CmykAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCmyk(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return CmykAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToCmyk(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(Rgb color)
{
Guard.NotNull(color, nameof(color));
return CmykAndRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="Cmyk"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Cmyk"/></returns>
public Cmyk ToCmyk(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return CmykAndRgbConverter.Convert(rgb);
}
}
}

198
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs

@ -0,0 +1,198 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.Hsl;
/// <content>
/// Allows conversion to <see cref="Hsl"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly HslAndRgbConverter HslAndRgbConverter = new HslAndRgbConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsl(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsl(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsl(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsl(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsl(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(CieXyz color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HslAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HslAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(Hsv color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HslAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsl(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HslAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsl(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(Rgb color)
{
Guard.NotNull(color, nameof(color));
return HslAndRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="Hsl"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsl"/></returns>
public Hsl ToHsl(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HslAndRgbConverter.Convert(rgb);
}
}
}

198
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs

@ -0,0 +1,198 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.Hsv;
/// <content>
/// Allows conversion to <see cref="Hsv"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly HsvAndRgbConverter HsvAndRgbConverter = new HsvAndRgbConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsv(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(CieXyz color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HsvAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HsvAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(Hsl color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HsvAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsv(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HsvAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHsv(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(Rgb color)
{
Guard.NotNull(color, nameof(color));
return HsvAndRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="Hsv"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Hsv"/></returns>
public Hsv ToHsv(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return HsvAndRgbConverter.Convert(rgb);
}
}
}

189
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs

@ -0,0 +1,189 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab;
/// <content>
/// Allows conversion to <see cref="HunterLab"/>.
/// </content>
internal partial class ColorSpaceConverter
{
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieXyz adapted = !this.WhitePoint.Equals(this.TargetHunterLabWhitePoint) && this.IsChromaticAdaptationPerformed
? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetHunterLabWhitePoint)
: color;
// Conversion
return new CieXyzToHunterLabConverter(this.TargetHunterLabWhitePoint).Convert(adapted);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(Hsl color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(Hsv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(Rgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
}
}

209
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs

@ -0,0 +1,209 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
/// <content>
/// Allows conversion to <see cref="LinearRgb"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter();
private CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter;
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieXyz adapted = this.TargetRgbWorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
? color
: this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint);
// Conversion
CieXyzToLinearRgbConverter xyzConverter = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace);
return xyzConverter.Convert(adapted);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return this.ToLinearRgb(rgb);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(Hsl color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return this.ToLinearRgb(rgb);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(Hsv color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return this.ToLinearRgb(rgb);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(Rgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return RgbToLinearRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return this.ToLinearRgb(rgb);
}
/// <summary>
/// Gets the correct converter for the given rgb working space.
/// </summary>
/// <param name="workingSpace">The target working space</param>
/// <returns>The <see cref="CieXyzToLinearRgbConverter"/></returns>
private CieXyzToLinearRgbConverter GetCieXyxToLinearRgbConverter(IRgbWorkingSpace workingSpace)
{
if (this.cieXyzToLinearRgbConverter != null && this.cieXyzToLinearRgbConverter.TargetWorkingSpace.Equals(workingSpace))
{
return this.cieXyzToLinearRgbConverter;
}
return this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(workingSpace);
}
}
}

184
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs

@ -0,0 +1,184 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
/// <content>
/// Allows conversion to <see cref="Lms"/>.
/// </content>
internal partial class ColorSpaceConverter
{
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return this.cachedCieXyzAndLmsConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(Hsl color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(Hsv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(Rgb color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(YCbCr color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
}
}

193
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs

@ -0,0 +1,193 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
/// <content>
/// Allows conversion to <see cref="Rgb"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly LinearRgbToRgbConverter LinearRgbToRgbConverter = new LinearRgbToRgbConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Conversion
var linear = this.ToLinearRgb(color);
// Compand
return this.ToRgb(linear);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(Cmyk color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return CmykAndRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(Hsv color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return HsvAndRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(Hsl color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return HslAndRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return LinearRgbToRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="YCbCr"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(YCbCr color)
{
Guard.NotNull(color, nameof(color));
// Conversion
Rgb rgb = YCbCrAndRgbConverter.Convert(color);
// Adaptation
// TODO: Check this!
return rgb.WorkingSpace.Equals(this.TargetRgbWorkingSpace) ? rgb : this.Adapt(rgb);
}
}
}

198
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs

@ -0,0 +1,198 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.YCbCr;
/// <content>
/// Allows conversion to <see cref="YCbCr"/>.
/// </content>
internal partial class ColorSpaceConverter
{
private static readonly YCbCrAndRgbConverter YCbCrAndRgbConverter = new YCbCrAndRgbConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(CieLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToYCbCr(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(CieLch color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToYCbCr(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLchuv"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(CieLchuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToYCbCr(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLuv"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(CieLuv color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToYCbCr(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyy"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(CieXyy color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToYCbCr(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(CieXyz color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return YCbCrAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Cmyk"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(Cmyk color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return YCbCrAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Hsl"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(Hsl color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return YCbCrAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Hsv"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(Hsv color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return YCbCrAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(HunterLab color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToYCbCr(xyzColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
var rgb = this.ToRgb(color);
return YCbCrAndRgbConverter.Convert(rgb);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(Lms color)
{
Guard.NotNull(color, nameof(color));
var xyzColor = this.ToCieXyz(color);
return this.ToYCbCr(xyzColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="YCbCr"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="YCbCr"/></returns>
public YCbCr ToYCbCr(Rgb color)
{
Guard.NotNull(color, nameof(color));
return YCbCrAndRgbConverter.Convert(color);
}
}
}

104
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs

@ -0,0 +1,104 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using System.Numerics;
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.Lms;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
internal partial class ColorSpaceConverter
{
/// <summary>
/// The default whitepoint used for converting to CieLab
/// </summary>
public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
private Matrix4x4 transformationMatrix;
private CieXyzAndLmsConverter cachedCieXyzAndLmsConverter;
/// <summary>
/// Initializes a new instance of the <see cref="ColorSpaceConverter"/> class.
/// </summary>
public ColorSpaceConverter()
{
// Note the order here this is important.
this.WhitePoint = DefaultWhitePoint;
this.LmsAdaptationMatrix = CieXyzAndLmsConverter.DefaultTransformationMatrix;
this.ChromaticAdaptation = new VonKriesChromaticAdaptation(this.cachedCieXyzAndLmsConverter);
this.TargetLuvWhitePoint = CieLuv.DefaultWhitePoint;
this.TargetLabWhitePoint = CieLab.DefaultWhitePoint;
this.TargetHunterLabWhitePoint = HunterLab.DefaultWhitePoint;
this.TargetRgbWorkingSpace = Rgb.DefaultWorkingSpace;
}
/// <summary>
/// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space.
/// When null, no adaptation will be performed.
/// </summary>
public CieXyz WhitePoint { get; set; }
/// <summary>
/// Gets or sets the white point used *when creating* Luv/LChuv colors. (Luv/LChuv colors on the input already contain the white point information)
/// Defaults to: <see cref="CieLuv.DefaultWhitePoint"/>.
/// </summary>
public CieXyz TargetLuvWhitePoint { get; set; }
/// <summary>
/// Gets or sets the white point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information)
/// Defaults to: <see cref="CieLab.DefaultWhitePoint"/>.
/// </summary>
public CieXyz TargetLabWhitePoint { get; set; }
/// <summary>
/// Gets or sets the white point used *when creating* HunterLab colors. (HunterLab colors on the input already contain the white point information)
/// Defaults to: <see cref="HunterLab.DefaultWhitePoint"/>.
/// </summary>
public CieXyz TargetHunterLabWhitePoint { get; set; }
/// <summary>
/// Gets or sets the target working space used *when creating* RGB colors. (RGB colors on the input already contain the working space information)
/// Defaults to: <see cref="Rgb.DefaultWorkingSpace"/>.
/// </summary>
public IRgbWorkingSpace TargetRgbWorkingSpace { get; set; }
/// <summary>
/// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed.
/// </summary>
public IChromaticAdaptation ChromaticAdaptation { get; set; }
/// <summary>
/// Gets or sets transformation matrix used in conversion to <see cref="Lms"/>,
/// also used in the default Von Kries Chromatic Adaptation method.
/// </summary>
public Matrix4x4 LmsAdaptationMatrix
{
get => this.transformationMatrix;
set
{
this.transformationMatrix = value;
if (this.cachedCieXyzAndLmsConverter == null)
{
this.cachedCieXyzAndLmsConverter = new CieXyzAndLmsConverter(value);
}
else
{
this.cachedCieXyzAndLmsConverter.TransformationMatrix = value;
}
}
}
/// <summary>
/// Gets a value indicating whether chromatic adaptation has been performed.
/// </summary>
private bool IsChromaticAdaptationPerformed => this.ChromaticAdaptation != null;
}
}

27
src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs

@ -0,0 +1,27 @@
// <copyright file="IChromaticAdaptation.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using ImageSharp.ColorSpaces;
/// <summary>
/// Chromatic adaptation.
/// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M]
/// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD).
/// </summary>
internal interface IChromaticAdaptation
{
/// <summary>
/// Performs a linear transformation of a source color in to the destination color.
/// </summary>
/// <remarks>Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).</remarks>
/// <param name="sourceColor">The source color.</param>
/// <param name="sourceWhitePoint">The source white point.</param>
/// <param name="targetWhitePoint">The target white point.</param>
/// <returns>The <see cref="CieXyz"/></returns>
CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint);
}
}

22
src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs

@ -0,0 +1,22 @@
// <copyright file="IColorConversion.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
/// <summary>
/// Converts color between two color spaces.
/// </summary>
/// <typeparam name="T">The input color type.</typeparam>
/// <typeparam name="TResult">The result color type.</typeparam>
internal interface IColorConversion<in T, out TResult>
{
/// <summary>
/// Performs the conversion from the input to an instance of the output type.
/// </summary>
/// <param name="input">The input color instance.</param>
/// <returns>The converted result</returns>
TResult Convert(T input);
}
}

50
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs

@ -0,0 +1,50 @@
// <copyright file="CieLabToCieXyzConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLab
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieLab"/> to <see cref="CieXyz"/>.
/// </summary>
internal class CieLabToCieXyzConverter : IColorConversion<CieLab, CieXyz>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz Convert(CieLab input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
float l = input.L, a = input.A, b = input.B;
float fy = (l + 16) / 116F;
float fx = (a / 500F) + fy;
float fz = fy - (b / 200F);
float fx3 = MathF.Pow(fx, 3F);
float fz3 = MathF.Pow(fz, 3F);
float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa;
float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? MathF.Pow((l + 16F) / 116F, 3F) : l / CieConstants.Kappa;
float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa;
float wx = input.WhitePoint.X, wy = input.WhitePoint.Y, wz = input.WhitePoint.Z;
// Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white)
xr = xr.Clamp(0, 1F);
yr = yr.Clamp(0, 1F);
zr = zr.Clamp(0, 1F);
float x = xr * wx;
float y = yr * wy;
float z = zr * wz;
return new CieXyz(x, y, z);
}
}
}

67
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs

@ -0,0 +1,67 @@
// <copyright file="CieXyzToCieLabConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLab
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieXyz"/> to <see cref="CieLab"/>.
/// </summary>
internal class CieXyzToCieLabConverter : IColorConversion<CieXyz, CieLab>
{
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToCieLabConverter"/> class.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzToCieLabConverter()
: this(CieLab.DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToCieLabConverter"/> class.
/// </summary>
/// <param name="labWhitePoint">The target reference lab white point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzToCieLabConverter(CieXyz labWhitePoint)
{
this.LabWhitePoint = labWhitePoint;
}
/// <summary>
/// Gets the target reference whitepoint. When not set, <see cref="CieLab.DefaultWhitePoint"/> is used.
/// </summary>
public CieXyz LabWhitePoint
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLab Convert(CieXyz input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z;
float xr = input.X / wx, yr = input.Y / wy, zr = input.Z / wz;
float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) / 116F;
float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) / 116F;
float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) / 116F;
float l = (116F * fy) - 16F;
float a = 500F * (fx - fy);
float b = 200F * (fy - fz);
return new CieLab(l, a, b, this.LabWhitePoint);
}
}
}

34
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs

@ -0,0 +1,34 @@
// <copyright file="CieLchToCieLabConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLch
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieLch"/> to <see cref="CieLab"/>.
/// </summary>
internal class CieLchToCieLabConverter : IColorConversion<CieLch, CieLab>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLab Convert(CieLch input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here:
// https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
float l = input.L, c = input.C, hDegrees = input.H;
float hRadians = MathF.DegreeToRadian(hDegrees);
float a = c * MathF.Cos(hRadians);
float b = c * MathF.Sin(hRadians);
return new CieLab(l, a, b, input.WhitePoint);
}
}
}

42
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs

@ -0,0 +1,42 @@
// <copyright file="CieLabToCieLchConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLch
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieLab"/> to <see cref="CieLch"/>.
/// </summary>
internal class CieLabToCieLchConverter : IColorConversion<CieLab, CieLch>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLch Convert(CieLab input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here:
// https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
float l = input.L, a = input.A, b = input.B;
float c = MathF.Sqrt((a * a) + (b * b));
float hRadians = MathF.Atan2(b, a);
float hDegrees = MathF.RadianToDegree(hRadians);
// Wrap the angle round at 360.
hDegrees = hDegrees % 360;
// Make sure it's not negative.
while (hDegrees < 0)
{
hDegrees += 360;
}
return new CieLch(l, c, hDegrees, input.WhitePoint);
}
}
}

34
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs

@ -0,0 +1,34 @@
// <copyright file="CieLchuvToCieLuvConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieLch"/> to <see cref="CieLab"/>.
/// </summary>
internal class CieLchuvToCieLuvConverter : IColorConversion<CieLchuv, CieLuv>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLuv Convert(CieLchuv input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here:
// https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29
float l = input.L, c = input.C, hDegrees = input.H;
float hRadians = MathF.DegreeToRadian(hDegrees);
float u = c * MathF.Cos(hRadians);
float v = c * MathF.Sin(hRadians);
return new CieLuv(l, u, v, input.WhitePoint);
}
}
}

42
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs

@ -0,0 +1,42 @@
// <copyright file="CieLuvToCieLchuvConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieLab"/> to <see cref="CieLch"/>.
/// </summary>
internal class CieLuvToCieLchuvConverter : IColorConversion<CieLuv, CieLchuv>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLchuv Convert(CieLuv input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here:
// https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29
float l = input.L, a = input.U, b = input.V;
float c = MathF.Sqrt((a * a) + (b * b));
float hRadians = MathF.Atan2(b, a);
float hDegrees = MathF.RadianToDegree(hRadians);
// Wrap the angle round at 360.
hDegrees = hDegrees % 360;
// Make sure it's not negative.
while (hDegrees < 0)
{
hDegrees += 360;
}
return new CieLchuv(l, c, hDegrees, input.WhitePoint);
}
}
}

80
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs

@ -0,0 +1,80 @@
// <copyright file="CieLuvToCieXyzConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieLuv"/> to <see cref="CieXyz"/>.
/// </summary>
internal class CieLuvToCieXyzConverter : IColorConversion<CieLuv, CieXyz>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz Convert(CieLuv input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html
float l = input.L, u = input.U, v = input.V;
float u0 = ComputeU0(input.WhitePoint);
float v0 = ComputeV0(input.WhitePoint);
float y = l > CieConstants.Kappa * CieConstants.Epsilon
? MathF.Pow((l + 16) / 116, 3)
: l / CieConstants.Kappa;
float a = ((52 * l / (u + (13 * l * u0))) - 1) / 3;
float b = -5 * y;
float c = -0.3333333F;
float d = y * ((39 * l / (v + (13 * l * v0))) - 5);
float x = (d - b) / (a - c);
float z = (x * a) + b;
if (float.IsNaN(x) || x < 0)
{
x = 0;
}
if (float.IsNaN(y) || y < 0)
{
y = 0;
}
if (float.IsNaN(z) || z < 0)
{
z = 0;
}
return new CieXyz(x, y, z);
}
/// <summary>
/// Calculates the blue-yellow chromacity based on the given whitepoint.
/// </summary>
/// <param name="input">The whitepoint</param>
/// <returns>The <see cref="float"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float ComputeU0(CieXyz input)
{
return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z));
}
/// <summary>
/// Calculates the red-green chromacity based on the given whitepoint.
/// </summary>
/// <param name="input">The whitepoint</param>
/// <returns>The <see cref="float"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float ComputeV0(CieXyz input)
{
return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z));
}
}
}

102
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs

@ -0,0 +1,102 @@
// <copyright file="CieXyzToCieLuvConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Converts from <see cref="CieXyz"/> to <see cref="CieLuv"/>.
/// </summary>
internal class CieXyzToCieLuvConverter : IColorConversion<CieXyz, CieLuv>
{
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToCieLuvConverter"/> class.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzToCieLuvConverter()
: this(CieLuv.DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToCieLuvConverter"/> class.
/// </summary>
/// <param name="luvWhitePoint">The target reference luv white point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzToCieLuvConverter(CieXyz luvWhitePoint)
{
this.LuvWhitePoint = luvWhitePoint;
}
/// <summary>
/// Gets the target reference whitepoint. When not set, <see cref="CieLuv.DefaultWhitePoint"/> is used.
/// </summary>
public CieXyz LuvWhitePoint
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLuv Convert(CieXyz input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html
float yr = input.Y / this.LuvWhitePoint.Y;
float up = ComputeUp(input);
float vp = ComputeVp(input);
float upr = ComputeUp(this.LuvWhitePoint);
float vpr = ComputeVp(this.LuvWhitePoint);
float l = yr > CieConstants.Epsilon ? ((116 * MathF.Pow(yr, 0.3333333F)) - 16F) : (CieConstants.Kappa * yr);
if (float.IsNaN(l) || l < 0)
{
l = 0;
}
float u = 13 * l * (up - upr);
float v = 13 * l * (vp - vpr);
if (float.IsNaN(u))
{
u = 0;
}
if (float.IsNaN(v))
{
v = 0;
}
return new CieLuv(l, u, v, this.LuvWhitePoint);
}
/// <summary>
/// Calculates the blue-yellow chromacity based on the given whitepoint.
/// </summary>
/// <param name="input">The whitepoint</param>
/// <returns>The <see cref="float"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float ComputeUp(CieXyz input)
{
return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z));
}
/// <summary>
/// Calculates the red-green chromacity based on the given whitepoint.
/// </summary>
/// <param name="input">The whitepoint</param>
/// <returns>The <see cref="float"/></returns>
private static float ComputeVp(CieXyz input)
{
return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z));
}
}
}

53
src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs

@ -0,0 +1,53 @@
// <copyright file="CieXyzAndCieXyyConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieXyy
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between CIE XYZ and CIE xyY
/// <see href="http://www.brucelindbloom.com/"/> for formulas.
/// </summary>
internal class CieXyzAndCieXyyConverter : IColorConversion<CieXyz, CieXyy>, IColorConversion<CieXyy, CieXyz>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyy Convert(CieXyz input)
{
DebugGuard.NotNull(input, nameof(input));
float x = input.X / (input.X + input.Y + input.Z);
float y = input.Y / (input.X + input.Y + input.Z);
if (float.IsNaN(x) || float.IsNaN(y))
{
return new CieXyy(0, 0, input.Y);
}
return new CieXyy(x, y, input.Y);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz Convert(CieXyy input)
{
DebugGuard.NotNull(input, nameof(input));
if (MathF.Abs(input.Y) < Constants.Epsilon)
{
return new CieXyz(0, 0, input.Yl);
}
float x = (input.X * input.Yl) / input.Y;
float y = input.Yl;
float z = ((1 - input.X - input.Y) * y) / input.Y;
return new CieXyz(x, y, z);
}
}
}

53
src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs

@ -0,0 +1,53 @@
// <copyright file="CmykAndRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Cmyk
{
using System;
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between CMYK and Rgb
/// </summary>
internal class CmykAndRgbConverter : IColorConversion<Cmyk, Rgb>, IColorConversion<Rgb, Cmyk>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb Convert(Cmyk input)
{
float r = (1F - input.C) * (1F - input.K);
float g = (1F - input.M) * (1F - input.K);
float b = (1F - input.Y) * (1F - input.K);
return new Rgb(r, g, b);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Cmyk Convert(Rgb input)
{
// To CMYK
float c = 1F - input.R;
float m = 1F - input.G;
float y = 1F - input.B;
// To CMYK
float k = MathF.Min(c, MathF.Min(m, y));
if (MathF.Abs(k - 1F) < Constants.Epsilon)
{
return new Cmyk(0, 0, 0, 1F);
}
c = (c - k) / (1F - k);
m = (m - k) / (1F - k);
y = (y - k) / (1F - k);
return new Cmyk(c, m, y, k);
}
}
}

159
src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs

@ -0,0 +1,159 @@
// <copyright file="HslAndRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Hsl
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between HSL and Rgb
/// See <see href="http://www.poynton.com/PDFs/coloureq.pdf"/> for formulas.
/// </summary>
internal class HslAndRgbConverter : IColorConversion<Hsl, Rgb>, IColorConversion<Rgb, Hsl>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb Convert(Hsl input)
{
DebugGuard.NotNull(input, nameof(input));
float rangedH = input.H / 360F;
float r = 0;
float g = 0;
float b = 0;
float s = input.S;
float l = input.L;
if (MathF.Abs(l) > Constants.Epsilon)
{
if (MathF.Abs(s) < Constants.Epsilon)
{
r = g = b = l;
}
else
{
float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s);
float temp1 = (2F * l) - temp2;
r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F);
g = GetColorComponent(temp1, temp2, rangedH);
b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F);
}
}
return new Rgb(r, g, b);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsl Convert(Rgb input)
{
DebugGuard.NotNull(input, nameof(input));
float r = input.R;
float g = input.G;
float b = input.B;
float max = MathF.Max(r, MathF.Max(g, b));
float min = MathF.Min(r, MathF.Min(g, b));
float chroma = max - min;
float h = 0F;
float s = 0F;
float l = (max + min) / 2F;
if (MathF.Abs(chroma) < Constants.Epsilon)
{
return new Hsl(0F, s, l);
}
if (MathF.Abs(r - max) < Constants.Epsilon)
{
h = (g - b) / chroma;
}
else if (MathF.Abs(g - max) < Constants.Epsilon)
{
h = 2F + ((b - r) / chroma);
}
else if (MathF.Abs(b - max) < Constants.Epsilon)
{
h = 4F + ((r - g) / chroma);
}
h *= 60F;
if (h < 0F)
{
h += 360F;
}
if (l <= .5F)
{
s = chroma / (max + min);
}
else
{
s = chroma / (2F - chroma);
}
return new Hsl(h, s, l);
}
/// <summary>
/// Gets the color component from the given values.
/// </summary>
/// <param name="first">The first value.</param>
/// <param name="second">The second value.</param>
/// <param name="third">The third value.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float GetColorComponent(float first, float second, float third)
{
third = MoveIntoRange(third);
if (third < 0.1666667F)
{
return first + ((second - first) * 6F * third);
}
if (third < .5F)
{
return second;
}
if (third < 0.6666667F)
{
return first + ((second - first) * (0.6666667F - third) * 6F);
}
return first;
}
/// <summary>
/// Moves the specific value within the acceptable range for
/// conversion.
/// <remarks>Used for converting <see cref="Hsl"/> colors to this type.</remarks>
/// </summary>
/// <param name="value">The value to shift.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float MoveIntoRange(float value)
{
if (value < 0F)
{
value += 1F;
}
else if (value > 1F)
{
value -= 1F;
}
return value;
}
}
}

130
src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs

@ -0,0 +1,130 @@
// <copyright file="HsvAndRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Hsv
{
using System;
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between HSV and Rgb
/// See <see href="http://www.poynton.com/PDFs/coloureq.pdf"/> for formulas.
/// </summary>
internal class HsvAndRgbConverter : IColorConversion<Hsv, Rgb>, IColorConversion<Rgb, Hsv>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb Convert(Hsv input)
{
DebugGuard.NotNull(input, nameof(input));
float s = input.S;
float v = input.V;
if (MathF.Abs(s) < Constants.Epsilon)
{
return new Rgb(v, v, v);
}
float h = (MathF.Abs(input.H - 360) < Constants.Epsilon) ? 0 : input.H / 60;
int i = (int)Math.Truncate(h);
float f = h - i;
float p = v * (1F - s);
float q = v * (1F - (s * f));
float t = v * (1F - (s * (1F - f)));
float r, g, b;
switch (i)
{
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
break;
}
return new Rgb(r, g, b);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsv Convert(Rgb input)
{
DebugGuard.NotNull(input, nameof(input));
float r = input.R;
float g = input.G;
float b = input.B;
float max = MathF.Max(r, MathF.Max(g, b));
float min = MathF.Min(r, MathF.Min(g, b));
float chroma = max - min;
float h = 0;
float s = 0;
float v = max;
if (MathF.Abs(chroma) < Constants.Epsilon)
{
return new Hsv(0, s, v);
}
if (MathF.Abs(r - max) < Constants.Epsilon)
{
h = (g - b) / chroma;
}
else if (MathF.Abs(g - max) < Constants.Epsilon)
{
h = 2 + ((b - r) / chroma);
}
else if (MathF.Abs(b - max) < Constants.Epsilon)
{
h = 4 + ((r - g) / chroma);
}
h *= 60;
if (h < 0.0)
{
h += 360;
}
s = chroma / v;
return new Hsv(h, s, v);
}
}
}

51
src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs

@ -0,0 +1,51 @@
// <copyright file="CieXyzAndHunterLabConverterBase.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab
{
using System.Runtime.CompilerServices;
/// <summary>
/// The base class for converting between <see cref="HunterLab"/> and <see cref="CieXyz"/> color spaces.
/// </summary>
internal abstract class CieXyzAndHunterLabConverterBase
{
/// <summary>
/// Returns the Ka coefficient that depends upon the whitepoint illuminant.
/// </summary>
/// <param name="whitePoint">The whitepoint</param>
/// <returns>The <see cref="float"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ComputeKa(CieXyz whitePoint)
{
DebugGuard.NotNull(whitePoint, nameof(whitePoint));
if (whitePoint.Equals(Illuminants.C))
{
return 175F;
}
return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y);
}
/// <summary>
/// Returns the Kb coefficient that depends upon the whitepoint illuminant.
/// </summary>
/// <param name="whitePoint">The whitepoint</param>
/// <returns>The <see cref="float"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ComputeKb(CieXyz whitePoint)
{
DebugGuard.NotNull(whitePoint, nameof(whitePoint));
if (whitePoint == Illuminants.C)
{
return 70F;
}
return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z);
}
}
}

75
src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs

@ -0,0 +1,75 @@
// <copyright file="CieXyzToHunterLabConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between CieXyz and HunterLab
/// </summary>
internal class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase, IColorConversion<CieXyz, HunterLab>
{
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToHunterLabConverter"/> class.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzToHunterLabConverter()
: this(HunterLab.DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToHunterLabConverter"/> class.
/// </summary>
/// <param name="labWhitePoint">The hunter Lab white point.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzToHunterLabConverter(CieXyz labWhitePoint)
{
this.HunterLabWhitePoint = labWhitePoint;
}
/// <summary>
/// Gets the target reference white. When not set, <see cref="HunterLab.DefaultWhitePoint"/> is used.
/// </summary>
public CieXyz HunterLabWhitePoint
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HunterLab Convert(CieXyz input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
float x = input.X, y = input.Y, z = input.Z;
float xn = this.HunterLabWhitePoint.X, yn = this.HunterLabWhitePoint.Y, zn = this.HunterLabWhitePoint.Z;
float ka = ComputeKa(this.HunterLabWhitePoint);
float kb = ComputeKb(this.HunterLabWhitePoint);
float l = 100 * MathF.Sqrt(y / yn);
float a = ka * (((x / xn) - (y / yn)) / MathF.Sqrt(y / yn));
float b = kb * (((y / yn) - (z / zn)) / MathF.Sqrt(y / yn));
if (float.IsNaN(a))
{
a = 0;
}
if (float.IsNaN(b))
{
b = 0;
}
return new HunterLab(l, a, b, this.HunterLabWhitePoint);
}
}
}

37
src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs

@ -0,0 +1,37 @@
// <copyright file="HunterLabToCieXyzConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab
{
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between HunterLab and CieXyz
/// </summary>
internal class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase, IColorConversion<HunterLab, CieXyz>
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz Convert(HunterLab input)
{
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
float l = input.L, a = input.A, b = input.B;
float xn = input.WhitePoint.X, yn = input.WhitePoint.Y, zn = input.WhitePoint.Z;
float ka = ComputeKa(input.WhitePoint);
float kb = ComputeKb(input.WhitePoint);
float y = MathF.Pow(l / 100F, 2) * yn;
float x = (((a / ka) * MathF.Sqrt(y / yn)) + (y / yn)) * xn;
float z = (((b / kb) * MathF.Sqrt(y / yn)) - (y / yn)) * (-zn);
return new CieXyz(x, y, z);
}
}
}

85
src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs

@ -0,0 +1,85 @@
// <copyright file="CieXyzAndLmsConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Lms
{
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between CIE XYZ and LMS
/// </summary>
internal class CieXyzAndLmsConverter : IColorConversion<CieXyz, Lms>, IColorConversion<Lms, CieXyz>
{
/// <summary>
/// Default transformation matrix used, when no other is set. (Bradford)
/// <see cref="LmsAdaptationMatrix"/>
/// </summary>
public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford;
private Matrix4x4 inverseTransformationMatrix;
private Matrix4x4 transformationMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzAndLmsConverter"/> class.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzAndLmsConverter()
: this(DefaultTransformationMatrix)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzAndLmsConverter"/> class.
/// </summary>
/// <param name="transformationMatrix">
/// Definition of the cone response domain (see <see cref="LmsAdaptationMatrix"/>),
/// if not set <see cref="DefaultTransformationMatrix"/> will be used.
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix)
{
this.TransformationMatrix = transformationMatrix;
}
/// <summary>
/// Gets or sets the transformation matrix used for the conversion (definition of the cone response domain).
/// <see cref="LmsAdaptationMatrix"/>
/// </summary>
public Matrix4x4 TransformationMatrix
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.transformationMatrix;
set
{
this.transformationMatrix = value;
Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix);
}
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Lms Convert(CieXyz input)
{
DebugGuard.NotNull(input, nameof(input));
Vector3 vector = Vector3.Transform(input.Vector, this.transformationMatrix);
return new Lms(vector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz Convert(Lms input)
{
DebugGuard.NotNull(input, nameof(input));
Vector3 vector = Vector3.Transform(input.Vector, this.inverseTransformationMatrix);
return new CieXyz(vector);
}
}
}

101
src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs

@ -0,0 +1,101 @@
// <copyright file="LmsAdaptationMatrix.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Lms
{
using System.Numerics;
/// <summary>
/// AdaptionMatrix3X3 used for transformation from XYZ to LMS, defining the cone response domain.
/// Used in <see cref="IChromaticAdaptation"/>
/// </summary>
/// <remarks>
/// Matrix data obtained from:
/// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization
/// S. Bianco, R. Schettini
/// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy
/// https://web.stanford.edu/~sujason/ColorBalancing/Papers/Two%20New%20von%20Kries%20Based%20Chromatic%20Adaptation.pdf
/// </remarks>
public static class LmsAdaptationMatrix
{
/// <summary>
/// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
/// </summary>
public static readonly Matrix4x4 VonKriesHPEAdjusted
= Matrix4x4.Transpose(new Matrix4x4
{
M11 = 0.40024F, M12 = 0.7076F, M13 = -0.08081F,
M21 = -0.2263F, M22 = 1.16532F, M23 = 0.0457F,
M31 = 0, M32 = 0, M33 = 0.91822F,
M44 = 1F // Important for inverse transforms.
});
/// <summary>
/// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy)
/// </summary>
public static readonly Matrix4x4 VonKriesHPE
= Matrix4x4.Transpose(new Matrix4x4
{
M11 = 0.3897F, M12 = 0.6890F, M13 = -0.0787F,
M21 = -0.2298F, M22 = 1.1834F, M23 = 0.0464F,
M31 = 0, M32 = 0, M33 = 1F,
M44 = 1F
});
/// <summary>
/// XYZ scaling chromatic adaptation transform matrix
/// </summary>
public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity);
/// <summary>
/// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
/// </summary>
public static readonly Matrix4x4 Bradford
= Matrix4x4.Transpose(new Matrix4x4
{
M11 = 0.8951F, M12 = 0.2664F, M13 = -0.1614F,
M21 = -0.7502F, M22 = 1.7135F, M23 = 0.0367F,
M31 = 0.0389F, M32 = -0.0685F, M33 = 1.0296F,
M44 = 1F
});
/// <summary>
/// Spectral sharpening and the Bradford transform
/// </summary>
public static readonly Matrix4x4 BradfordSharp
= Matrix4x4.Transpose(new Matrix4x4
{
M11 = 1.2694F, M12 = -0.0988F, M13 = -0.1706F,
M21 = -0.8364F, M22 = 1.8006F, M23 = 0.0357F,
M31 = 0.0297F, M32 = -0.0315F, M33 = 1.0018F,
M44 = 1F
});
/// <summary>
/// CMCCAT2000 (fitted from all available color data sets)
/// </summary>
public static readonly Matrix4x4 CMCCAT2000
= Matrix4x4.Transpose(new Matrix4x4
{
M11 = 0.7982F, M12 = 0.3389F, M13 = -0.1371F,
M21 = -0.5918F, M22 = 1.5512F, M23 = 0.0406F,
M31 = 0.0008F, M32 = 0.239F, M33 = 0.9753F,
M44 = 1F
});
/// <summary>
/// CAT02 (optimized for minimizing CIELAB differences)
/// </summary>
public static readonly Matrix4x4 CAT02
= Matrix4x4.Transpose(new Matrix4x4
{
M11 = 0.7328F, M12 = 0.4296F, M13 = -0.1624F,
M21 = -0.7036F, M22 = 1.6975F, M23 = 0.0061F,
M31 = 0.0030F, M32 = 0.0136F, M33 = 0.9834F,
M44 = 1F
});
}
}

52
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs

@ -0,0 +1,52 @@
// <copyright file="LinearRgbToRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ImageSharp.ColorSpaces.Rgb;
/// <summary>
/// Color converter between CieXyz and LinearRgb
/// </summary>
internal class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase, IColorConversion<CieXyz, LinearRgb>
{
private readonly Matrix4x4 conversionMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToLinearRgbConverter"/> class.
/// </summary>
public CieXyzToLinearRgbConverter()
: this(Rgb.DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToLinearRgbConverter"/> class.
/// </summary>
/// <param name="workingSpace">The target working space.</param>
public CieXyzToLinearRgbConverter(IRgbWorkingSpace workingSpace)
{
this.TargetWorkingSpace = workingSpace;
this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace);
}
/// <summary>
/// Gets the target working space
/// </summary>
public IRgbWorkingSpace TargetWorkingSpace { get; }
/// <inheritdoc/>
public LinearRgb Convert(CieXyz input)
{
DebugGuard.NotNull(input, nameof(input));
Matrix4x4.Invert(this.conversionMatrix, out Matrix4x4 inverted);
Vector3 vector = Vector3.Transform(input.Vector, inverted);
return new LinearRgb(vector, this.TargetWorkingSpace);
}
}
}

48
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs

@ -0,0 +1,48 @@
// <copyright file="GammaCompanding.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System;
using System.Runtime.CompilerServices;
/// <summary>
/// Implements gamma companding
/// </summary>
/// <remarks>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public class GammaCompanding : ICompanding
{
/// <summary>
/// Initializes a new instance of the <see cref="GammaCompanding"/> class.
/// </summary>
/// <param name="gamma">The gamma value.</param>
public GammaCompanding(float gamma)
{
this.Gamma = gamma;
}
/// <summary>
/// Gets the gamma value
/// </summary>
public float Gamma { get; }
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Expand(float channel)
{
return MathF.Pow(channel, this.Gamma);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Compress(float channel)
{
return MathF.Pow(channel, 1 / this.Gamma);
}
}
}

36
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs

@ -0,0 +1,36 @@
// <copyright file="LCompanding.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Runtime.CompilerServices;
/// <summary>
/// Implements L* companding
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public class LCompanding : ICompanding
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Expand(float channel)
{
return channel <= 0.08 ? 100 * channel / CieConstants.Kappa : MathF.Pow((channel + 0.16F) / 1.16F, 3);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Compress(float channel)
{
return channel <= CieConstants.Epsilon
? channel * CieConstants.Kappa / 100F
: MathF.Pow(1.16F * channel, 0.3333333F) - 0.16F;
}
}
}

69
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs

@ -0,0 +1,69 @@
// <copyright file="LinearRgbAndCieXyzConverterBase.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Numerics;
/// <summary>
/// Provides base methods for converting between Rgb and CieXyz color spaces.
/// </summary>
internal abstract class LinearRgbAndCieXyzConverterBase
{
/// <summary>
/// Geturns the correct matrix to convert between the Rgb and CieXyz color space.
/// </summary>
/// <param name="workingSpace">The Rgb working space.</param>
/// <returns>The <see cref="Matrix4x4"/> based on the chromaticity and working space.</returns>
public static Matrix4x4 GetRgbToCieXyzMatrix(IRgbWorkingSpace workingSpace)
{
DebugGuard.NotNull(workingSpace, nameof(workingSpace));
RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates;
float xr = chromaticity.R.X;
float xg = chromaticity.G.X;
float xb = chromaticity.B.X;
float yr = chromaticity.R.Y;
float yg = chromaticity.G.Y;
float yb = chromaticity.B.Y;
float mXr = xr / yr;
const float Yr = 1;
float mZr = (1 - xr - yr) / yr;
float mXg = xg / yg;
const float Yg = 1;
float mZg = (1 - xg - yg) / yg;
float mXb = xb / yb;
const float Yb = 1;
float mZb = (1 - xb - yb) / yb;
Matrix4x4 xyzMatrix = new Matrix4x4
{
M11 = mXr, M21 = mXg, M31 = mXb,
M12 = Yr, M22 = Yg, M32 = Yb,
M13 = mZr, M23 = mZg, M33 = mZb,
M44 = 1F
};
Matrix4x4 inverseXyzMatrix;
Matrix4x4.Invert(xyzMatrix, out inverseXyzMatrix);
Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix);
// Use transposed Rows/Coloumns
// TODO: Is there a built in method for this multiplication?
return new Matrix4x4
{
M11 = vector.X * mXr, M21 = vector.Y * mXg, M31 = vector.Z * mXb,
M12 = vector.X * Yr, M22 = vector.Y * Yg, M32 = vector.Z * Yb,
M13 = vector.X * mZr, M23 = vector.Y * mZg, M33 = vector.Z * mZb,
M44 = 1F
};
}
}
}

52
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs

@ -0,0 +1,52 @@
// <copyright file="LinearRgbToCieXyzConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ImageSharp.ColorSpaces.Rgb;
/// <summary>
/// Color converter between LinearRgb and CieXyz
/// </summary>
internal class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase, IColorConversion<LinearRgb, CieXyz>
{
private readonly Matrix4x4 conversionMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgbToCieXyzConverter"/> class.
/// </summary>
public LinearRgbToCieXyzConverter()
: this(Rgb.DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgbToCieXyzConverter"/> class.
/// </summary>
/// <param name="workingSpace">The target working space.</param>
public LinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace)
{
this.SourceWorkingSpace = workingSpace;
this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace);
}
/// <summary>
/// Gets the source working space
/// </summary>
public IRgbWorkingSpace SourceWorkingSpace { get; }
/// <inheritdoc/>
public CieXyz Convert(LinearRgb input)
{
DebugGuard.NotNull(input, nameof(input));
Guard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal.");
Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix);
return new CieXyz(vector);
}
}
}

30
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs

@ -0,0 +1,30 @@
// <copyright file="LinearRgbToRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ColorSpaces.Rgb;
/// <summary>
/// Color converter between LinearRgb and Rgb
/// </summary>
internal class LinearRgbToRgbConverter : IColorConversion<LinearRgb, Rgb>
{
/// <inheritdoc/>
public Rgb Convert(LinearRgb input)
{
DebugGuard.NotNull(input, nameof(input));
Vector3 vector = input.Vector;
vector.X = input.WorkingSpace.Companding.Compress(vector.X);
vector.Y = input.WorkingSpace.Companding.Compress(vector.Y);
vector.Z = input.WorkingSpace.Companding.Compress(vector.Z);
return new Rgb(vector, input.WorkingSpace);
}
}
}

107
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs

@ -0,0 +1,107 @@
// <copyright file="RgbPrimariesChromaticityCoordinates.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System;
/// <summary>
/// Represents the chromaticity coordinates of RGB primaries.
/// One of the specifiers of <see cref="IRgbWorkingSpace"/>.
/// </summary>
internal struct RgbPrimariesChromaticityCoordinates : IEquatable<RgbPrimariesChromaticityCoordinates>
{
/// <summary>
/// Initializes a new instance of the <see cref="RgbPrimariesChromaticityCoordinates"/> struct.
/// </summary>
/// <param name="r">The chomaticity coordinates of the red channel.</param>
/// <param name="g">The chomaticity coordinates of the green channel.</param>
/// <param name="b">The chomaticity coordinates of the blue channel.</param>
public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b)
{
this.R = r;
this.G = g;
this.B = b;
}
/// <summary>
/// Gets the chomaticity coordinates of the red channel.
/// </summary>
public CieXyChromaticityCoordinates R { get; }
/// <summary>
/// Gets the chomaticity coordinates of the green channel.
/// </summary>
public CieXyChromaticityCoordinates G { get; }
/// <summary>
/// Gets the chomaticity coordinates of the blue channel.
/// </summary>
public CieXyChromaticityCoordinates B { get; }
/// <summary>
/// Compares two <see cref="CieLab"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLab"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieLab"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="CieLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLab"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is RgbPrimariesChromaticityCoordinates)
{
return this.Equals((RgbPrimariesChromaticityCoordinates)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(RgbPrimariesChromaticityCoordinates other)
{
return this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
int hashCode = this.R.GetHashCode();
hashCode = (hashCode * 397) ^ this.G.GetHashCode();
hashCode = (hashCode * 397) ^ this.B.GetHashCode();
return hashCode;
}
}
}
}

33
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs

@ -0,0 +1,33 @@
// <copyright file="Rec2020Companding.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Runtime.CompilerServices;
/// <summary>
/// Implements Rec. 2020 companding function (for 12-bits).
/// </summary>
/// <remarks>
/// <see href="http://en.wikipedia.org/wiki/Rec._2020"/>
/// For 10-bits, companding is identical to <see cref="Rec709Companding"/>
/// </remarks>
public class Rec2020Companding : ICompanding
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Expand(float channel)
{
return channel < 0.08145F ? channel / 4.5F : MathF.Pow((channel + 0.0993F) / 1.0993F, 2.222222F);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Compress(float channel)
{
return channel < 0.0181F ? 4500F * channel : (1.0993F * channel) - 0.0993F;
}
}
}

32
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs

@ -0,0 +1,32 @@
// <copyright file="Rec2020Companding.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Runtime.CompilerServices;
/// <summary>
/// Implements the Rec. 709 companding function
/// </summary>
/// <remarks>
/// http://en.wikipedia.org/wiki/Rec._709
/// </remarks>
public class Rec709Companding : ICompanding
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Expand(float channel)
{
return channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, 2.222222F);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Compress(float channel)
{
return channel < 0.018F ? 4500F * channel : (1.099F * channel) - 0.099F;
}
}
}

30
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs

@ -0,0 +1,30 @@
// <copyright file="RgbToLinearRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ColorSpaces.Rgb;
/// <summary>
/// Color converter between Rgb and LinearRgb
/// </summary>
internal class RgbToLinearRgbConverter : IColorConversion<Rgb, LinearRgb>
{
/// <inheritdoc/>
public LinearRgb Convert(Rgb input)
{
Guard.NotNull(input, nameof(input));
Vector3 vector = input.Vector;
vector.X = input.WorkingSpace.Companding.Expand(vector.X);
vector.Y = input.WorkingSpace.Companding.Expand(vector.Y);
vector.Z = input.WorkingSpace.Companding.Expand(vector.Z);
return new LinearRgb(vector, input.WorkingSpace);
}
}
}

107
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs

@ -0,0 +1,107 @@
// <copyright file="RgbWorkingSpace.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
/// <summary>
/// Trivial implementation of <see cref="IRgbWorkingSpace"/>
/// </summary>
internal struct RgbWorkingSpace : IRgbWorkingSpace
{
/// <summary>
/// Initializes a new instance of the <see cref="RgbWorkingSpace"/> struct.
/// </summary>
/// <param name="referenceWhite">The reference white point.</param>
/// <param name="companding">The function pair for converting to <see cref="CieXyz"/> and back.</param>
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
public RgbWorkingSpace(CieXyz referenceWhite, ICompanding companding, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
{
this.WhitePoint = referenceWhite;
this.Companding = companding;
this.ChromaticityCoordinates = chromaticityCoordinates;
}
/// <summary>
/// Gets the reference white point
/// </summary>
public CieXyz WhitePoint { get; }
/// <summary>
/// Gets the function pair for converting to <see cref="CieXyz"/> and back.
/// </summary>
public ICompanding Companding { get; }
/// <summary>
/// Gets the chromaticity of the rgb primaries.
/// </summary>
public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
/// <summary>
/// Compares two <see cref="RgbWorkingSpace"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="RgbWorkingSpace"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RgbWorkingSpace"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(RgbWorkingSpace left, RgbWorkingSpace right)
{
return Equals(left, right);
}
/// <summary>
/// Compares two <see cref="RgbWorkingSpace"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="RgbWorkingSpace"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RgbWorkingSpace"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(RgbWorkingSpace left, RgbWorkingSpace right)
{
return !Equals(left, right);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is RgbWorkingSpace)
{
return this.Equals((RgbWorkingSpace)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(IRgbWorkingSpace other)
{
// TODO: Object.Equals for ICompanding will be slow.
return this.WhitePoint.Equals(other.WhitePoint)
&& this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates)
&& Equals(this.Companding, other.Companding);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.WhitePoint.GetHashCode();
hashCode = (hashCode * 397) ^ this.ChromaticityCoordinates.GetHashCode();
hashCode = (hashCode * 397) ^ (this.Companding?.GetHashCode() ?? 0);
return hashCode;
}
}
}
}

34
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs

@ -0,0 +1,34 @@
// <copyright file="SRgbCompanding.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
{
using System.Runtime.CompilerServices;
/// <summary>
/// Implements sRGB companding
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public class SRgbCompanding : ICompanding
{
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Expand(float channel)
{
return channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float Compress(float channel)
{
return channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
}
}
}

57
src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs

@ -0,0 +1,57 @@
// <copyright file="YCbCrAndRgbConverter .cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion.Implementation.YCbCr
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp.ColorSpaces;
/// <summary>
/// Color converter between YCbCr and Rgb
/// See <see href="https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion"/> for formulas.
/// </summary>
internal class YCbCrAndRgbConverter : IColorConversion<YCbCr, Rgb>, IColorConversion<Rgb, YCbCr>
{
private static readonly Vector3 MaxBytes = new Vector3(255F);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb Convert(YCbCr input)
{
DebugGuard.NotNull(input, nameof(input));
float y = input.Y;
float cb = input.Cb - 128F;
float cr = input.Cr - 128F;
float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
return new Rgb(new Vector3(r, g, b) / MaxBytes);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public YCbCr Convert(Rgb input)
{
DebugGuard.NotNull(input, nameof(input));
Vector3 rgb = input.Vector * MaxBytes;
float r = rgb.X;
float g = rgb.Y;
float b = rgb.Z;
float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
return new YCbCr(y, cb, cr);
}
}
}

75
src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs

@ -0,0 +1,75 @@
// <copyright file="VonKriesChromaticAdaptation.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces.Conversion
{
using System.Numerics;
using ImageSharp.ColorSpaces;
using ImageSharp.ColorSpaces.Conversion.Implementation.Lms;
/// <summary>
/// Basic implementation of the von Kries chromatic adaptation model
/// </summary>
/// <remarks>
/// Transformation described here:
/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
/// </remarks>
internal class VonKriesChromaticAdaptation : IChromaticAdaptation
{
private readonly CieXyzAndLmsConverter converter;
/// <summary>
/// Initializes a new instance of the <see cref="VonKriesChromaticAdaptation"/> class.
/// </summary>
public VonKriesChromaticAdaptation()
: this(new CieXyzAndLmsConverter())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="VonKriesChromaticAdaptation"/> class.
/// </summary>
/// <param name="transformationMatrix">
/// The transformation matrix used for the conversion (definition of the cone response domain).
/// <see cref="LmsAdaptationMatrix"/>
/// </param>
public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix)
: this(new CieXyzAndLmsConverter(transformationMatrix))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="VonKriesChromaticAdaptation"/> class.
/// </summary>
/// <param name="converter">The color converter</param>
public VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter)
{
this.converter = converter;
}
/// <inheritdoc/>
public CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint)
{
Guard.NotNull(sourceColor, nameof(sourceColor));
Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
Guard.NotNull(targetWhitePoint, nameof(targetWhitePoint));
if (sourceWhitePoint.Equals(targetWhitePoint))
{
return sourceColor;
}
Lms sourceColorLms = this.converter.Convert(sourceColor);
Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint);
Lms targetWhitePointLms = this.converter.Convert(targetWhitePoint);
var vector = new Vector3(targetWhitePointLms.L / sourceWhitePointLms.L, targetWhitePointLms.M / sourceWhitePointLms.M, targetWhitePointLms.S / sourceWhitePointLms.S);
var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.Vector));
return this.converter.Convert(targetColorLms);
}
}
}

109
src/ImageSharp/Colors/Spaces/Hsl.cs → src/ImageSharp/ColorSpaces/Hsl.cs

@ -3,28 +3,23 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using ImageSharp.PixelFormats;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents a Hsl (hue, saturation, lightness) color.
/// </summary>
public struct Hsl : IEquatable<Hsl>, IAlmostEquatable<Hsl, float>
internal struct Hsl : IColorVector, IEquatable<Hsl>, IAlmostEquatable<Hsl, float>
{
/// <summary>
/// Represents a <see cref="Hsl"/> that has H, S, and L values set to zero.
/// </summary>
public static readonly Hsl Empty = default(Hsl);
/// <summary>
/// Min range used for clamping
/// </summary>
private static readonly Vector3 VectorMin = Vector3.Zero;
/// <summary>
/// Max range used for clamping
/// </summary>
@ -41,28 +36,51 @@ namespace ImageSharp.Colors.Spaces
/// <param name="h">The h hue component.</param>
/// <param name="s">The s saturation component.</param>
/// <param name="l">The l value (lightness) component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsl(float h, float s, float l)
: this(new Vector3(h, s, l))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Hsl"/> struct.
/// </summary>
/// <param name="vector">The vector representing the h, s, l components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsl(Vector3 vector)
{
this.backingVector = Vector3.Clamp(new Vector3(h, s, l), VectorMin, VectorMax);
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax);
}
/// <summary>
/// Gets the hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public float H => this.backingVector.X;
public float H
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the saturation component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float S => this.backingVector.Y;
public float S
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the lightness component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float L => this.backingVector.Z;
public float L
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="Hsl"/> is empty.
@ -70,61 +88,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="Hsl"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Rgba32"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Hsl"/>.
/// </returns>
public static implicit operator Hsl(Rgba32 color)
/// <inheritdoc/>
public Vector3 Vector
{
float r = color.R / 255F;
float g = color.G / 255F;
float b = color.B / 255F;
float max = MathF.Max(r, MathF.Max(g, b));
float min = MathF.Min(r, MathF.Min(g, b));
float chroma = max - min;
float h = 0;
float s = 0;
float l = (max + min) / 2;
if (MathF.Abs(chroma) < Constants.Epsilon)
{
return new Hsl(0, s, l);
}
if (MathF.Abs(r - max) < Constants.Epsilon)
{
h = (g - b) / chroma;
}
else if (MathF.Abs(g - max) < Constants.Epsilon)
{
h = 2 + ((b - r) / chroma);
}
else if (MathF.Abs(b - max) < Constants.Epsilon)
{
h = 4 + ((r - g) / chroma);
}
h *= 60;
if (h < 0.0)
{
h += 360;
}
if (l <= .5f)
{
s = chroma / (max + min);
}
else
{
s = chroma / (2 - chroma);
}
return new Hsl(h, s, l);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
@ -139,6 +107,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Hsl left, Hsl right)
{
return left.Equals(right);
@ -156,6 +125,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Hsl left, Hsl right)
{
return !left.Equals(right);
@ -179,6 +149,7 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Hsl)
@ -190,19 +161,21 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Hsl other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Hsl other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}
}

58
src/ImageSharp/Colors/Spaces/Hsv.cs → src/ImageSharp/ColorSpaces/Hsv.cs

@ -3,28 +3,23 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using ImageSharp.PixelFormats;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
/// </summary>
public struct Hsv : IEquatable<Hsv>, IAlmostEquatable<Hsv, float>
internal struct Hsv : IColorVector, IEquatable<Hsv>, IAlmostEquatable<Hsv, float>
{
/// <summary>
/// Represents a <see cref="Hsv"/> that has H, S, and V values set to zero.
/// </summary>
public static readonly Hsv Empty = default(Hsv);
/// <summary>
/// Min range used for clamping
/// </summary>
private static readonly Vector3 VectorMin = Vector3.Zero;
/// <summary>
/// Max range used for clamping
/// </summary>
@ -41,28 +36,51 @@ namespace ImageSharp.Colors.Spaces
/// <param name="h">The h hue component.</param>
/// <param name="s">The s saturation component.</param>
/// <param name="v">The v value (brightness) component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsv(float h, float s, float v)
: this(new Vector3(h, s, v))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Hsv"/> struct.
/// </summary>
/// <param name="vector">The vector representing the h, s, v components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsv(Vector3 vector)
{
this.backingVector = Vector3.Clamp(new Vector3(h, s, v), VectorMin, VectorMax);
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax);
}
/// <summary>
/// Gets the hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public float H => this.backingVector.X;
public float H
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the saturation component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float S => this.backingVector.Y;
public float S
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the value (brightness) component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float V => this.backingVector.Z;
public float V
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="Hsv"/> is empty.
@ -70,6 +88,13 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc/>
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="Hsv"/>.
@ -132,6 +157,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Hsv left, Hsv right)
{
return left.Equals(right);
@ -149,6 +175,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Hsv left, Hsv right)
{
return !left.Equals(right);
@ -172,6 +199,7 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Hsv)
@ -183,19 +211,21 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Hsv other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Hsv other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}
}

223
src/ImageSharp/ColorSpaces/HunterLab.cs

@ -0,0 +1,223 @@
// <copyright file="HunterLab.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an Hunter LAB color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
/// </summary>
internal struct HunterLab : IColorVector, IEquatable<HunterLab>, IAlmostEquatable<HunterLab, float>
{
/// <summary>
/// D50 standard illuminant.
/// Used when reference white is not specified explicitly.
/// </summary>
public static readonly CieXyz DefaultWhitePoint = Illuminants.C;
/// <summary>
/// Represents a <see cref="HunterLab"/> that has L, A, B values set to zero.
/// </summary>
public static readonly HunterLab Empty = default(HunterLab);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="a">The a (green - magenta) component.</param>
/// <param name="b">The b (blue - yellow) component.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HunterLab(float l, float a, float b)
: this(new Vector3(l, a, b), DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="a">The a (green - magenta) component.</param>
/// <param name="b">The b (blue - yellow) component.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HunterLab(float l, float a, float b, CieXyz whitePoint)
: this(new Vector3(l, a, b), whitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, a, b components.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HunterLab(Vector3 vector)
: this(vector, DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l a b components.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HunterLab(Vector3 vector, CieXyz whitePoint)
: this()
{
this.backingVector = vector;
this.WhitePoint = whitePoint;
}
/// <summary>
/// Gets the reference white point of this color
/// </summary>
public CieXyz WhitePoint { get; }
/// <summary>
/// Gets the lightness dimension.
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
/// </summary>
public float L
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the a color component.
/// <remarks>A value ranging from -100 to 100. Negative is green, positive magenta.</remarks>
/// </summary>
public float A
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the b color component.
/// <remarks>A value ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
/// </summary>
public float B
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="HunterLab"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Compares two <see cref="HunterLab"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="HunterLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="HunterLab"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(HunterLab left, HunterLab right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="HunterLab"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="HunterLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="HunterLab"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(HunterLab left, HunterLab right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.WhitePoint.GetHashCode();
hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
return hashCode;
}
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "HunterLab [Empty]";
}
return $"HunterLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is HunterLab)
{
return this.Equals((HunterLab)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(HunterLab other)
{
return this.backingVector.Equals(other.backingVector)
&& this.WhitePoint.Equals(other.WhitePoint);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(HunterLab other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return this.WhitePoint.Equals(other.WhitePoint)
&& result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

4
src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs → src/ImageSharp/ColorSpaces/IAlmostEquatable.cs

@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
namespace ImageSharp.ColorSpaces
{
using System;
@ -27,4 +27,4 @@ namespace ImageSharp.Colors.Spaces
/// </returns>
bool AlmostEquals(TPixel other, TPrecision precision);
}
}
}

20
src/ImageSharp/ColorSpaces/IColorVector.cs

@ -0,0 +1,20 @@
// <copyright file="IColorVector.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System.Numerics;
/// <summary>
/// Color represented as a vector in its color space
/// </summary>
public interface IColorVector
{
/// <summary>
/// Gets the vector representation of the color
/// </summary>
Vector3 Vector { get; }
}
}

37
src/ImageSharp/ColorSpaces/ICompanding.cs

@ -0,0 +1,37 @@
// <copyright file="ICompanding.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
/// <summary>
/// Pair of companding functions for <see cref="IRgbWorkingSpace"/>.
/// Used for conversion to <see cref="CieXyz"/> and backwards.
/// See also: <seealso cref="IRgbWorkingSpace.Companding"/>
/// </summary>
internal interface ICompanding
{
/// <summary>
/// Expands a companded channel to its linear equivalent with respect to the energy.
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// </remarks>
/// <param name="channel">The channel value</param>
/// <returns>The linear channel value</returns>
float Expand(float channel);
/// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent (depends on the RGB color system).
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
/// <param name="channel">The channel value</param>
/// <returns>The nonlinear channel value</returns>
float Compress(float channel);
}
}

34
src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs

@ -0,0 +1,34 @@
// <copyright file="IRgbWorkingSpace.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
/// <summary>
/// Encasulates the RGB working color space
/// </summary>
internal interface IRgbWorkingSpace : IEquatable<IRgbWorkingSpace>
{
/// <summary>
/// Gets the reference white of the color space
/// </summary>
CieXyz WhitePoint { get; }
/// <summary>
/// Gets the chromaticity coordinates of the primaries
/// </summary>
RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
/// <summary>
/// Gets the companding function associated with the RGB color system. Used for conversion to XYZ and backwards.
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </summary>
ICompanding Companding { get; }
}
}

71
src/ImageSharp/ColorSpaces/Illuminants.cs

@ -0,0 +1,71 @@
namespace ImageSharp.ColorSpaces
{
/// <summary>
/// The well known standard illuminants.
/// Standard illuminants provide a basis for comparing images or colors recorded under different lighting
/// </summary>
/// <remarks>
/// Coefficients taken from:
/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
/// <br />
/// Descriptions taken from:
/// http://en.wikipedia.org/wiki/Standard_illuminant
/// </remarks>
internal static class Illuminants
{
/// <summary>
/// Incandescent / Tungsten
/// </summary>
public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F);
/// <summary>
/// Direct sunlight at noon (obsoleteF)
/// </summary>
public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F);
/// <summary>
/// Average / North sky Daylight (obsoleteF)
/// </summary>
public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F);
/// <summary>
/// Horizon Light. ICC profile PCS
/// </summary>
public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F);
/// <summary>
/// Mid-morning / Mid-afternoon Daylight
/// </summary>
public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F);
/// <summary>
/// Noon Daylight: TelevisionF, sRGB color space
/// </summary>
public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F);
/// <summary>
/// North sky Daylight
/// </summary>
public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F);
/// <summary>
/// Equal energy
/// </summary>
public static readonly CieXyz E = new CieXyz(1F, 1F, 1F);
/// <summary>
/// Cool White Fluorescent
/// </summary>
public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F);
/// <summary>
/// D65 simulatorF, Daylight simulator
/// </summary>
public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F);
/// <summary>
/// Philips TL84F, Ultralume 40
/// </summary>
public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F);
}
}

213
src/ImageSharp/ColorSpaces/LinearRgb.cs

@ -0,0 +1,213 @@
// <copyright file="LinearRgb.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an linear Rgb color with specified <see cref="IRgbWorkingSpace"/> working space
/// </summary>
internal struct LinearRgb : IColorVector, IEquatable<LinearRgb>, IAlmostEquatable<LinearRgb, float>
{
/// <summary>
/// Represents a <see cref="LinearRgb"/> that has R, G, and B values set to zero.
/// </summary>
public static readonly LinearRgb Empty = default(LinearRgb);
/// <summary>
/// The default LinearRgb working space
/// </summary>
public static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
/// </summary>
/// <param name="r">The red component ranging between 0 and 1.</param>
/// <param name="g">The green component ranging between 0 and 1.</param>
/// <param name="b">The blue component ranging between 0 and 1.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LinearRgb(float r, float g, float b)
: this(new Vector3(r, g, b))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
/// </summary>
/// <param name="r">The red component ranging between 0 and 1.</param>
/// <param name="g">The green component ranging between 0 and 1.</param>
/// <param name="b">The blue component ranging between 0 and 1.</param>
/// <param name="workingSpace">The rgb working space.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LinearRgb(float r, float g, float b, IRgbWorkingSpace workingSpace)
: this(new Vector3(r, g, b), workingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
/// </summary>
/// <param name="vector">The vector representing the r, g, b components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LinearRgb(Vector3 vector)
: this(vector, DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
/// </summary>
/// <param name="vector">The vector representing the r, g, b components.</param>
/// <param name="workingSpace">The LinearRgb working space.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LinearRgb(Vector3 vector, IRgbWorkingSpace workingSpace)
: this()
{
// Clamp to 0-1 range.
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One);
this.WorkingSpace = workingSpace;
}
/// <summary>
/// Gets the red component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float R
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the green component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float G
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the blue component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float B
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets the LinearRgb color space <seealso cref="RgbWorkingSpaces"/>
/// </summary>
public IRgbWorkingSpace WorkingSpace { get; }
/// <summary>
/// Gets a value indicating whether this <see cref="LinearRgb"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Compares two <see cref="LinearRgb"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="LinearRgb"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="LinearRgb"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(LinearRgb left, LinearRgb right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="LinearRgb"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="LinearRgb"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="LinearRgb"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(LinearRgb left, LinearRgb right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "LinearRgb [ Empty ]";
}
return $"LinearRgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is LinearRgb)
{
return this.Equals((LinearRgb)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(LinearRgb other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(LinearRgb other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

180
src/ImageSharp/ColorSpaces/Lms.cs

@ -0,0 +1,180 @@
// <copyright file="Lms.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// LMS is a color space represented by the response of the three types of cones of the human eye,
/// named after their responsivity (sensitivity) at long, medium and short wavelengths.
/// <see href="https://en.wikipedia.org/wiki/LMS_color_space"/>
/// </summary>
internal struct Lms : IColorVector, IEquatable<Lms>, IAlmostEquatable<Lms, float>
{
/// <summary>
/// Represents a <see cref="Lms"/> that has L, M, and S values set to zero.
/// </summary>
public static readonly Lms Empty = default(Lms);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Lms"/> struct.
/// </summary>
/// <param name="l">L represents the responsivity at long wavelengths.</param>
/// <param name="m">M represents the responsivity at medium wavelengths.</param>
/// <param name="s">S represents the responsivity at short wavelengths.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Lms(float l, float m, float s)
: this(new Vector3(l, m, s))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Lms"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, m, s components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Lms(Vector3 vector)
: this()
{
// Not clamping as documentation about this space seems to indicate "usual" ranges
this.backingVector = vector;
}
/// <summary>
/// Gets the L long component.
/// <remarks>A value usually ranging between -1 and 1.</remarks>
/// </summary>
public float L
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the M medium component.
/// <remarks>A value usually ranging between -1 and 1.</remarks>
/// </summary>
public float M
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the S short component.
/// <remarks>A value usually ranging between -1 and 1.</remarks>
/// </summary>
public float S
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="Lms"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Compares two <see cref="Lms"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Lms"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Lms"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Lms left, Lms right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Lms"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Lms"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Lms"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Lms left, Lms right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Lms [ Empty ]";
}
return $"Lms [ L={this.L:#0.##}, M={this.M:#0.##}, S={this.S:#0.##} ]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Lms)
{
return this.Equals((Lms)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Lms other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Lms other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

233
src/ImageSharp/ColorSpaces/Rgb.cs

@ -0,0 +1,233 @@
// <copyright file="Rgb.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an RGB color with specified <see cref="IRgbWorkingSpace"/> working space
/// </summary>
internal struct Rgb : IColorVector, IEquatable<Rgb>, IAlmostEquatable<Rgb, float>
{
/// <summary>
/// Represents a <see cref="Rgb"/> that has R, G, and B values set to zero.
/// </summary>
public static readonly Rgb Empty = default(Rgb);
/// <summary>
/// The default rgb working space
/// </summary>
public static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb"/> struct.
/// </summary>
/// <param name="r">The red component ranging between 0 and 1.</param>
/// <param name="g">The green component ranging between 0 and 1.</param>
/// <param name="b">The blue component ranging between 0 and 1.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb(float r, float g, float b)
: this(new Vector3(r, g, b))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgb"/> struct.
/// </summary>
/// <param name="r">The red component ranging between 0 and 1.</param>
/// <param name="g">The green component ranging between 0 and 1.</param>
/// <param name="b">The blue component ranging between 0 and 1.</param>
/// <param name="workingSpace">The rgb working space.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb(float r, float g, float b, IRgbWorkingSpace workingSpace)
: this(new Vector3(r, g, b), workingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgb"/> struct.
/// </summary>
/// <param name="vector">The vector representing the r, g, b components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb(Vector3 vector)
: this(vector, DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgb"/> struct.
/// </summary>
/// <param name="vector">The vector representing the r, g, b components.</param>
/// <param name="workingSpace">The rgb working space.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb(Vector3 vector, IRgbWorkingSpace workingSpace)
: this()
{
// Clamp to 0-1 range.
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One);
this.WorkingSpace = workingSpace;
}
/// <summary>
/// Gets the red component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float R
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the green component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float G
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the blue component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float B
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets the Rgb color space <seealso cref="RgbWorkingSpaces"/>
/// </summary>
public IRgbWorkingSpace WorkingSpace
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Gets a value indicating whether this <see cref="Rgb"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="Rgb"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Rgba32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Rgb"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Rgb(Rgba32 color)
{
return new Rgb(color.R / 255F, color.G / 255F, color.B / 255F);
}
/// <summary>
/// Compares two <see cref="Rgb"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Rgb"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Rgb"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rgb left, Rgb right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Rgb"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Rgb"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Rgb"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rgb left, Rgb right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Rgb [ Empty ]";
}
return $"Rgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]";
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Rgb)
{
return this.Equals((Rgb)obj);
}
return false;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rgb other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Rgb other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

117
src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs

@ -0,0 +1,117 @@
// <copyright file="RgbWorkingSpaces.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp.ColorSpaces
{
using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
/// <summary>
/// Chromaticity coordinates taken from:
/// <see href="http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html"/>
/// </summary>
internal static class RgbWorkingSpaces
{
/// <summary>
/// sRgb working space.
/// </summary>
/// <remarks>
/// Uses proper companding function, according to:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_Rgb_to_XYZ.html"/>
/// </remarks>
public static readonly IRgbWorkingSpace SRgb = new RgbWorkingSpace(Illuminants.D65, new SRgbCompanding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// Simplified sRgb working space (uses <see cref="GammaCompanding">gamma companding</see> instead of <see cref="SRgbCompanding"/>).
/// See also <see cref="SRgb"/>.
/// </summary>
public static readonly IRgbWorkingSpace SRgbSimplified = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// Rec. 709 (ITU-R Recommendation BT.709) working space
/// </summary>
public static readonly IRgbWorkingSpace Rec709 = new RgbWorkingSpace(Illuminants.D65, new Rec709Companding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F)));
/// <summary>
/// Rec. 2020 (ITU-R Recommendation BT.2020F) working space
/// </summary>
public static readonly IRgbWorkingSpace Rec2020 = new RgbWorkingSpace(Illuminants.D65, new Rec2020Companding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F)));
/// <summary>
/// ECI Rgb v2 working space
/// </summary>
public static readonly IRgbWorkingSpace ECIRgbv2 = new RgbWorkingSpace(Illuminants.D50, new LCompanding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F)));
/// <summary>
/// Adobe Rgb (1998) working space
/// </summary>
public static readonly IRgbWorkingSpace AdobeRgb1998 = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// Apple sRgb working space
/// </summary>
public static readonly IRgbWorkingSpace ApplesRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F)));
/// <summary>
/// Best Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace BestRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F)));
/// <summary>
/// Beta Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace BetaRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F)));
/// <summary>
/// Bruce Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace BruceRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// CIE Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace CIERgb = new RgbWorkingSpace(Illuminants.E, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F)));
/// <summary>
/// ColorMatch Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace ColorMatchRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F)));
/// <summary>
/// Don Rgb 4 working space
/// </summary>
public static readonly IRgbWorkingSpace DonRgb4 = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F)));
/// <summary>
/// Ekta Space PS5 working space
/// </summary>
public static readonly IRgbWorkingSpace EktaSpacePS5 = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F)));
/// <summary>
/// NTSC Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace NTSCRgb = new RgbWorkingSpace(Illuminants.C, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F)));
/// <summary>
/// PAL/SECAM Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace PALSECAMRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// ProPhoto Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace ProPhotoRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F)));
/// <summary>
/// SMPTE-C Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace SMPTECRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F)));
/// <summary>
/// Wide Gamut Rgb working space
/// </summary>
public static readonly IRgbWorkingSpace WideGamutRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F)));
}
}

89
src/ImageSharp/Colors/Spaces/YCbCr.cs → src/ImageSharp/ColorSpaces/YCbCr.cs

@ -3,33 +3,29 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using ImageSharp.PixelFormats;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an YCbCr (luminance, blue chroma, red chroma) color conforming to the full range standard used in digital imaging systems.
/// Represents an YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification for the JFIF use with Jpeg.
/// <see href="http://en.wikipedia.org/wiki/YCbCr"/>
/// <see href="http://www.ijg.org/files/T-REC-T.871-201105-I!!PDF-E.pdf"/>
/// </summary>
public struct YCbCr : IEquatable<YCbCr>
internal struct YCbCr : IColorVector, IEquatable<YCbCr>, IAlmostEquatable<YCbCr, float>
{
/// <summary>
/// Represents a <see cref="YCbCr"/> that has Y, Cb, and Cr values set to zero.
/// </summary>
public static readonly YCbCr Empty = default(YCbCr);
/// <summary>
/// Min range used for clamping
/// </summary>
private static readonly Vector3 VectorMin = Vector3.Zero;
/// <summary>
/// Vector which is used in clamping to the max value
/// </summary>
private static readonly Vector3 VectorMax = new Vector3(255);
private static readonly Vector3 VectorMax = new Vector3(255F);
/// <summary>
/// The backing vector for SIMD support.
@ -42,29 +38,51 @@ namespace ImageSharp.Colors.Spaces
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
public YCbCr(byte y, byte cb, byte cr)
: this()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public YCbCr(float y, float cb, float cr)
: this(new Vector3(y, cb, cr))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
/// </summary>
/// <param name="vector">The vector representing the y, cb, cr components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public YCbCr(Vector3 vector)
{
this.backingVector = Vector3.Clamp(new Vector3(y, cb, cr), VectorMin, VectorMax);
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax);
}
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public byte Y => (byte)this.backingVector.X;
public float Y
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.X;
}
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public byte Cb => (byte)this.backingVector.Y;
public float Cb
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Y;
}
/// <summary>
/// Gets the Cr chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public byte Cr => (byte)this.backingVector.Z;
public float Cr
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector.Z;
}
/// <summary>
/// Gets a value indicating whether this <see cref="YCbCr"/> is empty.
@ -72,27 +90,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="YCbCr"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Rgba32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="YCbCr"/>.
/// </returns>
public static implicit operator YCbCr(Rgba32 color)
/// <inheritdoc/>
public Vector3 Vector
{
byte r = color.R;
byte g = color.G;
byte b = color.B;
byte y = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b));
byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)));
byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)));
return new YCbCr(y, cb, cr);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.backingVector;
}
/// <summary>
@ -107,6 +109,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(YCbCr left, YCbCr right)
{
return left.Equals(right);
@ -124,6 +127,7 @@ namespace ImageSharp.Colors.Spaces
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(YCbCr left, YCbCr right)
{
return !left.Equals(right);
@ -147,6 +151,7 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is YCbCr)
@ -158,9 +163,21 @@ namespace ImageSharp.Colors.Spaces
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(YCbCr other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(YCbCr other, float precision)
{
var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

167
src/ImageSharp/Colors/Spaces/Bgra32.cs

@ -1,167 +0,0 @@
// <copyright file="Bgra32.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
{
using System;
using System.ComponentModel;
using System.Numerics;
using ImageSharp.PixelFormats;
/// <summary>
/// Represents an BGRA (blue, green, red, alpha) color.
/// </summary>
public struct Bgra32 : IEquatable<Bgra32>
{
/// <summary>
/// Represents a 32 bit <see cref="Bgra32"/> that has B, G, R, and A values set to zero.
/// </summary>
public static readonly Bgra32 Empty = default(Bgra32);
/// <summary>
/// Min range used for clamping
/// </summary>
private static readonly Vector4 VectorMin = Vector4.Zero;
/// <summary>
/// Max range used for clamping
/// </summary>
private static readonly Vector4 VectorMax = new Vector4(255);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32"/> struct.
/// </summary>
/// <param name="b">The blue component of this <see cref="Bgra32"/>.</param>
/// <param name="g">The green component of this <see cref="Bgra32"/>.</param>
/// <param name="r">The red component of this <see cref="Bgra32"/>.</param>
/// <param name="a">The alpha component of this <see cref="Bgra32"/>.</param>
public Bgra32(byte b, byte g, byte r, byte a = 255)
: this()
{
this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), VectorMin, VectorMax);
}
/// <summary>
/// Gets the blue component of the color
/// </summary>
public byte B => (byte)this.backingVector.X;
/// <summary>
/// Gets the green component of the color
/// </summary>
public byte G => (byte)this.backingVector.Y;
/// <summary>
/// Gets the red component of the color
/// </summary>
public byte R => (byte)this.backingVector.Z;
/// <summary>
/// Gets the alpha component of the color
/// </summary>
public byte A => (byte)this.backingVector.W;
/// <summary>
/// Gets the <see cref="Bgra32"/> integer representation of the color.
/// </summary>
public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24);
/// <summary>
/// Gets a value indicating whether this <see cref="Bgra32"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Rgba32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Bgra32(Rgba32 color)
{
return new Bgra32(color.B, color.G, color.R, color.A);
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra32"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra32"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Bgra32 left, Bgra32 right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra32"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra32"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Bgra32 left, Bgra32 right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Bgra32)
{
Bgra32 color = (Bgra32)obj;
return this.backingVector == color.backingVector;
}
return false;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Bgra32 [ Empty ]";
}
return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
}
/// <inheritdoc/>
public bool Equals(Bgra32 other)
{
return this.backingVector.Equals(other.backingVector);
}
}
}

13
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -102,19 +102,6 @@ namespace ImageSharp
return 0F;
}
/// <summary>
/// Returns the given degrees converted to radians.
/// </summary>
/// <param name="degrees">The angle in degrees.</param>
/// <returns>
/// The <see cref="float"/> representing the degree as radians.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{
return degrees * (MathF.PI / 180);
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary>

155
src/ImageSharp/Common/Helpers/MathF.cs

@ -19,28 +19,89 @@ namespace ImageSharp
/// </summary>
public const float PI = (float)Math.PI;
/// <summary>Returns the absolute value of a single-precision floating-point number.</summary>
/// <param name="f">A number that is greater than or equal to <see cref="F:System.Single.MinValue" />, but less than or equal to <see cref="F:System.Single.MaxValue" />.</param>
/// <returns>A single-precision floating-point number, x, such that 0 ≤ x ≤<see cref="F:System.Single.MaxValue" />.</returns>
/// <summary>
/// Returns the absolute value of a single-precision floating-point number.
/// </summary>
/// <param name="f">
/// A number that is greater than or equal to <see cref="F:System.Single.MinValue" />, but less than or equal to <see cref="F:System.Single.MaxValue" />.
/// </param>
/// <returns>
/// A single-precision floating-point number, x, such that 0 ≤ x ≤<see cref="F:System.Single.MaxValue" />.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Abs(float f)
{
return Math.Abs(f);
}
/// <summary>Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number.</summary>
/// <param name="f">A single-precision floating-point number. </param>
/// <returns>The smallest integral value that is greater than or equal to <paramref name="f" />.
/// <summary>
/// Returns the angle whose tangent is the quotient of two specified numbers.
/// </summary>
/// <param name="y">The y coordinate of a point.</param>
/// <param name="x">The x coordinate of a point.</param>
/// <returns>
/// An angle, θ, measured in radians, such that -π≤θ≤π, and tan(θ) = y / x, where
/// (x, y) is a point in the Cartesian plane. Observe the following: For (x, y) in
/// quadrant 1, 0 &lt; θ &lt; π/2.For (x, y) in quadrant 2, π/2 &lt; θ≤π.For (x, y) in quadrant
/// 3, -π &lt; θ &lt; -π/2.For (x, y) in quadrant 4, -π/2 &lt; θ &lt; 0.For points on the boundaries
/// of the quadrants, the return value is the following:If y is 0 and x is not negative,
/// θ = 0.If y is 0 and x is negative, θ = π.If y is positive and x is 0, θ = π/2.If
/// y is negative and x is 0, θ = -π/2.If y is 0 and x is 0, θ = 0. If x or y is
/// <see cref="F:System.Single.NaN"/>, or if x and y are either <see cref="F:System.Single.PositiveInfinity"/> or
/// <see cref="F:System.Single.NegativeInfinity"/>, the method returns <see cref="F:System.Single.NaN"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Atan2(float y, float x)
{
return (float)Math.Atan2(y, x);
}
/// <summary>
/// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number.
/// </summary>
/// <param name="f">A single-precision floating-point number.</param>
/// <returns>
/// The smallest integral value that is greater than or equal to <paramref name="f" />.
/// If <paramref name="f" /> is equal to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NegativeInfinity" />,
/// or <see cref="F:System.Single.PositiveInfinity" />, that value is returned.
/// Note that this method returns a <see cref="T:System.Single" /> instead of an integral type.</returns>
/// Note that this method returns a <see cref="T:System.Single" /> instead of an integral type.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Ceiling(float f)
{
return (float)Math.Ceiling(f);
}
/// <summary>Returns e raised to the specified power.</summary>
/// <summary>
/// Returns the cosine of the specified angle.
/// </summary>
/// <param name="f">An angle, measured in radians.</param>
/// <returns>
/// The cosine of <paramref name="f"/>. If <paramref name="f"/> is equal to <see cref="F:System.Float.NaN"/>, <see cref="F:System.Float.NegativeInfinity"/>,
/// or <see cref="F:System.Float.PositiveInfinity"/>, this method returns <see cref="F:System.Float.NaN"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Cos(float f)
{
return (float)Math.Cos(f);
}
/// <summary>
/// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle.
/// </summary>
/// <param name="degree">The angle in degrees.</param>
/// <returns>
/// The <see cref="float"/> representing the degree as radians.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreeToRadian(float degree)
{
return degree * (PI / 180F);
}
/// <summary>
/// Returns e raised to the specified power.
/// </summary>
/// <param name="f">A number specifying a power.</param>
/// <returns>
/// The number e raised to the power <paramref name="f" />.
@ -53,9 +114,12 @@ namespace ImageSharp
return (float)Math.Exp(f);
}
/// <summary>Returns the largest integer less than or equal to the specified single-precision floating-point number.</summary>
/// <summary>
/// Returns the largest integer less than or equal to the specified single-precision floating-point number.
/// </summary>
/// <param name="f">A single-precision floating-point number. </param>
/// <returns>The largest integer less than or equal to <paramref name="f" />.
/// <returns>
/// The largest integer less than or equal to <paramref name="f" />.
/// If <paramref name="f" /> is equal to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NegativeInfinity" />,
/// or <see cref="F:System.Single.PositiveInfinity" />, that value is returned.
/// </returns>
@ -65,10 +129,13 @@ namespace ImageSharp
return (float)Math.Floor(f);
}
/// <summary>Returns the larger of two single-precision floating-point numbers.</summary>
/// <summary>
/// Returns the larger of two single-precision floating-point numbers.
/// </summary>
/// <param name="val1">The first of two single-precision floating-point numbers to compare. </param>
/// <param name="val2">The second of two single-precision floating-point numbers to compare. </param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is larger.
/// <returns>
/// Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is larger.
/// If <paramref name="val1" />, or <paramref name="val2" />, or both <paramref name="val1" /> and <paramref name="val2" /> are
/// equal to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NaN" /> is returned.
/// </returns>
@ -78,19 +145,25 @@ namespace ImageSharp
return Math.Max(val1, val2);
}
/// <summary>Returns the smaller of two single-precision floating-point numbers.</summary>
/// <summary>
/// Returns the smaller of two single-precision floating-point numbers.
/// </summary>
/// <param name="val1">The first of two single-precision floating-point numbers to compare. </param>
/// <param name="val2">The second of two single-precision floating-point numbers to compare. </param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is smaller.
/// <returns>
/// Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is smaller.
/// If <paramref name="val1" />, <paramref name="val2" />, or both <paramref name="val1" /> and <paramref name="val2" /> are equal
/// to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NaN" /> is returned.</returns>
/// to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NaN" /> is returned.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Min(float val1, float val2)
{
return Math.Min(val1, val2);
}
/// <summary>Returns a specified number raised to the specified power.</summary>
/// <summary>
/// Returns a specified number raised to the specified power.
/// </summary>
/// <param name="x">A single-precision floating-point number to be raised to a power. </param>
/// <param name="y">A single-precision floating-point number that specifies a power. </param>
/// <returns>The number <paramref name="x" /> raised to the power <paramref name="y" />.</returns>
@ -100,8 +173,23 @@ namespace ImageSharp
return (float)Math.Pow(x, y);
}
/// <summary>Rounds a single-precision floating-point value to the nearest integral value.</summary>
/// <param name="f">A single-precision floating-point number to be rounded. </param>
/// <summary>
/// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle.
/// </summary>
/// <param name="radian">The angle in radians.</param>
/// <returns>
/// The <see cref="float"/> representing the degree as radians.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadianToDegree(float radian)
{
return radian / (PI / 180F);
}
/// <summary>
/// Rounds a single-precision floating-point value to the nearest integral value.
/// </summary>
/// <param name="f">A single-precision floating-point number to be rounded.</param>
/// <returns>
/// The integer nearest <paramref name="f" />.
/// If the fractional component of <paramref name="f" /> is halfway between two integers, one of which is even and the other odd, then the even number is returned.
@ -113,8 +201,29 @@ namespace ImageSharp
return (float)Math.Round(f);
}
/// <summary>Returns the sine of the specified angle.</summary>
/// <param name="f">An angle, measured in radians. </param>
/// <summary>
/// Rounds a single-precision floating-point value to the nearest integer.
/// A parameter specifies how to round the value if it is midway between two numbers.
/// </summary>
/// <param name="f">A single-precision floating-point number to be rounded. </param>
/// <param name="mode">Specification for how to round <paramref name="f" /> if it is midway between two other numbers.</param>
/// <returns>
/// The integer nearest <paramref name="f" />. If <paramref name="f" /> is halfway between two integers, one of which is even
/// and the other odd, then <paramref name="mode" /> determines which of the two is returned.
/// Note that this method returns a <see cref="T:System.Single" /> instead of an integral type.
/// </returns>
/// <exception cref="T:System.ArgumentException">
/// <paramref name="mode" /> is not a valid value of <see cref="T:System.MidpointRounding" />.</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Round(float f, MidpointRounding mode)
{
return (float)Math.Round(f, mode);
}
/// <summary>
/// Returns the sine of the specified angle.
/// </summary>
/// <param name="f">An angle, measured in radians.</param>
/// <returns>
/// The sine of <paramref name="f" />.
/// If <paramref name="f" /> is equal to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NegativeInfinity" />,
@ -146,8 +255,10 @@ namespace ImageSharp
return 1F;
}
/// <summary>Returns the square root of a specified number.</summary>
/// <param name="f">The number whose square root is to be found. </param>
/// <summary>
/// Returns the square root of a specified number.
/// </summary>
/// <param name="f">The number whose square root is to be found.</param>
/// <returns>
/// One of the values in the following table.
/// <paramref name="f" /> parameter Return value Zero or positive The positive square root of <paramref name="f" />.

5
src/ImageSharp/Formats/Jpeg/JpegConstants.cs

@ -200,6 +200,11 @@ namespace ImageSharp.Formats
/// </summary>
public const byte APP1 = 0xe1;
/// <summary>
/// Application specific marker for marking where to store ICC profile information.
/// </summary>
public const byte APP2 = 0xe2;
/// <summary>
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
/// </summary>

65
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
@ -413,6 +414,9 @@ namespace ImageSharp.Formats
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining, metadata);
break;
case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(remaining, metadata);
break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
@ -949,14 +953,64 @@ namespace ImageSharp.Formats
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
&& profile[5] == '\0')
if (profile[0] == 'E' &&
profile[1] == 'x' &&
profile[2] == 'i' &&
profile[3] == 'f' &&
profile[4] == '\0' &&
profile[5] == '\0')
{
this.isExif = true;
metadata.ExifProfile = new ExifProfile(profile);
}
}
/// <summary>
/// Processes the App2 marker retrieving any stored ICC profile information
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="metadata">The image.</param>
private void ProcessApp2Marker(int remaining, ImageMetaData metadata)
{
// Length is 14 though we only need to check 12.
const int Icclength = 14;
if (remaining < Icclength || this.options.IgnoreMetadata)
{
this.InputProcessor.Skip(remaining);
return;
}
byte[] identifier = new byte[Icclength];
this.InputProcessor.ReadFull(identifier, 0, Icclength);
if (identifier[0] == 'I' &&
identifier[1] == 'C' &&
identifier[2] == 'C' &&
identifier[3] == '_' &&
identifier[4] == 'P' &&
identifier[5] == 'R' &&
identifier[6] == 'O' &&
identifier[7] == 'F' &&
identifier[8] == 'I' &&
identifier[9] == 'L' &&
identifier[10] == 'E' &&
identifier[11] == '\0')
{
remaining -= Icclength;
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
if (metadata.IccProfile == null)
{
metadata.IccProfile = new IccProfile(profile);
}
else
{
metadata.IccProfile.Extend(profile);
}
}
}
/// <summary>
/// Processes the application header containing the JFIF identifier plus extra data.
/// </summary>
@ -973,8 +1027,11 @@ namespace ImageSharp.Formats
remaining -= 13;
// TODO: We should be using constants for this.
this.isJfif = this.Temp[0] == 'J' && this.Temp[1] == 'F' && this.Temp[2] == 'I' && this.Temp[3] == 'F'
&& this.Temp[4] == '\x00';
this.isJfif = this.Temp[0] == 'J' &&
this.Temp[1] == 'F' &&
this.Temp[2] == 'I' &&
this.Temp[3] == 'F' &&
this.Temp[4] == '\x00';
if (this.isJfif)
{

88
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -6,7 +6,9 @@
namespace ImageSharp.Formats
{
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using ImageSharp.Formats.Jpg;
using ImageSharp.Formats.Jpg.Components;
@ -672,7 +674,7 @@ namespace ImageSharp.Formats
/// <exception cref="ImageFormatException">
/// Thrown if the EXIF profile size exceeds the limit
/// </exception>
private void WriteProfile(ExifProfile exifProfile)
private void WriteExifProfile(ExifProfile exifProfile)
{
const int Max = 65533;
byte[] data = exifProfile?.ToByteArray();
@ -697,6 +699,87 @@ namespace ImageSharp.Formats
this.outputStream.Write(data, 0, data.Length);
}
/// <summary>
/// Writes the ICC profile.
/// </summary>
/// <param name="iccProfile">The ICC profile to write.</param>
/// <exception cref="ImageFormatException">
/// Thrown if any of the ICC profiles size exceeds the limit
/// </exception>
private void WriteIccProfile(IccProfile iccProfile)
{
// Just incase someone set the value to null by accident.
if (iccProfile == null)
{
return;
}
const int IccOverheadLength = 14;
const int Max = 65533;
const int MaxData = Max - IccOverheadLength;
byte[] data = iccProfile.ToByteArray();
if (data == null || data.Length == 0)
{
return;
}
// Calculate the number of markers we'll need, rounding up of course
int dataLength = data.Length;
int count = dataLength / MaxData;
if (count * MaxData != dataLength)
{
count++;
}
// Per spec, counting starts at 1.
int current = 1;
int offset = 0;
while (dataLength > 0)
{
int length = dataLength; // Number of bytes to write.
if (length > MaxData)
{
length = MaxData;
}
dataLength -= length;
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker
int markerLength = length + 16;
this.buffer[2] = (byte)((markerLength >> 8) & 0xFF);
this.buffer[3] = (byte)(markerLength & 0xFF);
this.outputStream.Write(this.buffer, 0, 4);
this.buffer[0] = (byte)'I';
this.buffer[1] = (byte)'C';
this.buffer[2] = (byte)'C';
this.buffer[3] = (byte)'_';
this.buffer[4] = (byte)'P';
this.buffer[5] = (byte)'R';
this.buffer[6] = (byte)'O';
this.buffer[7] = (byte)'F';
this.buffer[8] = (byte)'I';
this.buffer[9] = (byte)'L';
this.buffer[10] = (byte)'E';
this.buffer[11] = 0x00;
this.buffer[12] = (byte)current; // The position within the collection.
this.buffer[13] = (byte)count; // The total number of profiles.
this.outputStream.Write(this.buffer, 0, IccOverheadLength);
this.outputStream.Write(data, offset, length);
current++;
offset += length;
}
}
/// <summary>
/// Writes the metadata profiles to the image.
/// </summary>
@ -711,7 +794,8 @@ namespace ImageSharp.Formats
}
image.MetaData.SyncProfiles();
this.WriteProfile(image.MetaData.ExifProfile);
this.WriteExifProfile(image.MetaData.ExifProfile);
this.WriteIccProfile(image.MetaData.IccProfile);
}
/// <summary>

39
src/ImageSharp/MetaData/ImageMetaData.cs

@ -5,7 +5,6 @@
namespace ImageSharp
{
using System;
using System.Collections.Generic;
using ImageSharp.Formats;
@ -61,27 +60,23 @@ namespace ImageSharp
this.Properties.Add(new ImageProperty(property));
}
if (other.ExifProfile != null)
{
this.ExifProfile = new ExifProfile(other.ExifProfile);
}
else
{
this.ExifProfile = null;
}
this.ExifProfile = other.ExifProfile != null
? new ExifProfile(other.ExifProfile)
: null;
this.IccProfile = other.IccProfile != null
? new IccProfile(other.IccProfile)
: null;
}
/// <summary>
/// Gets or sets the resolution of the image in x- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// Gets or sets the resolution of the image in x- direction.
/// It is defined as the number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in x- direction.</value>
public double HorizontalResolution
{
get
{
return this.horizontalResolution;
}
get => this.horizontalResolution;
set
{
@ -93,16 +88,13 @@ namespace ImageSharp
}
/// <summary>
/// Gets or sets the resolution of the image in y- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// Gets or sets the resolution of the image in y- direction.
/// It is defined as the number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in y- direction.</value>
public double VerticalResolution
{
get
{
return this.verticalResolution;
}
get => this.verticalResolution;
set
{
@ -118,6 +110,11 @@ namespace ImageSharp
/// </summary>
public ExifProfile ExifProfile { get; set; }
/// <summary>
/// Gets or sets the list of ICC profiles.
/// </summary>
public IccProfile IccProfile { get; set; }
/// <inheritdoc/>
public int FrameDelay { get; set; }

45
src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs

@ -0,0 +1,45 @@
// <copyright file="IccDataWriter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
/// <summary>
/// A segment of a curve
/// </summary>
internal abstract class IccCurveSegment : IEquatable<IccCurveSegment>
{
/// <summary>
/// Initializes a new instance of the <see cref="IccCurveSegment"/> class.
/// </summary>
/// <param name="signature">The signature of this segment</param>
protected IccCurveSegment(IccCurveSegmentSignature signature)
{
this.Signature = signature;
}
/// <summary>
/// Gets the signature of this segment
/// </summary>
public IccCurveSegmentSignature Signature { get; }
/// <inheritdoc/>
public virtual bool Equals(IccCurveSegment other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return this.Signature == other.Signature;
}
}
}

95
src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs

@ -0,0 +1,95 @@
// <copyright file="IccDataWriter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
/// <summary>
/// A formula based curve segment
/// </summary>
internal sealed class IccFormulaCurveElement : IccCurveSegment, IEquatable<IccFormulaCurveElement>
{
/// <summary>
/// Initializes a new instance of the <see cref="IccFormulaCurveElement"/> class.
/// </summary>
/// <param name="type">The type of this segment</param>
/// <param name="gamma">Gamma segment parameter</param>
/// <param name="a">A segment parameter</param>
/// <param name="b">B segment parameter</param>
/// <param name="c">C segment parameter</param>
/// <param name="d">D segment parameter</param>
/// <param name="e">E segment parameter</param>
public IccFormulaCurveElement(IccFormulaCurveType type, float gamma, float a, float b, float c, float d, float e)
: base(IccCurveSegmentSignature.FormulaCurve)
{
this.Type = type;
this.Gamma = gamma;
this.A = a;
this.B = b;
this.C = c;
this.D = d;
this.E = e;
}
/// <summary>
/// Gets the type of this curve
/// </summary>
public IccFormulaCurveType Type { get; }
/// <summary>
/// Gets the gamma curve parameter
/// </summary>
public float Gamma { get; }
/// <summary>
/// Gets the A curve parameter
/// </summary>
public float A { get; }
/// <summary>
/// Gets the B curve parameter
/// </summary>
public float B { get; }
/// <summary>
/// Gets the C curve parameter
/// </summary>
public float C { get; }
/// <summary>
/// Gets the D curve parameter
/// </summary>
public float D { get; }
/// <summary>
/// Gets the E curve parameter
/// </summary>
public float E { get; }
/// <inheritdoc />
public override bool Equals(IccCurveSegment other)
{
if (base.Equals(other) && other is IccFormulaCurveElement segment)
{
return this.Type == segment.Type
&& this.Gamma == segment.Gamma
&& this.A == segment.A
&& this.B == segment.B
&& this.C == segment.C
&& this.D == segment.D
&& this.E == segment.E;
}
return false;
}
/// <inheritdoc />
public bool Equals(IccFormulaCurveElement other)
{
return this.Equals((IccCurveSegment)other);
}
}
}

60
src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs

@ -0,0 +1,60 @@
// <copyright file="IccDataWriter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Linq;
/// <summary>
/// A one dimensional curve
/// </summary>
internal sealed class IccOneDimensionalCurve : IEquatable<IccOneDimensionalCurve>
{
/// <summary>
/// Initializes a new instance of the <see cref="IccOneDimensionalCurve"/> class.
/// </summary>
/// <param name="breakPoints">The break points of this curve</param>
/// <param name="segments">The segments of this curve</param>
public IccOneDimensionalCurve(float[] breakPoints, IccCurveSegment[] segments)
{
Guard.NotNull(breakPoints, nameof(breakPoints));
Guard.NotNull(segments, nameof(segments));
bool isSizeCorrect = breakPoints.Length == segments.Length - 1;
Guard.IsTrue(isSizeCorrect, $"{nameof(breakPoints)},{nameof(segments)}", "Number of BreakPoints must be one less than number of Segments");
this.BreakPoints = breakPoints;
this.Segments = segments;
}
/// <summary>
/// Gets the breakpoints that separate two curve segments
/// </summary>
public float[] BreakPoints { get; }
/// <summary>
/// Gets an array of curve segments
/// </summary>
public IccCurveSegment[] Segments { get; }
/// <inheritdoc />
public bool Equals(IccOneDimensionalCurve other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return this.BreakPoints.SequenceEqual(other.BreakPoints)
&& this.Segments.SequenceEqual(other.Segments);
}
}
}

150
src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs

@ -0,0 +1,150 @@
// <copyright file="IccDataWriter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
/// <summary>
/// A parametric curve
/// </summary>
internal sealed class IccParametricCurve : IEquatable<IccParametricCurve>
{
/// <summary>
/// Initializes a new instance of the <see cref="IccParametricCurve"/> class.
/// </summary>
/// <param name="g">G curve parameter</param>
public IccParametricCurve(float g)
: this(IccParametricCurveType.Type1, g, 0, 0, 0, 0, 0, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IccParametricCurve"/> class.
/// </summary>
/// <param name="g">G curve parameter</param>
/// <param name="a">A curve parameter</param>
/// <param name="b">B curve parameter</param>
public IccParametricCurve(float g, float a, float b)
: this(IccParametricCurveType.Cie122_1996, g, a, b, 0, 0, 0, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IccParametricCurve"/> class.
/// </summary>
/// <param name="g">G curve parameter</param>
/// <param name="a">A curve parameter</param>
/// <param name="b">B curve parameter</param>
/// <param name="c">C curve parameter</param>
public IccParametricCurve(float g, float a, float b, float c)
: this(IccParametricCurveType.Iec61966_3, g, a, b, c, 0, 0, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IccParametricCurve"/> class.
/// </summary>
/// <param name="g">G curve parameter</param>
/// <param name="a">A curve parameter</param>
/// <param name="b">B curve parameter</param>
/// <param name="c">C curve parameter</param>
/// <param name="d">D curve parameter</param>
public IccParametricCurve(float g, float a, float b, float c, float d)
: this(IccParametricCurveType.SRgb, g, a, b, c, d, 0, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IccParametricCurve"/> class.
/// </summary>
/// <param name="g">G curve parameter</param>
/// <param name="a">A curve parameter</param>
/// <param name="b">B curve parameter</param>
/// <param name="c">C curve parameter</param>
/// <param name="d">D curve parameter</param>
/// <param name="e">E curve parameter</param>
/// <param name="f">F curve parameter</param>
public IccParametricCurve(float g, float a, float b, float c, float d, float e, float f)
: this(IccParametricCurveType.Type5, g, a, b, c, d, e, f)
{
}
private IccParametricCurve(IccParametricCurveType type, float g, float a, float b, float c, float d, float e, float f)
{
this.Type = type;
this.G = g;
this.A = a;
this.B = b;
this.C = c;
this.D = d;
this.E = e;
this.F = f;
}
/// <summary>
/// Gets the type of this curve
/// </summary>
public IccParametricCurveType Type { get; }
/// <summary>
/// Gets the G curve parameter
/// </summary>
public float G { get; }
/// <summary>
/// Gets the A curve parameter
/// </summary>
public float A { get; }
/// <summary>
/// Gets the B curve parameter
/// </summary>
public float B { get; }
/// <summary>
/// Gets the C curve parameter
/// </summary>
public float C { get; }
/// <summary>
/// Gets the D curve parameter
/// </summary>
public float D { get; }
/// <summary>
/// Gets the E curve parameter
/// </summary>
public float E { get; }
/// <summary>
/// Gets the F curve parameter
/// </summary>
public float F { get; }
/// <inheritdoc/>
public bool Equals(IccParametricCurve other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return this.Type == other.Type
&& this.G == other.G
&& this.A == other.A
&& this.B == other.B
&& this.C == other.C
&& this.D == other.D
&& this.E == other.E
&& this.F == other.F;
}
}
}

87
src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs

@ -0,0 +1,87 @@
// <copyright file="IccDataWriter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Linq;
using System.Numerics;
/// <summary>
/// A response curve
/// </summary>
internal sealed class IccResponseCurve : IEquatable<IccResponseCurve>
{
/// <summary>
/// Initializes a new instance of the <see cref="IccResponseCurve"/> class.
/// </summary>
/// <param name="curveType">The type of this curve</param>
/// <param name="xyzValues">The XYZ values</param>
/// <param name="responseArrays">The response arrays</param>
public IccResponseCurve(IccCurveMeasurementEncodings curveType, Vector3[] xyzValues, IccResponseNumber[][] responseArrays)
{
Guard.NotNull(xyzValues, nameof(xyzValues));
Guard.NotNull(responseArrays, nameof(responseArrays));
Guard.IsTrue(xyzValues.Length == responseArrays.Length, $"{nameof(xyzValues)},{nameof(responseArrays)}", "Arrays must have same length");
Guard.MustBeBetweenOrEqualTo(xyzValues.Length, 1, 15, nameof(xyzValues));
this.CurveType = curveType;
this.XyzValues = xyzValues;
this.ResponseArrays = responseArrays;
}
/// <summary>
/// Gets the type of this curve
/// </summary>
public IccCurveMeasurementEncodings CurveType { get; }
/// <summary>
/// Gets the XYZ values
/// </summary>
public Vector3[] XyzValues { get; }
/// <summary>
/// Gets the response arrays
/// </summary>
public IccResponseNumber[][] ResponseArrays { get; }
/// <inheritdoc/>
public bool Equals(IccResponseCurve other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return this.CurveType == other.CurveType
&& this.XyzValues.SequenceEqual(other.XyzValues)
&& this.EqualsResponseArray(other);
}
private bool EqualsResponseArray(IccResponseCurve other)
{
if (this.ResponseArrays.Length != other.ResponseArrays.Length)
{
return false;
}
for (int i = 0; i < this.ResponseArrays.Length; i++)
{
if (!this.ResponseArrays[i].SequenceEqual(other.ResponseArrays[i]))
{
return false;
}
}
return true;
}
}
}

51
src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs

@ -0,0 +1,51 @@
// <copyright file="IccDataWriter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Linq;
/// <summary>
/// A sampled curve segment
/// </summary>
internal sealed class IccSampledCurveElement : IccCurveSegment, IEquatable<IccSampledCurveElement>
{
/// <summary>
/// Initializes a new instance of the <see cref="IccSampledCurveElement"/> class.
/// </summary>
/// <param name="curveEntries">The curve values of this segment</param>
public IccSampledCurveElement(float[] curveEntries)
: base(IccCurveSegmentSignature.SampledCurve)
{
Guard.NotNull(curveEntries, nameof(curveEntries));
Guard.IsTrue(curveEntries.Length > 0, nameof(curveEntries), "There must be at least one value");
this.CurveEntries = curveEntries;
}
/// <summary>
/// Gets the curve values of this segment
/// </summary>
public float[] CurveEntries { get; }
/// <inheritdoc />
public override bool Equals(IccCurveSegment other)
{
if (base.Equals(other) && other is IccSampledCurveElement segment)
{
return this.CurveEntries.SequenceEqual(segment.CurveEntries);
}
return false;
}
/// <inheritdoc />
public bool Equals(IccSampledCurveElement other)
{
return this.Equals((IccCurveSegment)other);
}
}
}

221
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs

@ -0,0 +1,221 @@
// <copyright file="IccDataReader.Curves.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a <see cref="IccOneDimensionalCurve"/>
/// </summary>
/// <returns>The read curve</returns>
public IccOneDimensionalCurve ReadOneDimensionalCurve()
{
ushort segmentCount = this.ReadUInt16();
this.AddIndex(2); // 2 bytes reserved
float[] breakPoints = new float[segmentCount - 1];
for (int i = 0; i < breakPoints.Length; i++)
{
breakPoints[i] = this.ReadSingle();
}
IccCurveSegment[] segments = new IccCurveSegment[segmentCount];
for (int i = 0; i < segmentCount; i++)
{
segments[i] = this.ReadCurveSegment();
}
return new IccOneDimensionalCurve(breakPoints, segments);
}
/// <summary>
/// Reads a <see cref="IccResponseCurve"/>
/// </summary>
/// <param name="channelCount">The number of channels</param>
/// <returns>The read curve</returns>
public IccResponseCurve ReadResponseCurve(int channelCount)
{
var type = (IccCurveMeasurementEncodings)this.ReadUInt32();
uint[] measurment = new uint[channelCount];
for (int i = 0; i < channelCount; i++)
{
measurment[i] = this.ReadUInt32();
}
Vector3[] xyzValues = new Vector3[channelCount];
for (int i = 0; i < channelCount; i++)
{
xyzValues[i] = this.ReadXyzNumber();
}
IccResponseNumber[][] response = new IccResponseNumber[channelCount][];
for (int i = 0; i < channelCount; i++)
{
response[i] = new IccResponseNumber[measurment[i]];
for (uint j = 0; j < measurment[i]; j++)
{
response[i][j] = this.ReadResponseNumber();
}
}
return new IccResponseCurve(type, xyzValues, response);
}
/// <summary>
/// Reads a <see cref="IccParametricCurve"/>
/// </summary>
/// <returns>The read curve</returns>
public IccParametricCurve ReadParametricCurve()
{
ushort type = this.ReadUInt16();
this.AddIndex(2); // 2 bytes reserved
float gamma, a, b, c, d, e, f;
gamma = a = b = c = d = e = f = 0;
if (type <= 4)
{
gamma = this.ReadFix16();
}
if (type > 0 && type <= 4)
{
a = this.ReadFix16();
b = this.ReadFix16();
}
if (type > 1 && type <= 4)
{
c = this.ReadFix16();
}
if (type > 2 && type <= 4)
{
d = this.ReadFix16();
}
if (type == 4)
{
e = this.ReadFix16();
f = this.ReadFix16();
}
switch (type)
{
case 0: return new IccParametricCurve(gamma);
case 1: return new IccParametricCurve(gamma, a, b);
case 2: return new IccParametricCurve(gamma, a, b, c);
case 3: return new IccParametricCurve(gamma, a, b, c, d);
case 4: return new IccParametricCurve(gamma, a, b, c, d, e, f);
default: throw new InvalidIccProfileException($"Invalid parametric curve type of {type}");
}
}
/// <summary>
/// Reads a <see cref="IccCurveSegment"/>
/// </summary>
/// <returns>The read segment</returns>
public IccCurveSegment ReadCurveSegment()
{
var signature = (IccCurveSegmentSignature)this.ReadUInt32();
this.AddIndex(4); // 4 bytes reserved
switch (signature)
{
case IccCurveSegmentSignature.FormulaCurve:
return this.ReadFormulaCurveElement();
case IccCurveSegmentSignature.SampledCurve:
return this.ReadSampledCurveElement();
default:
throw new InvalidIccProfileException($"Invalid curve segment type of {signature}");
}
}
/// <summary>
/// Reads a <see cref="IccFormulaCurveElement"/>
/// </summary>
/// <returns>The read segment</returns>
public IccFormulaCurveElement ReadFormulaCurveElement()
{
var type = (IccFormulaCurveType)this.ReadUInt16();
this.AddIndex(2); // 2 bytes reserved
float gamma, a, b, c, d, e;
gamma = d = e = 0;
if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2)
{
gamma = this.ReadSingle();
}
a = this.ReadSingle();
b = this.ReadSingle();
c = this.ReadSingle();
if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3)
{
d = this.ReadSingle();
}
if (type == IccFormulaCurveType.Type3)
{
e = this.ReadSingle();
}
return new IccFormulaCurveElement(type, gamma, a, b, c, d, e);
}
/// <summary>
/// Reads a <see cref="IccSampledCurveElement"/>
/// </summary>
/// <returns>The read segment</returns>
public IccSampledCurveElement ReadSampledCurveElement()
{
uint count = this.ReadUInt32();
float[] entries = new float[count];
for (int i = 0; i < count; i++)
{
entries[i] = this.ReadSingle();
}
return new IccSampledCurveElement(entries);
}
/// <summary>
/// Reads curve data
/// </summary>
/// <param name="count">Number of input channels</param>
/// <returns>The curve data</returns>
private IccTagDataEntry[] ReadCurves(int count)
{
IccTagDataEntry[] tdata = new IccTagDataEntry[count];
for (int i = 0; i < count; i++)
{
IccTypeSignature type = this.ReadTagDataEntryHeader();
if (type != IccTypeSignature.Curve && type != IccTypeSignature.ParametricCurve)
{
throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" +
$" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries");
}
if (type == IccTypeSignature.Curve)
{
tdata[i] = this.ReadCurveTagDataEntry();
}
else if (type == IccTypeSignature.ParametricCurve)
{
tdata[i] = this.ReadParametricCurveTagDataEntry();
}
this.AddPadding();
}
return tdata;
}
}
}

173
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs

@ -0,0 +1,173 @@
// <copyright file="IccDataReader.Lut.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads an 8bit lookup table
/// </summary>
/// <returns>The read LUT</returns>
public IccLut ReadLut8()
{
return new IccLut(this.ReadBytes(256));
}
/// <summary>
/// Reads a 16bit lookup table
/// </summary>
/// <param name="count">The number of entries</param>
/// <returns>The read LUT</returns>
public IccLut ReadLut16(int count)
{
ushort[] values = new ushort[count];
for (int i = 0; i < count; i++)
{
values[i] = this.ReadUInt16();
}
return new IccLut(values);
}
/// <summary>
/// Reads a CLUT depending on type
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="isFloat">If true, it's read as CLUTf32,
/// else read as either CLUT8 or CLUT16 depending on embedded information</param>
/// <returns>The read CLUT</returns>
public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat)
{
// Grid-points are always 16 bytes long but only 0-inChCount are used
byte[] gridPointCount = new byte[inChannelCount];
Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount);
if (!isFloat)
{
byte size = this.data[this.AddIndex(4)]; // First byte is info, last 3 bytes are reserved
if (size == 1)
{
return this.ReadClut8(inChannelCount, outChannelCount, gridPointCount);
}
if (size == 2)
{
return this.ReadClut16(inChannelCount, outChannelCount, gridPointCount);
}
throw new InvalidIccProfileException($"Invalid CLUT size of {size}");
}
return this.ReadClutF32(inChannelCount, outChannelCount, gridPointCount);
}
/// <summary>
/// Reads an 8 bit CLUT
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUT8</returns>
public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount)
{
int start = this.currentIndex;
int length = 0;
for (int i = 0; i < inChannelCount; i++)
{
length += (int)Math.Pow(gridPointCount[i], inChannelCount);
}
length /= inChannelCount;
const float Max = byte.MaxValue;
float[][] values = new float[length][];
for (int i = 0; i < length; i++)
{
values[i] = new float[outChannelCount];
for (int j = 0; j < outChannelCount; j++)
{
values[i][j] = this.data[this.currentIndex++] / Max;
}
}
this.currentIndex = start + (length * outChannelCount);
return new IccClut(values, gridPointCount, IccClutDataType.UInt8);
}
/// <summary>
/// Reads a 16 bit CLUT
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUT16</returns>
public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount)
{
int start = this.currentIndex;
int length = 0;
for (int i = 0; i < inChannelCount; i++)
{
length += (int)Math.Pow(gridPointCount[i], inChannelCount);
}
length /= inChannelCount;
const float Max = ushort.MaxValue;
float[][] values = new float[length][];
for (int i = 0; i < length; i++)
{
values[i] = new float[outChannelCount];
for (int j = 0; j < outChannelCount; j++)
{
values[i][j] = this.ReadUInt16() / Max;
}
}
this.currentIndex = start + (length * outChannelCount * 2);
return new IccClut(values, gridPointCount, IccClutDataType.UInt16);
}
/// <summary>
/// Reads a 32bit floating point CLUT
/// </summary>
/// <param name="inChCount">Input channel count</param>
/// <param name="outChCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUTf32</returns>
public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount)
{
int start = this.currentIndex;
int length = 0;
for (int i = 0; i < inChCount; i++)
{
length += (int)Math.Pow(gridPointCount[i], inChCount);
}
length /= inChCount;
float[][] values = new float[length][];
for (int i = 0; i < length; i++)
{
values[i] = new float[outChCount];
for (int j = 0; j < outChCount; j++)
{
values[i][j] = this.ReadSingle();
}
}
this.currentIndex = start + (length * outChCount * 4);
return new IccClut(values, gridPointCount, IccClutDataType.Float);
}
}
}

65
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs

@ -0,0 +1,65 @@
// <copyright file="IccDataReader.Matrix.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a two dimensional matrix
/// </summary>
/// <param name="xCount">Number of values in X</param>
/// <param name="yCount">Number of values in Y</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The read matrix</returns>
public float[,] ReadMatrix(int xCount, int yCount, bool isSingle)
{
float[,] matrix = new float[xCount, yCount];
for (int y = 0; y < yCount; y++)
{
for (int x = 0; x < xCount; x++)
{
if (isSingle)
{
matrix[x, y] = this.ReadSingle();
}
else
{
matrix[x, y] = this.ReadFix16();
}
}
}
return matrix;
}
/// <summary>
/// Reads a one dimensional matrix
/// </summary>
/// <param name="yCount">Number of values</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The read matrix</returns>
public float[] ReadMatrix(int yCount, bool isSingle)
{
float[] matrix = new float[yCount];
for (int i = 0; i < yCount; i++)
{
if (isSingle)
{
matrix[i] = this.ReadSingle();
}
else
{
matrix[i] = this.ReadFix16();
}
}
return matrix;
}
}
}

87
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs

@ -0,0 +1,87 @@
// <copyright file="IccDataReader.MultiProcessElement.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a <see cref="IccMultiProcessElement"/>
/// </summary>
/// <returns>The read <see cref="IccMultiProcessElement"/></returns>
public IccMultiProcessElement ReadMultiProcessElement()
{
IccMultiProcessElementSignature signature = (IccMultiProcessElementSignature)this.ReadUInt32();
ushort inChannelCount = this.ReadUInt16();
ushort outChannelCount = this.ReadUInt16();
switch (signature)
{
case IccMultiProcessElementSignature.CurveSet:
return this.ReadCurveSetProcessElement(inChannelCount, outChannelCount);
case IccMultiProcessElementSignature.Matrix:
return this.ReadMatrixProcessElement(inChannelCount, outChannelCount);
case IccMultiProcessElementSignature.Clut:
return this.ReadClutProcessElement(inChannelCount, outChannelCount);
// Currently just placeholders for future ICC expansion
case IccMultiProcessElementSignature.BAcs:
this.AddIndex(8);
return new IccBAcsProcessElement(inChannelCount, outChannelCount);
case IccMultiProcessElementSignature.EAcs:
this.AddIndex(8);
return new IccEAcsProcessElement(inChannelCount, outChannelCount);
default:
throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {signature}");
}
}
/// <summary>
/// Reads a CurveSet <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="inChannelCount">Number of input channels</param>
/// <param name="outChannelCount">Number of output channels</param>
/// <returns>The read <see cref="IccCurveSetProcessElement"/></returns>
public IccCurveSetProcessElement ReadCurveSetProcessElement(int inChannelCount, int outChannelCount)
{
IccOneDimensionalCurve[] curves = new IccOneDimensionalCurve[inChannelCount];
for (int i = 0; i < inChannelCount; i++)
{
curves[i] = this.ReadOneDimensionalCurve();
this.AddPadding();
}
return new IccCurveSetProcessElement(curves);
}
/// <summary>
/// Reads a Matrix <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="inChannelCount">Number of input channels</param>
/// <param name="outChannelCount">Number of output channels</param>
/// <returns>The read <see cref="IccMatrixProcessElement"/></returns>
public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount)
{
return new IccMatrixProcessElement(
this.ReadMatrix(inChannelCount, outChannelCount, true),
this.ReadMatrix(outChannelCount, true));
}
/// <summary>
/// Reads a CLUT <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="inChannelCount">Number of input channels</param>
/// <param name="outChannelCount">Number of output channels</param>
/// <returns>The read <see cref="IccClutProcessElement"/></returns>
public IccClutProcessElement ReadClutProcessElement(int inChannelCount, int outChannelCount)
{
return new IccClutProcessElement(this.ReadClut(inChannelCount, outChannelCount, true));
}
}
}

183
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs

@ -0,0 +1,183 @@
// <copyright file="IccDataReader.NonPrimitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Numerics;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a DateTime
/// </summary>
/// <returns>the value</returns>
public DateTime ReadDateTime()
{
try
{
return new DateTime(
year: this.ReadUInt16(),
month: this.ReadUInt16(),
day: this.ReadUInt16(),
hour: this.ReadUInt16(),
minute: this.ReadUInt16(),
second: this.ReadUInt16(),
kind: DateTimeKind.Utc);
}
catch (ArgumentOutOfRangeException)
{
return DateTime.MinValue;
}
}
/// <summary>
/// Reads an ICC profile version number
/// </summary>
/// <returns>the version number</returns>
public Version ReadVersionNumber()
{
int version = this.ReadInt32();
int major = (version >> 24) & 0xFF;
int minor = (version >> 20) & 0x0F;
int bugfix = (version >> 16) & 0x0F;
return new Version(major, minor, bugfix);
}
/// <summary>
/// Reads an XYZ number
/// </summary>
/// <returns>the XYZ number</returns>
public Vector3 ReadXyzNumber()
{
return new Vector3(
x: this.ReadFix16(),
y: this.ReadFix16(),
z: this.ReadFix16());
}
/// <summary>
/// Reads a profile ID
/// </summary>
/// <returns>the profile ID</returns>
public IccProfileId ReadProfileId()
{
return new IccProfileId(
p1: this.ReadUInt32(),
p2: this.ReadUInt32(),
p3: this.ReadUInt32(),
p4: this.ReadUInt32());
}
/// <summary>
/// Reads a position number
/// </summary>
/// <returns>the position number</returns>
public IccPositionNumber ReadPositionNumber()
{
return new IccPositionNumber(
offset: this.ReadUInt32(),
size: this.ReadUInt32());
}
/// <summary>
/// Reads a response number
/// </summary>
/// <returns>the response number</returns>
public IccResponseNumber ReadResponseNumber()
{
return new IccResponseNumber(
deviceCode: this.ReadUInt16(),
measurementValue: this.ReadFix16());
}
/// <summary>
/// Reads a named color
/// </summary>
/// <param name="deviceCoordCount">Number of device coordinates</param>
/// <returns>the named color</returns>
public IccNamedColor ReadNamedColor(uint deviceCoordCount)
{
string name = this.ReadAsciiString(32);
ushort[] pcsCoord = { this.ReadUInt16(), this.ReadUInt16(), this.ReadUInt16() };
ushort[] deviceCoord = new ushort[deviceCoordCount];
for (int i = 0; i < deviceCoordCount; i++)
{
deviceCoord[i] = this.ReadUInt16();
}
return new IccNamedColor(name, pcsCoord, deviceCoord);
}
/// <summary>
/// Reads a profile description
/// </summary>
/// <returns>the profile description</returns>
public IccProfileDescription ReadProfileDescription()
{
uint manufacturer = this.ReadUInt32();
uint model = this.ReadUInt32();
var attributes = (IccDeviceAttribute)this.ReadInt64();
var technologyInfo = (IccProfileTag)this.ReadUInt32();
IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = ReadText();
IccMultiLocalizedUnicodeTagDataEntry modelInfo = ReadText();
return new IccProfileDescription(
manufacturer,
model,
attributes,
technologyInfo,
manufacturerInfo.Texts,
modelInfo.Texts);
IccMultiLocalizedUnicodeTagDataEntry ReadText()
{
IccTypeSignature type = this.ReadTagDataEntryHeader();
switch (type)
{
case IccTypeSignature.MultiLocalizedUnicode:
return this.ReadMultiLocalizedUnicodeTagDataEntry();
case IccTypeSignature.TextDescription:
return (IccMultiLocalizedUnicodeTagDataEntry)this.ReadTextDescriptionTagDataEntry();
default:
throw new InvalidIccProfileException("Profile description can only have multi-localized Unicode or text description entries");
}
}
}
/// <summary>
/// Reads a colorant table entry
/// </summary>
/// <returns>the profile description</returns>
public IccColorantTableEntry ReadColorantTableEntry()
{
return new IccColorantTableEntry(
name: this.ReadAsciiString(32),
pcs1: this.ReadUInt16(),
pcs2: this.ReadUInt16(),
pcs3: this.ReadUInt16());
}
/// <summary>
/// Reads a screening channel
/// </summary>
/// <returns>the screening channel</returns>
public IccScreeningChannel ReadScreeningChannel()
{
return new IccScreeningChannel(
frequency: this.ReadFix16(),
angle: this.ReadFix16(),
spotShape: (IccScreeningSpotType)this.ReadInt32());
}
}
}

178
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs

@ -0,0 +1,178 @@
// <copyright file="IccDataReader.Primitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Text;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads an ushort
/// </summary>
/// <returns>the value</returns>
public ushort ReadUInt16()
{
return this.converter.ToUInt16(this.data, this.AddIndex(2));
}
/// <summary>
/// Reads a short
/// </summary>
/// <returns>the value</returns>
public short ReadInt16()
{
return this.converter.ToInt16(this.data, this.AddIndex(2));
}
/// <summary>
/// Reads an uint
/// </summary>
/// <returns>the value</returns>
public uint ReadUInt32()
{
return this.converter.ToUInt32(this.data, this.AddIndex(4));
}
/// <summary>
/// Reads an int
/// </summary>
/// <returns>the value</returns>
public int ReadInt32()
{
return this.converter.ToInt32(this.data, this.AddIndex(4));
}
/// <summary>
/// Reads an ulong
/// </summary>
/// <returns>the value</returns>
public ulong ReadUInt64()
{
return this.converter.ToUInt64(this.data, this.AddIndex(8));
}
/// <summary>
/// Reads a long
/// </summary>
/// <returns>the value</returns>
public long ReadInt64()
{
return this.converter.ToInt64(this.data, this.AddIndex(8));
}
/// <summary>
/// Reads a float
/// </summary>
/// <returns>the value</returns>
public float ReadSingle()
{
return this.converter.ToSingle(this.data, this.AddIndex(4));
}
/// <summary>
/// Reads a double
/// </summary>
/// <returns>the value</returns>
public double ReadDouble()
{
return this.converter.ToDouble(this.data, this.AddIndex(8));
}
/// <summary>
/// Reads an ASCII encoded string
/// </summary>
/// <param name="length">number of bytes to read</param>
/// <returns>The value as a string</returns>
public string ReadAsciiString(int length)
{
if (length == 0)
{
return string.Empty;
}
Guard.MustBeGreaterThan(length, 0, nameof(length));
string value = AsciiEncoding.GetString(this.data, this.AddIndex(length), length);
// remove data after (potential) null terminator
int pos = value.IndexOf('\0');
if (pos >= 0)
{
value = value.Substring(0, pos);
}
return value;
}
/// <summary>
/// Reads an UTF-16 big-endian encoded string
/// </summary>
/// <param name="length">number of bytes to read</param>
/// <returns>The value as a string</returns>
public string ReadUnicodeString(int length)
{
if (length == 0)
{
return string.Empty;
}
Guard.MustBeGreaterThan(length, 0, nameof(length));
return Encoding.BigEndianUnicode.GetString(this.data, this.AddIndex(length), length);
}
/// <summary>
/// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadFix16()
{
return this.ReadInt32() / 65536f;
}
/// <summary>
/// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadUFix16()
{
return this.ReadUInt32() / 65536f;
}
/// <summary>
/// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadU1Fix15()
{
return this.ReadUInt16() / 32768f;
}
/// <summary>
/// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadUFix8()
{
return this.ReadUInt16() / 256f;
}
/// <summary>
/// Reads a number of bytes and advances the index
/// </summary>
/// <param name="count">The number of bytes to read</param>
/// <returns>The read bytes</returns>
public byte[] ReadBytes(int count)
{
byte[] bytes = new byte[count];
Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count);
return bytes;
}
}
}

875
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs

@ -0,0 +1,875 @@
// <copyright file="IccDataReader.TagDataEntry.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Globalization;
using System.Numerics;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a tag data entry
/// </summary>
/// <param name="info">The table entry with reading information</param>
/// <returns>the tag data entry</returns>
public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info)
{
this.currentIndex = (int)info.Offset;
IccTypeSignature type = this.ReadTagDataEntryHeader();
switch (type)
{
case IccTypeSignature.Chromaticity:
return this.ReadChromaticityTagDataEntry();
case IccTypeSignature.ColorantOrder:
return this.ReadColorantOrderTagDataEntry();
case IccTypeSignature.ColorantTable:
return this.ReadColorantTableTagDataEntry();
case IccTypeSignature.Curve:
return this.ReadCurveTagDataEntry();
case IccTypeSignature.Data:
return this.ReadDataTagDataEntry(info.DataSize);
case IccTypeSignature.DateTime:
return this.ReadDateTimeTagDataEntry();
case IccTypeSignature.Lut16:
return this.ReadLut16TagDataEntry();
case IccTypeSignature.Lut8:
return this.ReadLut8TagDataEntry();
case IccTypeSignature.LutAToB:
return this.ReadLutAtoBTagDataEntry();
case IccTypeSignature.LutBToA:
return this.ReadLutBtoATagDataEntry();
case IccTypeSignature.Measurement:
return this.ReadMeasurementTagDataEntry();
case IccTypeSignature.MultiLocalizedUnicode:
return this.ReadMultiLocalizedUnicodeTagDataEntry();
case IccTypeSignature.MultiProcessElements:
return this.ReadMultiProcessElementsTagDataEntry();
case IccTypeSignature.NamedColor2:
return this.ReadNamedColor2TagDataEntry();
case IccTypeSignature.ParametricCurve:
return this.ReadParametricCurveTagDataEntry();
case IccTypeSignature.ProfileSequenceDesc:
return this.ReadProfileSequenceDescTagDataEntry();
case IccTypeSignature.ProfileSequenceIdentifier:
return this.ReadProfileSequenceIdentifierTagDataEntry();
case IccTypeSignature.ResponseCurveSet16:
return this.ReadResponseCurveSet16TagDataEntry();
case IccTypeSignature.S15Fixed16Array:
return this.ReadFix16ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.Signature:
return this.ReadSignatureTagDataEntry();
case IccTypeSignature.Text:
return this.ReadTextTagDataEntry(info.DataSize);
case IccTypeSignature.U16Fixed16Array:
return this.ReadUFix16ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt16Array:
return this.ReadUInt16ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt32Array:
return this.ReadUInt32ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt64Array:
return this.ReadUInt64ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt8Array:
return this.ReadUInt8ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.ViewingConditions:
return this.ReadViewingConditionsTagDataEntry();
case IccTypeSignature.Xyz:
return this.ReadXyzTagDataEntry(info.DataSize);
// V2 Types:
case IccTypeSignature.TextDescription:
return this.ReadTextDescriptionTagDataEntry();
case IccTypeSignature.CrdInfo:
return this.ReadCrdInfoTagDataEntry();
case IccTypeSignature.Screening:
return this.ReadScreeningTagDataEntry();
case IccTypeSignature.UcrBg:
return this.ReadUcrBgTagDataEntry(info.DataSize);
// Unsupported or unknown
case IccTypeSignature.DeviceSettings:
case IccTypeSignature.NamedColor:
case IccTypeSignature.Unknown:
default:
return this.ReadUnknownTagDataEntry(info.DataSize);
}
}
/// <summary>
/// Reads the header of a <see cref="IccTagDataEntry"/>
/// </summary>
/// <returns>The read signature</returns>
public IccTypeSignature ReadTagDataEntryHeader()
{
var type = (IccTypeSignature)this.ReadUInt32();
this.AddIndex(4); // 4 bytes are not used
return type;
}
/// <summary>
/// Reads the header of a <see cref="IccTagDataEntry"/> and checks if it's the expected value
/// </summary>
/// <param name="expected">expected value to check against</param>
public void ReadCheckTagDataEntryHeader(IccTypeSignature expected)
{
IccTypeSignature type = this.ReadTagDataEntryHeader();
if (expected != (IccTypeSignature)uint.MaxValue && type != expected)
{
throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}");
}
}
/// <summary>
/// Reads a <see cref="IccTagDataEntry"/> with an unknown <see cref="IccTypeSignature"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size)
{
int count = (int)size - 8; // 8 is the tag header size
return new IccUnknownTagDataEntry(this.ReadBytes(count));
}
/// <summary>
/// Reads a <see cref="IccChromaticityTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry()
{
ushort channelCount = this.ReadUInt16();
var colorant = (IccColorantEncoding)this.ReadUInt16();
if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown)
{
// The type is known and so are the values (they are constant)
// channelCount should always be 3 but it doesn't really matter if it's not
return new IccChromaticityTagDataEntry(colorant);
}
else
{
// The type is not know, so the values need be read
double[][] values = new double[channelCount][];
for (int i = 0; i < channelCount; i++)
{
values[i] = new double[2];
values[i][0] = this.ReadUFix16();
values[i][1] = this.ReadUFix16();
}
return new IccChromaticityTagDataEntry(values);
}
}
/// <summary>
/// Reads a <see cref="IccColorantOrderTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry()
{
uint colorantCount = this.ReadUInt32();
byte[] number = this.ReadBytes((int)colorantCount);
return new IccColorantOrderTagDataEntry(number);
}
/// <summary>
/// Reads a <see cref="IccColorantTableTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry()
{
uint colorantCount = this.ReadUInt32();
IccColorantTableEntry[] cdata = new IccColorantTableEntry[colorantCount];
for (int i = 0; i < colorantCount; i++)
{
cdata[i] = this.ReadColorantTableEntry();
}
return new IccColorantTableTagDataEntry(cdata);
}
/// <summary>
/// Reads a <see cref="IccCurveTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccCurveTagDataEntry ReadCurveTagDataEntry()
{
uint pointCount = this.ReadUInt32();
if (pointCount == 0)
{
return new IccCurveTagDataEntry();
}
if (pointCount == 1)
{
return new IccCurveTagDataEntry(this.ReadUFix8());
}
float[] cdata = new float[pointCount];
for (int i = 0; i < pointCount; i++)
{
cdata[i] = this.ReadUInt16() / 65535f;
}
return new IccCurveTagDataEntry(cdata);
// TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768).
}
/// <summary>
/// Reads a <see cref="IccDataTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccDataTagDataEntry ReadDataTagDataEntry(uint size)
{
this.AddIndex(3); // first 3 bytes are zero
byte b = this.data[this.AddIndex(1)];
// last bit of 4th byte is either 0 = ASCII or 1 = binary
bool ascii = this.GetBit(b, 7);
int length = (int)size - 12;
byte[] cdata = this.ReadBytes(length);
return new IccDataTagDataEntry(cdata, ascii);
}
/// <summary>
/// Reads a <see cref="IccDateTimeTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry()
{
return new IccDateTimeTagDataEntry(this.ReadDateTime());
}
/// <summary>
/// Reads a <see cref="IccLut16TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccLut16TagDataEntry ReadLut16TagDataEntry()
{
byte inChCount = this.data[this.AddIndex(1)];
byte outChCount = this.data[this.AddIndex(1)];
byte clutPointCount = this.data[this.AddIndex(1)];
this.AddIndex(1); // 1 byte reserved
float[,] matrix = this.ReadMatrix(3, 3, false);
ushort inTableCount = this.ReadUInt16();
ushort outTableCount = this.ReadUInt16();
// Input LUT
IccLut[] inValues = new IccLut[inChCount];
byte[] gridPointCount = new byte[inChCount];
for (int i = 0; i < inChCount; i++)
{
inValues[i] = this.ReadLut16(inTableCount);
gridPointCount[i] = clutPointCount;
}
// CLUT
IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount);
// Output LUT
IccLut[] outValues = new IccLut[outChCount];
for (int i = 0; i < outChCount; i++)
{
outValues[i] = this.ReadLut16(outTableCount);
}
return new IccLut16TagDataEntry(matrix, inValues, clut, outValues);
}
/// <summary>
/// Reads a <see cref="IccLut8TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccLut8TagDataEntry ReadLut8TagDataEntry()
{
byte inChCount = this.data[this.AddIndex(1)];
byte outChCount = this.data[this.AddIndex(1)];
byte clutPointCount = this.data[this.AddIndex(1)];
this.AddIndex(1); // 1 byte reserved
float[,] matrix = this.ReadMatrix(3, 3, false);
// Input LUT
IccLut[] inValues = new IccLut[inChCount];
byte[] gridPointCount = new byte[inChCount];
for (int i = 0; i < inChCount; i++)
{
inValues[i] = this.ReadLut8();
gridPointCount[i] = clutPointCount;
}
// CLUT
IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount);
// Output LUT
IccLut[] outValues = new IccLut[outChCount];
for (int i = 0; i < outChCount; i++)
{
outValues[i] = this.ReadLut8();
}
return new IccLut8TagDataEntry(matrix, inValues, clut, outValues);
}
/// <summary>
/// Reads a <see cref="IccLutAToBTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
byte inChCount = this.data[this.AddIndex(1)];
byte outChCount = this.data[this.AddIndex(1)];
this.AddIndex(2); // 2 bytes reserved
uint bCurveOffset = this.ReadUInt32();
uint matrixOffset = this.ReadUInt32();
uint mCurveOffset = this.ReadUInt32();
uint clutOffset = this.ReadUInt32();
uint aCurveOffset = this.ReadUInt32();
IccTagDataEntry[] bCurve = null;
IccTagDataEntry[] mCurve = null;
IccTagDataEntry[] aCurve = null;
IccClut clut = null;
float[,] matrix3x3 = null;
float[] matrix3x1 = null;
if (bCurveOffset != 0)
{
this.currentIndex = (int)bCurveOffset + start;
bCurve = this.ReadCurves(outChCount);
}
if (mCurveOffset != 0)
{
this.currentIndex = (int)mCurveOffset + start;
mCurve = this.ReadCurves(outChCount);
}
if (aCurveOffset != 0)
{
this.currentIndex = (int)aCurveOffset + start;
aCurve = this.ReadCurves(inChCount);
}
if (clutOffset != 0)
{
this.currentIndex = (int)clutOffset + start;
clut = this.ReadClut(inChCount, outChCount, false);
}
if (matrixOffset != 0)
{
this.currentIndex = (int)matrixOffset + start;
matrix3x3 = this.ReadMatrix(3, 3, false);
matrix3x1 = this.ReadMatrix(3, false);
}
return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve);
}
/// <summary>
/// Reads a <see cref="IccLutBToATagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccLutBToATagDataEntry ReadLutBtoATagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
byte inChCount = this.data[this.AddIndex(1)];
byte outChCount = this.data[this.AddIndex(1)];
this.AddIndex(2); // 2 bytes reserved
uint bCurveOffset = this.ReadUInt32();
uint matrixOffset = this.ReadUInt32();
uint mCurveOffset = this.ReadUInt32();
uint clutOffset = this.ReadUInt32();
uint aCurveOffset = this.ReadUInt32();
IccTagDataEntry[] bCurve = null;
IccTagDataEntry[] mCurve = null;
IccTagDataEntry[] aCurve = null;
IccClut clut = null;
float[,] matrix3x3 = null;
float[] matrix3x1 = null;
if (bCurveOffset != 0)
{
this.currentIndex = (int)bCurveOffset + start;
bCurve = this.ReadCurves(inChCount);
}
if (mCurveOffset != 0)
{
this.currentIndex = (int)mCurveOffset + start;
mCurve = this.ReadCurves(inChCount);
}
if (aCurveOffset != 0)
{
this.currentIndex = (int)aCurveOffset + start;
aCurve = this.ReadCurves(outChCount);
}
if (clutOffset != 0)
{
this.currentIndex = (int)clutOffset + start;
clut = this.ReadClut(inChCount, outChCount, false);
}
if (matrixOffset != 0)
{
this.currentIndex = (int)matrixOffset + start;
matrix3x3 = this.ReadMatrix(3, 3, false);
matrix3x1 = this.ReadMatrix(3, false);
}
return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve);
}
/// <summary>
/// Reads a <see cref="IccMeasurementTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry()
{
return new IccMeasurementTagDataEntry(
observer: (IccStandardObserver)this.ReadUInt32(),
xyzBacking: this.ReadXyzNumber(),
geometry: (IccMeasurementGeometry)this.ReadUInt32(),
flare: this.ReadUFix16(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
}
/// <summary>
/// Reads a <see cref="IccMultiLocalizedUnicodeTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
uint recordCount = this.ReadUInt32();
// TODO: Why are we storing variable
uint recordSize = this.ReadUInt32();
IccLocalizedString[] text = new IccLocalizedString[recordCount];
string[] culture = new string[recordCount];
uint[] length = new uint[recordCount];
uint[] offset = new uint[recordCount];
for (int i = 0; i < recordCount; i++)
{
culture[i] = $"{this.ReadAsciiString(2)}-{this.ReadAsciiString(2)}";
length[i] = this.ReadUInt32();
offset[i] = this.ReadUInt32();
}
for (int i = 0; i < recordCount; i++)
{
this.currentIndex = (int)(start + offset[i]);
text[i] = new IccLocalizedString(new CultureInfo(culture[i]), this.ReadUnicodeString((int)length[i]));
}
return new IccMultiLocalizedUnicodeTagDataEntry(text);
}
/// <summary>
/// Reads a <see cref="IccMultiProcessElementsTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry()
{
int start = this.currentIndex - 8;
// TODO: Why are we storing variable
ushort inChannelCount = this.ReadUInt16();
ushort outChannelCount = this.ReadUInt16();
uint elementCount = this.ReadUInt32();
IccPositionNumber[] positionTable = new IccPositionNumber[elementCount];
for (int i = 0; i < elementCount; i++)
{
positionTable[i] = this.ReadPositionNumber();
}
IccMultiProcessElement[] elements = new IccMultiProcessElement[elementCount];
for (int i = 0; i < elementCount; i++)
{
this.currentIndex = (int)positionTable[i].Offset + start;
elements[i] = this.ReadMultiProcessElement();
}
return new IccMultiProcessElementsTagDataEntry(elements);
}
/// <summary>
/// Reads a <see cref="IccNamedColor2TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry()
{
int vendorFlag = this.ReadInt32();
uint colorCount = this.ReadUInt32();
uint coordCount = this.ReadUInt32();
string prefix = this.ReadAsciiString(32);
string suffix = this.ReadAsciiString(32);
IccNamedColor[] colors = new IccNamedColor[colorCount];
for (int i = 0; i < colorCount; i++)
{
colors[i] = this.ReadNamedColor(coordCount);
}
return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors);
}
/// <summary>
/// Reads a <see cref="IccParametricCurveTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry()
{
return new IccParametricCurveTagDataEntry(this.ReadParametricCurve());
}
/// <summary>
/// Reads a <see cref="IccProfileSequenceDescTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry()
{
uint count = this.ReadUInt32();
IccProfileDescription[] description = new IccProfileDescription[count];
for (int i = 0; i < count; i++)
{
description[i] = this.ReadProfileDescription();
}
return new IccProfileSequenceDescTagDataEntry(description);
}
/// <summary>
/// Reads a <see cref="IccProfileSequenceIdentifierTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
uint count = this.ReadUInt32();
IccPositionNumber[] table = new IccPositionNumber[count];
for (int i = 0; i < count; i++)
{
table[i] = this.ReadPositionNumber();
}
IccProfileSequenceIdentifier[] entries = new IccProfileSequenceIdentifier[count];
for (int i = 0; i < count; i++)
{
this.currentIndex = (int)(start + table[i].Offset);
IccProfileId id = this.ReadProfileId();
this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode);
IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry();
entries[i] = new IccProfileSequenceIdentifier(id, description.Texts);
}
return new IccProfileSequenceIdentifierTagDataEntry(entries);
}
/// <summary>
/// Reads a <see cref="IccResponseCurveSet16TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
ushort channelCount = this.ReadUInt16();
ushort measurmentCount = this.ReadUInt16();
uint[] offset = new uint[measurmentCount];
for (int i = 0; i < measurmentCount; i++)
{
offset[i] = this.ReadUInt32();
}
IccResponseCurve[] curves = new IccResponseCurve[measurmentCount];
for (int i = 0; i < measurmentCount; i++)
{
this.currentIndex = (int)(start + offset[i]);
curves[i] = this.ReadResponseCurve(channelCount);
}
return new IccResponseCurveSet16TagDataEntry(curves);
}
/// <summary>
/// Reads a <see cref="IccFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
float[] arrayData = new float[count];
for (int i = 0; i < count; i++)
{
arrayData[i] = this.ReadFix16() / 256f;
}
return new IccFix16ArrayTagDataEntry(arrayData);
}
/// <summary>
/// Reads a <see cref="IccSignatureTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccSignatureTagDataEntry ReadSignatureTagDataEntry()
{
return new IccSignatureTagDataEntry(this.ReadAsciiString(4));
}
/// <summary>
/// Reads a <see cref="IccTextTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccTextTagDataEntry ReadTextTagDataEntry(uint size)
{
return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size
}
/// <summary>
/// Reads a <see cref="IccUFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
float[] arrayData = new float[count];
for (int i = 0; i < count; i++)
{
arrayData[i] = this.ReadUFix16();
}
return new IccUFix16ArrayTagDataEntry(arrayData);
}
/// <summary>
/// Reads a <see cref="IccUInt16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 2;
ushort[] arrayData = new ushort[count];
for (int i = 0; i < count; i++)
{
arrayData[i] = this.ReadUInt16();
}
return new IccUInt16ArrayTagDataEntry(arrayData);
}
/// <summary>
/// Reads a <see cref="IccUInt32ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
uint[] arrayData = new uint[count];
for (int i = 0; i < count; i++)
{
arrayData[i] = this.ReadUInt32();
}
return new IccUInt32ArrayTagDataEntry(arrayData);
}
/// <summary>
/// Reads a <see cref="IccUInt64ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 8;
ulong[] arrayData = new ulong[count];
for (int i = 0; i < count; i++)
{
arrayData[i] = this.ReadUInt64();
}
return new IccUInt64ArrayTagDataEntry(arrayData);
}
/// <summary>
/// Reads a <see cref="IccUInt8ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size)
{
int count = (int)size - 8; // 8 is the tag header size
byte[] adata = this.ReadBytes(count);
return new IccUInt8ArrayTagDataEntry(adata);
}
/// <summary>
/// Reads a <see cref="IccViewingConditionsTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry()
{
return new IccViewingConditionsTagDataEntry(
illuminantXyz: this.ReadXyzNumber(),
surroundXyz: this.ReadXyzNumber(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
}
/// <summary>
/// Reads a <see cref="IccXyzTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size)
{
uint count = (size - 8) / 12;
Vector3[] arrayData = new Vector3[count];
for (int i = 0; i < count; i++)
{
arrayData[i] = this.ReadXyzNumber();
}
return new IccXyzTagDataEntry(arrayData);
}
/// <summary>
/// Reads a <see cref="IccTextDescriptionTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry()
{
string unicodeValue, scriptcodeValue;
string asciiValue = unicodeValue = scriptcodeValue = null;
int asciiCount = (int)this.ReadUInt32();
if (asciiCount > 0)
{
asciiValue = this.ReadAsciiString(asciiCount - 1);
this.AddIndex(1); // Null terminator
}
uint unicodeLangCode = this.ReadUInt32();
int unicodeCount = (int)this.ReadUInt32();
if (unicodeCount > 0)
{
unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2);
this.AddIndex(2); // Null terminator
}
ushort scriptcodeCode = this.ReadUInt16();
int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67);
if (scriptcodeCount > 0)
{
scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1);
this.AddIndex(1); // Null terminator
}
return new IccTextDescriptionTagDataEntry(
asciiValue,
unicodeValue,
scriptcodeValue,
unicodeLangCode,
scriptcodeCode);
}
/// <summary>
/// Reads a <see cref="IccTextDescriptionTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry()
{
uint productNameCount = this.ReadUInt32();
string productName = this.ReadAsciiString((int)productNameCount);
uint crd0Count = this.ReadUInt32();
string crd0Name = this.ReadAsciiString((int)crd0Count);
uint crd1Count = this.ReadUInt32();
string crd1Name = this.ReadAsciiString((int)crd1Count);
uint crd2Count = this.ReadUInt32();
string crd2Name = this.ReadAsciiString((int)crd2Count);
uint crd3Count = this.ReadUInt32();
string crd3Name = this.ReadAsciiString((int)crd3Count);
return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name);
}
/// <summary>
/// Reads a <see cref="IccScreeningTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccScreeningTagDataEntry ReadScreeningTagDataEntry()
{
var flags = (IccScreeningFlag)this.ReadInt32();
uint channelCount = this.ReadUInt32();
IccScreeningChannel[] channels = new IccScreeningChannel[channelCount];
for (int i = 0; i < channels.Length; i++)
{
channels[i] = this.ReadScreeningChannel();
}
return new IccScreeningTagDataEntry(flags, channels);
}
/// <summary>
/// Reads a <see cref="IccUcrBgTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size)
{
uint ucrCount = this.ReadUInt32();
ushort[] ucrCurve = new ushort[ucrCount];
for (int i = 0; i < ucrCurve.Length; i++)
{
ucrCurve[i] = this.ReadUInt16();
}
uint bgCount = this.ReadUInt32();
ushort[] bgCurve = new ushort[bgCount];
for (int i = 0; i < bgCurve.Length; i++)
{
bgCurve[i] = this.ReadUInt16();
}
// ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount)
uint dataSize = ((ucrCount + bgCount) * 2) + 8;
int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size
string description = this.ReadAsciiString(descriptionLength);
return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description);
}
}
}

106
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs

@ -0,0 +1,106 @@
// <copyright file="IccDataReader.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Text;
using ImageSharp.IO;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// The data that is read
/// </summary>
private readonly byte[] data;
/// <summary>
/// The bit converter
/// </summary>
private readonly EndianBitConverter converter = new BigEndianBitConverter();
/// <summary>
/// The current reading position
/// </summary>
private int currentIndex;
/// <summary>
/// Initializes a new instance of the <see cref="IccDataReader"/> class.
/// </summary>
/// <param name="data">The data to read</param>
public IccDataReader(byte[] data)
{
Guard.NotNull(data, nameof(data));
this.data = data;
}
/// <summary>
/// Sets the reading position to the given value
/// </summary>
/// <param name="index">The new index position</param>
public void SetIndex(int index)
{
this.currentIndex = index.Clamp(0, this.data.Length);
}
/// <summary>
/// Returns the current <see cref="currentIndex"/> without increment and adds the given increment
/// </summary>
/// <param name="increment">The value to increment <see cref="currentIndex"/></param>
/// <returns>The current <see cref="currentIndex"/> without the increment</returns>
private int AddIndex(int increment)
{
int tmp = this.currentIndex;
this.currentIndex += increment;
return tmp;
}
/// <summary>
/// Calculates the 4 byte padding and adds it to the <see cref="currentIndex"/> variable
/// </summary>
private void AddPadding()
{
this.currentIndex += this.CalcPadding();
}
/// <summary>
/// Calculates the 4 byte padding
/// </summary>
/// <returns>the number of bytes to pad</returns>
private int CalcPadding()
{
int p = 4 - (this.currentIndex % 4);
return p >= 4 ? 0 : p;
}
/// <summary>
/// Gets the bit value at a specified position
/// </summary>
/// <param name="value">The value from where the bit will be extracted</param>
/// <param name="position">Position of the bit. Zero based index from left to right.</param>
/// <returns>The bit value at specified position</returns>
private bool GetBit(byte value, int position)
{
return ((value >> (7 - position)) & 1) == 1;
}
/// <summary>
/// Gets the bit value at a specified position
/// </summary>
/// <param name="value">The value from where the bit will be extracted</param>
/// <param name="position">Position of the bit. Zero based index from left to right.</param>
/// <returns>The bit value at specified position</returns>
private bool GetBit(ushort value, int position)
{
return ((value >> (15 - position)) & 1) == 1;
}
}
}

179
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs

@ -0,0 +1,179 @@
// <copyright file="IccDataWriter.Curves.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
/// <content>
/// Provides methods to write ICC data types
/// </content>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a <see cref="IccOneDimensionalCurve"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
public int WriteOneDimensionalCurve(IccOneDimensionalCurve value)
{
int count = this.WriteUInt16((ushort)value.Segments.Length);
count += this.WriteEmpty(2);
foreach (float point in value.BreakPoints)
{
count += this.WriteSingle(point);
}
foreach (IccCurveSegment segment in value.Segments)
{
count += this.WriteCurveSegment(segment);
}
return count;
}
/// <summary>
/// Writes a <see cref="IccResponseCurve"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
public int WriteResponseCurve(IccResponseCurve value)
{
int count = this.WriteUInt32((uint)value.CurveType);
int channels = value.XyzValues.Length;
foreach (IccResponseNumber[] responseArray in value.ResponseArrays)
{
count += this.WriteUInt32((uint)responseArray.Length);
}
foreach (Vector3 xyz in value.XyzValues)
{
count += this.WriteXyzNumber(xyz);
}
foreach (IccResponseNumber[] responseArray in value.ResponseArrays)
{
foreach (IccResponseNumber response in responseArray)
{
count += this.WriteResponseNumber(response);
}
}
return count;
}
/// <summary>
/// Writes a <see cref="IccParametricCurve"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
public int WriteParametricCurve(IccParametricCurve value)
{
ushort typeValue = (ushort)value.Type;
int count = this.WriteUInt16(typeValue);
count += this.WriteEmpty(2);
if (typeValue <= 4)
{
count += this.WriteFix16(value.G);
}
if (typeValue > 0 && typeValue <= 4)
{
count += this.WriteFix16(value.A);
count += this.WriteFix16(value.B);
}
if (typeValue > 1 && typeValue <= 4)
{
count += this.WriteFix16(value.C);
}
if (typeValue > 2 && typeValue <= 4)
{
count += this.WriteFix16(value.D);
}
if (typeValue == 4)
{
count += this.WriteFix16(value.E);
count += this.WriteFix16(value.F);
}
return count;
}
/// <summary>
/// Writes a <see cref="IccCurveSegment"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
public int WriteCurveSegment(IccCurveSegment value)
{
int count = this.WriteUInt32((uint)value.Signature);
count += this.WriteEmpty(4);
switch (value.Signature)
{
case IccCurveSegmentSignature.FormulaCurve:
return count + this.WriteFormulaCurveElement(value as IccFormulaCurveElement);
case IccCurveSegmentSignature.SampledCurve:
return count + this.WriteSampledCurveElement(value as IccSampledCurveElement);
default:
throw new InvalidIccProfileException($"Invalid CurveSegment type of {value.Signature}");
}
}
/// <summary>
/// Writes a <see cref="IccFormulaCurveElement"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
public int WriteFormulaCurveElement(IccFormulaCurveElement value)
{
int count = this.WriteUInt16((ushort)value.Type);
count += this.WriteEmpty(2);
if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2)
{
count += this.WriteSingle(value.Gamma);
}
count += this.WriteSingle(value.A);
count += this.WriteSingle(value.B);
count += this.WriteSingle(value.C);
if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3)
{
count += this.WriteSingle(value.D);
}
if (value.Type == IccFormulaCurveType.Type3)
{
count += this.WriteSingle(value.E);
}
return count;
}
/// <summary>
/// Writes a <see cref="IccSampledCurveElement"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
public int WriteSampledCurveElement(IccSampledCurveElement value)
{
int count = this.WriteUInt32((uint)value.CurveEntries.Length);
foreach (float entry in value.CurveEntries)
{
count += this.WriteSingle(entry);
}
return count;
}
}
}

128
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs

@ -0,0 +1,128 @@
// <copyright file="IccDataWriter.Lut.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <content>
/// Provides methods to write ICC data types
/// </content>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes an 8bit lookup table
/// </summary>
/// <param name="value">The LUT to write</param>
/// <returns>The number of bytes written</returns>
public int WriteLut8(IccLut value)
{
foreach (float item in value.Values)
{
this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue));
}
return value.Values.Length;
}
/// <summary>
/// Writes an 16bit lookup table
/// </summary>
/// <param name="value">The LUT to write</param>
/// <returns>The number of bytes written</returns>
public int WriteLut16(IccLut value)
{
foreach (float item in value.Values)
{
this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
}
return value.Values.Length * 2;
}
/// <summary>
/// Writes an color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
public int WriteClut(IccClut value)
{
int count = this.WriteArray(value.GridPointCount);
count += this.WriteEmpty(16 - value.GridPointCount.Length);
switch (value.DataType)
{
case IccClutDataType.Float:
return count + this.WriteClutF32(value);
case IccClutDataType.UInt8:
count += this.WriteByte(1);
count += this.WriteEmpty(3);
return count + this.WriteClut8(value);
case IccClutDataType.UInt16:
count += this.WriteByte(2);
count += this.WriteEmpty(3);
return count + this.WriteClut16(value);
default:
throw new InvalidIccProfileException($"Invalid CLUT data type of {value.DataType}");
}
}
/// <summary>
/// Writes a 8bit color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
public int WriteClut8(IccClut value)
{
int count = 0;
foreach (float[] inArray in value.Values)
{
foreach (float item in inArray)
{
count += this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue));
}
}
return count;
}
/// <summary>
/// Writes a 16bit color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
public int WriteClut16(IccClut value)
{
int count = 0;
foreach (float[] inArray in value.Values)
{
foreach (float item in inArray)
{
count += this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
}
}
return count;
}
/// <summary>
/// Writes a 32bit float color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
public int WriteClutF32(IccClut value)
{
int count = 0;
foreach (float[] inArray in value.Values)
{
foreach (float item in inArray)
{
count += this.WriteSingle(item);
}
}
return count;
}
}
}

162
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs

@ -0,0 +1,162 @@
// <copyright file="IccDataWriter.Matrix.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
using ImageSharp.Memory;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a two dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrix(Matrix4x4 value, bool isSingle)
{
int count = 0;
if (isSingle)
{
count += this.WriteSingle(value.M11);
count += this.WriteSingle(value.M21);
count += this.WriteSingle(value.M31);
count += this.WriteSingle(value.M12);
count += this.WriteSingle(value.M22);
count += this.WriteSingle(value.M32);
count += this.WriteSingle(value.M13);
count += this.WriteSingle(value.M23);
count += this.WriteSingle(value.M33);
}
else
{
count += this.WriteFix16(value.M11);
count += this.WriteFix16(value.M21);
count += this.WriteFix16(value.M31);
count += this.WriteFix16(value.M12);
count += this.WriteFix16(value.M22);
count += this.WriteFix16(value.M32);
count += this.WriteFix16(value.M13);
count += this.WriteFix16(value.M23);
count += this.WriteFix16(value.M33);
}
return count;
}
/// <summary>
/// Writes a two dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrix(Fast2DArray<float> value, bool isSingle)
{
int count = 0;
for (int y = 0; y < value.Height; y++)
{
for (int x = 0; x < value.Width; x++)
{
if (isSingle)
{
count += this.WriteSingle(value[x, y]);
}
else
{
count += this.WriteFix16(value[x, y]);
}
}
}
return count;
}
/// <summary>
/// Writes a two dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrix(float[,] value, bool isSingle)
{
int count = 0;
for (int y = 0; y < value.GetLength(1); y++)
{
for (int x = 0; x < value.GetLength(0); x++)
{
if (isSingle)
{
count += this.WriteSingle(value[x, y]);
}
else
{
count += this.WriteFix16(value[x, y]);
}
}
}
return count;
}
/// <summary>
/// Writes a one dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrix(Vector3 value, bool isSingle)
{
int count = 0;
if (isSingle)
{
count += this.WriteSingle(value.X);
count += this.WriteSingle(value.Y);
count += this.WriteSingle(value.Z);
}
else
{
count += this.WriteFix16(value.X);
count += this.WriteFix16(value.Y);
count += this.WriteFix16(value.Z);
}
return count;
}
/// <summary>
/// Writes a one dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrix(float[] value, bool isSingle)
{
int count = 0;
for (int i = 0; i < value.Length; i++)
{
if (isSingle)
{
count += this.WriteSingle(value[i]);
}
else
{
count += this.WriteFix16(value[i]);
}
}
return count;
}
}
}

80
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs

@ -0,0 +1,80 @@
// <copyright file="IccDataWriter.MultiProcessElement.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
public int WriteMultiProcessElement(IccMultiProcessElement value)
{
int count = this.WriteUInt32((uint)value.Signature);
count += this.WriteUInt16((ushort)value.InputChannelCount);
count += this.WriteUInt16((ushort)value.OutputChannelCount);
switch (value.Signature)
{
case IccMultiProcessElementSignature.CurveSet:
return count + this.WriteCurveSetProcessElement(value as IccCurveSetProcessElement);
case IccMultiProcessElementSignature.Matrix:
return count + this.WriteMatrixProcessElement(value as IccMatrixProcessElement);
case IccMultiProcessElementSignature.Clut:
return count + this.WriteClutProcessElement(value as IccClutProcessElement);
case IccMultiProcessElementSignature.BAcs:
case IccMultiProcessElementSignature.EAcs:
return count + this.WriteEmpty(8);
default:
throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {value.Signature}");
}
}
/// <summary>
/// Writes a CurveSet <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
public int WriteCurveSetProcessElement(IccCurveSetProcessElement value)
{
int count = 0;
foreach (IccOneDimensionalCurve curve in value.Curves)
{
count += this.WriteOneDimensionalCurve(curve);
count += this.WritePadding();
}
return count;
}
/// <summary>
/// Writes a Matrix <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrixProcessElement(IccMatrixProcessElement value)
{
return this.WriteMatrix(value.MatrixIxO, true)
+ this.WriteMatrix(value.MatrixOx1, true);
}
/// <summary>
/// Writes a CLUT <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
public int WriteClutProcessElement(IccClutProcessElement value)
{
return this.WriteClut(value.ClutValue);
}
}
}

137
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs

@ -0,0 +1,137 @@
// <copyright file="IccDataWriter.NonPrimitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Numerics;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a DateTime
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteDateTime(DateTime value)
{
return this.WriteUInt16((ushort)value.Year)
+ this.WriteUInt16((ushort)value.Month)
+ this.WriteUInt16((ushort)value.Day)
+ this.WriteUInt16((ushort)value.Hour)
+ this.WriteUInt16((ushort)value.Minute)
+ this.WriteUInt16((ushort)value.Second);
}
/// <summary>
/// Writes an ICC profile version number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteVersionNumber(Version value)
{
int major = value.Major.Clamp(0, byte.MaxValue);
int minor = value.Minor.Clamp(0, 15);
int bugfix = value.Build.Clamp(0, 15);
// TODO: This is not used?
byte mb = (byte)((minor << 4) | bugfix);
int version = (major << 24) | (minor << 20) | (bugfix << 16);
return this.WriteInt32(version);
}
/// <summary>
/// Writes an XYZ number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteXyzNumber(Vector3 value)
{
return this.WriteFix16(value.X)
+ this.WriteFix16(value.Y)
+ this.WriteFix16(value.Z);
}
/// <summary>
/// Writes a profile ID
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteProfileId(IccProfileId value)
{
return this.WriteUInt32(value.Part1)
+ this.WriteUInt32(value.Part2)
+ this.WriteUInt32(value.Part3)
+ this.WriteUInt32(value.Part4);
}
/// <summary>
/// Writes a position number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WritePositionNumber(IccPositionNumber value)
{
return this.WriteUInt32(value.Offset)
+ this.WriteUInt32(value.Size);
}
/// <summary>
/// Writes a response number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteResponseNumber(IccResponseNumber value)
{
return this.WriteUInt16(value.DeviceCode)
+ this.WriteFix16(value.MeasurementValue);
}
/// <summary>
/// Writes a named color
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteNamedColor(IccNamedColor value)
{
return this.WriteAsciiString(value.Name, 32, true)
+ this.WriteArray(value.PcsCoordinates)
+ this.WriteArray(value.DeviceCoordinates);
}
/// <summary>
/// Writes a profile description
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteProfileDescription(IccProfileDescription value)
{
return this.WriteUInt32(value.DeviceManufacturer)
+ this.WriteUInt32(value.DeviceModel)
+ this.WriteInt64((long)value.DeviceAttributes)
+ this.WriteUInt32((uint)value.TechnologyInformation)
+ this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode)
+ this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceManufacturerInfo))
+ this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode)
+ this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceModelInfo));
}
/// <summary>
/// Writes a screening channel
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteScreeningChannel(IccScreeningChannel value)
{
return this.WriteFix16(value.Frequency)
+ this.WriteFix16(value.Angle)
+ this.WriteInt32((int)value.SpotShape);
}
}
}

248
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs

@ -0,0 +1,248 @@
// <copyright file="IccDataWriter.Primitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Text;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a byte
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteByte(byte value)
{
this.dataStream.WriteByte(value);
return 1;
}
/// <summary>
/// Writes an ushort
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteUInt16(ushort value)
{
return this.WriteBytes((byte*)&value, 2);
}
/// <summary>
/// Writes a short
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteInt16(short value)
{
return this.WriteBytes((byte*)&value, 2);
}
/// <summary>
/// Writes an uint
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteUInt32(uint value)
{
return this.WriteBytes((byte*)&value, 4);
}
/// <summary>
/// Writes an int
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteInt32(int value)
{
return this.WriteBytes((byte*)&value, 4);
}
/// <summary>
/// Writes an ulong
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteUInt64(ulong value)
{
return this.WriteBytes((byte*)&value, 8);
}
/// <summary>
/// Writes a long
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteInt64(long value)
{
return this.WriteBytes((byte*)&value, 8);
}
/// <summary>
/// Writes a float
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteSingle(float value)
{
return this.WriteBytes((byte*)&value, 4);
}
/// <summary>
/// Writes a double
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteDouble(double value)
{
return this.WriteBytes((byte*)&value, 8);
}
/// <summary>
/// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteFix16(double value)
{
const double Max = short.MaxValue + (65535d / 65536d);
const double Min = short.MinValue;
value = value.Clamp(Min, Max);
value *= 65536d;
return this.WriteInt32((int)Math.Round(value, MidpointRounding.AwayFromZero));
}
/// <summary>
/// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteUFix16(double value)
{
const double Max = ushort.MaxValue + (65535d / 65536d);
const double Min = ushort.MinValue;
value = value.Clamp(Min, Max);
value *= 65536d;
return this.WriteUInt32((uint)Math.Round(value, MidpointRounding.AwayFromZero));
}
/// <summary>
/// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteU1Fix15(double value)
{
const double Max = 1 + (32767d / 32768d);
const double Min = 0;
value = value.Clamp(Min, Max);
value *= 32768d;
return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero));
}
/// <summary>
/// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteUFix8(double value)
{
const double Max = byte.MaxValue + (255d / 256d);
const double Min = byte.MinValue;
value = value.Clamp(Min, Max);
value *= 256d;
return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero));
}
/// <summary>
/// Writes an ASCII encoded string
/// </summary>
/// <param name="value">the string to write</param>
/// <returns>the number of bytes written</returns>
public int WriteAsciiString(string value)
{
if (string.IsNullOrEmpty(value))
{
return 0;
}
byte[] data = AsciiEncoding.GetBytes(value);
this.dataStream.Write(data, 0, data.Length);
return data.Length;
}
/// <summary>
/// Writes an ASCII encoded string resizes it to the given length
/// </summary>
/// <param name="value">The string to write</param>
/// <param name="length">The desired length of the string (including potential null terminator)</param>
/// <param name="ensureNullTerminator">If True, there will be a \0 added at the end</param>
/// <returns>the number of bytes written</returns>
public int WriteAsciiString(string value, int length, bool ensureNullTerminator)
{
if (length == 0)
{
return 0;
}
Guard.MustBeGreaterThan(length, 0, nameof(length));
if (value == null)
{
value = string.Empty;
}
byte paddingChar = (byte)' ';
int lengthAdjust = 0;
if (ensureNullTerminator)
{
paddingChar = 0;
lengthAdjust = 1;
}
value = value.Substring(0, Math.Min(length - lengthAdjust, value.Length));
byte[] textData = AsciiEncoding.GetBytes(value);
int actualLength = Math.Min(length - lengthAdjust, textData.Length);
this.dataStream.Write(textData, 0, actualLength);
for (int i = 0; i < length - actualLength; i++)
{
this.dataStream.WriteByte(paddingChar);
}
return length;
}
/// <summary>
/// Writes an UTF-16 big-endian encoded string
/// </summary>
/// <param name="value">the string to write</param>
/// <returns>the number of bytes written</returns>
public int WriteUnicodeString(string value)
{
if (string.IsNullOrEmpty(value))
{
return 0;
}
byte[] data = Encoding.BigEndianUnicode.GetBytes(value);
this.dataStream.Write(data, 0, data.Length);
return data.Length;
}
}
}

1003
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs

File diff suppressed because it is too large

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

Loading…
Cancel
Save