diff --git a/README.md b/README.md index ca2427546..561768199 100644 --- a/README.md +++ b/README.md @@ -106,17 +106,22 @@ using (Image image = Image.Load(stream)) } ``` -Setting individual pixel values is perfomed as follows: +Setting individual pixel values can be perfomed as follows: ```csharp +// Individual pixels using (Image image = new Image(400, 400) -using (PixelAccessor pixels = image.Lock()) { - pixels[200, 200] = Rgba32.White; + image[200, 200] = Rgba32.White; } ``` -For advanced usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. +For optimized access within a loop it is recommended that the following methods are used. + +1. `image.GetRowSpan(y)` +2. `image.GetRowSpan(x, y)` + +For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs index 9534c7a88..8cb731782 100644 --- a/src/ImageSharp.Drawing/Brushes/IBrush.cs +++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Drawing /// /// Creates the applicator for this brush. /// - /// The pixel source. + /// The source image. /// The region the brush will be applied to. /// The graphic options /// @@ -32,6 +32,6 @@ namespace ImageSharp.Drawing /// The when being applied to things like shapes would usually be the /// bounding box of the shape not necessarily the bounds of the whole image /// - BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); + BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 6f851e5c3..59dbd3926 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Drawing.Brushes { using System; - using System.Numerics; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -22,21 +21,21 @@ namespace ImageSharp.Drawing.Brushes /// /// The image to paint. /// - private readonly IImageBase image; + private readonly ImageBase image; /// /// Initializes a new instance of the class. /// /// The image. - public ImageBrush(IImageBase image) + public ImageBrush(ImageBase image) { this.image = image; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new ImageBrushApplicator(sourcePixels, this.image, region, options); + return new ImageBrushApplicator(source, this.image, region, options); } /// @@ -45,9 +44,9 @@ namespace ImageSharp.Drawing.Brushes private class ImageBrushApplicator : BrushApplicator { /// - /// The source pixel accessor. + /// The source image. /// - private readonly PixelAccessor source; + private readonly ImageBase source; /// /// The y-length. @@ -72,20 +71,14 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// - /// The image. - /// - /// - /// The region. - /// + /// The target image. + /// The image. + /// The region. /// The options - /// - /// The sourcePixels. - /// - public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region, GraphicsOptions options) - : base(sourcePixels, options) + public ImageBrushApplicator(ImageBase target, ImageBase image, RectangleF region, GraphicsOptions options) + : base(target, options) { - this.source = image.Lock(); + this.source = image; this.xLength = image.Width; this.yLength = image.Height; this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); @@ -119,9 +112,9 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - // create a span for colors - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + // Create a span for colors + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { int sourceY = (y - this.offsetY) % this.yLength; int offsetX = x - this.offsetX; diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index 90990e54a..5dd57bda6 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -62,8 +62,8 @@ namespace ImageSharp.Drawing.Brushes /// The pattern. internal PatternBrush(TPixel foreColor, TPixel backColor, Fast2DArray pattern) { - Vector4 foreColorVector = foreColor.ToVector4(); - Vector4 backColorVector = backColor.ToVector4(); + var foreColorVector = foreColor.ToVector4(); + var backColorVector = backColor.ToVector4(); this.pattern = new Fast2DArray(pattern.Width, pattern.Height); this.patternVector = new Fast2DArray(pattern.Width, pattern.Height); for (int i = 0; i < pattern.Data.Length; i++) @@ -92,9 +92,9 @@ namespace ImageSharp.Drawing.Brushes } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector, options); + return new PatternBrushApplicator(source, this.pattern, this.patternVector, options); } /// @@ -111,12 +111,12 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// The sourcePixels. + /// The source image. /// The pattern. /// The patternVector. /// The options - public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) - : base(sourcePixels, options) + public PatternBrushApplicator(ImageBase source, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) + : base(source, options) { this.pattern = pattern; this.patternVector = patternVector; @@ -152,8 +152,8 @@ namespace ImageSharp.Drawing.Brushes internal override void Apply(Span scanline, int x, int y) { int patternY = y % this.pattern.Height; - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 29629324a..29c625d7f 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Drawing.Processors { using System; - using System.Numerics; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -24,7 +23,7 @@ namespace ImageSharp.Drawing.Processors /// /// The target. /// The options. - internal BrushApplicator(PixelAccessor target, GraphicsOptions options) + internal BrushApplicator(ImageBase target, GraphicsOptions options) { this.Target = target; @@ -41,7 +40,7 @@ namespace ImageSharp.Drawing.Processors /// /// Gets the destinaion /// - protected PixelAccessor Target { get; } + protected ImageBase Target { get; } /// /// Gets the blend percentage @@ -68,8 +67,8 @@ namespace ImageSharp.Drawing.Processors /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. internal virtual void Apply(Span scanline, int x, int y) { - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index 64b91e384..96d824b2f 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -57,9 +57,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel TargeTPixel { get; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options); + return new RecolorBrushApplicator(source, this.SourceColor, this.TargeTPixel, this.Threshold, options); } /// @@ -87,22 +87,22 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// The source pixels. + /// The source image. /// Color of the source. /// Color of the target. /// The threshold . /// The options - public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) - : base(sourcePixels, options) + public RecolorBrushApplicator(ImageBase source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) + : base(source, options) { this.sourceColor = sourceColor.ToVector4(); this.targetColor = targetColor.ToVector4(); this.targetColorPixel = targetColor; - // Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :) - TPixel maxColor = default(TPixel); + // Lets hack a min max extreams for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :) + var maxColor = default(TPixel); maxColor.PackFromVector4(new Vector4(float.MaxValue)); - TPixel minColor = default(TPixel); + var minColor = default(TPixel); minColor.PackFromVector4(new Vector4(float.MinValue)); this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; } @@ -121,7 +121,7 @@ namespace ImageSharp.Drawing.Brushes { // Offset the requested pixel by the value in the rectangle (the shapes position) TPixel result = this.Target[x, y]; - Vector4 background = result.ToVector4(); + var background = result.ToVector4(); float distance = Vector4.DistanceSquared(background, this.sourceColor); if (distance <= this.threshold) { @@ -144,8 +144,8 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index 28f7b0e45..453b4d29e 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -42,9 +42,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel Color => this.color; /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new SolidBrushApplicator(sourcePixels, this.color, options); + return new SolidBrushApplicator(source, this.color, options); } /// @@ -55,13 +55,13 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// + /// The source image. /// The color. /// The options - /// The sourcePixels. - public SolidBrushApplicator(PixelAccessor sourcePixels, TPixel color, GraphicsOptions options) - : base(sourcePixels, options) + public SolidBrushApplicator(ImageBase source, TPixel color, GraphicsOptions options) + : base(source, options) { - this.Colors = new Buffer(sourcePixels.Width); + this.Colors = new Buffer(source.Width); for (int i = 0; i < this.Colors.Length; i++) { this.Colors[i] = color; @@ -94,7 +94,7 @@ namespace ImageSharp.Drawing.Brushes { Span destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 975bce9ed..db40132c8 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -1,129 +1,129 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Drawing.Processors; - using ImageSharp.PixelFormats; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The pixel format. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// The options. - /// The . - public static Image DrawImage(this Image source, Image image, Size size, Point location, GraphicsOptions options) - where TPixel : struct, IPixel - { - if (size == default(Size)) - { - size = new Size(image.Width, image.Height); - } - - if (location == default(Point)) - { - location = Point.Empty; - } - - source.ApplyProcessor(new DrawImageProcessor(image, size, location, options), source.Bounds); - return source; - } - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The . - public static Image Blend(this Image source, Image image, float percent) - where TPixel : struct, IPixel - { - GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; - return DrawImage(source, image, default(Size), default(Point), options); - } - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The blending mode. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The . - public static Image Blend(this Image source, Image image, PixelBlenderMode blender, float percent) - where TPixel : struct, IPixel - { - GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; - options.BlenderMode = blender; - return DrawImage(source, image, default(Size), default(Point), options); - } - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The options, including the blending type and belnding amount. - /// The . - public static Image Blend(this Image source, Image image, GraphicsOptions options) - where TPixel : struct, IPixel - { - return DrawImage(source, image, default(Size), default(Point), options); - } - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// The . - public static Image DrawImage(this Image source, Image image, float percent, Size size, Point location) - where TPixel : struct, IPixel - { - GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; - return source.DrawImage(image, size, location, options); - } - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The pixel format. - /// The type of bending to apply. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// The . - public static Image DrawImage(this Image source, Image image, PixelBlenderMode blender, float percent, Size size, Point location) - where TPixel : struct, IPixel - { - GraphicsOptions options = GraphicsOptions.Default; - options.BlenderMode = blender; - options.BlendPercentage = percent; - return source.DrawImage(image, size, location, options); - } - } +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Drawing.Processors; + using ImageSharp.PixelFormats; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The options. + /// The . + public static Image DrawImage(this Image source, Image image, Size size, Point location, GraphicsOptions options) + where TPixel : struct, IPixel + { + if (size == default(Size)) + { + size = new Size(image.Width, image.Height); + } + + if (location == default(Point)) + { + location = Point.Empty; + } + + source.ApplyProcessor(new DrawImageProcessor(image, size, location, options), source.Bounds); + return source; + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The . + public static Image Blend(this Image source, Image image, float percent) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + return DrawImage(source, image, default(Size), default(Point), options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The blending mode. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The . + public static Image Blend(this Image source, Image image, PixelBlenderMode blender, float percent) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + options.BlenderMode = blender; + return DrawImage(source, image, default(Size), default(Point), options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The options, including the blending type and belnding amount. + /// The . + public static Image Blend(this Image source, Image image, GraphicsOptions options) + where TPixel : struct, IPixel + { + return DrawImage(source, image, default(Size), default(Point), options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, float percent, Size size, Point location) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + return source.DrawImage(image, size, location, options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The type of bending to apply. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, PixelBlenderMode blender, float percent, Size size, Point location) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlenderMode = blender; + options.BlendPercentage = percent; + return source.DrawImage(image, size, location, options); + } + } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs index d488dbfb0..81d273091 100644 --- a/src/ImageSharp.Drawing/Pens/IPen.cs +++ b/src/ImageSharp.Drawing/Pens/IPen.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Drawing.Pens /// /// Creates the applicator for applying this pen to an Image /// - /// The pixel source. + /// The source image. /// The region the pen will be applied to. /// The currently active graphic options. /// @@ -27,6 +27,6 @@ namespace ImageSharp.Drawing.Pens /// /// The when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image. /// - PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); + PenApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options); } } diff --git a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs index 1da50e0d6..53a3c8c99 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs @@ -101,7 +101,7 @@ namespace ImageSharp.Drawing.Pens /// /// Creates the applicator for applying this pen to an Image /// - /// The source pixels. + /// The source image. /// The region the pen will be applied to. /// The Graphics options /// @@ -111,16 +111,16 @@ namespace ImageSharp.Drawing.Pens /// The when being applied to things like shapes would ussually be the /// bounding box of the shape not necorserrally the shape of the whole image /// - public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public PenApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { if (this.pattern == null || this.pattern.Length < 2) { // if there is only one item in the pattern then 100% of it will // be solid so use the quicker applicator - return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width, options); + return new SolidPenApplicator(source, this.Brush, region, this.Width, options); } - return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options); + return new PatternPenApplicator(source, this.Brush, region, this.Width, this.pattern, options); } private class SolidPenApplicator : PenApplicator @@ -128,7 +128,7 @@ namespace ImageSharp.Drawing.Pens private readonly BrushApplicator brush; private readonly float halfWidth; - public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, GraphicsOptions options) + public SolidPenApplicator(ImageBase sourcePixels, IBrush brush, RectangleF region, float width, GraphicsOptions options) { this.brush = brush.CreateApplicator(sourcePixels, region, options); this.halfWidth = width / 2; @@ -147,7 +147,7 @@ namespace ImageSharp.Drawing.Pens public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { - ColoredPointInfo result = default(ColoredPointInfo); + var result = default(ColoredPointInfo); result.Color = this.brush[x, y]; if (info.DistanceFromPath < this.halfWidth) @@ -171,9 +171,9 @@ namespace ImageSharp.Drawing.Pens private readonly float[] pattern; private readonly float totalLength; - public PatternPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, float[] pattern, GraphicsOptions options) + public PatternPenApplicator(ImageBase source, IBrush brush, RectangleF region, float width, float[] pattern, GraphicsOptions options) { - this.brush = brush.CreateApplicator(sourcePixels, region, options); + this.brush = brush.CreateApplicator(source, region, options); this.halfWidth = width / 2; this.totalLength = 0; @@ -200,7 +200,7 @@ namespace ImageSharp.Drawing.Pens public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { - ColoredPointInfo infoResult = default(ColoredPointInfo); + var infoResult = default(ColoredPointInfo); infoResult.DistanceFromElement = float.MaxValue; // is really outside the element float length = info.DistanceAlongPath % this.totalLength; diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index d1332c435..860c4c4f0 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -56,8 +56,7 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options)) + using (PenApplicator applicator = this.Pen.CreateApplicator(source, this.Path.Bounds, this.Options)) { Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); @@ -99,8 +98,8 @@ namespace ImageSharp.Drawing.Processors { int offsetY = y - polyStartY; - using (Buffer amount = new Buffer(width)) - using (Buffer colors = new Buffer(width)) + using (var amount = new Buffer(width)) + using (var colors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -112,7 +111,7 @@ namespace ImageSharp.Drawing.Processors colors[i] = color.Color; } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); + Span destination = source.GetRowSpan(offsetY).Slice(minX - startX, width); blender.Blend(destination, destination, colors, amount); } }); diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index fa6f48156..8c7cd4e8c 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -66,12 +66,11 @@ namespace ImageSharp.Drawing.Processors int width = maxX - minX; - // we could possibly do some optermising by having knowledge about the individual brushes operate + // We could possibly do some optimization by having knowledge about the individual brushes operate // for example If brush is SolidBrush then we could just get the color upfront // and skip using the IBrushApplicator?. - using (PixelAccessor sourcePixels = source.Lock()) - using (Buffer amount = new Buffer(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options)) + using (var amount = new Buffer(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options)) { for (int i = 0; i < width; i++) { diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index a57be3a5a..ae828e112 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors { using System; using System.Buffers; + using System.Runtime.CompilerServices; using Drawing; using ImageSharp.Memory; @@ -89,12 +90,11 @@ namespace ImageSharp.Drawing.Processors } } - using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options)) + using (BrushApplicator applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; - using (Buffer scanline = new Buffer(scanlineWidth)) + using (var scanline = new Buffer(scanlineWidth)) { try { @@ -193,6 +193,7 @@ namespace ImageSharp.Drawing.Processors } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Swap(float[] data, int left, int right) { float tmp = data[left]; diff --git a/src/ImageSharp/Colors/Spaces/CieLab.cs b/src/ImageSharp/ColorSpaces/CieLab.cs similarity index 51% rename from src/ImageSharp/Colors/Spaces/CieLab.cs rename to src/ImageSharp/ColorSpaces/CieLab.cs index c1e5cba5a..d382bbedb 100644 --- a/src/ImageSharp/Colors/Spaces/CieLab.cs +++ b/src/ImageSharp/ColorSpaces/CieLab.cs @@ -3,33 +3,29 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Colors.Spaces +namespace ImageSharp.ColorSpaces { using System; using System.ComponentModel; using System.Numerics; - using ImageSharp.PixelFormats; + using System.Runtime.CompilerServices; /// - /// Represents an CIE LAB 1976 color. + /// Represents a CIE L*a*b* 1976 color. /// /// - public struct CieLab : IEquatable, IAlmostEquatable + internal struct CieLab : IColorVector, IEquatable, IAlmostEquatable { /// - /// Represents a that has L, A, B values set to zero. + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. /// - public static readonly CieLab Empty = default(CieLab); + public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; /// - /// Min range used for clamping - /// - private static readonly Vector3 VectorMin = new Vector3(0, -100, -100); - - /// - /// Max range used for clamping + /// Represents a that has L, A, B values set to zero. /// - private static readonly Vector3 VectorMax = new Vector3(100); + public static readonly CieLab Empty = default(CieLab); /// /// The backing vector for SIMD support. @@ -42,29 +38,88 @@ namespace ImageSharp.Colors.Spaces /// The lightness dimension. /// The a (green - magenta) component. /// The b (blue - yellow) component. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieLab(float l, float a, float b) + : this(new Vector3(l, a, b), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLab(float l, float a, float b, CieXyz whitePoint) + : this(new Vector3(l, a, b), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLab(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// The reference white point. + [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; + } + + /// + /// Gets the reference white point of this color + /// + public CieXyz WhitePoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; } /// /// Gets the lightness dimension. /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). /// - public float L => this.backingVector.X; + public float L + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } /// /// Gets the a color component. - /// Negative is green, positive magenta. + /// A value ranging from -100 to 100. Negative is green, positive magenta. /// - public float A => this.backingVector.Y; + public float A + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } /// /// Gets the b color component. - /// Negative is blue, positive is yellow + /// A value ranging from -100 to 100. Negative is blue, positive is yellow /// - public float B => this.backingVector.Z; + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } /// /// Gets a value indicating whether this is empty. @@ -72,39 +127,11 @@ namespace ImageSharp.Colors.Spaces [EditorBrowsable(EditorBrowsableState.Never)] public bool IsEmpty => this.Equals(Empty); - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - public static implicit operator CieLab(Rgba32 color) + /// + 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; } /// @@ -119,6 +146,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(CieLab left, CieLab right) { return left.Equals(right); @@ -136,6 +164,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is unequal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(CieLab left, CieLab right) { return !left.Equals(right); @@ -144,7 +173,12 @@ namespace ImageSharp.Colors.Spaces /// public override int GetHashCode() { - return this.backingVector.GetHashCode(); + unchecked + { + int hashCode = this.WhitePoint.GetHashCode(); + hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode(); + return hashCode; + } } /// @@ -159,6 +193,7 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is CieLab) @@ -170,19 +205,23 @@ namespace ImageSharp.Colors.Spaces } /// + [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); } /// + [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; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs new file mode 100644 index 000000000..03cf80bf1 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieLch.cs @@ -0,0 +1,247 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. + /// + /// + internal struct CieLch : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; + + /// + /// Represents a that has L, C, H values set to zero. + /// + public static readonly CieLch Empty = default(CieLch); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLch(float l, float c, float h) + : this(new Vector3(l, c, h), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLch(float l, float c, float h, CieXyz whitePoint) + : this(new Vector3(l, c, h), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLch(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLch(Vector3 vector, CieXyz whitePoint) + : this() + { + this.backingVector = vector; + this.WhitePoint = whitePoint; + } + + /// + /// Gets the reference white point of this color + /// + public CieXyz WhitePoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public float L + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 100. + /// + public float C + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. + /// + public float H + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(CieLch left, CieLch right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(CieLch left, CieLch right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.WhitePoint.GetHashCode(); + hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieLch [Empty]"; + } + + return $"CieLch [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is CieLch) + { + return this.Equals((CieLch)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(CieLch other) + { + return this.backingVector.Equals(other.backingVector) + && this.WhitePoint.Equals(other.WhitePoint); + } + + /// + [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; + } + + /// + /// Computes the saturation of the color (chroma normalized by lightness) + /// + /// + /// A value ranging from 0 to 100. + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Saturation() + { + float result = 100 * (this.C / this.L); + + if (float.IsNaN(result)) + { + return 0; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLchuv.cs b/src/ImageSharp/ColorSpaces/CieLchuv.cs new file mode 100644 index 000000000..a4e8b424d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieLchuv.cs @@ -0,0 +1,247 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. + /// + /// + internal struct CieLchuv : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; + + /// + /// Represents a that has L, C, H values set to zero. + /// + public static readonly CieLchuv Empty = default(CieLchuv); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLchuv(float l, float c, float h) + : this(new Vector3(l, c, h), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLchuv(float l, float c, float h, CieXyz whitePoint) + : this(new Vector3(l, c, h), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLchuv(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLchuv(Vector3 vector, CieXyz whitePoint) + : this() + { + this.backingVector = vector; + this.WhitePoint = whitePoint; + } + + /// + /// Gets the reference white point of this color + /// + public CieXyz WhitePoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public float L + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 100. + /// + public float C + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. + /// + public float H + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(CieLchuv left, CieLchuv right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(CieLchuv left, CieLchuv right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.WhitePoint.GetHashCode(); + hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieLchuv [Empty]"; + } + + return $"CieLchuv [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is CieLchuv) + { + return this.Equals((CieLchuv)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(CieLchuv other) + { + return this.backingVector.Equals(other.backingVector) + && this.WhitePoint.Equals(other.WhitePoint); + } + + /// + [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; + } + + /// + /// Computes the saturation of the color (chroma normalized by lightness) + /// + /// + /// A value ranging from 0 to 100. + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Saturation() + { + float result = 100 * (this.C / this.L); + + if (float.IsNaN(result)) + { + return 0; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLuv.cs b/src/ImageSharp/ColorSpaces/CieLuv.cs new file mode 100644 index 000000000..c3fd626e6 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieLuv.cs @@ -0,0 +1,229 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// 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 + /// + /// + internal struct CieLuv : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// D65 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; + + /// + /// Represents a that has L, U, and V values set to zero. + /// + public static readonly CieLuv Empty = default(CieLuv); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The blue-yellow chromaticity coordinate of the given whitepoint. + /// The red-green chromaticity coordinate of the given whitepoint. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLuv(float l, float u, float v) + : this(new Vector3(l, u, v), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The blue-yellow chromaticity coordinate of the given whitepoint. + /// The red-green chromaticity coordinate of the given whitepoint. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLuv(float l, float u, float v, CieXyz whitePoint) + : this(new Vector3(l, u, v), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, u, v components. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLuv(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, u, v components. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieLuv(Vector3 vector, CieXyz whitePoint) + : this() + { + this.backingVector = vector; + this.WhitePoint = whitePoint; + } + + /// + /// Gets the reference white point of this color + /// + public CieXyz WhitePoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// Gets the lightness dimension + /// A value usually ranging between 0 and 100. + /// + public float L + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the blue-yellow chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public float U + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the red-green chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public float V + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(CieLuv left, CieLuv right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(CieLuv left, CieLuv right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.WhitePoint.GetHashCode(); + hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieLuv [ Empty ]"; + } + + return $"CieLuv [ L={this.L:#0.##}, U={this.U:#0.##}, V={this.V:#0.##} ]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is CieLuv) + { + return this.Equals((CieLuv)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(CieLuv other) + { + return this.backingVector.Equals(other.backingVector) + && this.WhitePoint.Equals(other.WhitePoint); + } + + /// + [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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs new file mode 100644 index 000000000..d9bfe88cd --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs @@ -0,0 +1,161 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents the coordinates of CIEXY chromaticity space + /// + internal struct CieXyChromaticityCoordinates : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has X, Y values set to zero. + /// + public static readonly CieXyChromaticityCoordinates Empty = default(CieXyChromaticityCoordinates); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector2 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// Chromaticity coordinate x (usually from 0 to 1) + /// Chromaticity coordinate y (usually from 0 to 1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyChromaticityCoordinates(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the XY Chromaticity coordinates + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyChromaticityCoordinates(Vector2 vector) + { + this.backingVector = vector; + } + + /// + /// Gets the chromaticity X-coordinate. + /// + /// + /// Ranges usually from 0 to 1. + /// + public float X + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the chromaticity Y-coordinate + /// + /// + /// Ranges usually from 0 to 1. + /// + public float Y + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return this.backingVector.GetHashCode(); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieXyChromaticityCoordinates [Empty]"; + } + + return $"CieXyChromaticityCoordinates [ X={this.X:#0.##}, Y={this.Y:#0.##}]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is CieXyChromaticityCoordinates) + { + return this.Equals((CieXyChromaticityCoordinates)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(CieXyChromaticityCoordinates other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + [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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyy.cs b/src/ImageSharp/ColorSpaces/CieXyy.cs new file mode 100644 index 000000000..1578f1ac3 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/CieXyy.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents an CIE xyY 1931 color + /// + /// + internal struct CieXyy : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// Represents a that has X, Y, and Y values set to zero. + /// + public static readonly CieXyy Empty = default(CieXyy); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The x chroma component. + /// The y chroma component. + /// The y luminance component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyy(float x, float y, float yl) + : this(new Vector3(x, y, yl)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the x, y, Y components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyy(Vector3 vector) + : this() + { + // Not clamping as documentation about this space seems to indicate "usual" ranges + this.backingVector = vector; + } + + /// + /// Gets the X chrominance component. + /// A value usually ranging between 0 and 1. + /// + public float X + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the Y chrominance component. + /// A value usually ranging between 0 and 1. + /// + public float Y + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. + /// + public float Yl + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(CieXyy left, CieXyy right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(CieXyy left, CieXyy right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return this.backingVector.GetHashCode(); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieXyy [ Empty ]"; + } + + return $"CieXyy [ X={this.X:#0.##}, Y={this.Y:#0.##}, Yl={this.Yl:#0.##} ]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is CieXyy) + { + return this.Equals((CieXyy)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(CieXyy other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + [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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs similarity index 65% rename from src/ImageSharp/Colors/Spaces/CieXyz.cs rename to src/ImageSharp/ColorSpaces/CieXyz.cs index 9c6c9bf60..8d3255e65 100644 --- a/src/ImageSharp/Colors/Spaces/CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/CieXyz.cs @@ -3,21 +3,21 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Colors.Spaces +namespace ImageSharp.ColorSpaces { using System; using System.ComponentModel; using System.Numerics; - using ImageSharp.PixelFormats; + using System.Runtime.CompilerServices; /// - /// Represents an CIE 1931 color - /// + /// Represents an CIE XYZ 1931 color + /// /// - public struct CieXyz : IEquatable, IAlmostEquatable + internal struct CieXyz : IColorVector, IEquatable, IAlmostEquatable { /// - /// Represents a that has Y, Cb, and Cr values set to zero. + /// Represents a that has X, Y, and Z values set to zero. /// public static readonly CieXyz Empty = default(CieXyz); @@ -32,30 +32,53 @@ namespace ImageSharp.Colors.Spaces /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative /// The y luminance component. /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieXyz(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the x, y, z components. + [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; } /// - /// Gets the Y luminance component. - /// A value ranging between 380 and 780. + /// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative. + /// A value usually ranging between 0 and 1. /// - public float X => this.backingVector.X; + public float X + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } /// - /// Gets the Cb chroma component. - /// A value ranging between 380 and 780. + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. /// - public float Y => this.backingVector.Y; + public float Y + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } /// - /// Gets the Cr chroma component. - /// A value ranging between 380 and 780. + /// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response + /// A value usually ranging between 0 and 1. /// - public float Z => this.backingVector.Z; + public float Z + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } /// /// Gets a value indicating whether this is empty. @@ -63,29 +86,11 @@ namespace ImageSharp.Colors.Spaces [EditorBrowsable(EditorBrowsableState.Never)] public bool IsEmpty => this.Equals(Empty); - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - public static implicit operator CieXyz(Rgba32 color) + /// + 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; } /// @@ -100,6 +105,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(CieXyz left, CieXyz right) { return left.Equals(right); @@ -117,6 +123,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is unequal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(CieXyz left, CieXyz right) { return !left.Equals(right); @@ -140,6 +147,7 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is CieXyz) @@ -151,19 +159,21 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(CieXyz other) { return this.backingVector.Equals(other.backingVector); } /// + [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; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs similarity index 72% rename from src/ImageSharp/Colors/Spaces/Cmyk.cs rename to src/ImageSharp/ColorSpaces/Cmyk.cs index 4ca9f018c..eeaef21bd 100644 --- a/src/ImageSharp/Colors/Spaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -3,33 +3,23 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Colors.Spaces +namespace ImageSharp.ColorSpaces { using System; using System.ComponentModel; using System.Numerics; - using ImageSharp.PixelFormats; + using System.Runtime.CompilerServices; /// /// Represents an CMYK (cyan, magenta, yellow, keyline) color. /// - public struct Cmyk : IEquatable, IAlmostEquatable + internal struct Cmyk : IEquatable, IAlmostEquatable { /// /// Represents a that has C, M, Y, and K values set to zero. /// public static readonly Cmyk Empty = default(Cmyk); - /// - /// Min range used for clamping - /// - private static readonly Vector4 VectorMin = Vector4.Zero; - - /// - /// Max range used for clamping - /// - private static readonly Vector4 VectorMax = Vector4.One; - /// /// The backing vector for SIMD support. /// @@ -42,35 +32,62 @@ namespace ImageSharp.Colors.Spaces /// The magenta component. /// The yellow component. /// The keyline black component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Cmyk(float c, float m, float y, float k) + : this(new Vector4(c, m, y, k)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the c, m, y, k components. + [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); } /// /// Gets the cyan color component. /// A value ranging between 0 and 1. /// - public float C => this.backingVector.X; + public float C + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } /// /// Gets the magenta color component. /// A value ranging between 0 and 1. /// - public float M => this.backingVector.Y; + public float M + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } /// /// Gets the yellow color component. /// A value ranging between 0 and 1. /// - public float Y => this.backingVector.Z; + public float Y + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } /// /// Gets the keyline black color component. /// A value ranging between 0 and 1. /// - public float K => this.backingVector.W; + public float K + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.W; + } /// /// Gets a value indicating whether this is empty. @@ -78,36 +95,6 @@ namespace ImageSharp.Colors.Spaces [EditorBrowsable(EditorBrowsableState.Never)] public bool IsEmpty => this.Equals(Empty); - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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); - } - /// /// Compares two objects for equality. /// @@ -120,6 +107,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Cmyk left, Cmyk right) { return left.Equals(right); @@ -137,6 +125,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is unequal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Cmyk left, Cmyk right) { return !left.Equals(right); @@ -160,6 +149,7 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is Cmyk) @@ -171,15 +161,17 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Cmyk other) { return this.backingVector.Equals(other.backingVector); } /// + [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; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs new file mode 100644 index 000000000..29200823e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + /// + /// Constants use for Cie conversion calculations + /// + /// + internal static class CieConstants + { + /// + /// 216F / 24389F + /// + public const float Epsilon = 0.008856452F; + + /// + /// 24389F / 27F + /// + public const float Kappa = 903.2963F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs new file mode 100644 index 000000000..acdb356a2 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -0,0 +1,198 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using System; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb; + + /// + /// Performs chromatic adaptation on the various color spaces. + /// + internal partial class ColorSpaceConverter + { + /// + /// Performs chromatic adaptation of given color. + /// Target white point is . + /// + /// The color to adapt + /// The white point to adapt for + /// The adapted color + 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); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + 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); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + 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); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + 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); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + 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); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + 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); + } + + /// + /// Adapts a color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + 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); + } + + /// + /// Adapts an color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + public Rgb Adapt(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + LinearRgb linearInput = this.ToLinearRgb(color); + LinearRgb linearOutput = this.Adapt(linearInput); + return this.ToRgb(linearOutput); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs new file mode 100644 index 000000000..2c274c17a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -0,0 +1,205 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.CieLab; + using ImageSharp.ColorSpaces.Conversion.Implementation.CieLch; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + /// + /// The converter for converting between CieLch to CieLab. + /// + private static readonly CieLchToCieLabConverter CieLchToCieLabConverter = new CieLchToCieLabConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(Lms color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs new file mode 100644 index 000000000..e3b3975a4 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -0,0 +1,192 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces.Conversion.Implementation.CieLch; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + /// + /// The converter for converting between CieLab to CieLch. + /// + private static readonly CieLabToCieLchConverter CieLabToCieLchConverter = new CieLabToCieLchConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + // Adaptation + CieLab adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color; + + // Conversion + return CieLabToCieLchConverter.Convert(adapted); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(Lms color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs new file mode 100644 index 000000000..7f2d18480 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs @@ -0,0 +1,192 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + /// + /// The converter for converting between CieLab to CieLchuv. + /// + private static readonly CieLuvToCieLchuvConverter CieLuvToCieLchuvConverter = new CieLuvToCieLchuvConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + // Adaptation + CieLuv adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color; + + // Conversion + return CieLuvToCieLchuvConverter.Convert(adapted); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLchuv(labColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(Lms color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs new file mode 100644 index 000000000..dc63e7a67 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs @@ -0,0 +1,202 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv; + using ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly CieLchuvToCieLuvConverter CieLchuvToCieLuvConverter = new CieLchuvToCieLuvConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs new file mode 100644 index 000000000..6c4b6ca53 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs @@ -0,0 +1,197 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces.Conversion.Implementation.CieXyy; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly CieXyzAndCieXyyConverter CieXyzAndCieXyyConverter = new CieXyzAndCieXyyConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + return CieXyzAndCieXyyConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCieXyy(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs new file mode 100644 index 000000000..ca8b31d03 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -0,0 +1,253 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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; + + /// + /// Allows conversion to . + /// + 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; + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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; + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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; + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return CieXyzAndCieXyyConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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; + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(Lms color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return this.cachedCieXyzAndLmsConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + LinearRgb linear = RgbToLinearRgbConverter.Convert(color); + return this.ToCieXyz(linear); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + var rgb = this.ToRgb(color); + + return this.ToCieXyz(rgb); + } + + /// + /// Gets the correct converter for the given rgb working space. + /// + /// The source working space + /// The + private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace) + { + if (this.linearRgbToCieXyzConverter != null && this.linearRgbToCieXyzConverter.SourceWorkingSpace.Equals(workingSpace)) + { + return this.linearRgbToCieXyzConverter; + } + + return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs new file mode 100644 index 000000000..9cfa8f0c3 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs @@ -0,0 +1,198 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.Cmyk; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly CmykAndRgbConverter CmykAndRgbConverter = new CmykAndRgbConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToCmyk(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + return CmykAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return CmykAndRgbConverter.Convert(rgb); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs new file mode 100644 index 000000000..9e4a8d9c3 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs @@ -0,0 +1,198 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.Hsl; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly HslAndRgbConverter HslAndRgbConverter = new HslAndRgbConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsl(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + return HslAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HslAndRgbConverter.Convert(rgb); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs new file mode 100644 index 000000000..80b235894 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs @@ -0,0 +1,198 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.Hsv; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly HsvAndRgbConverter HsvAndRgbConverter = new HsvAndRgbConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToHsv(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + return HsvAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return HsvAndRgbConverter.Convert(rgb); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs new file mode 100644 index 000000000..b6d9d4cb9 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs @@ -0,0 +1,189 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs new file mode 100644 index 000000000..5fcc2cd5e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -0,0 +1,209 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter(); + + private CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter; + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return RgbToLinearRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + return this.ToLinearRgb(rgb); + } + + /// + /// Gets the correct converter for the given rgb working space. + /// + /// The target working space + /// The + private CieXyzToLinearRgbConverter GetCieXyxToLinearRgbConverter(IRgbWorkingSpace workingSpace) + { + if (this.cieXyzToLinearRgbConverter != null && this.cieXyzToLinearRgbConverter.TargetWorkingSpace.Equals(workingSpace)) + { + return this.cieXyzToLinearRgbConverter; + } + + return this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(workingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs new file mode 100644 index 000000000..8d888182f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return this.cachedCieXyzAndLmsConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(YCbCr color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs new file mode 100644 index 000000000..1cfd565e8 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -0,0 +1,193 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly LinearRgbToRgbConverter LinearRgbToRgbConverter = new LinearRgbToRgbConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + var linear = this.ToLinearRgb(color); + + // Compand + return this.ToRgb(linear); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return CmykAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return HsvAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return HslAndRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion + return LinearRgbToRgbConverter.Convert(color); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs new file mode 100644 index 000000000..6660f579a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs @@ -0,0 +1,198 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.YCbCr; + + /// + /// Allows conversion to . + /// + internal partial class ColorSpaceConverter + { + private static readonly YCbCrAndRgbConverter YCbCrAndRgbConverter = new YCbCrAndRgbConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(CieLchuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(CieLuv color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(CieXyy color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(Cmyk color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(Hsl color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(Hsv color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + var rgb = this.ToRgb(color); + + return YCbCrAndRgbConverter.Convert(rgb); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(Lms color) + { + Guard.NotNull(color, nameof(color)); + + var xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + return YCbCrAndRgbConverter.Convert(color); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs new file mode 100644 index 000000000..a52207cb4 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using System.Numerics; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.Lms; + + /// + /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation. + /// + internal partial class ColorSpaceConverter + { + /// + /// The default whitepoint used for converting to CieLab + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; + + private Matrix4x4 transformationMatrix; + + private CieXyzAndLmsConverter cachedCieXyzAndLmsConverter; + + /// + /// Initializes a new instance of the class. + /// + 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; + } + + /// + /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space. + /// When null, no adaptation will be performed. + /// + public CieXyz WhitePoint { get; set; } + + /// + /// 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: . + /// + public CieXyz TargetLuvWhitePoint { get; set; } + + /// + /// 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: . + /// + public CieXyz TargetLabWhitePoint { get; set; } + + /// + /// Gets or sets the white point used *when creating* HunterLab colors. (HunterLab colors on the input already contain the white point information) + /// Defaults to: . + /// + public CieXyz TargetHunterLabWhitePoint { get; set; } + + /// + /// 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: . + /// + public IRgbWorkingSpace TargetRgbWorkingSpace { get; set; } + + /// + /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed. + /// + public IChromaticAdaptation ChromaticAdaptation { get; set; } + + /// + /// Gets or sets transformation matrix used in conversion to , + /// also used in the default Von Kries Chromatic Adaptation method. + /// + public Matrix4x4 LmsAdaptationMatrix + { + get => this.transformationMatrix; + + set + { + this.transformationMatrix = value; + if (this.cachedCieXyzAndLmsConverter == null) + { + this.cachedCieXyzAndLmsConverter = new CieXyzAndLmsConverter(value); + } + else + { + this.cachedCieXyzAndLmsConverter.TransformationMatrix = value; + } + } + } + + /// + /// Gets a value indicating whether chromatic adaptation has been performed. + /// + private bool IsChromaticAdaptationPerformed => this.ChromaticAdaptation != null; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs new file mode 100644 index 000000000..aead65e59 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using ImageSharp.ColorSpaces; + + /// + /// 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). + /// + internal interface IChromaticAdaptation + { + /// + /// Performs a linear transformation of a source color in to the destination color. + /// + /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). + /// The source color. + /// The source white point. + /// The target white point. + /// The + CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs b/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs new file mode 100644 index 000000000..920fba18f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + /// + /// Converts color between two color spaces. + /// + /// The input color type. + /// The result color type. + internal interface IColorConversion + { + /// + /// Performs the conversion from the input to an instance of the output type. + /// + /// The input color instance. + /// The converted result + TResult Convert(T input); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs new file mode 100644 index 000000000..71b1596ca --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLab +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieLabToCieXyzConverter : IColorConversion + { + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs new file mode 100644 index 000000000..8d877503a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLab +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieXyzToCieLabConverter : IColorConversion + { + /// + /// Initializes a new instance of the class. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzToCieLabConverter() + : this(CieLab.DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target reference lab white point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzToCieLabConverter(CieXyz labWhitePoint) + { + this.LabWhitePoint = labWhitePoint; + } + + /// + /// Gets the target reference whitepoint. When not set, is used. + /// + public CieXyz LabWhitePoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs new file mode 100644 index 000000000..5b63b167e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLch +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieLchToCieLabConverter : IColorConversion + { + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs new file mode 100644 index 000000000..b93dce75a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLch +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieLabToCieLchConverter : IColorConversion + { + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs new file mode 100644 index 000000000..3e399016a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieLchuvToCieLuvConverter : IColorConversion + { + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs new file mode 100644 index 000000000..5339f1bcd --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieLuvToCieLchuvConverter : IColorConversion + { + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs new file mode 100644 index 000000000..36c458828 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv +{ + using System.Runtime.CompilerServices; + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieLuvToCieXyzConverter : IColorConversion + { + /// + [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); + } + + /// + /// Calculates the blue-yellow chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ComputeU0(CieXyz input) + { + return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + } + + /// + /// Calculates the red-green chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ComputeV0(CieXyz input) + { + return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs new file mode 100644 index 000000000..3883f3a1e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs @@ -0,0 +1,102 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Converts from to . + /// + internal class CieXyzToCieLuvConverter : IColorConversion + { + /// + /// Initializes a new instance of the class. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzToCieLuvConverter() + : this(CieLuv.DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target reference luv white point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzToCieLuvConverter(CieXyz luvWhitePoint) + { + this.LuvWhitePoint = luvWhitePoint; + } + + /// + /// Gets the target reference whitepoint. When not set, is used. + /// + public CieXyz LuvWhitePoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + [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); + } + + /// + /// Calculates the blue-yellow chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ComputeUp(CieXyz input) + { + return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + } + + /// + /// Calculates the red-green chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + private static float ComputeVp(CieXyz input) + { + return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs new file mode 100644 index 000000000..dc4a5ccf4 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieXyy +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between CIE XYZ and CIE xyY + /// for formulas. + /// + internal class CieXyzAndCieXyyConverter : IColorConversion, IColorConversion + { + /// + [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); + } + + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs new file mode 100644 index 000000000..b50e89b18 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Cmyk +{ + using System; + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between CMYK and Rgb + /// + internal class CmykAndRgbConverter : IColorConversion, IColorConversion + { + /// + [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); + } + + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs new file mode 100644 index 000000000..6c72cf294 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs @@ -0,0 +1,159 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Hsl +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between HSL and Rgb + /// See for formulas. + /// + internal class HslAndRgbConverter : IColorConversion, IColorConversion + { + /// + [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); + } + + /// + [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); + } + + /// + /// Gets the color component from the given values. + /// + /// The first value. + /// The second value. + /// The third value. + /// + /// The . + /// + [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; + } + + /// + /// Moves the specific value within the acceptable range for + /// conversion. + /// Used for converting colors to this type. + /// + /// The value to shift. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float MoveIntoRange(float value) + { + if (value < 0F) + { + value += 1F; + } + else if (value > 1F) + { + value -= 1F; + } + + return value; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs new file mode 100644 index 000000000..54c8e405f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs @@ -0,0 +1,130 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Hsv +{ + using System; + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between HSV and Rgb + /// See for formulas. + /// + internal class HsvAndRgbConverter : IColorConversion, IColorConversion + { + /// + [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); + } + + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs new file mode 100644 index 000000000..19fc78e9a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab +{ + using System.Runtime.CompilerServices; + + /// + /// The base class for converting between and color spaces. + /// + internal abstract class CieXyzAndHunterLabConverterBase + { + /// + /// Returns the Ka coefficient that depends upon the whitepoint illuminant. + /// + /// The whitepoint + /// The + [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); + } + + /// + /// Returns the Kb coefficient that depends upon the whitepoint illuminant. + /// + /// The whitepoint + /// The + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs new file mode 100644 index 000000000..7f2df8336 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between CieXyz and HunterLab + /// + internal class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase, IColorConversion + { + /// + /// Initializes a new instance of the class. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzToHunterLabConverter() + : this(HunterLab.DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The hunter Lab white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzToHunterLabConverter(CieXyz labWhitePoint) + { + this.HunterLabWhitePoint = labWhitePoint; + } + + /// + /// Gets the target reference white. When not set, is used. + /// + public CieXyz HunterLabWhitePoint + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs new file mode 100644 index 000000000..af7f4f370 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab +{ + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between HunterLab and CieXyz + /// + internal class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase, IColorConversion + { + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs new file mode 100644 index 000000000..c856c7ff7 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs @@ -0,0 +1,85 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Lms +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between CIE XYZ and LMS + /// + internal class CieXyzAndLmsConverter : IColorConversion, IColorConversion + { + /// + /// Default transformation matrix used, when no other is set. (Bradford) + /// + /// + public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford; + + private Matrix4x4 inverseTransformationMatrix; + private Matrix4x4 transformationMatrix; + + /// + /// Initializes a new instance of the class. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzAndLmsConverter() + : this(DefaultTransformationMatrix) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Definition of the cone response domain (see ), + /// if not set will be used. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix) + { + this.TransformationMatrix = transformationMatrix; + } + + /// + /// Gets or sets the transformation matrix used for the conversion (definition of the cone response domain). + /// + /// + public Matrix4x4 TransformationMatrix + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.transformationMatrix; + + set + { + this.transformationMatrix = value; + Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix); + } + } + + /// + [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); + } + + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs new file mode 100644 index 000000000..279044baa --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Lms +{ + using System.Numerics; + + /// + /// AdaptionMatrix3X3 used for transformation from XYZ to LMS, defining the cone response domain. + /// Used in + /// + /// + /// 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 + /// + public static class LmsAdaptationMatrix + { + /// + /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65) + /// + 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. + }); + + /// + /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy) + /// + 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 + }); + + /// + /// XYZ scaling chromatic adaptation transform matrix + /// + public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity); + + /// + /// Bradford chromatic adaptation transform matrix (used in CMCCAT97) + /// + 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 + }); + + /// + /// Spectral sharpening and the Bradford transform + /// + 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 + }); + + /// + /// CMCCAT2000 (fitted from all available color data sets) + /// + 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 + }); + + /// + /// CAT02 (optimized for minimizing CIELAB differences) + /// + 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 + }); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs new file mode 100644 index 000000000..c5c612409 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Numerics; + + using Rgb = ImageSharp.ColorSpaces.Rgb; + + /// + /// Color converter between CieXyz and LinearRgb + /// + internal class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase, IColorConversion + { + private readonly Matrix4x4 conversionMatrix; + + /// + /// Initializes a new instance of the class. + /// + public CieXyzToLinearRgbConverter() + : this(Rgb.DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target working space. + public CieXyzToLinearRgbConverter(IRgbWorkingSpace workingSpace) + { + this.TargetWorkingSpace = workingSpace; + this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); + } + + /// + /// Gets the target working space + /// + public IRgbWorkingSpace TargetWorkingSpace { get; } + + /// + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs new file mode 100644 index 000000000..08200ce6b --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Implements gamma companding + /// + /// + /// + /// + /// + public class GammaCompanding : ICompanding + { + /// + /// Initializes a new instance of the class. + /// + /// The gamma value. + public GammaCompanding(float gamma) + { + this.Gamma = gamma; + } + + /// + /// Gets the gamma value + /// + public float Gamma { get; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Expand(float channel) + { + return MathF.Pow(channel, this.Gamma); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Compress(float channel) + { + return MathF.Pow(channel, 1 / this.Gamma); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs new file mode 100644 index 000000000..bbf12ca00 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Runtime.CompilerServices; + + /// + /// Implements L* companding + /// + /// + /// For more info see: + /// + /// + /// + public class LCompanding : ICompanding + { + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Expand(float channel) + { + return channel <= 0.08 ? 100 * channel / CieConstants.Kappa : MathF.Pow((channel + 0.16F) / 1.16F, 3); + } + + /// + [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; + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs new file mode 100644 index 000000000..4c46e4d8c --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Numerics; + + /// + /// Provides base methods for converting between Rgb and CieXyz color spaces. + /// + internal abstract class LinearRgbAndCieXyzConverterBase + { + /// + /// Geturns the correct matrix to convert between the Rgb and CieXyz color space. + /// + /// The Rgb working space. + /// The based on the chromaticity and working space. + 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 + }; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs new file mode 100644 index 000000000..4a04185e8 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Numerics; + + using Rgb = ImageSharp.ColorSpaces.Rgb; + + /// + /// Color converter between LinearRgb and CieXyz + /// + internal class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase, IColorConversion + { + private readonly Matrix4x4 conversionMatrix; + + /// + /// Initializes a new instance of the class. + /// + public LinearRgbToCieXyzConverter() + : this(Rgb.DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target working space. + public LinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace) + { + this.SourceWorkingSpace = workingSpace; + this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); + } + + /// + /// Gets the source working space + /// + public IRgbWorkingSpace SourceWorkingSpace { get; } + + /// + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs new file mode 100644 index 000000000..cf4638ae7 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Numerics; + + using Rgb = ColorSpaces.Rgb; + + /// + /// Color converter between LinearRgb and Rgb + /// + internal class LinearRgbToRgbConverter : IColorConversion + { + /// + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs new file mode 100644 index 000000000..0148b91d5 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System; + + /// + /// Represents the chromaticity coordinates of RGB primaries. + /// One of the specifiers of . + /// + internal struct RgbPrimariesChromaticityCoordinates : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The chomaticity coordinates of the red channel. + /// The chomaticity coordinates of the green channel. + /// The chomaticity coordinates of the blue channel. + public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b) + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Gets the chomaticity coordinates of the red channel. + /// + public CieXyChromaticityCoordinates R { get; } + + /// + /// Gets the chomaticity coordinates of the green channel. + /// + public CieXyChromaticityCoordinates G { get; } + + /// + /// Gets the chomaticity coordinates of the blue channel. + /// + public CieXyChromaticityCoordinates B { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + if (obj is RgbPrimariesChromaticityCoordinates) + { + return this.Equals((RgbPrimariesChromaticityCoordinates)obj); + } + + return false; + } + + /// + public bool Equals(RgbPrimariesChromaticityCoordinates other) + { + return this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.R.GetHashCode(); + hashCode = (hashCode * 397) ^ this.G.GetHashCode(); + hashCode = (hashCode * 397) ^ this.B.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs new file mode 100644 index 000000000..630faedca --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Runtime.CompilerServices; + + /// + /// Implements Rec. 2020 companding function (for 12-bits). + /// + /// + /// + /// For 10-bits, companding is identical to + /// + public class Rec2020Companding : ICompanding + { + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Expand(float channel) + { + return channel < 0.08145F ? channel / 4.5F : MathF.Pow((channel + 0.0993F) / 1.0993F, 2.222222F); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Compress(float channel) + { + return channel < 0.0181F ? 4500F * channel : (1.0993F * channel) - 0.0993F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs new file mode 100644 index 000000000..24dd6ca17 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Runtime.CompilerServices; + + /// + /// Implements the Rec. 709 companding function + /// + /// + /// http://en.wikipedia.org/wiki/Rec._709 + /// + public class Rec709Companding : ICompanding + { + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Expand(float channel) + { + return channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, 2.222222F); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Compress(float channel) + { + return channel < 0.018F ? 4500F * channel : (1.099F * channel) - 0.099F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs new file mode 100644 index 000000000..0d0d58828 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Numerics; + + using Rgb = ColorSpaces.Rgb; + + /// + /// Color converter between Rgb and LinearRgb + /// + internal class RgbToLinearRgbConverter : IColorConversion + { + /// + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs new file mode 100644 index 000000000..89e403e76 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + /// + /// Trivial implementation of + /// + internal struct RgbWorkingSpace : IRgbWorkingSpace + { + /// + /// Initializes a new instance of the struct. + /// + /// The reference white point. + /// The function pair for converting to and back. + /// The chromaticity of the rgb primaries. + public RgbWorkingSpace(CieXyz referenceWhite, ICompanding companding, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + { + this.WhitePoint = referenceWhite; + this.Companding = companding; + this.ChromaticityCoordinates = chromaticityCoordinates; + } + + /// + /// Gets the reference white point + /// + public CieXyz WhitePoint { get; } + + /// + /// Gets the function pair for converting to and back. + /// + public ICompanding Companding { get; } + + /// + /// Gets the chromaticity of the rgb primaries. + /// + public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(RgbWorkingSpace left, RgbWorkingSpace right) + { + return Equals(left, right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(RgbWorkingSpace left, RgbWorkingSpace right) + { + return !Equals(left, right); + } + + /// + public override bool Equals(object obj) + { + if (obj is RgbWorkingSpace) + { + return this.Equals((RgbWorkingSpace)obj); + } + + return false; + } + + /// + 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); + } + + /// + 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; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs new file mode 100644 index 000000000..18ec94c51 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb +{ + using System.Runtime.CompilerServices; + + /// + /// Implements sRGB companding + /// + /// + /// For more info see: + /// + /// + /// + public class SRgbCompanding : ICompanding + { + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Expand(float channel) + { + return channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Compress(float channel) + { + return channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs new file mode 100644 index 000000000..e58da580d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion.Implementation.YCbCr +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + using ImageSharp.ColorSpaces; + + /// + /// Color converter between YCbCr and Rgb + /// See for formulas. + /// + internal class YCbCrAndRgbConverter : IColorConversion, IColorConversion + { + private static readonly Vector3 MaxBytes = new Vector3(255F); + + /// + [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); + } + + /// + [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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs new file mode 100644 index 000000000..55d1d65a3 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces.Conversion +{ + using System.Numerics; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion.Implementation.Lms; + + /// + /// Basic implementation of the von Kries chromatic adaptation model + /// + /// + /// Transformation described here: + /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html + /// + internal class VonKriesChromaticAdaptation : IChromaticAdaptation + { + private readonly CieXyzAndLmsConverter converter; + + /// + /// Initializes a new instance of the class. + /// + public VonKriesChromaticAdaptation() + : this(new CieXyzAndLmsConverter()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The transformation matrix used for the conversion (definition of the cone response domain). + /// + /// + public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix) + : this(new CieXyzAndLmsConverter(transformationMatrix)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The color converter + public VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) + { + this.converter = converter; + } + + /// + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Hsl.cs b/src/ImageSharp/ColorSpaces/Hsl.cs similarity index 66% rename from src/ImageSharp/Colors/Spaces/Hsl.cs rename to src/ImageSharp/ColorSpaces/Hsl.cs index de706c350..5bbbeec30 100644 --- a/src/ImageSharp/Colors/Spaces/Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Hsl.cs @@ -3,28 +3,23 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Colors.Spaces +namespace ImageSharp.ColorSpaces { using System; using System.ComponentModel; using System.Numerics; - using ImageSharp.PixelFormats; + using System.Runtime.CompilerServices; /// /// Represents a Hsl (hue, saturation, lightness) color. /// - public struct Hsl : IEquatable, IAlmostEquatable + internal struct Hsl : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has H, S, and L values set to zero. /// public static readonly Hsl Empty = default(Hsl); - /// - /// Min range used for clamping - /// - private static readonly Vector3 VectorMin = Vector3.Zero; - /// /// Max range used for clamping /// @@ -41,28 +36,51 @@ namespace ImageSharp.Colors.Spaces /// The h hue component. /// The s saturation component. /// The l value (lightness) component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Hsl(float h, float s, float l) + : this(new Vector3(h, s, l)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the h, s, l components. + [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); } /// /// Gets the hue component. /// A value ranging between 0 and 360. /// - public float H => this.backingVector.X; + public float H + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } /// /// Gets the saturation component. /// A value ranging between 0 and 1. /// - public float S => this.backingVector.Y; + public float S + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } /// /// Gets the lightness component. /// A value ranging between 0 and 1. /// - public float L => this.backingVector.Z; + public float L + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } /// /// Gets a value indicating whether this is empty. @@ -70,61 +88,11 @@ namespace ImageSharp.Colors.Spaces [EditorBrowsable(EditorBrowsableState.Never)] public bool IsEmpty => this.Equals(Empty); - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Hsl(Rgba32 color) + /// + 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; } /// @@ -139,6 +107,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Hsl left, Hsl right) { return left.Equals(right); @@ -156,6 +125,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is unequal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Hsl left, Hsl right) { return !left.Equals(right); @@ -179,6 +149,7 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is Hsl) @@ -190,19 +161,21 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Hsl other) { return this.backingVector.Equals(other.backingVector); } /// + [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; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Hsv.cs b/src/ImageSharp/ColorSpaces/Hsv.cs similarity index 78% rename from src/ImageSharp/Colors/Spaces/Hsv.cs rename to src/ImageSharp/ColorSpaces/Hsv.cs index 2b3d79afe..dcbb73692 100644 --- a/src/ImageSharp/Colors/Spaces/Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Hsv.cs @@ -3,28 +3,23 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Colors.Spaces +namespace ImageSharp.ColorSpaces { using System; using System.ComponentModel; using System.Numerics; - using ImageSharp.PixelFormats; + using System.Runtime.CompilerServices; /// /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). /// - public struct Hsv : IEquatable, IAlmostEquatable + internal struct Hsv : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has H, S, and V values set to zero. /// public static readonly Hsv Empty = default(Hsv); - /// - /// Min range used for clamping - /// - private static readonly Vector3 VectorMin = Vector3.Zero; - /// /// Max range used for clamping /// @@ -41,28 +36,51 @@ namespace ImageSharp.Colors.Spaces /// The h hue component. /// The s saturation component. /// The v value (brightness) component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Hsv(float h, float s, float v) + : this(new Vector3(h, s, v)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the h, s, v components. + [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); } /// /// Gets the hue component. /// A value ranging between 0 and 360. /// - public float H => this.backingVector.X; + public float H + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } /// /// Gets the saturation component. /// A value ranging between 0 and 1. /// - public float S => this.backingVector.Y; + public float S + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } /// /// Gets the value (brightness) component. /// A value ranging between 0 and 1. /// - public float V => this.backingVector.Z; + public float V + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } /// /// Gets a value indicating whether this is empty. @@ -70,6 +88,13 @@ namespace ImageSharp.Colors.Spaces [EditorBrowsable(EditorBrowsableState.Never)] public bool IsEmpty => this.Equals(Empty); + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + /// /// Allows the implicit conversion of an instance of to a /// . @@ -132,6 +157,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Hsv left, Hsv right) { return left.Equals(right); @@ -149,6 +175,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is unequal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Hsv left, Hsv right) { return !left.Equals(right); @@ -172,6 +199,7 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is Hsv) @@ -183,19 +211,21 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Hsv other) { return this.backingVector.Equals(other.backingVector); } /// + [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; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs new file mode 100644 index 000000000..2b49ee944 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/HunterLab.cs @@ -0,0 +1,223 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents an Hunter LAB color. + /// + /// + internal struct HunterLab : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.C; + + /// + /// Represents a that has L, A, B values set to zero. + /// + public static readonly HunterLab Empty = default(HunterLab); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HunterLab(float l, float a, float b) + : this(new Vector3(l, a, b), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HunterLab(float l, float a, float b, CieXyz whitePoint) + : this(new Vector3(l, a, b), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// Uses as white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HunterLab(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l a b components. + /// The reference white point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HunterLab(Vector3 vector, CieXyz whitePoint) + : this() + { + this.backingVector = vector; + this.WhitePoint = whitePoint; + } + + /// + /// Gets the reference white point of this color + /// + public CieXyz WhitePoint { get; } + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public float L + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the a color component. + /// A value ranging from -100 to 100. Negative is green, positive magenta. + /// + public float A + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the b color component. + /// A value ranging from -100 to 100. Negative is blue, positive is yellow + /// + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(HunterLab left, HunterLab right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(HunterLab left, HunterLab right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.WhitePoint.GetHashCode(); + hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "HunterLab [Empty]"; + } + + return $"HunterLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is HunterLab) + { + return this.Equals((HunterLab)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(HunterLab other) + { + return this.backingVector.Equals(other.backingVector) + && this.WhitePoint.Equals(other.WhitePoint); + } + + /// + [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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs b/src/ImageSharp/ColorSpaces/IAlmostEquatable.cs similarity index 97% rename from src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs rename to src/ImageSharp/ColorSpaces/IAlmostEquatable.cs index 04ea91cba..a67eaeb06 100644 --- a/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs +++ b/src/ImageSharp/ColorSpaces/IAlmostEquatable.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Colors.Spaces +namespace ImageSharp.ColorSpaces { using System; @@ -27,4 +27,4 @@ namespace ImageSharp.Colors.Spaces /// bool AlmostEquals(TPixel other, TPrecision precision); } -} +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/IColorVector.cs b/src/ImageSharp/ColorSpaces/IColorVector.cs new file mode 100644 index 000000000..08b70e482 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/IColorVector.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System.Numerics; + + /// + /// Color represented as a vector in its color space + /// + public interface IColorVector + { + /// + /// Gets the vector representation of the color + /// + Vector3 Vector { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/ICompanding.cs b/src/ImageSharp/ColorSpaces/ICompanding.cs new file mode 100644 index 000000000..e4f0e4a4c --- /dev/null +++ b/src/ImageSharp/ColorSpaces/ICompanding.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + /// + /// Pair of companding functions for . + /// Used for conversion to and backwards. + /// See also: + /// + internal interface ICompanding + { + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// + /// For more info see: + /// + /// + /// The channel value + /// The linear channel value + float Expand(float channel); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent (depends on the RGB color system). + /// + /// + /// For more info see: + /// + /// + /// The channel value + /// The nonlinear channel value + float Compress(float channel); + } +} diff --git a/src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs new file mode 100644 index 000000000..236085434 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + + using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb; + + /// + /// Encasulates the RGB working color space + /// + internal interface IRgbWorkingSpace : IEquatable + { + /// + /// Gets the reference white of the color space + /// + CieXyz WhitePoint { get; } + + /// + /// Gets the chromaticity coordinates of the primaries + /// + RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } + + /// + /// Gets the companding function associated with the RGB color system. Used for conversion to XYZ and backwards. + /// + /// + /// + ICompanding Companding { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Illuminants.cs b/src/ImageSharp/ColorSpaces/Illuminants.cs new file mode 100644 index 000000000..224cf9939 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Illuminants.cs @@ -0,0 +1,71 @@ +namespace ImageSharp.ColorSpaces +{ + /// + /// The well known standard illuminants. + /// Standard illuminants provide a basis for comparing images or colors recorded under different lighting + /// + /// + /// Coefficients taken from: + /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html + ///
+ /// Descriptions taken from: + /// http://en.wikipedia.org/wiki/Standard_illuminant + ///
+ internal static class Illuminants + { + /// + /// Incandescent / Tungsten + /// + public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F); + + /// + /// Direct sunlight at noon (obsoleteF) + /// + public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F); + + /// + /// Average / North sky Daylight (obsoleteF) + /// + public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F); + + /// + /// Horizon Light. ICC profile PCS + /// + public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F); + + /// + /// Mid-morning / Mid-afternoon Daylight + /// + public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F); + + /// + /// Noon Daylight: TelevisionF, sRGB color space + /// + public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F); + + /// + /// North sky Daylight + /// + public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F); + + /// + /// Equal energy + /// + public static readonly CieXyz E = new CieXyz(1F, 1F, 1F); + + /// + /// Cool White Fluorescent + /// + public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F); + + /// + /// D65 simulatorF, Daylight simulator + /// + public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F); + + /// + /// Philips TL84F, Ultralume 40 + /// + public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs new file mode 100644 index 000000000..fdfc0d266 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -0,0 +1,213 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents an linear Rgb color with specified working space + /// + internal struct LinearRgb : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// Represents a that has R, G, and B values set to zero. + /// + public static readonly LinearRgb Empty = default(LinearRgb); + + /// + /// The default LinearRgb working space + /// + public static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinearRgb(float r, float g, float b) + : this(new Vector3(r, g, b)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinearRgb(float r, float g, float b, IRgbWorkingSpace workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinearRgb(Vector3 vector) + : this(vector, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + /// The LinearRgb working space. + [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; + } + + /// + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets the LinearRgb color space + /// + public IRgbWorkingSpace WorkingSpace { get; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(LinearRgb left, LinearRgb right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(LinearRgb left, LinearRgb right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return this.backingVector.GetHashCode(); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "LinearRgb [ Empty ]"; + } + + return $"LinearRgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is LinearRgb) + { + return this.Equals((LinearRgb)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(LinearRgb other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + [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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Lms.cs b/src/ImageSharp/ColorSpaces/Lms.cs new file mode 100644 index 000000000..c76d87253 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Lms.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// 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. + /// + /// + internal struct Lms : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// Represents a that has L, M, and S values set to zero. + /// + public static readonly Lms Empty = default(Lms); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// L represents the responsivity at long wavelengths. + /// M represents the responsivity at medium wavelengths. + /// S represents the responsivity at short wavelengths. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Lms(float l, float m, float s) + : this(new Vector3(l, m, s)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, m, s components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Lms(Vector3 vector) + : this() + { + // Not clamping as documentation about this space seems to indicate "usual" ranges + this.backingVector = vector; + } + + /// + /// Gets the L long component. + /// A value usually ranging between -1 and 1. + /// + public float L + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the M medium component. + /// A value usually ranging between -1 and 1. + /// + public float M + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the S short component. + /// A value usually ranging between -1 and 1. + /// + public float S + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Lms left, Lms right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Lms left, Lms right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return this.backingVector.GetHashCode(); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Lms [ Empty ]"; + } + + return $"Lms [ L={this.L:#0.##}, M={this.M:#0.##}, S={this.S:#0.##} ]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is Lms) + { + return this.Equals((Lms)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Lms other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + [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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs new file mode 100644 index 000000000..898c81730 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.ColorSpaces +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents an RGB color with specified working space + /// + internal struct Rgb : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// Represents a that has R, G, and B values set to zero. + /// + public static readonly Rgb Empty = default(Rgb); + + /// + /// The default rgb working space + /// + public static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb(float r, float g, float b) + : this(new Vector3(r, g, b)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb(float r, float g, float b, IRgbWorkingSpace workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb(Vector3 vector) + : this(vector, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + /// The rgb working space. + [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; + } + + /// + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public float R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public float G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public float B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } + + /// + /// Gets the Rgb color space + /// + public IRgbWorkingSpace WorkingSpace + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + public Vector3 Vector + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector; + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Rgb(Rgba32 color) + { + return new Rgb(color.R / 255F, color.G / 255F, color.B / 255F); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb left, Rgb right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb left, Rgb right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return this.backingVector.GetHashCode(); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Rgb [ Empty ]"; + } + + return $"Rgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]"; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (obj is Rgb) + { + return this.Equals((Rgb)obj); + } + + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rgb other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + [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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs new file mode 100644 index 000000000..20b937394 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -0,0 +1,117 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming +namespace ImageSharp.ColorSpaces +{ + using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb; + + /// + /// Chromaticity coordinates taken from: + /// + /// + internal static class RgbWorkingSpaces + { + /// + /// sRgb working space. + /// + /// + /// Uses proper companding function, according to: + /// + /// + 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))); + + /// + /// Simplified sRgb working space (uses gamma companding instead of ). + /// See also . + /// + 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))); + + /// + /// Rec. 709 (ITU-R Recommendation BT.709) working space + /// + 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))); + + /// + /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space + /// + 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))); + + /// + /// ECI Rgb v2 working space + /// + 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))); + + /// + /// Adobe Rgb (1998) working space + /// + 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))); + + /// + /// Apple sRgb working space + /// + 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))); + + /// + /// Best Rgb working space + /// + 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))); + + /// + /// Beta Rgb working space + /// + 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))); + + /// + /// Bruce Rgb working space + /// + 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))); + + /// + /// CIE Rgb working space + /// + 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))); + + /// + /// ColorMatch Rgb working space + /// + 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))); + + /// + /// Don Rgb 4 working space + /// + 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))); + + /// + /// Ekta Space PS5 working space + /// + 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))); + + /// + /// NTSC Rgb working space + /// + 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))); + + /// + /// PAL/SECAM Rgb working space + /// + 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))); + + /// + /// ProPhoto Rgb working space + /// + 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))); + + /// + /// SMPTE-C Rgb working space + /// + 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))); + + /// + /// Wide Gamut Rgb working space + /// + 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))); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs similarity index 66% rename from src/ImageSharp/Colors/Spaces/YCbCr.cs rename to src/ImageSharp/ColorSpaces/YCbCr.cs index 06696af9e..cbba02305 100644 --- a/src/ImageSharp/Colors/Spaces/YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/YCbCr.cs @@ -3,33 +3,29 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Colors.Spaces +namespace ImageSharp.ColorSpaces { using System; using System.ComponentModel; using System.Numerics; - using ImageSharp.PixelFormats; + using System.Runtime.CompilerServices; /// - /// 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. /// + /// /// - public struct YCbCr : IEquatable + internal struct YCbCr : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has Y, Cb, and Cr values set to zero. /// public static readonly YCbCr Empty = default(YCbCr); - /// - /// Min range used for clamping - /// - private static readonly Vector3 VectorMin = Vector3.Zero; - /// /// Vector which is used in clamping to the max value /// - private static readonly Vector3 VectorMax = new Vector3(255); + private static readonly Vector3 VectorMax = new Vector3(255F); /// /// The backing vector for SIMD support. @@ -42,29 +38,51 @@ namespace ImageSharp.Colors.Spaces /// The y luminance component. /// The cb chroma component. /// The cr chroma component. - 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)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the y, cb, cr components. + [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); } /// /// Gets the Y luminance component. /// A value ranging between 0 and 255. /// - public byte Y => (byte)this.backingVector.X; + public float Y + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.X; + } /// /// Gets the Cb chroma component. /// A value ranging between 0 and 255. /// - public byte Cb => (byte)this.backingVector.Y; + public float Cb + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Y; + } /// /// Gets the Cr chroma component. /// A value ranging between 0 and 255. /// - public byte Cr => (byte)this.backingVector.Z; + public float Cr + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.backingVector.Z; + } /// /// Gets a value indicating whether this is empty. @@ -72,27 +90,11 @@ namespace ImageSharp.Colors.Spaces [EditorBrowsable(EditorBrowsableState.Never)] public bool IsEmpty => this.Equals(Empty); - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - public static implicit operator YCbCr(Rgba32 color) + /// + 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; } /// @@ -107,6 +109,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(YCbCr left, YCbCr right) { return left.Equals(right); @@ -124,6 +127,7 @@ namespace ImageSharp.Colors.Spaces /// /// True if the current left is unequal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(YCbCr left, YCbCr right) { return !left.Equals(right); @@ -147,6 +151,7 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is YCbCr) @@ -158,9 +163,21 @@ namespace ImageSharp.Colors.Spaces } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(YCbCr other) { return this.backingVector.Equals(other.backingVector); } + + /// + [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; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Bgra32.cs b/src/ImageSharp/Colors/Spaces/Bgra32.cs deleted file mode 100644 index b1f72033d..000000000 --- a/src/ImageSharp/Colors/Spaces/Bgra32.cs +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Colors.Spaces -{ - using System; - using System.ComponentModel; - using System.Numerics; - using ImageSharp.PixelFormats; - - /// - /// Represents an BGRA (blue, green, red, alpha) color. - /// - public struct Bgra32 : IEquatable - { - /// - /// Represents a 32 bit that has B, G, R, and A values set to zero. - /// - public static readonly Bgra32 Empty = default(Bgra32); - - /// - /// Min range used for clamping - /// - private static readonly Vector4 VectorMin = Vector4.Zero; - - /// - /// Max range used for clamping - /// - private static readonly Vector4 VectorMax = new Vector4(255); - - /// - /// The backing vector for SIMD support. - /// - private readonly Vector4 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The blue component of this . - /// The green component of this . - /// The red component of this . - /// The alpha component of this . - public Bgra32(byte b, byte g, byte r, byte a = 255) - : this() - { - this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), VectorMin, VectorMax); - } - - /// - /// Gets the blue component of the color - /// - public byte B => (byte)this.backingVector.X; - - /// - /// Gets the green component of the color - /// - public byte G => (byte)this.backingVector.Y; - - /// - /// Gets the red component of the color - /// - public byte R => (byte)this.backingVector.Z; - - /// - /// Gets the alpha component of the color - /// - public byte A => (byte)this.backingVector.W; - - /// - /// Gets the integer representation of the color. - /// - public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - public static implicit operator Bgra32(Rgba32 color) - { - return new Bgra32(color.B, color.G, color.R, color.A); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Bgra32 left, Bgra32 right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Bgra32 left, Bgra32 right) - { - return !left.Equals(right); - } - - /// - public override bool Equals(object obj) - { - if (obj is Bgra32) - { - Bgra32 color = (Bgra32)obj; - - return this.backingVector == color.backingVector; - } - - return false; - } - - /// - public override int GetHashCode() - { - return this.backingVector.GetHashCode(); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Bgra32 [ Empty ]"; - } - - return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; - } - - /// - public bool Equals(Bgra32 other) - { - return this.backingVector.Equals(other.backingVector); - } - } -} diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 0bfcec361..9c4dee503 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -102,19 +102,6 @@ namespace ImageSharp return 0F; } - /// - /// Returns the given degrees converted to radians. - /// - /// The angle in degrees. - /// - /// The representing the degree as radians. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float DegreesToRadians(float degrees) - { - return degrees * (MathF.PI / 180); - } - /// /// Gets the bounding from the given points. /// @@ -170,10 +157,10 @@ namespace ImageSharp { int width = bitmap.Width; int height = bitmap.Height; - Point topLeft = default(Point); - Point bottomRight = default(Point); + var topLeft = default(Point); + var bottomRight = default(Point); - Func, int, int, float, bool> delegateFunc; + Func, int, int, float, bool> delegateFunc; // Determine which channel to check against switch (channel) @@ -195,7 +182,7 @@ namespace ImageSharp break; } - int GetMinY(PixelAccessor pixels) + int GetMinY(ImageBase pixels) { for (int y = 0; y < height; y++) { @@ -211,7 +198,7 @@ namespace ImageSharp return 0; } - int GetMaxY(PixelAccessor pixels) + int GetMaxY(ImageBase pixels) { for (int y = height - 1; y > -1; y--) { @@ -227,7 +214,7 @@ namespace ImageSharp return height; } - int GetMinX(PixelAccessor pixels) + int GetMinX(ImageBase pixels) { for (int x = 0; x < width; x++) { @@ -243,7 +230,7 @@ namespace ImageSharp return 0; } - int GetMaxX(PixelAccessor pixels) + int GetMaxX(ImageBase pixels) { for (int x = width - 1; x > -1; x--) { @@ -259,13 +246,10 @@ namespace ImageSharp return height; } - using (PixelAccessor bitmapPixels = bitmap.Lock()) - { - topLeft.Y = GetMinY(bitmapPixels); - topLeft.X = GetMinX(bitmapPixels); - bottomRight.Y = (GetMaxY(bitmapPixels) + 1).Clamp(0, height); - bottomRight.X = (GetMaxX(bitmapPixels) + 1).Clamp(0, width); - } + topLeft.Y = GetMinY(bitmap); + topLeft.X = GetMinX(bitmap); + bottomRight.Y = (GetMaxY(bitmap) + 1).Clamp(0, height); + bottomRight.X = (GetMaxX(bitmap) + 1).Clamp(0, width); return GetBoundingRectangle(topLeft, bottomRight); } diff --git a/src/ImageSharp/Common/Helpers/MathF.cs b/src/ImageSharp/Common/Helpers/MathF.cs index 1877fe8af..5ce5b5d5d 100644 --- a/src/ImageSharp/Common/Helpers/MathF.cs +++ b/src/ImageSharp/Common/Helpers/MathF.cs @@ -19,28 +19,89 @@ namespace ImageSharp /// public const float PI = (float)Math.PI; - /// Returns the absolute value of a single-precision floating-point number. - /// A number that is greater than or equal to , but less than or equal to . - /// A single-precision floating-point number, x, such that 0 ≤ x ≤. + /// + /// Returns the absolute value of a single-precision floating-point number. + /// + /// + /// A number that is greater than or equal to , but less than or equal to . + /// + /// + /// A single-precision floating-point number, x, such that 0 ≤ x ≤. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Abs(float f) { return Math.Abs(f); } - /// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number. - /// A single-precision floating-point number. - /// The smallest integral value that is greater than or equal to . + /// + /// Returns the angle whose tangent is the quotient of two specified numbers. + /// + /// The y coordinate of a point. + /// The x coordinate of a point. + /// + /// 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 < θ < π/2.For (x, y) in quadrant 2, π/2 < θ≤π.For (x, y) in quadrant + /// 3, -π < θ < -π/2.For (x, y) in quadrant 4, -π/2 < θ < 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 + /// , or if x and y are either or + /// , the method returns . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Atan2(float y, float x) + { + return (float)Math.Atan2(y, x); + } + + /// + /// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number. + /// + /// A single-precision floating-point number. + /// + /// The smallest integral value that is greater than or equal to . /// If is equal to , , /// or , that value is returned. - /// Note that this method returns a instead of an integral type. + /// Note that this method returns a instead of an integral type. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Ceiling(float f) { return (float)Math.Ceiling(f); } - /// Returns e raised to the specified power. + /// + /// Returns the cosine of the specified angle. + /// + /// An angle, measured in radians. + /// + /// The cosine of . If is equal to , , + /// or , this method returns . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float f) + { + return (float)Math.Cos(f); + } + + /// + /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. + /// + /// The angle in degrees. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreeToRadian(float degree) + { + return degree * (PI / 180F); + } + + /// + /// Returns e raised to the specified power. + /// /// A number specifying a power. /// /// The number e raised to the power . @@ -53,9 +114,12 @@ namespace ImageSharp return (float)Math.Exp(f); } - /// Returns the largest integer less than or equal to the specified single-precision floating-point number. + /// + /// Returns the largest integer less than or equal to the specified single-precision floating-point number. + /// /// A single-precision floating-point number. - /// The largest integer less than or equal to . + /// + /// The largest integer less than or equal to . /// If is equal to , , /// or , that value is returned. /// @@ -65,10 +129,13 @@ namespace ImageSharp return (float)Math.Floor(f); } - /// Returns the larger of two single-precision floating-point numbers. + /// + /// Returns the larger of two single-precision floating-point numbers. + /// /// The first of two single-precision floating-point numbers to compare. /// The second of two single-precision floating-point numbers to compare. - /// Parameter or , whichever is larger. + /// + /// Parameter or , whichever is larger. /// If , or , or both and are /// equal to , is returned. /// @@ -78,19 +145,25 @@ namespace ImageSharp return Math.Max(val1, val2); } - /// Returns the smaller of two single-precision floating-point numbers. + /// + /// Returns the smaller of two single-precision floating-point numbers. + /// /// The first of two single-precision floating-point numbers to compare. /// The second of two single-precision floating-point numbers to compare. - /// Parameter or , whichever is smaller. + /// + /// Parameter or , whichever is smaller. /// If , , or both and are equal - /// to , is returned. + /// to , is returned. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Min(float val1, float val2) { return Math.Min(val1, val2); } - /// Returns a specified number raised to the specified power. + /// + /// Returns a specified number raised to the specified power. + /// /// A single-precision floating-point number to be raised to a power. /// A single-precision floating-point number that specifies a power. /// The number raised to the power . @@ -100,8 +173,23 @@ namespace ImageSharp return (float)Math.Pow(x, y); } - /// Rounds a single-precision floating-point value to the nearest integral value. - /// A single-precision floating-point number to be rounded. + /// + /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. + /// + /// The angle in radians. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RadianToDegree(float radian) + { + return radian / (PI / 180F); + } + + /// + /// Rounds a single-precision floating-point value to the nearest integral value. + /// + /// A single-precision floating-point number to be rounded. /// /// The integer nearest . /// If the fractional component of 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); } - /// Returns the sine of the specified angle. - /// An angle, measured in radians. + /// + /// 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. + /// + /// A single-precision floating-point number to be rounded. + /// Specification for how to round if it is midway between two other numbers. + /// + /// The integer nearest . If is halfway between two integers, one of which is even + /// and the other odd, then determines which of the two is returned. + /// Note that this method returns a instead of an integral type. + /// + /// + /// is not a valid value of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Round(float f, MidpointRounding mode) + { + return (float)Math.Round(f, mode); + } + + /// + /// Returns the sine of the specified angle. + /// + /// An angle, measured in radians. /// /// The sine of . /// If is equal to , , @@ -146,8 +255,10 @@ namespace ImageSharp return 1F; } - /// Returns the square root of a specified number. - /// The number whose square root is to be found. + /// + /// Returns the square root of a specified number. + /// + /// The number whose square root is to be found. /// /// One of the values in the following table. /// parameter Return value Zero or positive The positive square root of . diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 7a5fabdb3..408e6c383 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Dithering { + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -71,7 +72,7 @@ namespace ImageSharp.Dithering /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) + public void Dither(ImageBase pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) where TPixel : struct, IPixel { this.Dither(pixels, source, transformed, x, y, width, height, true); @@ -79,13 +80,13 @@ namespace ImageSharp.Dithering /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) + public void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) where TPixel : struct, IPixel { if (replacePixel) { // Assign the transformed pixel to the array. - pixels[x, y] = transformed; + image[x, y] = transformed; } // Calculate the error @@ -95,30 +96,33 @@ namespace ImageSharp.Dithering for (int row = 0; row < this.matrixHeight; row++) { int matrixY = y + row; - - for (int col = 0; col < this.matrixWidth; col++) + if (matrixY > 0 && matrixY < height) { - int matrixX = x + (col - this.startingOffset); + Span rowSpan = image.GetRowSpan(matrixY); - if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) + for (int col = 0; col < this.matrixWidth; col++) { - float coefficient = this.matrix[row, col]; + int matrixX = x + (col - this.startingOffset); - // Good to disable here as we are not comparing mathematical output. - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (coefficient == 0) + if (matrixX > 0 && matrixX < width) { - continue; + float coefficient = this.matrix[row, col]; + + // Good to disable here as we are not comparing mathematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (coefficient == 0) + { + continue; + } + + ref TPixel pixel = ref rowSpan[matrixX]; + var offsetColor = pixel.ToVector4(); + var coefficientVector = new Vector4(coefficient); + + Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; + result.W = offsetColor.W; + pixel.PackFromVector4(result); } - - Vector4 coefficientVector = new Vector4(coefficient); - Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); - Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; - result.W = offsetColor.W; - - TPixel packed = default(TPixel); - packed.PackFromVector4(result); - pixels[matrixX, matrixY] = packed; } } } diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index f49e7e62d..bc785e897 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The transformed pixel /// The column index. @@ -23,13 +23,13 @@ namespace ImageSharp.Dithering /// The image width. /// The image height. /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) + void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height) where TPixel : struct, IPixel; /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The transformed pixel /// The column index. @@ -41,7 +41,7 @@ namespace ImageSharp.Dithering /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false. /// /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) + void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 3f7cf4988..c69cddefe 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The color to apply to the pixels above the threshold. /// The color to apply to the pixels below the threshold. @@ -26,7 +26,7 @@ namespace ImageSharp.Dithering /// The image width. /// The image height. /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) + void Dither(ImageBase image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) where TPixel : struct, IPixel; } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs index 48d6c3f6a..a180888f7 100644 --- a/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs @@ -28,14 +28,14 @@ namespace ImageSharp.Dithering.Ordered } /// - public void Dither(PixelAccessor pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) + public void Dither(ImageBase image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) where TPixel : struct, IPixel { // TODO: This doesn't really cut it for me. - // I'd rather be using float but we need to add some sort of movalization vector methods to all IPixel implementations + // I'd rather be using float but we need to add some sort of normalization vector methods to all IPixel implementations // before we can do that as the vectors all cover different ranges. source.ToXyzwBytes(bytes, 0); - pixels[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper; + image[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 272e4d075..618d268f7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -191,7 +191,7 @@ namespace ImageSharp.Formats byte packed = this.buffer[8]; - GifImageDescriptor imageDescriptor = new GifImageDescriptor + var imageDescriptor = new GifImageDescriptor { Left = BitConverter.ToInt16(this.buffer, 0), Top = BitConverter.ToInt16(this.buffer, 2), @@ -337,7 +337,7 @@ namespace ImageSharp.Formats private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices) { int dataSize = this.currentStream.ReadByte(); - using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream)) + using (var lzwDecoder = new LzwDecoder(this.currentStream)) { lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); } @@ -396,62 +396,60 @@ namespace ImageSharp.Formats int interlaceIncrement = 8; // The interlacing line increment int interlaceY = 0; // The current interlaced line - using (PixelAccessor pixelAccessor = image.Lock()) + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) { - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) { - // Check if this image is interlaced. - int writeY; // the target y offset to write to - if (descriptor.InterlaceFlag) + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (interlaceY >= descriptor.Height) { - // If so then we read lines at predetermined offsets. - // When an entire image height worth of offset lines has been read we consider this a pass. - // With each pass the number of offset lines changes and the starting line changes. - if (interlaceY >= descriptor.Height) + interlacePass++; + switch (interlacePass) { - interlacePass++; - switch (interlacePass) - { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; - } + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; } + } - writeY = interlaceY + descriptor.Top; + writeY = interlaceY + descriptor.Top; - interlaceY += interlaceIncrement; - } - else - { - writeY = y; - } + interlaceY += interlaceIncrement; + } + else + { + writeY = y; + } - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) - { - int index = indices[i]; + Span rowSpan = image.GetRowSpan(writeY); - if (this.graphicsControlExtension == null || - this.graphicsControlExtension.TransparencyFlag == false || - this.graphicsControlExtension.TransparencyIndex != index) - { - int indexOffset = index * 3; + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + int index = indices[i]; - TPixel pixel = default(TPixel); - pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); - pixelAccessor[x, writeY] = pixel; - } + if (this.graphicsControlExtension == null || + this.graphicsControlExtension.TransparencyFlag == false || + this.graphicsControlExtension.TransparencyIndex != index) + { + int indexOffset = index * 3; - i++; + ref TPixel pixel = ref rowSpan[x]; + pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); } + + i++; } } @@ -492,7 +490,7 @@ namespace ImageSharp.Formats } else { - using (PixelArea emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) + using (var emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) { using (PixelAccessor pixelAccessor = frame.Lock()) { diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 5dc1a150d..5ef7ca165 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -69,7 +69,7 @@ namespace ImageSharp.Formats this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer(); // Do not use IDisposable pattern here as we want to preserve the stream. - EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); + var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; @@ -82,7 +82,7 @@ namespace ImageSharp.Formats this.hasFrames = image.Frames.Any(); // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. - IQuantizer ditheredQuantizer = (IQuantizer)this.Quantizer; + var ditheredQuantizer = (IQuantizer)this.Quantizer; ditheredQuantizer.Dither = !this.hasFrames; QuantizedImage quantized = ditheredQuantizer.Quantize(image, quality); @@ -96,7 +96,7 @@ namespace ImageSharp.Formats this.WriteLogicalScreenDescriptor(image, writer, index); // Write the first frame. - this.WriteGraphicalControlExtension(image, writer, index); + this.WriteGraphicalControlExtension(image.MetaData, writer, index); this.WriteComments(image, writer); this.WriteImageDescriptor(image, writer); this.WriteColorTable(quantized, writer); @@ -113,7 +113,7 @@ namespace ImageSharp.Formats ImageFrame frame = image.Frames[i]; QuantizedImage quantizedFrame = ditheredQuantizer.Quantize(frame, quality); - this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); + this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteImageDescriptor(frame, writer); this.WriteColorTable(quantizedFrame, writer); this.WriteImageData(quantizedFrame, writer); @@ -137,31 +137,20 @@ namespace ImageSharp.Formats private int GetTransparentIndex(QuantizedImage quantized) where TPixel : struct, IPixel { - // Find the lowest alpha value and make it the transparent index. - int index = 255; - byte alpha = 255; - bool hasEmpty = false; - - // Some images may have more than one quantized pixel returned with an alpha value of zero - // so we should always ignore if we have empty pixels present. - for (int i = 0; i < quantized.Palette.Length; i++) + // Transparent pixels are much more likely to be found at the end of a palette + int index = -1; + for (int i = quantized.Palette.Length - 1; i >= 0; i--) { quantized.Palette[i].ToXyzwBytes(this.buffer, 0); - if (!hasEmpty) + if (this.buffer[3] > 0) { - if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0) - { - alpha = this.buffer[3]; - index = i; - hasEmpty = true; - } - - if (this.buffer[3] < alpha) - { - alpha = this.buffer[3]; - index = i; - } + continue; + } + else + { + index = i; + break; } } @@ -183,23 +172,23 @@ namespace ImageSharp.Formats /// The pixel format. /// The image to encode. /// The writer to write to the stream with. - /// The transparency index to set the default background index to. - private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) + /// The transparency index to set the default background index to. + private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int transparencyIndex) where TPixel : struct, IPixel { - GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor + var descriptor = new GifLogicalScreenDescriptor { Width = (short)image.Width, Height = (short)image.Height, GlobalColorTableFlag = false, // TODO: Always false for now. GlobalColorTableSize = this.bitDepth - 1, - BackgroundColorIndex = (byte)tranparencyIndex + BackgroundColorIndex = unchecked((byte)transparencyIndex) }; writer.Write((ushort)descriptor.Width); writer.Write((ushort)descriptor.Height); - PackedField field = default(PackedField); + var field = default(PackedField); field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution field.SetBit(4, false); // 5 : GCT sort flag = 0 @@ -251,7 +240,7 @@ namespace ImageSharp.Formats private void WriteComments(Image image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - if (this.options.IgnoreMetadata == true) + if (this.options.IgnoreMetadata) { return; } @@ -278,45 +267,16 @@ namespace ImageSharp.Formats /// /// Writes the graphics control extension to the stream. /// - /// The pixel format. - /// The to encode. - /// The stream to write to. - /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(Image image, EndianBinaryWriter writer, int transparencyIndex) - where TPixel : struct, IPixel - { - this.WriteGraphicalControlExtension(image, image.MetaData, writer, transparencyIndex); - } - - /// - /// Writes the graphics control extension to the stream. - /// - /// The pixel format. - /// The to encode. - /// The stream to write to. - /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageFrame imageFrame, EndianBinaryWriter writer, int transparencyIndex) - where TPixel : struct, IPixel - { - this.WriteGraphicalControlExtension(imageFrame, imageFrame.MetaData, writer, transparencyIndex); - } - - /// - /// Writes the graphics control extension to the stream. - /// - /// The pixel format. - /// The to encode. /// The metadata of the image or frame. /// The stream to write to. /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageBase image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) - where TPixel : struct, IPixel + private void WriteGraphicalControlExtension(IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) { - GifGraphicsControlExtension extension = new GifGraphicsControlExtension() + var extension = new GifGraphicsControlExtension { DisposalMethod = metaData.DisposalMethod, - TransparencyFlag = transparencyIndex < 255, - TransparencyIndex = transparencyIndex, + TransparencyFlag = transparencyIndex > -1, + TransparencyIndex = unchecked((byte)transparencyIndex), DelayTime = metaData.FrameDelay }; @@ -326,7 +286,7 @@ namespace ImageSharp.Formats this.buffer[2] = 4; writer.Write(this.buffer, 0, 3); - PackedField field = default(PackedField); + var field = default(PackedField); field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal // TODO: Allow this as an option. @@ -335,7 +295,7 @@ namespace ImageSharp.Formats writer.Write(field.Byte); writer.Write((ushort)extension.DelayTime); - writer.Write((byte)extension.TransparencyIndex); + writer.Write(extension.TransparencyIndex); writer.Write(GifConstants.Terminator); } @@ -356,7 +316,7 @@ namespace ImageSharp.Formats writer.Write((ushort)image.Width); writer.Write((ushort)image.Height); - PackedField field = default(PackedField); + var field = default(PackedField); field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) field.SetBit(1, false); // 2: Interlace flag 0 field.SetBit(2, false); // 3: Sort flag 0 @@ -409,7 +369,7 @@ namespace ImageSharp.Formats private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - using (LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) + using (var encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) { encoder.Encode(writer.BaseStream); } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs index 79d98f5fb..503bd4fdf 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Formats /// The Transparency Index is such that when encountered, the corresponding pixel /// of the display device is not modified and processing goes on to the next pixel. /// - public int TransparencyIndex { get; set; } + public byte TransparencyIndex { get; set; } /// /// Gets or sets the delay time. diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index 74f9a3c07..dcda39842 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -200,6 +200,11 @@ namespace ImageSharp.Formats /// public const byte APP1 = 0xe1; + /// + /// Application specific marker for marking where to store ICC profile information. + /// + public const byte APP2 = 0xe2; + /// /// Application specific marker used by Adobe for storing encoding information for DCT filters. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 9d9ecacfe..13c51db24 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/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); } } + /// + /// Processes the App2 marker retrieving any stored ICC profile information + /// + /// The remaining bytes in the segment block. + /// The image. + 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); + } + } + } + /// /// Processes the application header containing the JFIF identifier plus extra data. /// @@ -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) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index fd7062729..b65a56e73 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/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 /// /// Thrown if the EXIF profile size exceeds the limit /// - 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); } + /// + /// Writes the ICC profile. + /// + /// The ICC profile to write. + /// + /// Thrown if any of the ICC profiles size exceeds the limit + /// + 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; + } + } + /// /// Writes the metadata profiles to the image. /// @@ -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); } /// diff --git a/src/ImageSharp/Image/IImageBase{TPixel}.cs b/src/ImageSharp/Image/IImageBase{TPixel}.cs index 08d25709b..8b4977b7d 100644 --- a/src/ImageSharp/Image/IImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/IImageBase{TPixel}.cs @@ -16,19 +16,8 @@ namespace ImageSharp where TPixel : struct, IPixel { /// - /// Gets the pixels as an array of the given packed pixel format. - /// Important. Due to the nature in the way this is constructed do not rely on the length - /// of the array for calculations. Use Width * Height. + /// Gets the representation of the pixels as an area of contiguous memory in the given pixel format. /// - TPixel[] Pixels { get; } - - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The - PixelAccessor Lock(); + Span Pixels { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.LoadPixelData.cs b/src/ImageSharp/Image/Image.LoadPixelData.cs new file mode 100644 index 000000000..7b6a4d668 --- /dev/null +++ b/src/ImageSharp/Image/Image.LoadPixelData.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.IO; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + using Formats; + using ImageSharp.Memory; + using ImageSharp.PixelFormats; + + /// + /// Adds static methods allowing the creation of new image from raw pixel data. + /// + public static partial class Image + { + /// + /// Create a new instance of the class from the raw data. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Span data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); + + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Span data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); + + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, Span data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(config, data.NonPortableCast(), width, height); + + /// + /// Create a new instance of the class from the raw data. + /// + /// The config for the decoder. + /// The Span containing the image Pixel data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, Span data, int width, int height) + where TPixel : struct, IPixel + { + int count = width * height; + Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + + var image = new Image(config, width, height); + SpanHelper.Copy(data, image.Pixels, count); + + return image; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageBase{TPixel}.cs b/src/ImageSharp/Image/ImageBase{TPixel}.cs index 4fd9d26cb..508c73eb2 100644 --- a/src/ImageSharp/Image/ImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/ImageBase{TPixel}.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Diagnostics; + using System.Runtime.CompilerServices; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -31,10 +32,12 @@ namespace ImageSharp /// public const int MaxHeight = int.MaxValue; +#pragma warning disable SA1401 // Fields must be private /// - /// The image pixels + /// The image pixels. Not private as Buffer2D requires an array in its constructor. /// - private TPixel[] pixelBuffer; + internal TPixel[] PixelBuffer; +#pragma warning restore SA1401 // Fields must be private /// /// A value indicating whether this instance of the given entity has been disposed. @@ -110,7 +113,7 @@ namespace ImageSharp } /// - public TPixel[] Pixels => this.pixelBuffer; + public Span Pixels => new Span(this.PixelBuffer, 0, this.Width * this.Height); /// public int Width { get; private set; } @@ -129,6 +132,67 @@ namespace ImageSharp /// public Configuration Configuration { get; private set; } + /// + /// Gets or sets the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + public TPixel this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(x, y); + return this.PixelBuffer[(y * this.Width) + x]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.CheckCoordinates(x, y); + this.PixelBuffer[(y * this.Width) + x] = value; + } + } + + /// + /// Gets a reference to the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TPixel GetPixelReference(int x, int y) + { + this.CheckCoordinates(x, y); + return ref this.PixelBuffer[(y * this.Width) + x]; + } + + /// + /// Gets a representing the row 'y' beginning from the the first pixel on that row. + /// + /// The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the image. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + this.CheckCoordinates(y); + return this.Pixels.Slice(y * this.Width, this.Width); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The x coordinate (position in the row) + /// The y (row) coordinate + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int x, int y) + { + this.CheckCoordinates(x, y); + return this.Pixels.Slice((y * this.Width) + x, this.Width - x); + } + /// /// Applies the processor. /// @@ -152,12 +216,27 @@ namespace ImageSharp GC.SuppressFinalize(this); } - /// - public PixelAccessor Lock() + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + internal PixelAccessor Lock() { return new PixelAccessor(this); } + /// + /// Copies the pixels to another of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(PixelAccessor target) + { + SpanHelper.Copy(this.Pixels, target.PixelBuffer.Span); + } + /// /// Switches the buffers used by the image and the PixelAccessor meaning that the Image will "own" the buffer from the PixelAccessor and the PixelAccessor will now own the Images buffer. /// @@ -169,11 +248,11 @@ namespace ImageSharp int newWidth = pixelSource.Width; int newHeight = pixelSource.Height; - // Push my memory into the accessor (which in turn unpins the old puffer ready for the images use) - TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer); + // Push my memory into the accessor (which in turn unpins the old buffer ready for the images use) + TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.PixelBuffer); this.Width = newWidth; this.Height = newHeight; - this.pixelBuffer = newPixels; + this.PixelBuffer = newPixels; } /// @@ -224,7 +303,7 @@ namespace ImageSharp /// private void RentPixels() { - this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); + this.PixelBuffer = PixelDataPool.Rent(this.Width * this.Height); } /// @@ -232,8 +311,8 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelDataPool.Return(this.pixelBuffer); - this.pixelBuffer = null; + PixelDataPool.Return(this.PixelBuffer); + this.PixelBuffer = null; } /// @@ -241,7 +320,45 @@ namespace ImageSharp /// private void ClearPixels() { - Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); + Array.Clear(this.PixelBuffer, 0, this.Width * this.Height); + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int y) + { + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds."); + } + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int x, int y) + { + if (x < 0 || x >= this.Width) + { + throw new ArgumentOutOfRangeException(nameof(x), x, $"{x} is outwith the image bounds."); + } + + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds."); + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index a54c03b63..4baae8615 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -17,9 +17,16 @@ namespace ImageSharp /// Provides per-pixel access to generic pixels. /// /// The pixel format. - public sealed class PixelAccessor : IDisposable, IBuffer2D + internal sealed class PixelAccessor : IDisposable, IBuffer2D where TPixel : struct, IPixel { +#pragma warning disable SA1401 // Fields must be private + /// + /// The containing the pixel data. + /// + internal Buffer2D PixelBuffer; +#pragma warning restore SA1401 // Fields must be private + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -31,11 +38,6 @@ namespace ImageSharp /// private bool isDisposed; - /// - /// The containing the pixel data. - /// - private Buffer2D pixelBuffer; - /// /// Initializes a new instance of the class. /// @@ -46,7 +48,7 @@ namespace ImageSharp Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Height, 0, "image height"); - this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels); + this.SetPixelBufferUnsafe(image.Width, image.Height, image.PixelBuffer); this.ParallelOptions = image.Configuration.ParallelOptions; } @@ -88,7 +90,7 @@ namespace ImageSharp /// /// Gets the pixel buffer array. /// - public TPixel[] PixelArray => this.pixelBuffer.Array; + public TPixel[] PixelArray => this.PixelBuffer.Array; /// /// Gets the size of a single pixel in the number of bytes. @@ -116,7 +118,7 @@ namespace ImageSharp public ParallelOptions ParallelOptions { get; } /// - Span IBuffer2D.Span => this.pixelBuffer; + Span IBuffer2D.Span => this.PixelBuffer; private static PixelOperations Operations => PixelOperations.Instance; @@ -128,12 +130,14 @@ namespace ImageSharp /// The at the specified position. public TPixel this[int x, int y] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { this.CheckCoordinates(x, y); return this.PixelArray[(y * this.Width) + x]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.CheckCoordinates(x, y); @@ -154,7 +158,7 @@ namespace ImageSharp // Note disposing is done. this.isDisposed = true; - this.pixelBuffer.Dispose(); + this.PixelBuffer.Dispose(); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to @@ -169,7 +173,7 @@ namespace ImageSharp /// public void Reset() { - this.pixelBuffer.Clear(); + this.PixelBuffer.Clear(); } /// @@ -241,7 +245,7 @@ namespace ImageSharp /// If is true then caller is responsible for ensuring is called. internal TPixel[] ReturnCurrentColorsAndReplaceThemInternally(int width, int height, TPixel[] pixels) { - TPixel[] oldPixels = this.pixelBuffer.TakeArrayOwnership(); + TPixel[] oldPixels = this.PixelBuffer.TakeArrayOwnership(); this.SetPixelBufferUnsafe(width, height, pixels); return oldPixels; } @@ -252,7 +256,7 @@ namespace ImageSharp /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - SpanHelper.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span); + SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span); } /// @@ -423,7 +427,7 @@ namespace ImageSharp /// The pixel buffer private void SetPixelBufferUnsafe(int width, int height, Buffer2D pixels) { - this.pixelBuffer = pixels; + this.PixelBuffer = pixels; this.Width = width; this.Height = height; diff --git a/src/ImageSharp/Memory/Buffer2D.cs b/src/ImageSharp/Memory/Buffer2D.cs index e5ccfbd19..59cabb1bd 100644 --- a/src/ImageSharp/Memory/Buffer2D.cs +++ b/src/ImageSharp/Memory/Buffer2D.cs @@ -69,7 +69,7 @@ namespace ImageSharp.Memory /// The instance public static Buffer2D CreateClean(int width, int height) { - Buffer2D buffer = new Buffer2D(width, height); + var buffer = new Buffer2D(width, height); buffer.Clear(); return buffer; } diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 51e558281..046bfd81f 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Memory } /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. /// /// The buffer /// The y (row) coordinate diff --git a/src/ImageSharp/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Memory/PixelDataPool{T}.cs index a8b5501cc..643f1c6ca 100644 --- a/src/ImageSharp/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Memory/PixelDataPool{T}.cs @@ -49,12 +49,13 @@ namespace ImageSharp.Memory // ReSharper disable once SuspiciousTypeConversion.Global if (default(T) is IPixel) { - const int MaximumExpectedImageSize = 16384; - return MaximumExpectedImageSize * MaximumExpectedImageSize; + const int MaximumExpectedImageSize = 16384 * 16384; + return MaximumExpectedImageSize; } else { - return int.MaxValue; + const int MaxArrayLength = 1024 * 1024; // Match default pool. + return MaxArrayLength; } } } diff --git a/src/ImageSharp/Memory/SpanHelper.cs b/src/ImageSharp/Memory/SpanHelper.cs index 57b771591..0e794e1b5 100644 --- a/src/ImageSharp/Memory/SpanHelper.cs +++ b/src/ImageSharp/Memory/SpanHelper.cs @@ -70,7 +70,7 @@ namespace ImageSharp.Memory public static void Copy(Span source, Span destination) where T : struct { - Copy(source, destination, source.Length); + Copy(source, destination, Math.Min(source.Length, destination.Length)); } /// diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 127ae720f..91ea9ac4b 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/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; } /// - /// 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. /// /// The density of the image in x- direction. public double HorizontalResolution { - get - { - return this.horizontalResolution; - } + get => this.horizontalResolution; set { @@ -93,16 +88,13 @@ namespace ImageSharp } /// - /// 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. /// /// The density of the image in y- direction. public double VerticalResolution { - get - { - return this.verticalResolution; - } + get => this.verticalResolution; set { @@ -118,6 +110,11 @@ namespace ImageSharp /// public ExifProfile ExifProfile { get; set; } + /// + /// Gets or sets the list of ICC profiles. + /// + public IccProfile IccProfile { get; set; } + /// public int FrameDelay { get; set; } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs new file mode 100644 index 000000000..f34f006e7 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A segment of a curve + /// + internal abstract class IccCurveSegment : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The signature of this segment + protected IccCurveSegment(IccCurveSegmentSignature signature) + { + this.Signature = signature; + } + + /// + /// Gets the signature of this segment + /// + public IccCurveSegmentSignature Signature { get; } + + /// + public virtual bool Equals(IccCurveSegment other) + { + if (other == null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Signature == other.Signature; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs new file mode 100644 index 000000000..ad03c8ff8 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A formula based curve segment + /// + internal sealed class IccFormulaCurveElement : IccCurveSegment, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The type of this segment + /// Gamma segment parameter + /// A segment parameter + /// B segment parameter + /// C segment parameter + /// D segment parameter + /// E segment parameter + 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; + } + + /// + /// Gets the type of this curve + /// + public IccFormulaCurveType Type { get; } + + /// + /// Gets the gamma curve parameter + /// + public float Gamma { get; } + + /// + /// Gets the A curve parameter + /// + public float A { get; } + + /// + /// Gets the B curve parameter + /// + public float B { get; } + + /// + /// Gets the C curve parameter + /// + public float C { get; } + + /// + /// Gets the D curve parameter + /// + public float D { get; } + + /// + /// Gets the E curve parameter + /// + public float E { get; } + + /// + 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; + } + + /// + public bool Equals(IccFormulaCurveElement other) + { + return this.Equals((IccCurveSegment)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs new file mode 100644 index 000000000..3f6497ccc --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// A one dimensional curve + /// + internal sealed class IccOneDimensionalCurve : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The break points of this curve + /// The segments of this curve + 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; + } + + /// + /// Gets the breakpoints that separate two curve segments + /// + public float[] BreakPoints { get; } + + /// + /// Gets an array of curve segments + /// + public IccCurveSegment[] Segments { get; } + + /// + 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); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs new file mode 100644 index 000000000..14aaf153e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs @@ -0,0 +1,183 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A parametric curve + /// + internal sealed class IccParametricCurve : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + public IccParametricCurve(float g) + : this(IccParametricCurveType.Type1, g, 0, 0, 0, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + public IccParametricCurve(float g, float a, float b) + : this(IccParametricCurveType.Cie122_1996, g, a, b, 0, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + public IccParametricCurve(float g, float a, float b, float c) + : this(IccParametricCurveType.Iec61966_3, g, a, b, c, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + /// D curve parameter + public IccParametricCurve(float g, float a, float b, float c, float d) + : this(IccParametricCurveType.SRgb, g, a, b, c, d, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + /// D curve parameter + /// E curve parameter + /// F curve parameter + 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; + } + + /// + /// Gets the type of this curve + /// + public IccParametricCurveType Type { get; } + + /// + /// Gets the G curve parameter + /// + public float G { get; } + + /// + /// Gets the A curve parameter + /// + public float A { get; } + + /// + /// Gets the B curve parameter + /// + public float B { get; } + + /// + /// Gets the C curve parameter + /// + public float C { get; } + + /// + /// Gets the D curve parameter + /// + public float D { get; } + + /// + /// Gets the E curve parameter + /// + public float E { get; } + + /// + /// Gets the F curve parameter + /// + public float F { get; } + + /// + public bool Equals(IccParametricCurve other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Type == other.Type + && this.G.Equals(other.G) + && this.A.Equals(other.A) + && this.B.Equals(other.B) + && this.C.Equals(other.C) + && this.D.Equals(other.D) + && this.E.Equals(other.E) + && this.F.Equals(other.F); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccParametricCurve && this.Equals((IccParametricCurve)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int)this.Type; + hashCode = (hashCode * 397) ^ this.G.GetHashCode(); + hashCode = (hashCode * 397) ^ this.A.GetHashCode(); + hashCode = (hashCode * 397) ^ this.B.GetHashCode(); + hashCode = (hashCode * 397) ^ this.C.GetHashCode(); + hashCode = (hashCode * 397) ^ this.D.GetHashCode(); + hashCode = (hashCode * 397) ^ this.E.GetHashCode(); + hashCode = (hashCode * 397) ^ this.F.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs new file mode 100644 index 000000000..a5816e212 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs @@ -0,0 +1,115 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// A response curve + /// + internal sealed class IccResponseCurve : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The type of this curve + /// The XYZ values + /// The response arrays + 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; + } + + /// + /// Gets the type of this curve + /// + public IccCurveMeasurementEncodings CurveType { get; } + + /// + /// Gets the XYZ values + /// + public Vector3[] XyzValues { get; } + + /// + /// Gets the response arrays + /// + public IccResponseNumber[][] ResponseArrays { get; } + + /// + public bool Equals(IccResponseCurve other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.CurveType == other.CurveType + && this.XyzValues.SequenceEqual(other.XyzValues) + && this.EqualsResponseArray(other); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccResponseCurve && this.Equals((IccResponseCurve)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int)this.CurveType; + hashCode = (hashCode * 397) ^ (this.XyzValues != null ? this.XyzValues.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.ResponseArrays != null ? this.ResponseArrays.GetHashCode() : 0); + return hashCode; + } + } + + 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; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs new file mode 100644 index 000000000..859d43338 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// A sampled curve segment + /// + internal sealed class IccSampledCurveElement : IccCurveSegment, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The curve values of this segment + 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; + } + + /// + /// Gets the curve values of this segment + /// + public float[] CurveEntries { get; } + + /// + public override bool Equals(IccCurveSegment other) + { + if (base.Equals(other) && other is IccSampledCurveElement segment) + { + return this.CurveEntries.SequenceEqual(segment.CurveEntries); + } + + return false; + } + + /// + public bool Equals(IccSampledCurveElement other) + { + return this.Equals((IccCurveSegment)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs new file mode 100644 index 000000000..ba32586fe --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs @@ -0,0 +1,221 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a + /// + /// The read curve + 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); + } + + /// + /// Reads a + /// + /// The number of channels + /// The read curve + 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); + } + + /// + /// Reads a + /// + /// The read curve + 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}"); + } + } + + /// + /// Reads a + /// + /// The read segment + 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}"); + } + } + + /// + /// Reads a + /// + /// The read segment + 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); + } + + /// + /// Reads a + /// + /// The read segment + 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); + } + + /// + /// Reads curve data + /// + /// Number of input channels + /// The curve data + 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; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs new file mode 100644 index 000000000..a34ee42fc --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs @@ -0,0 +1,173 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads an 8bit lookup table + /// + /// The read LUT + public IccLut ReadLut8() + { + return new IccLut(this.ReadBytes(256)); + } + + /// + /// Reads a 16bit lookup table + /// + /// The number of entries + /// The read LUT + 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); + } + + /// + /// Reads a CLUT depending on type + /// + /// Input channel count + /// Output channel count + /// If true, it's read as CLUTf32, + /// else read as either CLUT8 or CLUT16 depending on embedded information + /// The read CLUT + 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); + } + + /// + /// Reads an 8 bit CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUT8 + 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); + } + + /// + /// Reads a 16 bit CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUT16 + 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); + } + + /// + /// Reads a 32bit floating point CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUTf32 + 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); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs new file mode 100644 index 000000000..23ad9feb2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a two dimensional matrix + /// + /// Number of values in X + /// Number of values in Y + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The read matrix + 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; + } + + /// + /// Reads a one dimensional matrix + /// + /// Number of values + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The read matrix + 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; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs new file mode 100644 index 000000000..371c5c42a --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a + /// + /// The read + 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}"); + } + } + + /// + /// Reads a CurveSet + /// + /// Number of input channels + /// Number of output channels + /// The read + 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); + } + + /// + /// Reads a Matrix + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount) + { + return new IccMatrixProcessElement( + this.ReadMatrix(inChannelCount, outChannelCount, true), + this.ReadMatrix(outChannelCount, true)); + } + + /// + /// Reads a CLUT + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccClutProcessElement ReadClutProcessElement(int inChannelCount, int outChannelCount) + { + return new IccClutProcessElement(this.ReadClut(inChannelCount, outChannelCount, true)); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs new file mode 100644 index 000000000..44a892084 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs @@ -0,0 +1,183 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a DateTime + /// + /// the value + 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; + } + } + + /// + /// Reads an ICC profile version number + /// + /// the version number + 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); + } + + /// + /// Reads an XYZ number + /// + /// the XYZ number + public Vector3 ReadXyzNumber() + { + return new Vector3( + x: this.ReadFix16(), + y: this.ReadFix16(), + z: this.ReadFix16()); + } + + /// + /// Reads a profile ID + /// + /// the profile ID + public IccProfileId ReadProfileId() + { + return new IccProfileId( + p1: this.ReadUInt32(), + p2: this.ReadUInt32(), + p3: this.ReadUInt32(), + p4: this.ReadUInt32()); + } + + /// + /// Reads a position number + /// + /// the position number + public IccPositionNumber ReadPositionNumber() + { + return new IccPositionNumber( + offset: this.ReadUInt32(), + size: this.ReadUInt32()); + } + + /// + /// Reads a response number + /// + /// the response number + public IccResponseNumber ReadResponseNumber() + { + return new IccResponseNumber( + deviceCode: this.ReadUInt16(), + measurementValue: this.ReadFix16()); + } + + /// + /// Reads a named color + /// + /// Number of device coordinates + /// the named color + 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); + } + + /// + /// Reads a profile description + /// + /// the profile description + 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"); + } + } + } + + /// + /// Reads a colorant table entry + /// + /// the profile description + public IccColorantTableEntry ReadColorantTableEntry() + { + return new IccColorantTableEntry( + name: this.ReadAsciiString(32), + pcs1: this.ReadUInt16(), + pcs2: this.ReadUInt16(), + pcs3: this.ReadUInt16()); + } + + /// + /// Reads a screening channel + /// + /// the screening channel + public IccScreeningChannel ReadScreeningChannel() + { + return new IccScreeningChannel( + frequency: this.ReadFix16(), + angle: this.ReadFix16(), + spotShape: (IccScreeningSpotType)this.ReadInt32()); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs new file mode 100644 index 000000000..85d80c7a8 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs @@ -0,0 +1,178 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Text; + + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads an ushort + /// + /// the value + public ushort ReadUInt16() + { + return this.converter.ToUInt16(this.data, this.AddIndex(2)); + } + + /// + /// Reads a short + /// + /// the value + public short ReadInt16() + { + return this.converter.ToInt16(this.data, this.AddIndex(2)); + } + + /// + /// Reads an uint + /// + /// the value + public uint ReadUInt32() + { + return this.converter.ToUInt32(this.data, this.AddIndex(4)); + } + + /// + /// Reads an int + /// + /// the value + public int ReadInt32() + { + return this.converter.ToInt32(this.data, this.AddIndex(4)); + } + + /// + /// Reads an ulong + /// + /// the value + public ulong ReadUInt64() + { + return this.converter.ToUInt64(this.data, this.AddIndex(8)); + } + + /// + /// Reads a long + /// + /// the value + public long ReadInt64() + { + return this.converter.ToInt64(this.data, this.AddIndex(8)); + } + + /// + /// Reads a float + /// + /// the value + public float ReadSingle() + { + return this.converter.ToSingle(this.data, this.AddIndex(4)); + } + + /// + /// Reads a double + /// + /// the value + public double ReadDouble() + { + return this.converter.ToDouble(this.data, this.AddIndex(8)); + } + + /// + /// Reads an ASCII encoded string + /// + /// number of bytes to read + /// The value as a string + 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; + } + + /// + /// Reads an UTF-16 big-endian encoded string + /// + /// number of bytes to read + /// The value as a string + 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); + } + + /// + /// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits + /// + /// The number as double + public float ReadFix16() + { + return this.ReadInt32() / 65536f; + } + + /// + /// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits + /// + /// The number as double + public float ReadUFix16() + { + return this.ReadUInt32() / 65536f; + } + + /// + /// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits + /// + /// The number as double + public float ReadU1Fix15() + { + return this.ReadUInt16() / 32768f; + } + + /// + /// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits + /// + /// The number as double + public float ReadUFix8() + { + return this.ReadUInt16() / 256f; + } + + /// + /// Reads a number of bytes and advances the index + /// + /// The number of bytes to read + /// The read bytes + public byte[] ReadBytes(int count) + { + byte[] bytes = new byte[count]; + Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count); + return bytes; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs new file mode 100644 index 000000000..1c130b54d --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -0,0 +1,875 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + using System.Numerics; + + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + /// + /// Reads a tag data entry + /// + /// The table entry with reading information + /// the tag data entry + 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); + } + } + + /// + /// Reads the header of a + /// + /// The read signature + public IccTypeSignature ReadTagDataEntryHeader() + { + var type = (IccTypeSignature)this.ReadUInt32(); + this.AddIndex(4); // 4 bytes are not used + return type; + } + + /// + /// Reads the header of a and checks if it's the expected value + /// + /// expected value to check against + 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}"); + } + } + + /// + /// Reads a with an unknown + /// + /// The size of the entry in bytes + /// The read entry + public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size) + { + int count = (int)size - 8; // 8 is the tag header size + return new IccUnknownTagDataEntry(this.ReadBytes(count)); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + } + + /// + /// Reads a + /// + /// The read entry + public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry() + { + uint colorantCount = this.ReadUInt32(); + byte[] number = this.ReadBytes((int)colorantCount); + return new IccColorantOrderTagDataEntry(number); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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). + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() + { + return new IccDateTimeTagDataEntry(this.ReadDateTime()); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() + { + return new IccMeasurementTagDataEntry( + observer: (IccStandardObserver)this.ReadUInt32(), + xyzBacking: this.ReadXyzNumber(), + geometry: (IccMeasurementGeometry)this.ReadUInt32(), + flare: this.ReadUFix16(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() + { + return new IccParametricCurveTagDataEntry(this.ReadParametricCurve()); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + public IccSignatureTagDataEntry ReadSignatureTagDataEntry() + { + return new IccSignatureTagDataEntry(this.ReadAsciiString(4)); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccTextTagDataEntry ReadTextTagDataEntry(uint size) + { + return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() + { + return new IccViewingConditionsTagDataEntry( + illuminantXyz: this.ReadXyzNumber(), + surroundXyz: this.ReadXyzNumber(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The read entry + 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); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + 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); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs new file mode 100644 index 000000000..cb1fe5b55 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs @@ -0,0 +1,106 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Text; + using ImageSharp.IO; + + /// + /// Provides methods to read ICC data types + /// + internal sealed partial class IccDataReader + { + private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; + private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// The data that is read + /// + private readonly byte[] data; + + /// + /// The bit converter + /// + private readonly EndianBitConverter converter = new BigEndianBitConverter(); + + /// + /// The current reading position + /// + private int currentIndex; + + /// + /// Initializes a new instance of the class. + /// + /// The data to read + public IccDataReader(byte[] data) + { + Guard.NotNull(data, nameof(data)); + this.data = data; + } + + /// + /// Sets the reading position to the given value + /// + /// The new index position + public void SetIndex(int index) + { + this.currentIndex = index.Clamp(0, this.data.Length); + } + + /// + /// Returns the current without increment and adds the given increment + /// + /// The value to increment + /// The current without the increment + private int AddIndex(int increment) + { + int tmp = this.currentIndex; + this.currentIndex += increment; + return tmp; + } + + /// + /// Calculates the 4 byte padding and adds it to the variable + /// + private void AddPadding() + { + this.currentIndex += this.CalcPadding(); + } + + /// + /// Calculates the 4 byte padding + /// + /// the number of bytes to pad + private int CalcPadding() + { + int p = 4 - (this.currentIndex % 4); + return p >= 4 ? 0 : p; + } + + /// + /// Gets the bit value at a specified position + /// + /// The value from where the bit will be extracted + /// Position of the bit. Zero based index from left to right. + /// The bit value at specified position + private bool GetBit(byte value, int position) + { + return ((value >> (7 - position)) & 1) == 1; + } + + /// + /// Gets the bit value at a specified position + /// + /// The value from where the bit will be extracted + /// Position of the bit. Zero based index from left to right. + /// The bit value at specified position + private bool GetBit(ushort value, int position) + { + return ((value >> (15 - position)) & 1) == 1; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs new file mode 100644 index 000000000..4b6e454a1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + 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; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + 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; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + 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; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + 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}"); + } + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + 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; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + 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; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs new file mode 100644 index 000000000..f85d59714 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs @@ -0,0 +1,128 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes an 8bit lookup table + /// + /// The LUT to write + /// The number of bytes written + 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; + } + + /// + /// Writes an 16bit lookup table + /// + /// The LUT to write + /// The number of bytes written + 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; + } + + /// + /// Writes an color lookup table + /// + /// The CLUT to write + /// The number of bytes written + 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}"); + } + } + + /// + /// Writes a 8bit color lookup table + /// + /// The CLUT to write + /// The number of bytes written + 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; + } + + /// + /// Writes a 16bit color lookup table + /// + /// The CLUT to write + /// The number of bytes written + 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; + } + + /// + /// Writes a 32bit float color lookup table + /// + /// The CLUT to write + /// The number of bytes written + 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; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs new file mode 100644 index 000000000..13a15b483 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -0,0 +1,162 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + using ImageSharp.Memory; + + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + 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; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(Fast2DArray 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; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + 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; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + 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; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + 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; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs new file mode 100644 index 000000000..b3ddb538c --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a + /// + /// The element to write + /// The number of bytes written + 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}"); + } + } + + /// + /// Writes a CurveSet + /// + /// The element to write + /// The number of bytes written + public int WriteCurveSetProcessElement(IccCurveSetProcessElement value) + { + int count = 0; + foreach (IccOneDimensionalCurve curve in value.Curves) + { + count += this.WriteOneDimensionalCurve(curve); + count += this.WritePadding(); + } + + return count; + } + + /// + /// Writes a Matrix + /// + /// The element to write + /// The number of bytes written + public int WriteMatrixProcessElement(IccMatrixProcessElement value) + { + return this.WriteMatrix(value.MatrixIxO, true) + + this.WriteMatrix(value.MatrixOx1, true); + } + + /// + /// Writes a CLUT + /// + /// The element to write + /// The number of bytes written + public int WriteClutProcessElement(IccClutProcessElement value) + { + return this.WriteClut(value.ClutValue); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs new file mode 100644 index 000000000..d90c1ff54 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs @@ -0,0 +1,137 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a DateTime + /// + /// The value to write + /// the number of bytes written + 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); + } + + /// + /// Writes an ICC profile version number + /// + /// The value to write + /// the number of bytes written + 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); + } + + /// + /// Writes an XYZ number + /// + /// The value to write + /// the number of bytes written + public int WriteXyzNumber(Vector3 value) + { + return this.WriteFix16(value.X) + + this.WriteFix16(value.Y) + + this.WriteFix16(value.Z); + } + + /// + /// Writes a profile ID + /// + /// The value to write + /// the number of bytes written + public int WriteProfileId(IccProfileId value) + { + return this.WriteUInt32(value.Part1) + + this.WriteUInt32(value.Part2) + + this.WriteUInt32(value.Part3) + + this.WriteUInt32(value.Part4); + } + + /// + /// Writes a position number + /// + /// The value to write + /// the number of bytes written + public int WritePositionNumber(IccPositionNumber value) + { + return this.WriteUInt32(value.Offset) + + this.WriteUInt32(value.Size); + } + + /// + /// Writes a response number + /// + /// The value to write + /// the number of bytes written + public int WriteResponseNumber(IccResponseNumber value) + { + return this.WriteUInt16(value.DeviceCode) + + this.WriteFix16(value.MeasurementValue); + } + + /// + /// Writes a named color + /// + /// The value to write + /// the number of bytes written + public int WriteNamedColor(IccNamedColor value) + { + return this.WriteAsciiString(value.Name, 32, true) + + this.WriteArray(value.PcsCoordinates) + + this.WriteArray(value.DeviceCoordinates); + } + + /// + /// Writes a profile description + /// + /// The value to write + /// the number of bytes written + 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)); + } + + /// + /// Writes a screening channel + /// + /// The value to write + /// the number of bytes written + public int WriteScreeningChannel(IccScreeningChannel value) + { + return this.WriteFix16(value.Frequency) + + this.WriteFix16(value.Angle) + + this.WriteInt32((int)value.SpotShape); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs new file mode 100644 index 000000000..fbb7065e6 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs @@ -0,0 +1,248 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Text; + + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a byte + /// + /// The value to write + /// the number of bytes written + public int WriteByte(byte value) + { + this.dataStream.WriteByte(value); + return 1; + } + + /// + /// Writes an ushort + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt16(ushort value) + { + return this.WriteBytes((byte*)&value, 2); + } + + /// + /// Writes a short + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt16(short value) + { + return this.WriteBytes((byte*)&value, 2); + } + + /// + /// Writes an uint + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt32(uint value) + { + return this.WriteBytes((byte*)&value, 4); + } + + /// + /// Writes an int + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt32(int value) + { + return this.WriteBytes((byte*)&value, 4); + } + + /// + /// Writes an ulong + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt64(ulong value) + { + return this.WriteBytes((byte*)&value, 8); + } + + /// + /// Writes a long + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt64(long value) + { + return this.WriteBytes((byte*)&value, 8); + } + + /// + /// Writes a float + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteSingle(float value) + { + return this.WriteBytes((byte*)&value, 4); + } + + /// + /// Writes a double + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteDouble(double value) + { + return this.WriteBytes((byte*)&value, 8); + } + + /// + /// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits + /// + /// The value to write + /// the number of bytes written + 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)); + } + + /// + /// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits + /// + /// The value to write + /// the number of bytes written + 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)); + } + + /// + /// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits + /// + /// The value to write + /// the number of bytes written + 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)); + } + + /// + /// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits + /// + /// The value to write + /// the number of bytes written + 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)); + } + + /// + /// Writes an ASCII encoded string + /// + /// the string to write + /// the number of bytes written + 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; + } + + /// + /// Writes an ASCII encoded string resizes it to the given length + /// + /// The string to write + /// The desired length of the string (including potential null terminator) + /// If True, there will be a \0 added at the end + /// the number of bytes written + 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; + } + + /// + /// Writes an UTF-16 big-endian encoded string + /// + /// the string to write + /// the number of bytes written + 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; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs new file mode 100644 index 000000000..8e8065cee --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs @@ -0,0 +1,1003 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter + { + /// + /// Writes a tag data entry + /// + /// The entry to write + /// The table entry for the written data entry + /// The number of bytes written (excluding padding) + public int WriteTagDataEntry(IccTagDataEntry data, out IccTagTableEntry table) + { + uint offset = (uint)this.dataStream.Position; + int count = this.WriteTagDataEntry(data); + this.WritePadding(); + table = new IccTagTableEntry(data.TagSignature, offset, (uint)count); + return count; + } + + /// + /// Writes a tag data entry (without padding) + /// + /// The entry to write + /// The number of bytes written + public int WriteTagDataEntry(IccTagDataEntry entry) + { + int count = this.WriteTagDataEntryHeader(entry.Signature); + + switch (entry.Signature) + { + case IccTypeSignature.Chromaticity: + count += this.WriteChromaticityTagDataEntry(entry as IccChromaticityTagDataEntry); + break; + case IccTypeSignature.ColorantOrder: + count += this.WriteColorantOrderTagDataEntry(entry as IccColorantOrderTagDataEntry); + break; + case IccTypeSignature.ColorantTable: + count += this.WriteColorantTableTagDataEntry(entry as IccColorantTableTagDataEntry); + break; + case IccTypeSignature.Curve: + count += this.WriteCurveTagDataEntry(entry as IccCurveTagDataEntry); + break; + case IccTypeSignature.Data: + count += this.WriteDataTagDataEntry(entry as IccDataTagDataEntry); + break; + case IccTypeSignature.DateTime: + count += this.WriteDateTimeTagDataEntry(entry as IccDateTimeTagDataEntry); + break; + case IccTypeSignature.Lut16: + count += this.WriteLut16TagDataEntry(entry as IccLut16TagDataEntry); + break; + case IccTypeSignature.Lut8: + count += this.WriteLut8TagDataEntry(entry as IccLut8TagDataEntry); + break; + case IccTypeSignature.LutAToB: + count += this.WriteLutAtoBTagDataEntry(entry as IccLutAToBTagDataEntry); + break; + case IccTypeSignature.LutBToA: + count += this.WriteLutBtoATagDataEntry(entry as IccLutBToATagDataEntry); + break; + case IccTypeSignature.Measurement: + count += this.WriteMeasurementTagDataEntry(entry as IccMeasurementTagDataEntry); + break; + case IccTypeSignature.MultiLocalizedUnicode: + count += this.WriteMultiLocalizedUnicodeTagDataEntry(entry as IccMultiLocalizedUnicodeTagDataEntry); + break; + case IccTypeSignature.MultiProcessElements: + count += this.WriteMultiProcessElementsTagDataEntry(entry as IccMultiProcessElementsTagDataEntry); + break; + case IccTypeSignature.NamedColor2: + count += this.WriteNamedColor2TagDataEntry(entry as IccNamedColor2TagDataEntry); + break; + case IccTypeSignature.ParametricCurve: + count += this.WriteParametricCurveTagDataEntry(entry as IccParametricCurveTagDataEntry); + break; + case IccTypeSignature.ProfileSequenceDesc: + count += this.WriteProfileSequenceDescTagDataEntry(entry as IccProfileSequenceDescTagDataEntry); + break; + case IccTypeSignature.ProfileSequenceIdentifier: + count += this.WriteProfileSequenceIdentifierTagDataEntry(entry as IccProfileSequenceIdentifierTagDataEntry); + break; + case IccTypeSignature.ResponseCurveSet16: + count += this.WriteResponseCurveSet16TagDataEntry(entry as IccResponseCurveSet16TagDataEntry); + break; + case IccTypeSignature.S15Fixed16Array: + count += this.WriteFix16ArrayTagDataEntry(entry as IccFix16ArrayTagDataEntry); + break; + case IccTypeSignature.Signature: + count += this.WriteSignatureTagDataEntry(entry as IccSignatureTagDataEntry); + break; + case IccTypeSignature.Text: + count += this.WriteTextTagDataEntry(entry as IccTextTagDataEntry); + break; + case IccTypeSignature.U16Fixed16Array: + count += this.WriteUFix16ArrayTagDataEntry(entry as IccUFix16ArrayTagDataEntry); + break; + case IccTypeSignature.UInt16Array: + count += this.WriteUInt16ArrayTagDataEntry(entry as IccUInt16ArrayTagDataEntry); + break; + case IccTypeSignature.UInt32Array: + count += this.WriteUInt32ArrayTagDataEntry(entry as IccUInt32ArrayTagDataEntry); + break; + case IccTypeSignature.UInt64Array: + count += this.WriteUInt64ArrayTagDataEntry(entry as IccUInt64ArrayTagDataEntry); + break; + case IccTypeSignature.UInt8Array: + count += this.WriteUInt8ArrayTagDataEntry(entry as IccUInt8ArrayTagDataEntry); + break; + case IccTypeSignature.ViewingConditions: + count += this.WriteViewingConditionsTagDataEntry(entry as IccViewingConditionsTagDataEntry); + break; + case IccTypeSignature.Xyz: + count += this.WriteXyzTagDataEntry(entry as IccXyzTagDataEntry); + break; + + // V2 Types: + case IccTypeSignature.TextDescription: + count += this.WriteTextDescriptionTagDataEntry(entry as IccTextDescriptionTagDataEntry); + break; + case IccTypeSignature.CrdInfo: + count += this.WriteCrdInfoTagDataEntry(entry as IccCrdInfoTagDataEntry); + break; + case IccTypeSignature.Screening: + count += this.WriteScreeningTagDataEntry(entry as IccScreeningTagDataEntry); + break; + case IccTypeSignature.UcrBg: + count += this.WriteUcrBgTagDataEntry(entry as IccUcrBgTagDataEntry); + break; + + // Unsupported or unknown + case IccTypeSignature.DeviceSettings: + case IccTypeSignature.NamedColor: + case IccTypeSignature.Unknown: + default: + count += this.WriteUnknownTagDataEntry(entry as IccUnknownTagDataEntry); + break; + } + + return count; + } + + /// + /// Writes the header of a + /// + /// The signature of the entry + /// The number of bytes written + public int WriteTagDataEntryHeader(IccTypeSignature signature) + { + return this.WriteUInt32((uint)signature) + + this.WriteEmpty(4); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteChromaticityTagDataEntry(IccChromaticityTagDataEntry value) + { + int count = this.WriteUInt16((ushort)value.ChannelCount); + count += this.WriteUInt16((ushort)value.ColorantType); + + for (int i = 0; i < value.ChannelCount; i++) + { + count += this.WriteUFix16(value.ChannelValues[i][0]); + count += this.WriteUFix16(value.ChannelValues[i][1]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value) + { + return this.WriteUInt32((uint)value.ColorantNumber.Length) + + this.WriteArray(value.ColorantNumber); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteColorantTableTagDataEntry(IccColorantTableTagDataEntry value) + { + int count = this.WriteUInt32((uint)value.ColorantData.Length); + foreach (IccColorantTableEntry colorant in value.ColorantData) + { + count += this.WriteAsciiString(colorant.Name, 32, true); + count += this.WriteUInt16(colorant.Pcs1); + count += this.WriteUInt16(colorant.Pcs2); + count += this.WriteUInt16(colorant.Pcs3); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteCurveTagDataEntry(IccCurveTagDataEntry value) + { + int count = 0; + + if (value.IsIdentityResponse) + { + count += this.WriteUInt32(0); + } + else if (value.IsGamma) + { + count += this.WriteUInt32(1); + count += this.WriteUFix8(value.Gamma); + } + else + { + count += this.WriteUInt32((uint)value.CurveData.Length); + for (int i = 0; i < value.CurveData.Length; i++) + { + count += this.WriteUInt16((ushort)((value.CurveData[i] * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue)); + } + } + + return count; + + // TODO: Page 48: 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). + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteDataTagDataEntry(IccDataTagDataEntry value) + { + return this.WriteEmpty(3) + + this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00)) + + this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) + { + return this.WriteDateTime(value.Value); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLut16TagDataEntry(IccLut16TagDataEntry value) + { + int count = this.WriteByte((byte)value.InputValues.Length); + count += this.WriteByte((byte)value.OutputValues.Length); + count += this.WriteByte(value.ClutValues.GridPointCount[0]); + count += this.WriteEmpty(1); + + count += this.WriteMatrix(value.Matrix, false); + + count += this.WriteUInt16((ushort)value.InputValues[0].Values.Length); + count += this.WriteUInt16((ushort)value.OutputValues[0].Values.Length); + + foreach (IccLut lut in value.InputValues) + { + count += this.WriteLut16(lut); + } + + count += this.WriteClut16(value.ClutValues); + + foreach (IccLut lut in value.OutputValues) + { + count += this.WriteLut16(lut); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLut8TagDataEntry(IccLut8TagDataEntry value) + { + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteByte((byte)value.ClutValues.Values[0].Length); + count += this.WriteEmpty(1); + + count += this.WriteMatrix(value.Matrix, false); + + foreach (IccLut lut in value.InputValues) + { + count += this.WriteLut8(lut); + } + + count += this.WriteClut8(value.ClutValues); + + foreach (IccLut lut in value.OutputValues) + { + count += this.WriteLut8(lut); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLutAtoBTagDataEntry(IccLutAToBTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteEmpty(2); + + long bCurveOffset = 0; + long matrixOffset = 0; + long mCurveOffset = 0; + long clutOffset = 0; + long aCurveOffset = 0; + + // Jump over offset values + long offsetpos = this.dataStream.Position; + this.dataStream.Position += 5 * 4; + + if (value.CurveB != null) + { + bCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveB); + count += this.WritePadding(); + } + + if (value.Matrix3x1 != null && value.Matrix3x3 != null) + { + matrixOffset = this.dataStream.Position; + count += this.WriteMatrix(value.Matrix3x3.Value, false); + count += this.WriteMatrix(value.Matrix3x1.Value, false); + count += this.WritePadding(); + } + + if (value.CurveM != null) + { + mCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveM); + count += this.WritePadding(); + } + + if (value.ClutValues != null) + { + clutOffset = this.dataStream.Position; + count += this.WriteClut(value.ClutValues); + count += this.WritePadding(); + } + + if (value.CurveA != null) + { + aCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveA); + count += this.WritePadding(); + } + + // Set offset values + long lpos = this.dataStream.Position; + this.dataStream.Position = offsetpos; + + if (bCurveOffset != 0) + { + bCurveOffset -= start; + } + + if (matrixOffset != 0) + { + matrixOffset -= start; + } + + if (mCurveOffset != 0) + { + mCurveOffset -= start; + } + + if (clutOffset != 0) + { + clutOffset -= start; + } + + if (aCurveOffset != 0) + { + aCurveOffset -= start; + } + + count += this.WriteUInt32((uint)bCurveOffset); + count += this.WriteUInt32((uint)matrixOffset); + count += this.WriteUInt32((uint)mCurveOffset); + count += this.WriteUInt32((uint)clutOffset); + count += this.WriteUInt32((uint)aCurveOffset); + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLutBtoATagDataEntry(IccLutBToATagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteEmpty(2); + + long bCurveOffset = 0; + long matrixOffset = 0; + long mCurveOffset = 0; + long clutOffset = 0; + long aCurveOffset = 0; + + // Jump over offset values + long offsetpos = this.dataStream.Position; + this.dataStream.Position += 5 * 4; + + if (value.CurveB != null) + { + bCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveB); + count += this.WritePadding(); + } + + if (value.Matrix3x1 != null && value.Matrix3x3 != null) + { + matrixOffset = this.dataStream.Position; + count += this.WriteMatrix(value.Matrix3x3.Value, false); + count += this.WriteMatrix(value.Matrix3x1.Value, false); + count += this.WritePadding(); + } + + if (value.CurveM != null) + { + mCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveM); + count += this.WritePadding(); + } + + if (value.ClutValues != null) + { + clutOffset = this.dataStream.Position; + count += this.WriteClut(value.ClutValues); + count += this.WritePadding(); + } + + if (value.CurveA != null) + { + aCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveA); + count += this.WritePadding(); + } + + // Set offset values + long lpos = this.dataStream.Position; + this.dataStream.Position = offsetpos; + + if (bCurveOffset != 0) + { + bCurveOffset -= start; + } + + if (matrixOffset != 0) + { + matrixOffset -= start; + } + + if (mCurveOffset != 0) + { + mCurveOffset -= start; + } + + if (clutOffset != 0) + { + clutOffset -= start; + } + + if (aCurveOffset != 0) + { + aCurveOffset -= start; + } + + count += this.WriteUInt32((uint)bCurveOffset); + count += this.WriteUInt32((uint)matrixOffset); + count += this.WriteUInt32((uint)mCurveOffset); + count += this.WriteUInt32((uint)clutOffset); + count += this.WriteUInt32((uint)aCurveOffset); + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMeasurementTagDataEntry(IccMeasurementTagDataEntry value) + { + return this.WriteUInt32((uint)value.Observer) + + this.WriteXyzNumber(value.XyzBacking) + + this.WriteUInt32((uint)value.Geometry) + + this.WriteUFix16(value.Flare) + + this.WriteUInt32((uint)value.Illuminant); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int cultureCount = value.Texts.Length; + + int count = this.WriteUInt32((uint)cultureCount); + count += this.WriteUInt32(12); // One record has always 12 bytes size + + // Jump over position table + long tpos = this.dataStream.Position; + this.dataStream.Position += cultureCount * 12; + + uint[] offset = new uint[cultureCount]; + int[] lengths = new int[cultureCount]; + + for (int i = 0; i < cultureCount; i++) + { + offset[i] = (uint)(this.dataStream.Position - start); + count += lengths[i] = this.WriteUnicodeString(value.Texts[i].Text); + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tpos; + for (int i = 0; i < cultureCount; i++) + { + string[] code = value.Texts[i].Culture.Name.Split('-'); + + count += this.WriteAsciiString(code[0].ToLower(), 2, false); + count += this.WriteAsciiString(code[1].ToUpper(), 2, false); + + count += this.WriteUInt32((uint)lengths[i]); + count += this.WriteUInt32(offset[i]); + } + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + + int count = this.WriteUInt16((ushort)value.InputChannelCount); + count += this.WriteUInt16((ushort)value.OutputChannelCount); + count += this.WriteUInt32((uint)value.Data.Length); + + // Jump over position table + long tpos = this.dataStream.Position; + this.dataStream.Position += value.Data.Length * 8; + + IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length]; + for (int i = 0; i < value.Data.Length; i++) + { + uint offset = (uint)(this.dataStream.Position - start); + int size = this.WriteMultiProcessElement(value.Data[i]); + count += this.WritePadding(); + posTable[i] = new IccPositionNumber(offset, (uint)size); + count += size; + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tpos; + foreach (IccPositionNumber pos in posTable) + { + count += this.WritePositionNumber(pos); + } + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value) + { + int count = this.WriteInt32(value.VendorFlags) + + this.WriteUInt32((uint)value.Colors.Length) + + this.WriteUInt32((uint)value.CoordinateCount) + + this.WriteAsciiString(value.Prefix, 32, true) + + this.WriteAsciiString(value.Suffix, 32, true); + + foreach (IccNamedColor color in value.Colors) + { + count += this.WriteNamedColor(color); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) + { + return this.WriteParametricCurve(value.Curve); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteProfileSequenceDescTagDataEntry(IccProfileSequenceDescTagDataEntry value) + { + int count = this.WriteUInt32((uint)value.Descriptions.Length); + foreach (IccProfileDescription desc in value.Descriptions) + { + count += this.WriteProfileDescription(desc); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifierTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + int length = value.Data.Length; + + int count = this.WriteUInt32((uint)length); + + // Jump over position table + long tablePosition = this.dataStream.Position; + this.dataStream.Position += length * 8; + IccPositionNumber[] table = new IccPositionNumber[length]; + + for (int i = 0; i < length; i++) + { + uint offset = (uint)(this.dataStream.Position - start); + int size = this.WriteProfileId(value.Data[i].Id); + size += this.WriteTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.Data[i].Description)); + size += this.WritePadding(); + table[i] = new IccPositionNumber(offset, (uint)size); + count += size; + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tablePosition; + foreach (IccPositionNumber pos in table) + { + count += this.WritePositionNumber(pos); + } + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteResponseCurveSet16TagDataEntry(IccResponseCurveSet16TagDataEntry value) + { + long start = this.dataStream.Position - 8; + + int count = this.WriteUInt16(value.ChannelCount); + count += this.WriteUInt16((ushort)value.Curves.Length); + + // Jump over position table + long tablePosition = this.dataStream.Position; + this.dataStream.Position += value.Curves.Length * 4; + + uint[] offset = new uint[value.Curves.Length]; + + for (int i = 0; i < value.Curves.Length; i++) + { + offset[i] = (uint)(this.dataStream.Position - start); + count += this.WriteResponseCurve(value.Curves[i]); + count += this.WritePadding(); + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tablePosition; + count += this.WriteArray(offset); + + this.dataStream.Position = lpos; + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteFix16(value.Data[i] * 256d); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value) + { + return this.WriteAsciiString(value.SignatureData, 4, false); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteTextTagDataEntry(IccTextTagDataEntry value) + { + return this.WriteAsciiString(value.Text); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteUFix16(value.Data[i]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value) + { + return this.WriteArray(value.Data); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value) + { + return this.WriteXyzNumber(value.IlluminantXyz) + + this.WriteXyzNumber(value.SurroundXyz) + + this.WriteUInt32((uint)value.Illuminant); + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteXyzTagDataEntry(IccXyzTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteXyzNumber(value.Data[i]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteTextDescriptionTagDataEntry(IccTextDescriptionTagDataEntry value) + { + int size, count = 0; + + if (value.Ascii == null) + { + count += this.WriteUInt32(0); + } + else + { + this.dataStream.Position += 4; + count += size = this.WriteAsciiString(value.Ascii + '\0'); + this.dataStream.Position -= size + 4; + count += this.WriteUInt32((uint)size); + this.dataStream.Position += size; + } + + if (value.Unicode == null) + { + count += this.WriteUInt32(0); + count += this.WriteUInt32(0); + } + else + { + this.dataStream.Position += 8; + count += size = this.WriteUnicodeString(value.Unicode + '\0'); + this.dataStream.Position -= size + 8; + count += this.WriteUInt32(value.UnicodeLanguageCode); + count += this.WriteUInt32((uint)value.Unicode.Length + 1); + this.dataStream.Position += size; + } + + if (value.ScriptCode == null) + { + count += this.WriteUInt16(0); + count += this.WriteByte(0); + count += this.WriteEmpty(67); + } + else + { + this.dataStream.Position += 3; + count += size = this.WriteAsciiString(value.ScriptCode, 67, true); + this.dataStream.Position -= size + 3; + count += this.WriteUInt16(value.ScriptCodeCode); + count += this.WriteByte((byte)(value.ScriptCode.Length > 66 ? 67 : value.ScriptCode.Length + 1)); + this.dataStream.Position += size; + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteCrdInfoTagDataEntry(IccCrdInfoTagDataEntry value) + { + int count = 0; + WriteString(value.PostScriptProductName); + WriteString(value.RenderingIntent0Crd); + WriteString(value.RenderingIntent1Crd); + WriteString(value.RenderingIntent2Crd); + WriteString(value.RenderingIntent3Crd); + + return count; + + void WriteString(string text) + { + int textLength; + if (string.IsNullOrEmpty(text)) + { + textLength = 0; + } + else + { + textLength = text.Length + 1; // + 1 for null terminator + } + + count += this.WriteUInt32((uint)textLength); + count += this.WriteAsciiString(text, textLength, true); + } + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteScreeningTagDataEntry(IccScreeningTagDataEntry value) + { + int count = 0; + + count += this.WriteInt32((int)value.Flags); + count += this.WriteUInt32((uint)value.Channels.Length); + for (int i = 0; i < value.Channels.Length; i++) + { + count += this.WriteScreeningChannel(value.Channels[i]); + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUcrBgTagDataEntry(IccUcrBgTagDataEntry value) + { + int count = 0; + + count += this.WriteUInt32((uint)value.UcrCurve.Length); + for (int i = 0; i < value.UcrCurve.Length; i++) + { + count += this.WriteUInt16(value.UcrCurve[i]); + } + + count += this.WriteUInt32((uint)value.BgCurve.Length); + for (int i = 0; i < value.BgCurve.Length; i++) + { + count += this.WriteUInt16(value.BgCurve[i]); + } + + count += this.WriteAsciiString(value.Description + '\0'); + + return count; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs new file mode 100644 index 000000000..a9a65b80b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs @@ -0,0 +1,253 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.IO; + using System.Text; + + /// + /// Provides methods to write ICC data types + /// + internal sealed partial class IccDataWriter : IDisposable + { + private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; + private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// The underlying stream where the data is written to + /// + private readonly MemoryStream dataStream; + + /// + /// To detect redundant calls + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + public IccDataWriter() + { + this.dataStream = new MemoryStream(); + } + + /// + /// Gets the currently written length in bytes + /// + public uint Length => (uint)this.dataStream.Length; + + /// + /// Gets the written data bytes + /// + /// The written data + public byte[] GetData() + { + return this.dataStream.ToArray(); + } + + /// + /// Sets the writing position to the given value + /// + /// The new index position + public void SetIndex(int index) + { + this.dataStream.Position = index; + } + + /// + /// Writes a byte array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(byte[] data) + { + this.dataStream.Write(data, 0, data.Length); + return data.Length; + } + + /// + /// Writes a ushort array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(ushort[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt16(data[i]); + } + + return data.Length * 2; + } + + /// + /// Writes a short array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(short[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteInt16(data[i]); + } + + return data.Length * 2; + } + + /// + /// Writes a uint array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(uint[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt32(data[i]); + } + + return data.Length * 4; + } + + /// + /// Writes an int array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(int[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteInt32(data[i]); + } + + return data.Length * 4; + } + + /// + /// Writes a ulong array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(ulong[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt64(data[i]); + } + + return data.Length * 8; + } + + /// + /// Write a number of empty bytes + /// + /// The number of bytes to write + /// The number of bytes written + public int WriteEmpty(int length) + { + for (int i = 0; i < length; i++) + { + this.dataStream.WriteByte(0); + } + + return length; + } + + /// + /// Writes empty bytes to a 4-byte margin + /// + /// The number of bytes written + public int WritePadding() + { + int p = 4 - ((int)this.dataStream.Position % 4); + return this.WriteEmpty(p >= 4 ? 0 : p); + } + + /// + public void Dispose() + { + this.Dispose(true); + } + + /// + /// Writes given bytes from pointer + /// + /// Pointer to the bytes to write + /// The number of bytes to write + /// The number of bytes written + private unsafe int WriteBytes(byte* data, int length) + { + if (IsLittleEndian) + { + for (int i = length - 1; i >= 0; i--) + { + this.dataStream.WriteByte(data[i]); + } + } + else + { + this.WriteBytesDirect(data, length); + } + + return length; + } + + /// + /// Writes given bytes from pointer ignoring endianness + /// + /// Pointer to the bytes to write + /// The number of bytes to write + /// The number of bytes written + private unsafe int WriteBytesDirect(byte* data, int length) + { + for (int i = 0; i < length; i++) + { + this.dataStream.WriteByte(data[i]); + } + + return length; + } + + /// + /// Writes curve data + /// + /// The curves to write + /// The number of bytes written + private int WriteCurves(IccTagDataEntry[] curves) + { + int count = 0; + foreach (IccTagDataEntry curve in curves) + { + if (curve.Signature != IccTypeSignature.Curve && curve.Signature != 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"); + } + + count += this.WriteTagDataEntry(curve); + count += this.WritePadding(); + } + + return count; + } + + private void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.dataStream?.Dispose(); + } + + this.isDisposed = true; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs new file mode 100644 index 000000000..066cbe848 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Color lookup table data type + /// + internal enum IccClutDataType + { + /// + /// 32bit floating point + /// + Float, + + /// + /// 8bit unsigned integer (byte) + /// + UInt8, + + /// + /// 16bit unsigned integer (ushort) + /// + UInt16, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs new file mode 100644 index 000000000..3f57ded74 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs @@ -0,0 +1,138 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Color Space Type + /// + public enum IccColorSpaceType : uint + { + /// + /// CIE XYZ + /// + CieXyz = 0x58595A20, // XYZ + + /// + /// CIE Lab + /// + CieLab = 0x4C616220, // Lab + + /// + /// CIE Luv + /// + CieLuv = 0x4C757620, // Luv + + /// + /// YCbCr + /// + YCbCr = 0x59436272, // YCbr + + /// + /// CIE Yxy + /// + CieYxy = 0x59787920, // Yxy + + /// + /// RGB + /// + Rgb = 0x52474220, // RGB + + /// + /// Gray + /// + Gray = 0x47524159, // GRAY + + /// + /// HSV + /// + Hsv = 0x48535620, // HSV + + /// + /// HLS + /// + Hls = 0x484C5320, // HLS + + /// + /// CMYK + /// + Cmyk = 0x434D594B, // CMYK + + /// + /// CMY + /// + Cmy = 0x434D5920, // CMY + + /// + /// Generic 2 channel color + /// + Color2 = 0x32434C52, // 2CLR + + /// + /// Generic 3 channel color + /// + Color3 = 0x33434C52, // 3CLR + + /// + /// Generic 4 channel color + /// + Color4 = 0x34434C52, // 4CLR + + /// + /// Generic 5 channel color + /// + Color5 = 0x35434C52, // 5CLR + + /// + /// Generic 6 channel color + /// + Color6 = 0x36434C52, // 6CLR + + /// + /// Generic 7 channel color + /// + Color7 = 0x37434C52, // 7CLR + + /// + /// Generic 8 channel color + /// + Color8 = 0x38434C52, // 8CLR + + /// + /// Generic 9 channel color + /// + Color9 = 0x39434C52, // 9CLR + + /// + /// Generic 10 channel color + /// + Color10 = 0x41434C52, // ACLR + + /// + /// Generic 11 channel color + /// + Color11 = 0x42434C52, // BCLR + + /// + /// Generic 12 channel color + /// + Color12 = 0x43434C52, // CCLR + + /// + /// Generic 13 channel color + /// + Color13 = 0x44434C52, // DCLR + + /// + /// Generic 14 channel color + /// + Color14 = 0x45434C52, // ECLR + + /// + /// Generic 15 channel color + /// + Color15 = 0x46434C52, // FCLR + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs new file mode 100644 index 000000000..251a848b7 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Colorant Encoding + /// + internal enum IccColorantEncoding : ushort + { + /// + /// Unknown colorant encoding + /// + Unknown = 0x0000, + + /// + /// ITU-R BT.709-2 colorant encoding + /// + ItuRBt709_2 = 0x0001, + + /// + /// SMPTE RP145 colorant encoding + /// + SmpteRp145 = 0x0002, + + /// + /// EBU Tech.3213-E colorant encoding + /// + EbuTech3213E = 0x0003, + + /// + /// P22 colorant encoding + /// + P22 = 0x0004, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs new file mode 100644 index 000000000..14515c113 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Curve Measurement Encodings + /// + internal enum IccCurveMeasurementEncodings : uint + { + /// + /// ISO 5-3 densitometer response. This is the accepted standard for + /// reflection densitometers for measuring photographic color prints + /// + StatusA = 0x53746141, // StaA + + /// + /// ISO 5-3 densitometer response which is the accepted standard in + /// Europe for color reflection densitometers + /// + StatusE = 0x53746145, // StaE + + /// + /// ISO 5-3 densitometer response commonly referred to as narrow band + /// or interference-type response. + /// + StatusI = 0x53746149, // StaI + + /// + /// ISO 5-3 wide band color reflection densitometer response which is + /// the accepted standard in the United States for color reflection densitometers + /// + StatusT = 0x53746154, // StaT + + /// + /// ISO 5-3 densitometer response for measuring color negatives + /// + StatusM = 0x5374614D, // StaM + + /// + /// DIN 16536-2 densitometer response, with no polarizing filter + /// + DinE = 0x434E2020, // DN + + /// + /// DIN 16536-2 densitometer response, with polarizing filter + /// + DinEPol = 0x434E2050, // DNP + + /// + /// DIN 16536-2 narrow band densitometer response, with no polarizing filter + /// + DinI = 0x434E4E20, // DNN + + /// + /// DIN 16536-2 narrow band densitometer response, with polarizing filter + /// + DinIPol = 0x434E4E50, // DNNP + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs new file mode 100644 index 000000000..77ded0d1b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Curve Segment Signature + /// + internal enum IccCurveSegmentSignature : uint + { + /// + /// Curve defined by a formula + /// + FormulaCurve = 0x70617266, // parf + + /// + /// Curve defined by multiple segments + /// + SampledCurve = 0x73616D66, // samf + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs new file mode 100644 index 000000000..a4ca4befa --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0 + /// Section 4.2 to 4.15 + /// + internal enum IccDataType + { + /// + /// A 12-byte value representation of the time and date + /// + DateTime, + + /// + /// A single-precision 32-bit floating-point as specified in IEEE 754, + /// excluding un-normalized s, infinities, and not a "" (NaN) values + /// + Float32, + + /// + /// Positions of some data elements are indicated using a position offset with the data element's size. + /// + Position, + + /// + /// An 8-byte value, used to associate a normalized device code with a measurement value + /// + Response16, + + /// + /// A fixed signed 4-byte (32-bit) quantity which has 16 fractional bits + /// + S15Fixed16, + + /// + /// A fixed unsigned 4-byte (32-bit) quantity having 16 fractional bits + /// + U16Fixed16, + + /// + /// A fixed unsigned 2-byte (16-bit) quantity having15 fractional bits + /// + U1Fixed15, + + /// + /// A fixed unsigned 2-byte (16-bit) quantity having 8 fractional bits + /// + U8Fixed8, + + /// + /// An unsigned 2-byte (16-bit) integer + /// + UInt16, + + /// + /// An unsigned 4-byte (32-bit) integer + /// + UInt32, + + /// + /// An unsigned 8-byte (64-bit) integer + /// + UInt64, + + /// + /// An unsigned 1-byte (8-bit) integer + /// + UInt8, + + /// + /// A set of three fixed signed 4-byte (32-bit) quantities used to encode CIEXYZ, nCIEXYZ, and PCSXYZ tristimulus values + /// + Xyz, + + /// + /// Alpha-numeric values, and other input and output codes, shall conform to the American Standard Code for + /// Information Interchange (ASCII) specified in ISO/IEC 646. + /// + Ascii + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs new file mode 100644 index 000000000..c57cf4f97 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Device attributes. Can be combined with a logical OR + /// The least-significant 32 bits are defined by the ICC, + /// the rest can be used for vendor specific values + /// + [Flags] + public enum IccDeviceAttribute : long + { + /// + /// Opacity transparent + /// + OpacityTransparent = 1 << 0, + + /// + /// Opacity reflective + /// + OpacityReflective = 0, + + /// + /// Reflectivity matte + /// + ReflectivityMatte = 1 << 1, + + /// + /// Reflectivity glossy + /// + ReflectivityGlossy = 0, + + /// + /// Polarity negative + /// + PolarityNegative = 1 << 2, + + /// + /// Polarity positive + /// + PolarityPositive = 0, + + /// + /// Chroma black and white + /// + ChromaBlackWhite = 1 << 3, + + /// + /// Chroma color + /// + ChromaColor = 0, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs new file mode 100644 index 000000000..eacc1eb28 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Formula curve segment type + /// + internal enum IccFormulaCurveType : ushort + { + /// + /// Type 1: Y = (a * X + b)^γ + c + /// + Type1 = 0, + + /// + /// Type 1: Y = a * log10 (b * X^γ + c) + d + /// + Type2 = 1, + + /// + /// Type 3: Y = a * b^(c * X + d) + e + /// + Type3 = 2 + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs new file mode 100644 index 000000000..fdfb78049 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Measurement Geometry + /// + internal enum IccMeasurementGeometry : uint + { + /// + /// Unknown geometry + /// + Unknown = 0, + + /// + /// Geometry of 0°:45° or 45°:0° + /// + Degree0To45Or45To0 = 1, + + /// + /// Geometry of 0°:d or d:0° + /// + Degree0ToDOrDTo0 = 2, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs new file mode 100644 index 000000000..8ab690b64 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Multi process element signature + /// + internal enum IccMultiProcessElementSignature : uint + { + /// + /// Set of curves + /// + CurveSet = 0x6D666C74, // cvst + + /// + /// Matrix transformation + /// + Matrix = 0x6D617466, // matf + + /// + /// Color lookup table + /// + Clut = 0x636C7574, // clut + + /// + /// Reserved for future expansion. Do not use! + /// + BAcs = 0x62414353, // bACS + + /// + /// Reserved for future expansion. Do not use! + /// + EAcs = 0x65414353, // eACS + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs new file mode 100644 index 000000000..823b41340 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Formula curve segment type + /// + internal enum IccParametricCurveType : ushort + { + /// + /// Type 1: Y = X^g + /// + Type1 = 0, + + /// + /// CIE 122-1996: + /// For X >= -b/a: Y =(a * X + b)^g + /// For X $lt; -b/a: Y = 0 + /// + Cie122_1996 = 1, + + /// + /// IEC 61966-3: + /// For X >= -b/a: Y =(a * X + b)^g + c + /// For X $lt; -b/a: Y = c + /// + Iec61966_3 = 2, + + /// + /// IEC 61966-2-1 (sRGB): + /// For X >= d: Y =(a * X + b)^g + /// For X $lt; d: Y = c * X + /// + SRgb = 3, + + /// + /// Type 5: + /// For X >= d: Y =(a * X + b)^g + c + /// For X $lt; d: Y = c * X + f + /// + Type5 = 4, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs new file mode 100644 index 000000000..8fdeb3f41 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the primary platform/operating system framework for which the profile was created + /// + public enum IccPrimaryPlatformType : uint + { + /// + /// No platform identified + /// + NotIdentified = 0x00000000, + + /// + /// Apple Computer, Inc. + /// + AppleComputerInc = 0x4150504C, // APPL + + /// + /// Microsoft Corporation + /// + MicrosoftCorporation = 0x4D534654, // MSFT + + /// + /// Silicon Graphics, Inc. + /// + SiliconGraphicsInc = 0x53474920, // SGI + + /// + /// Sun Microsystems, Inc. + /// + SunMicrosystemsInc = 0x53554E57, // SUNW + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs new file mode 100644 index 000000000..9fb0b51f3 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Profile Class Name + /// + public enum IccProfileClass : uint + { + /// + /// Input profiles are generally used with devices such as scanners and + /// digital cameras. The types of profiles available for use as Input + /// profiles are N-component LUT-based, Three-component matrix-based, + /// and monochrome. + /// + InputDevice = 0x73636E72, // scnr + + /// + /// This class of profiles represents display devices such as monitors. + /// The types of profiles available for use as Display profiles are + /// N-component LUT-based, Three-component matrix-based, and monochrome. + /// + DisplayDevice = 0x6D6E7472, // mntr + + /// + /// Output profiles are used to support devices such as printers and + /// film recorders. The types of profiles available for use as Output + /// profiles are N-component LUT-based and Monochrome. + /// + OutputDevice = 0x70727472, // prtr + + /// + /// This profile contains a pre-evaluated transform that cannot be undone, + /// which represents a one-way link or connection between devices. It does + /// not represent any device model nor can it be embedded into images. + /// + DeviceLink = 0x6C696E6B, // link + + /// + /// This profile provides the relevant information to perform a transformation + /// between colour encodings and the PCS. This type of profile is based on + /// modelling rather than device measurement or characterization data. + /// ColorSpace profiles may be embedded in images. + /// + ColorSpace = 0x73706163, // spac + + /// + /// This profile represents abstract transforms and does not represent any + /// device model. Colour transformations using Abstract profiles are performed + /// from PCS to PCS. Abstract profiles cannot be embedded in images. + /// + Abstract = 0x61627374, // abst + + /// + /// NamedColor profiles can be thought of as sibling profiles to device profiles. + /// For a given device there would be one or more device profiles to handle + /// process colour conversions and one or more named colour profiles to handle + /// named colours. + /// + NamedColor = 0x6E6D636C, // nmcl + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs new file mode 100644 index 000000000..aa8df88bd --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Profile flags. Can be combined with a logical OR. + /// The least-significant 16 bits are reserved for the ICC, + /// the rest can be used for vendor specific values + /// + [Flags] + public enum IccProfileFlag : int + { + /// + /// No flags (equivalent to NotEmbedded and Independent) + /// + None = 0, + + /// + /// Profile is embedded within another file + /// + Embedded = 1 << 0, + + /// + /// Profile is embedded within another file + /// + NotEmbedded = 0, + + /// + /// Profile cannot be used independently of the embedded colour data + /// + NotIndependent = 1 << 1, + + /// + /// Profile can be used independently of the embedded colour data + /// + Independent = 0, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs new file mode 100644 index 000000000..6eab5b3fe --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs @@ -0,0 +1,364 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable InconsistentNaming +namespace ImageSharp +{ + /// + /// Enumerates the ICC Profile Tags as defined in ICC.1:2010 version 4.3.0.0 + /// Section 9 + /// + /// Each tag value represent the size of the tag in the profile. + /// + /// + public enum IccProfileTag : uint + { + /// + /// Unknown tag + /// + Unknown, + + /// + /// A2B0 - This tag defines a colour transform from Device, Colour Encoding or PCS, to PCS, or a colour transform + /// from Device 1 to Device 2, using lookup table tag element structures + /// + AToB0 = 0x41324230, + + /// + /// A2B2 - This tag describes the colour transform from Device or Colour Encoding to PCS using lookup table tag element structures + /// + AToB1 = 0x41324231, + + /// + /// A2B2 - This tag describes the colour transform from Device or Colour Encoding to PCS using lookup table tag element structures + /// + AToB2 = 0x41324232, + + /// + /// bXYZ - This tag contains the third column in the matrix used in matrix/TRC transforms. + /// + BlueMatrixColumn = 0x6258595A, + + /// + /// bTRC - This tag contains the blue channel tone reproduction curve. The first element represents no colorant (white) or + /// phosphor (black) and the last element represents 100 % colorant (blue) or 100 % phosphor (blue). + /// + BlueTrc = 0x62545243, + + /// + /// B2A0 - This tag defines a colour transform from PCS to Device or Colour Encoding using the lookup table tag element structures + /// + BToA0 = 0x42324130, + + /// + /// B2A1 - This tag defines a colour transform from PCS to Device or Colour Encoding using the lookup table tag element structures. + /// + BToA1 = 0x42324131, + + /// + /// B2A2 - This tag defines a colour transform from PCS to Device or Colour Encoding using the lookup table tag element structures. + /// + BToA2 = 0x42324132, + + /// + /// B2D0 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA0 tag. + /// + BToD0 = 0x42324430, + + /// + /// B2D1 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA1 tag. + /// + BToD1 = 0x42324431, + + /// + /// B2D2 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA2 tag. + /// + BToD2 = 0x42324432, + + /// + /// B2D3 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA1 tag. + /// + BToD3 = 0x42324433, + + /// + /// calt - This tag contains the profile calibration date and time. This allows applications and utilities to verify if this profile matches a + /// vendor's profile and how recently calibration has been performed. + /// + CalibrationDateTime = 0x63616C74, + + /// + /// targ - This tag contains the name of the registered characterization data set, or it contains the measurement + /// data for a characterization target. + /// + CharTarget = 0x74617267, + + /// + /// chad - This tag contains a matrix, which shall be invertible, and which converts an nCIEXYZ colour, measured using the actual illumination + /// conditions and relative to the actual adopted white, to an nCIEXYZ colour relative to the PCS adopted white + /// + ChromaticAdaptation = 0x63686164, + + /// + /// chrm - This tag contains the type and the data of the phosphor/colorant chromaticity set used. + /// + Chromaticity = 0x6368726D, + + /// + /// clro - This tag specifies the laydown order of colorants. + /// + ColorantOrder = 0x636C726F, + + /// + /// clrt + /// + ColorantTable = 0x636C7274, + + /// + /// clot - This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values. + /// When used in DeviceLink profiles only the PCSLAB values shall be permitted. + /// + ColorantTableOut = 0x636C6F74, + + /// + /// ciis - This tag indicates the image state of PCS colorimetry produced using the colorimetric intent transforms. + /// + ColorimetricIntentImageStat = 0x63696973, + + /// + /// cprt - This tag contains the text copyright information for the profile. + /// + Copyright = 0x63707274, + + /// + /// crdi - Removed in V4 + /// + CrdInfo = 0x63726469, + + /// + /// data - Removed in V4 + /// + Data = 0x64617461, + + /// + /// dtim - Removed in V4 + /// + DateTime = 0x6474696D, + + /// + /// dmnd - This tag describes the structure containing invariant and localizable + /// versions of the device manufacturer for display + /// + DeviceManufacturerDescription = 0x646D6E64, + + /// + /// dmdd - This tag describes the structure containing invariant and localizable + /// versions of the device model for display. + /// + DeviceModelDescription = 0x646D6464, + + /// + /// devs - Removed in V4 + /// + DeviceSettings = 0x64657673, + + /// + /// D2B0 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB0 tag + /// + DToB0 = 0x44324230, + + /// + /// D2B1 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB1 = 0x44324230, + + /// + /// D2B2 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB2 = 0x44324230, + + /// + /// D2B3 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB3 = 0x44324230, + + /// + /// gamt - This tag provides a table in which PCS values are the input and a single + /// output value for each input value is the output. If the output value is 0, the PCS colour is in-gamut. + /// If the output is non-zero, the PCS colour is out-of-gamut + /// + Gamut = 0x67616D74, + + /// + /// kTRC - This tag contains the grey tone reproduction curve. The tone reproduction curve provides the necessary + /// information to convert between a single device channel and the PCSXYZ or PCSLAB encoding. + /// + GrayTrc = 0x6b545243, + + /// + /// gXYZ - This tag contains the second column in the matrix, which is used in matrix/TRC transforms. + /// + GreenMatrixColumn = 0x6758595A, + + /// + /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no + /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green). + /// + GreenTrc = 0x67545243, + + /// + /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square metre as described by the Y channel. + /// + Luminance = 0x6C756d69, + + /// + /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50. + /// + Measurement = 0x6D656173, + + /// + /// bkpt - Removed in V4 + /// + MediaBlackPoint = 0x626B7074, + + /// + /// wtpt - This tag, which is used for generating the ICC-absolute colorimetric intent, specifies the chromatically + /// adapted nCIEXYZ tristimulus values of the media white point. + /// + MediaWhitePoint = 0x77747074, + + /// + /// ncol - OBSOLETE, use + /// + NamedColor = 0x6E636f6C, + + /// + /// ncl2 - This tag contains the named colour information providing a PCS and optional device representation + /// for a list of named colours. + /// + NamedColor2 = 0x6E636C32, + + /// + /// resp - This tag describes the structure containing a description of the device response for which the profile is intended. + /// + OutputResponse = 0x72657370, + + /// + /// rig0 - There is only one standard reference medium gamut, as defined in ISO 12640-3 + /// + PerceptualRenderingIntentGamut = 0x72696730, + + /// + /// pre0 - This tag contains the preview transformation from PCS to device space and back to the PCS. + /// + Preview0 = 0x70726530, + + /// + /// pre1 - This tag defines the preview transformation from PCS to device space and back to the PCS. + /// + Preview1 = 0x70726531, + + /// + /// pre2 - This tag contains the preview transformation from PCS to device space and back to the PCS. + /// + Preview2 = 0x70726532, + + /// + /// desc - This tag describes the structure containing invariant and localizable versions of the profile + /// description for display. + /// + ProfileDescription = 0x64657363, + + /// + /// pseq - This tag describes the structure containing a description of the profile sequence from source to + /// destination, typically used with the DeviceLink profile. + /// + ProfileSequenceDescription = 0x70736571, + + /// + /// psd0 - Removed in V4 + /// + PostScript2Crd0 = 0x70736430, + + /// + /// psd1 - Removed in V4 + /// + PostScript2Crd1 = 0x70736431, + + /// + /// psd2 - Removed in V4 + /// + PostScript2Crd2 = 0x70736432, + + /// + /// psd3 - Removed in V4 + /// + PostScript2Crd3 = 0x70736433, + + /// + /// ps2s - Removed in V4 + /// + PostScript2Csa = 0x70733273, + + /// + /// psd2i- Removed in V4 + /// + PostScript2RenderingIntent = 0x70733269, + + /// + /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms. + /// + RedMatrixColumn = 0x7258595A, + + /// + /// This tag contains the red channel tone reproduction curve. The first element represents no colorant + /// (white) or phosphor (black) and the last element represents 100 % colorant (red) or 100 % phosphor (red). + /// + RedTrc = 0x72545243, + + /// + /// rig2 - There is only one standard reference medium gamut, as defined in ISO 12640-3. + /// + SaturationRenderingIntentGamut = 0x72696732, + + /// + /// scrd - Removed in V4 + /// + ScreeningDescription = 0x73637264, + + /// + /// scrn - Removed in V4 + /// + Screening = 0x7363726E, + + /// + /// tech - The device technology signature + /// + Technology = 0x74656368, + + /// + /// bfd - Removed in V4 + /// + UcrBgSpecification = 0x62666420, + + /// + /// vued - This tag describes the structure containing invariant and localizable + /// versions of the viewing conditions. + /// + ViewingCondDescription = 0x76756564, + + /// + /// view - This tag defines the viewing conditions parameters + /// + ViewingConditions = 0x76696577, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs new file mode 100644 index 000000000..10e0fbac9 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Rendering intent + /// + public enum IccRenderingIntent : uint + { + /// + /// In perceptual transforms the PCS values represent hypothetical + /// measurements of a colour reproduction on the reference reflective + /// medium. By extension, for the perceptual intent, the PCS represents + /// the appearance of that reproduction as viewed in the reference viewing + /// environment by a human observer adapted to that environment. The exact + /// colour rendering of the perceptual intent is vendor specific. + /// + Perceptual = 0, + + /// + /// Transformations for this intent shall re-scale the in-gamut, + /// chromatically adapted tristimulus values such that the white + /// point of the actual medium is mapped to the PCS white point + /// (for either input or output) + /// + MediaRelativeColorimetric = 1, + + /// + /// The exact colour rendering of the saturation intent is vendor + /// specific and involves compromises such as trading off + /// preservation of hue in order to preserve the vividness of pure colours. + /// + Saturation = 2, + + /// + /// Transformations for this intent shall leave the chromatically + /// adapted nCIEXYZ tristimulus values of the in-gamut colours unchanged. + /// + AbsoluteColorimetric = 3, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs new file mode 100644 index 000000000..2938d4469 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Screening flags. Can be combined with a logical OR. + /// + [Flags] + internal enum IccScreeningFlag : int + { + /// + /// No flags (equivalent to NotDefaultScreens and UnitLinesPerCm) + /// + None = 0, + + /// + /// Use printer default screens + /// + DefaultScreens = 1 << 0, + + /// + /// Don't use printer default screens + /// + NotDefaultScreens = 0, + + /// + /// Frequency units in Lines/Inch + /// + UnitLinesPerInch = 1 << 1, + + /// + /// Frequency units in Lines/cm + /// + UnitLinesPerCm = 0, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs new file mode 100644 index 000000000..0d24c3ae5 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Enumerates the screening spot types + /// + internal enum IccScreeningSpotType : int + { + /// + /// Unknown spot type + /// + Unknown = 0, + + /// + /// Default printer spot type + /// + PrinterDefault = 1, + + /// + /// Round stop type + /// + Round = 2, + + /// + /// Diamond spot type + /// + Diamond = 3, + + /// + /// Ellipse spot type + /// + Ellipse = 4, + + /// + /// Line spot type + /// + Line = 5, + + /// + /// Square spot type + /// + Square = 6, + + /// + /// Cross spot type + /// + Cross = 7, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs new file mode 100644 index 000000000..5fc2fa228 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs @@ -0,0 +1,178 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Signature Name + /// + internal enum IccSignatureName : uint + { + /// + /// Unknown signature + /// + Unknown = 0, + + /// + /// Scene Colorimetry Estimates + /// + SceneColorimetryEstimates = 0x73636F65, // scoe + + /// + /// Scene Appearance Estimates + /// + SceneAppearanceEstimates = 0x73617065, // sape + + /// + /// Focal Plane Colorimetry Estimates + /// + FocalPlaneColorimetryEstimates = 0x66706365, // fpce + + /// + /// Reflection Hardcopy Original Colorimetry + /// + ReflectionHardcopyOriginalColorimetry = 0x72686F63, // rhoc + + /// + /// Reflection Print Output Colorimetry + /// + ReflectionPrintOutputColorimetry = 0x72706F63, // rpoc + + /// + /// Perceptual Reference Medium Gamut + /// + PerceptualReferenceMediumGamut = 0x70726D67, // prmg + + /// + /// Film Scanner + /// + FilmScanner = 0x6673636E, // fscn + + /// + /// Digital Camera + /// + DigitalCamera = 0x6463616D, // dcam + + /// + /// Reflective Scanner + /// + ReflectiveScanner = 0x7273636E, // rscn + + /// + /// InkJet Printer + /// + InkJetPrinter = 0x696A6574, // ijet + + /// + /// Thermal Wax Printer + /// + ThermalWaxPrinter = 0x74776178, // twax + + /// + /// Electrophotographic Printer + /// + ElectrophotographicPrinter = 0x6570686F, // epho + + /// + /// Electrostatic Printer + /// + ElectrostaticPrinter = 0x65737461, // esta + + /// + /// Dye Sublimation Printer + /// + DyeSublimationPrinter = 0x64737562, // dsub + + /// + /// Photographic Paper Printer + /// + PhotographicPaperPrinter = 0x7270686F, // rpho + + /// + /// Film Writer + /// + FilmWriter = 0x6670726E, // fprn + + /// + /// Video Monitor + /// + VideoMonitor = 0x7669646D, // vidm + + /// + /// Video Camera + /// + VideoCamera = 0x76696463, // vidc + + /// + /// Projection Television + /// + ProjectionTelevision = 0x706A7476, // pjtv + + /// + /// Cathode Ray Tube Display + /// + CathodeRayTubeDisplay = 0x43525420, // CRT + + /// + /// Passive Matrix Display + /// + PassiveMatrixDisplay = 0x504D4420, // PMD + + /// + /// Active Matrix Display + /// + ActiveMatrixDisplay = 0x414D4420, // AMD + + /// + /// Photo CD + /// + PhotoCD = 0x4B504344, // KPCD + + /// + /// Photographic Image Setter + /// + PhotographicImageSetter = 0x696D6773, // imgs + + /// + /// Gravure + /// + Gravure = 0x67726176, // grav + + /// + /// Offset Lithography + /// + OffsetLithography = 0x6F666673, // offs + + /// + /// Silkscreen + /// + Silkscreen = 0x73696C6B, // silk + + /// + /// Flexography + /// + Flexography = 0x666C6578, // flex + + /// + /// Motion Picture Film Scanner + /// + MotionPictureFilmScanner = 0x6D706673, // mpfs + + /// + /// Motion Picture Film Recorder + /// + MotionPictureFilmRecorder = 0x6D706672, // mpfr + + /// + /// Digital Motion Picture Camera + /// + DigitalMotionPictureCamera = 0x646D7063, // dmpc + + /// + /// Digital Cinema Projector + /// + DigitalCinemaProjector = 0x64636A70, // dcpj + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs new file mode 100644 index 000000000..3526887ed --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Standard Illuminant + /// + internal enum IccStandardIlluminant : uint + { + /// + /// Unknown illuminant + /// + Unknown = 0, + + /// + /// D50 illuminant + /// + D50 = 1, + + /// + /// D65 illuminant + /// + D65 = 2, + + /// + /// D93 illuminant + /// + D93 = 3, + + /// + /// F2 illuminant + /// + F2 = 4, + + /// + /// D55 illuminant + /// + D55 = 5, + + /// + /// A illuminant + /// + A = 6, + + /// + /// D50 illuminant + /// + EquiPowerE = 7, + + /// + /// F8 illuminant + /// + F8 = 8, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs new file mode 100644 index 000000000..0efc5fd40 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Standard Observer + /// + internal enum IccStandardObserver : uint + { + /// + /// Unknown observer + /// + Unkown = 0, + + /// + /// CIE 1931 observer + /// + Cie1931Observer = 1, + + /// + /// CIE 1964 observer + /// + Cie1964Observer = 2, + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs new file mode 100644 index 000000000..a42cc8cd1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs @@ -0,0 +1,273 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Type Signature + /// + public enum IccTypeSignature : uint + { + /// + /// Unknown type signature + /// + Unknown, + + /// + /// The chromaticity tag type provides basic chromaticity data and type of + /// phosphors or colorants of a monitor to applications and utilities + /// + Chromaticity = 0x6368726D, + + /// + /// This is an optional tag which specifies the laydown order in which colorants + /// will be printed on an n-colorant device. The laydown order may be the same + /// as the channel generation order listed in the colorantTableTag or the channel + /// order of a colour encoding type such as CMYK, in which case this tag is not + /// needed. When this is not the case (for example, ink-towers sometimes use + /// the order KCMY), this tag may be used to specify the laydown order of the + /// colorants + /// + ColorantOrder = 0x636c726f, + + /// + /// The purpose of this tag is to identify the colorants used in the profile + /// by a unique name and set of PCSXYZ or PCSLAB values to give the colorant + /// an unambiguous value. The first colorant listed is the colorant of the + /// first device channel of a LUT tag. The second colorant listed is the + /// colorant of the second device channel of a LUT tag, and so on + /// + ColorantTable = 0x636c7274, + + /// + /// The curveType embodies a one-dimensional function which maps an input + /// value in the domain of the function to an output value in the range + /// of the function + /// + Curve = 0x63757276, + + /// + /// The dataType is a simple data structure that contains either 7-bit ASCII + /// or binary data + /// + Data = 0x64617461, + + /// + /// Date and time defined by 6 unsigned 16bit integers + /// (year, month, day, hour, minute, second) + /// + DateTime = 0x6474696D, + + /// + /// This structure represents a colour transform using tables with 16-bit + /// precision. This type contains four processing elements: a 3 × 3 matrix + /// (which shall be the identity matrix unless the input colour space is + /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional + /// lookup table, and a set of one-dimensional output tables + /// + Lut16 = 0x6D667432, + + /// + /// This structure represents a colour transform using tables of 8-bit + /// precision. This type contains four processing elements: a 3 × 3 matrix + /// (which shall be the identity matrix unless the input colour space is + /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional + /// lookup table, and a set of one-dimensional output tables. + /// + Lut8 = 0x6D667431, + + /// + /// This structure represents a colour transform. The type contains up + /// to five processing elements which are stored in the AToBTag tag + /// in the following order: a set of one-dimensional curves, a 3 × 3 + /// matrix with offset terms, a set of one-dimensional curves, a + /// multi-dimensional lookup table, and a set of one-dimensional + /// output curves + /// + LutAToB = 0x6D414220, + + /// + /// This structure represents a colour transform. The type contains + /// up to five processing elements which are stored in the BToATag + /// in the following order: a set of one-dimensional curves, a 3 × 3 + /// matrix with offset terms, a set of one-dimensional curves, a + /// multi-dimensional lookup table, and a set of one-dimensional curves. + /// + LutBToA = 0x6D424120, + + /// + /// This information refers only to the internal + /// profile data and is meant to provide profile makers an alternative + /// to the default measurement specifications + /// + Measurement = 0x6D656173, + + /// + /// This tag structure contains a set of records each referencing a + /// multilingual Unicode string associated with a profile. Each string + /// is referenced in a separate record with the information about what + /// language and region the string is for. + /// + MultiLocalizedUnicode = 0x6D6C7563, + + /// + /// This structure represents a colour transform, containing a sequence + /// of processing elements. The processing elements contained in the + /// structure are defined in the structure itself, allowing for a flexible + /// structure. Currently supported processing elements are: a set of one + /// dimensional curves, a matrix with offset terms, and a multidimensional + /// lookup table (CLUT). Other processing element types may be added in + /// the future. Each type of processing element may be contained any + /// number of times in the structure. + /// + MultiProcessElements = 0x6D706574, + + /// + /// This type is a count value and array of structures that provide colour + /// coordinates for colour names. For each named colour, a PCS and optional + /// device representation of the colour are given. Both representations are + /// 16-bit values and PCS values shall be relative colorimetric. The device + /// representation corresponds to the header’s "data colour space" field. + /// This representation should be consistent with the "number of device + /// coordinates" field in the namedColor2Type. If this field is 0, device + /// coordinates are not provided. The PCS representation corresponds to the + /// header's PCS field. The PCS representation is always provided. Colour + /// names are fixed-length, 32-byte fields including null termination. In + /// order to maintain maximum portability, it is strongly recommended that + /// special characters of the 7-bit ASCII set not be used. + /// + NamedColor2 = 0x6E636C32, + + /// + /// This type describes a one-dimensional curve by specifying one of a + /// predefined set of functions using the parameters. + /// + ParametricCurve = 0x70617261, + + /// + /// This type is an array of structures, each of which contains information + /// from the header fields and tags from the original profiles which were + /// combined to create the final profile. The order of the structures is + /// the order in which the profiles were combined and includes a structure + /// for the final profile. This provides a description of the profile + /// sequence from source to destination, typically used with the DeviceLink + /// profile. + /// + ProfileSequenceDesc = 0x70736571, + + /// + /// This type is an array of structures, each of which contains information + /// for identification of a profile used in a sequence. + /// + ProfileSequenceIdentifier = 0x70736964, + + /// + /// The purpose of this tag type is to provide a mechanism to relate physical + /// colorant amounts with the normalized device codes produced by lut8Type, + /// lut16Type, lutAToBType, lutBToAType or multiProcessElementsType tags + /// so that corrections can be made for variation in the device without + /// having to produce a new profile. The mechanism can be used by applications + /// to allow users with relatively inexpensive and readily available + /// instrumentation to apply corrections to individual output colour + /// channels in order to achieve consistent results. + /// + ResponseCurveSet16 = 0x72637332, + + /// + /// Array of signed floating point numbers with 1 sign bit, 15 value bits and 16 fractional bits + /// + S15Fixed16Array = 0x73663332, + + /// + /// The signatureType contains a 4-byte sequence. Sequences of less than four + /// characters are padded at the end with spaces. Typically this type is used + /// for registered tags that can be displayed on many development systems as + /// a sequence of four characters. + /// + Signature = 0x73696720, + + /// + /// Simple ASCII text + /// + Text = 0x74657874, + + /// + /// Array of unsigned floating point numbers with 16 value bits and 16 fractional bits + /// + U16Fixed16Array = 0x75663332, + + /// + /// Array of unsigned 16bit integers (ushort) + /// + UInt16Array = 0x75693136, + + /// + /// Array of unsigned 32bit integers (uint) + /// + UInt32Array = 0x75693332, + + /// + /// Array of unsigned 64bit integers (ulong) + /// + UInt64Array = 0x75693634, + + /// + /// Array of unsigned 8bit integers (byte) + /// + UInt8Array = 0x75693038, + + /// + /// This type represents a set of viewing condition parameters. + /// + ViewingConditions = 0x76696577, + + /// + /// 3 floating point values describing a XYZ color value + /// + Xyz = 0x58595A20, + + /// + /// REMOVED IN V4 - The textDescriptionType is a complex structure that contains three + /// types of text description structures: 7-bit ASCII, Unicode and ScriptCode. Since no + /// single standard method for specifying localizable character sets exists across + /// the major platform vendors, including all three provides access for the major + /// operating systems. The 7-bit ASCII description is to be an invariant, + /// nonlocalizable name for consistent reference. It is preferred that both the + /// Unicode and ScriptCode structures be properly localized. + /// + TextDescription = 0x64657363, + + /// + /// REMOVED IN V4 - This type contains the PostScript product name to which this + /// profile corresponds and the names of the companion CRDs + /// + CrdInfo = 0x63726469, + + /// + /// REMOVED IN V4 - The screeningType describes various screening parameters including + /// screen frequency, screening angle, and spot shape + /// + Screening = 0x7363726E, + + /// + /// REMOVED IN V4 - This type contains curves representing the under color removal and + /// black generation and a text string which is a general description of the method + /// used for the UCR and BG + /// + UcrBg = 0x62666420, + + /// + /// REMOVED IN V4 - This type is an array of structures each of which contains + /// platform-specific information about the settings of the device for which + /// this profile is valid. This type is not supported. + /// + DeviceSettings = 0x64657673, // not supported + + /// + /// REMOVED IN V2 - use instead. This type is not supported. + /// + NamedColor = 0x6E636F6C, // not supported + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs new file mode 100644 index 000000000..54fe7c764 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Represents an error that happened while reading or writing a corrupt/invalid ICC profile + /// + public class InvalidIccProfileException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public InvalidIccProfileException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error + public InvalidIccProfileException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified + public InvalidIccProfileException(string message, Exception inner) + : base(message, inner) + { + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs new file mode 100644 index 000000000..978d5bc24 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -0,0 +1,193 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; +#if !NETSTANDARD1_1 + using System.Security.Cryptography; +#endif + + /// + /// Represents an ICC profile + /// + public sealed class IccProfile + { + /// + /// The byte array to read the ICC profile from + /// + private byte[] data; + + /// + /// The backing file for the property + /// + private List entries; + + /// + /// ICC profile header + /// + private IccProfileHeader header; + + /// + /// Initializes a new instance of the class. + /// + public IccProfile() + : this((byte[])null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw ICC profile data + public IccProfile(byte[] data) + { + this.data = data; + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another ICC profile. + /// + /// The other ICC profile, where the clone should be made from. + /// is null.> + public IccProfile(IccProfile other) + { + Guard.NotNull(other, nameof(other)); + + // TODO: Do we need to copy anything else? + this.data = other.data; + } + + /// + /// Initializes a new instance of the class. + /// + /// The profile header + /// The actual profile data + internal IccProfile(IccProfileHeader header, IEnumerable entries) + { + Guard.NotNull(header, nameof(header)); + Guard.NotNull(entries, nameof(entries)); + + this.header = header; + this.entries = new List(entries); + } + + /// + /// Gets or sets the profile header + /// + public IccProfileHeader Header + { + get + { + this.InitializeHeader(); + return this.header; + } + + set => this.header = value; + } + + /// + /// Gets the actual profile data + /// + public List Entries + { + get + { + this.InitializeEntries(); + return this.entries; + } + } + +#if !NETSTANDARD1_1 + + /// + /// Calculates the MD5 hash value of an ICC profile header + /// + /// The data of which to calculate the hash value + /// The calculated hash + public static IccProfileId CalculateHash(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); + + byte[] header = new byte[128]; + Buffer.BlockCopy(data, 0, header, 0, 128); + + using (var md5 = MD5.Create()) + { + // Zero out some values + Array.Clear(header, 44, 4); // Profile flags + Array.Clear(header, 64, 4); // Rendering Intent + Array.Clear(header, 84, 16); // Profile ID + + // Calculate hash + byte[] hash = md5.ComputeHash(data); + + // Read values from hash + var reader = new IccDataReader(hash); + return reader.ReadProfileId(); + } + } + +#endif + + /// + /// Extends the profile with additional data. + /// + /// The array containing addition profile data. + public void Extend(byte[] bytes) + { + int currentLength = this.data.Length; + Array.Resize(ref this.data, currentLength + bytes.Length); + Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length); + } + + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + var writer = new IccWriter(); + return writer.Write(this); + } + + private void InitializeHeader() + { + if (this.header != null) + { + return; + } + + if (this.data == null) + { + this.header = new IccProfileHeader(); + return; + } + + var reader = new IccReader(); + this.header = reader.ReadHeader(this.data); + } + + private void InitializeEntries() + { + if (this.entries != null) + { + return; + } + + if (this.data == null) + { + this.entries = new List(); + return; + } + + var reader = new IccReader(); + this.entries = new List(reader.ReadTagData(this.data)); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs new file mode 100644 index 000000000..8237dc7a8 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Contains all values of an ICC profile header + /// + public sealed class IccProfileHeader + { + /// + /// Gets or sets the profile size in bytes (will be ignored when writing a profile) + /// + public uint Size { get; set; } + + /// + /// Gets or sets the preferred CMM (Color Management Module) type + /// + public string CmmType { get; set; } + + /// + /// Gets or sets the profiles version number + /// + public Version Version { get; set; } + + /// + /// Gets or sets the type of the profile + /// + public IccProfileClass Class { get; set; } + + /// + /// Gets or sets the data colorspace + /// + public IccColorSpaceType DataColorSpace { get; set; } + + /// + /// Gets or sets the profile connection space + /// + public IccColorSpaceType ProfileConnectionSpace { get; set; } + + /// + /// Gets or sets the date and time this profile was created + /// + public DateTime CreationDate { get; set; } + + /// + /// Gets or sets the file signature. Should always be "acsp". + /// Value will be ignored when writing a profile. + /// + public string FileSignature { get; set; } + + /// + /// Gets or sets the primary platform this profile as created for + /// + public IccPrimaryPlatformType PrimaryPlatformSignature { get; set; } + + /// + /// Gets or sets the profile flags to indicate various options for the CMM + /// such as distributed processing and caching options + /// + public IccProfileFlag Flags { get; set; } + + /// + /// Gets or sets the device manufacturer of the device for which this profile is created + /// + public uint DeviceManufacturer { get; set; } + + /// + /// Gets or sets the model of the device for which this profile is created + /// + public uint DeviceModel { get; set; } + + /// + /// Gets or sets the device attributes unique to the particular device setup such as media type + /// + public IccDeviceAttribute DeviceAttributes { get; set; } + + /// + /// Gets or sets the rendering Intent + /// + public IccRenderingIntent RenderingIntent { get; set; } + + /// + /// Gets or sets The normalized XYZ values of the illuminant of the PCS + /// + public Vector3 PcsIlluminant { get; set; } + + /// + /// Gets or sets Profile creator signature + /// + public string CreatorSignature { get; set; } + + /// + /// Gets or sets the profile ID (hash) + /// + public IccProfileId Id { get; set; } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs new file mode 100644 index 000000000..d7f556a81 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs @@ -0,0 +1,116 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Reads and parses ICC data from a byte array + /// + internal sealed class IccReader + { + /// + /// Reads an ICC profile + /// + /// The raw ICC data + /// The read ICC profile + public IccProfile Read(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); + + var reader = new IccDataReader(data); + IccProfileHeader header = this.ReadHeader(reader); + IccTagDataEntry[] tagData = this.ReadTagData(reader); + + return new IccProfile(header, tagData); + } + + /// + /// Reads an ICC profile header + /// + /// The raw ICC data + /// The read ICC profile header + public IccProfileHeader ReadHeader(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); + + var reader = new IccDataReader(data); + return this.ReadHeader(reader); + } + + /// + /// Reads the ICC profile tag data + /// + /// The raw ICC data + /// The read ICC profile tag data + public IccTagDataEntry[] ReadTagData(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); + + var reader = new IccDataReader(data); + return this.ReadTagData(reader); + } + + private IccProfileHeader ReadHeader(IccDataReader reader) + { + reader.SetIndex(0); + + return new IccProfileHeader + { + Size = reader.ReadUInt32(), + CmmType = reader.ReadAsciiString(4), + Version = reader.ReadVersionNumber(), + Class = (IccProfileClass)reader.ReadUInt32(), + DataColorSpace = (IccColorSpaceType)reader.ReadUInt32(), + ProfileConnectionSpace = (IccColorSpaceType)reader.ReadUInt32(), + CreationDate = reader.ReadDateTime(), + FileSignature = reader.ReadAsciiString(4), + PrimaryPlatformSignature = (IccPrimaryPlatformType)reader.ReadUInt32(), + Flags = (IccProfileFlag)reader.ReadInt32(), + DeviceManufacturer = reader.ReadUInt32(), + DeviceModel = reader.ReadUInt32(), + DeviceAttributes = (IccDeviceAttribute)reader.ReadInt64(), + RenderingIntent = (IccRenderingIntent)reader.ReadUInt32(), + PcsIlluminant = reader.ReadXyzNumber(), + CreatorSignature = reader.ReadAsciiString(4), + Id = reader.ReadProfileId(), + }; + } + + private IccTagDataEntry[] ReadTagData(IccDataReader reader) + { + IccTagTableEntry[] tagTable = this.ReadTagTable(reader); + IccTagDataEntry[] entries = new IccTagDataEntry[tagTable.Length]; + for (int i = 0; i < tagTable.Length; i++) + { + IccTagDataEntry entry = reader.ReadTagDataEntry(tagTable[i]); + entry.TagSignature = tagTable[i].Signature; + entries[i] = entry; + } + + return entries; + } + + private IccTagTableEntry[] ReadTagTable(IccDataReader reader) + { + reader.SetIndex(128); // An ICC header is 128 bytes long + + uint tagCount = reader.ReadUInt32(); + IccTagTableEntry[] table = new IccTagTableEntry[tagCount]; + + for (int i = 0; i < tagCount; i++) + { + uint tagSignature = reader.ReadUInt32(); + uint tagOffset = reader.ReadUInt32(); + uint tagSize = reader.ReadUInt32(); + table[i] = new IccTagTableEntry((IccProfileTag)tagSignature, tagOffset, tagSize); + } + + return table; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs new file mode 100644 index 000000000..5db3d96ea --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// The data of an ICC tag entry + /// + public abstract class IccTagDataEntry : IEquatable + { + /// + /// Initializes a new instance of the class. + /// TagSignature will be + /// + /// Type Signature + protected IccTagDataEntry(IccTypeSignature signature) + : this(signature, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type Signature + /// Tag Signature + protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature) + { + this.Signature = signature; + this.TagSignature = tagSignature; + } + + /// + /// Gets the type Signature + /// + public IccTypeSignature Signature { get; } + + /// + /// Gets or sets the tag Signature + /// + public IccProfileTag TagSignature { get; set; } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + var entry = obj as IccTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (int)this.Signature * 397; + } + } + + /// + public virtual bool Equals(IccTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Signature == other.Signature; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs new file mode 100644 index 000000000..b4e5f2868 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs @@ -0,0 +1,109 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Contains methods for writing ICC profiles. + /// + internal sealed class IccWriter + { + /// + /// Writes the ICC profile into a byte array + /// + /// The ICC profile to write + /// The ICC profile as a byte array + public byte[] Write(IccProfile profile) + { + Guard.NotNull(profile, nameof(profile)); + + using (var writer = new IccDataWriter()) + { + IccTagTableEntry[] tagTable = this.WriteTagData(writer, profile.Entries); + this.WriteTagTable(writer, tagTable); + this.WriteHeader(writer, profile.Header); + return writer.GetData(); + } + } + + private void WriteHeader(IccDataWriter writer, IccProfileHeader header) + { + writer.SetIndex(0); + + writer.WriteUInt32(writer.Length); + writer.WriteAsciiString(header.CmmType, 4, false); + writer.WriteVersionNumber(header.Version); + writer.WriteUInt32((uint)header.Class); + writer.WriteUInt32((uint)header.DataColorSpace); + writer.WriteUInt32((uint)header.ProfileConnectionSpace); + writer.WriteDateTime(header.CreationDate); + writer.WriteAsciiString("acsp"); + writer.WriteUInt32((uint)header.PrimaryPlatformSignature); + writer.WriteInt32((int)header.Flags); + writer.WriteUInt32(header.DeviceManufacturer); + writer.WriteUInt32(header.DeviceModel); + writer.WriteInt64((long)header.DeviceAttributes); + writer.WriteUInt32((uint)header.RenderingIntent); + writer.WriteXyzNumber(header.PcsIlluminant); + writer.WriteAsciiString(header.CreatorSignature, 4, false); + +#if !NETSTANDARD1_1 + IccProfileId id = IccProfile.CalculateHash(writer.GetData()); + writer.WriteProfileId(id); +#else + writer.WriteProfileId(IccProfileId.Zero); +#endif + } + + private void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table) + { + // 128 = size of ICC header + writer.SetIndex(128); + + writer.WriteUInt32((uint)table.Length); + foreach (IccTagTableEntry entry in table) + { + writer.WriteUInt32((uint)entry.Signature); + writer.WriteUInt32(entry.Offset); + writer.WriteUInt32(entry.DataSize); + } + } + + private IccTagTableEntry[] WriteTagData(IccDataWriter writer, List entries) + { + var inData = new List(entries); + var dupData = new List(); + + while (inData.Count > 0) + { + IccTagDataEntry[] items = inData.Where(t => inData[0].Equals(t)).ToArray(); + dupData.Add(items); + foreach (IccTagDataEntry item in items) + { + inData.Remove(item); + } + } + + var table = new List(); + + // (Header size) + (entry count) + (nr of entries) * (size of table entry) + writer.SetIndex(128 + 4 + (entries.Count * 12)); + + foreach (IccTagDataEntry[] entry in dupData) + { + writer.WriteTagDataEntry(entry[0], out IccTagTableEntry tentry); + foreach (IccTagDataEntry item in entry) + { + table.Add(new IccTagTableEntry(item.TagSignature, tentry.Offset, tentry.DataSize)); + } + } + + return table.ToArray(); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs new file mode 100644 index 000000000..a20a52d8e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A placeholder (might be used for future ICC versions) + /// + internal sealed class IccBAcsProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Number of input channels + /// Number of output channels + public IccBAcsProcessElement(int inChannelCount, int outChannelCount) + : base(IccMultiProcessElementSignature.BAcs, inChannelCount, outChannelCount) + { + } + + /// + public bool Equals(IccBAcsProcessElement other) + { + return base.Equals(other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs new file mode 100644 index 000000000..7d5855168 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A CLUT (color lookup table) element to process data + /// + internal sealed class IccClutProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The color lookup table of this element + public IccClutProcessElement(IccClut clutValue) + : base(IccMultiProcessElementSignature.Clut, clutValue?.InputChannelCount ?? 1, clutValue?.OutputChannelCount ?? 1) + { + Guard.NotNull(clutValue, nameof(clutValue)); + this.ClutValue = clutValue; + } + + /// + /// Gets the color lookup table of this element + /// + public IccClut ClutValue { get; } + + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccClutProcessElement element) + { + return this.ClutValue.Equals(element.ClutValue); + } + + return false; + } + + /// + public bool Equals(IccClutProcessElement other) + { + return this.Equals((IccMultiProcessElement)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs new file mode 100644 index 000000000..c16ca93d2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// A set of curves to process data + /// + internal sealed class IccCurveSetProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// An array with one dimensional curves + public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves) + : base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1) + { + Guard.NotNull(curves, nameof(curves)); + this.Curves = curves; + } + + /// + /// Gets an array of one dimensional curves + /// + public IccOneDimensionalCurve[] Curves { get; } + + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccCurveSetProcessElement element) + { + return this.Curves.SequenceEqual(element.Curves); + } + + return false; + } + + /// + public bool Equals(IccCurveSetProcessElement other) + { + return this.Equals((IccMultiProcessElement)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs new file mode 100644 index 000000000..9219f5200 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A placeholder (might be used for future ICC versions) + /// + internal sealed class IccEAcsProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Number of input channels + /// Number of output channels + public IccEAcsProcessElement(int inChannelCount, int outChannelCount) + : base(IccMultiProcessElementSignature.EAcs, inChannelCount, outChannelCount) + { + } + + /// + public bool Equals(IccEAcsProcessElement other) + { + return base.Equals(other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs new file mode 100644 index 000000000..259f71489 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + using ImageSharp.Memory; + + /// + /// A matrix element to process data + /// + internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Two dimensional matrix with size of Input-Channels x Output-Channels + /// One dimensional matrix with size of Output-Channels x 1 + public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1) + : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1) + { + Guard.NotNull(matrixIxO, nameof(matrixIxO)); + Guard.NotNull(matrixOx1, nameof(matrixOx1)); + + bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length; + Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match"); + + this.MatrixIxO = matrixIxO; + this.MatrixOx1 = matrixOx1; + } + + /// + /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels + /// + public Fast2DArray MatrixIxO { get; } + + /// + /// Gets the one dimensional matrix with size of Output-Channels x 1 + /// + public float[] MatrixOx1 { get; } + + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccMatrixProcessElement element) + { + return this.EqualsMatrix(element) + && this.MatrixOx1.SequenceEqual(element.MatrixOx1); + } + + return false; + } + + /// + public bool Equals(IccMatrixProcessElement other) + { + return this.Equals((IccMultiProcessElement)other); + } + + private bool EqualsMatrix(IccMatrixProcessElement element) + { + for (int x = 0; x < this.MatrixIxO.Width; x++) + { + for (int y = 0; y < this.MatrixIxO.Height; y++) + { + if (this.MatrixIxO[x, y] != element.MatrixIxO[x, y]) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs new file mode 100644 index 000000000..205e61f01 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// An element to process data + /// + internal abstract class IccMultiProcessElement : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The signature of this element + /// Number of input channels + /// Number of output channels + protected IccMultiProcessElement(IccMultiProcessElementSignature signature, int inChannelCount, int outChannelCount) + { + Guard.MustBeBetweenOrEqualTo(inChannelCount, 1, 15, nameof(inChannelCount)); + Guard.MustBeBetweenOrEqualTo(outChannelCount, 1, 15, nameof(outChannelCount)); + + this.Signature = signature; + this.InputChannelCount = inChannelCount; + this.OutputChannelCount = outChannelCount; + } + + /// + /// Gets the signature of this element + /// + public IccMultiProcessElementSignature Signature { get; } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + public virtual bool Equals(IccMultiProcessElement other) + { + if (other == null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Signature == other.Signature + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs new file mode 100644 index 000000000..85f40f5e4 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs @@ -0,0 +1,190 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// The chromaticity tag type provides basic chromaticity data + /// and type of phosphors or colorants of a monitor to applications and utilities. + /// + internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Colorant Type + public IccChromaticityTagDataEntry(IccColorantEncoding colorantType) + : this(colorantType, GetColorantArray(colorantType), IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Values per channel + public IccChromaticityTagDataEntry(double[][] channelValues) + : this(IccColorantEncoding.Unknown, channelValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Colorant Type + /// Tag Signature + public IccChromaticityTagDataEntry(IccColorantEncoding colorantType, IccProfileTag tagSignature) + : this(colorantType, GetColorantArray(colorantType), tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Values per channel + /// Tag Signature + public IccChromaticityTagDataEntry(double[][] channelValues, IccProfileTag tagSignature) + : this(IccColorantEncoding.Unknown, channelValues, tagSignature) + { + } + + private IccChromaticityTagDataEntry(IccColorantEncoding colorantType, double[][] channelValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Chromaticity, tagSignature) + { + Guard.NotNull(channelValues, nameof(channelValues)); + Guard.MustBeBetweenOrEqualTo(channelValues.Length, 1, 15, nameof(channelValues)); + + this.ColorantType = colorantType; + this.ChannelValues = channelValues; + + int channelLength = channelValues[0].Length; + bool channelsNotSame = channelValues.Any(t => t == null || t.Length != channelLength); + Guard.IsFalse(channelsNotSame, nameof(channelValues), "The number of values per channel is not the same for all channels"); + } + + /// + /// Gets the number of channels + /// + public int ChannelCount => this.ChannelValues.Length; + + /// + /// Gets the colorant type + /// + public IccColorantEncoding ColorantType { get; } + + /// + /// Gets the values per channel + /// + public double[][] ChannelValues { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccChromaticityTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccChromaticityTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.ColorantType == other.ColorantType && this.EqualsChannelValues(other); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccChromaticityTagDataEntry && this.Equals((IccChromaticityTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.ColorantType; + hashCode = (hashCode * 397) ^ (this.ChannelValues != null ? this.ChannelValues.GetHashCode() : 0); + return hashCode; + } + } + + private static double[][] GetColorantArray(IccColorantEncoding colorantType) + { + switch (colorantType) + { + case IccColorantEncoding.EbuTech3213E: + return new[] + { + new[] { 0.640, 0.330 }, + new[] { 0.290, 0.600 }, + new[] { 0.150, 0.060 }, + }; + case IccColorantEncoding.ItuRBt709_2: + return new[] + { + new[] { 0.640, 0.330 }, + new[] { 0.300, 0.600 }, + new[] { 0.150, 0.060 }, + }; + case IccColorantEncoding.P22: + return new[] + { + new[] { 0.625, 0.340 }, + new[] { 0.280, 0.605 }, + new[] { 0.155, 0.070 }, + }; + case IccColorantEncoding.SmpteRp145: + return new[] + { + new[] { 0.630, 0.340 }, + new[] { 0.310, 0.595 }, + new[] { 0.155, 0.070 }, + }; + default: + throw new ArgumentException("Unrecognized colorant encoding"); + } + } + + private bool EqualsChannelValues(IccChromaticityTagDataEntry entry) + { + if (this.ChannelValues.Length != entry.ChannelValues.Length) + { + return false; + } + + for (int i = 0; i < this.ChannelValues.Length; i++) + { + if (!this.ChannelValues[i].SequenceEqual(entry.ChannelValues[i])) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs new file mode 100644 index 000000000..9be5541a2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This tag specifies the laydown order in which colorants + /// will be printed on an n-colorant device. + /// + internal sealed class IccColorantOrderTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Colorant order numbers + public IccColorantOrderTagDataEntry(byte[] colorantNumber) + : this(colorantNumber, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Colorant order numbers + /// Tag Signature + public IccColorantOrderTagDataEntry(byte[] colorantNumber, IccProfileTag tagSignature) + : base(IccTypeSignature.ColorantOrder, tagSignature) + { + Guard.NotNull(colorantNumber, nameof(colorantNumber)); + Guard.MustBeBetweenOrEqualTo(colorantNumber.Length, 1, 15, nameof(colorantNumber)); + + this.ColorantNumber = colorantNumber; + } + + /// + /// Gets the colorant order numbers + /// + public byte[] ColorantNumber { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccColorantOrderTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccColorantOrderTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.ColorantNumber.SequenceEqual(other.ColorantNumber); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccColorantOrderTagDataEntry && this.Equals((IccColorantOrderTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.ColorantNumber != null ? this.ColorantNumber.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs new file mode 100644 index 000000000..041f74f39 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs @@ -0,0 +1,94 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// The purpose of this tag is to identify the colorants used in + /// the profile by a unique name and set of PCSXYZ or PCSLAB values + /// to give the colorant an unambiguous value. + /// + internal sealed class IccColorantTableTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Colorant Data + public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData) + : this(colorantData, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Colorant Data + /// Tag Signature + public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData, IccProfileTag tagSignature) + : base(IccTypeSignature.ColorantTable, tagSignature) + { + Guard.NotNull(colorantData, nameof(colorantData)); + Guard.MustBeBetweenOrEqualTo(colorantData.Length, 1, 15, nameof(colorantData)); + + this.ColorantData = colorantData; + } + + /// + /// Gets the colorant data + /// + public IccColorantTableEntry[] ColorantData { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccColorantTableTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccColorantTableTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.ColorantData.SequenceEqual(other.ColorantData); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccColorantTableTagDataEntry && this.Equals((IccColorantTableTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.ColorantData != null ? this.ColorantData.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs new file mode 100644 index 000000000..73588bbf0 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs @@ -0,0 +1,149 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// This type contains the PostScript product name to which this profile + /// corresponds and the names of the companion CRDs + /// + internal sealed class IccCrdInfoTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// the PostScript product name + /// the rendering intent 0 CRD name + /// the rendering intent 1 CRD name + /// the rendering intent 2 CRD name + /// the rendering intent 3 CRD name + public IccCrdInfoTagDataEntry( + string postScriptProductName, + string renderingIntent0Crd, + string renderingIntent1Crd, + string renderingIntent2Crd, + string renderingIntent3Crd) + : this( + postScriptProductName, + renderingIntent0Crd, + renderingIntent1Crd, + renderingIntent2Crd, + renderingIntent3Crd, + IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// the PostScript product name + /// the rendering intent 0 CRD name + /// the rendering intent 1 CRD name + /// the rendering intent 2 CRD name + /// the rendering intent 3 CRD name + /// Tag Signature + public IccCrdInfoTagDataEntry( + string postScriptProductName, + string renderingIntent0Crd, + string renderingIntent1Crd, + string renderingIntent2Crd, + string renderingIntent3Crd, + IccProfileTag tagSignature) + : base(IccTypeSignature.CrdInfo, tagSignature) + { + this.PostScriptProductName = postScriptProductName; + this.RenderingIntent0Crd = renderingIntent0Crd; + this.RenderingIntent1Crd = renderingIntent1Crd; + this.RenderingIntent2Crd = renderingIntent2Crd; + this.RenderingIntent3Crd = renderingIntent3Crd; + } + + /// + /// Gets the PostScript product name + /// + public string PostScriptProductName { get; } + + /// + /// Gets the rendering intent 0 CRD name + /// + public string RenderingIntent0Crd { get; } + + /// + /// Gets the rendering intent 1 CRD name + /// + public string RenderingIntent1Crd { get; } + + /// + /// Gets the rendering intent 2 CRD name + /// + public string RenderingIntent2Crd { get; } + + /// + /// Gets the rendering intent 3 CRD name + /// + public string RenderingIntent3Crd { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccCrdInfoTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccCrdInfoTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && string.Equals(this.PostScriptProductName, other.PostScriptProductName) + && string.Equals(this.RenderingIntent0Crd, other.RenderingIntent0Crd) + && string.Equals(this.RenderingIntent1Crd, other.RenderingIntent1Crd) + && string.Equals(this.RenderingIntent2Crd, other.RenderingIntent2Crd) + && string.Equals(this.RenderingIntent3Crd, other.RenderingIntent3Crd); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccCrdInfoTagDataEntry && this.Equals((IccCrdInfoTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.PostScriptProductName != null ? this.PostScriptProductName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.RenderingIntent0Crd != null ? this.RenderingIntent0Crd.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.RenderingIntent1Crd != null ? this.RenderingIntent1Crd.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.RenderingIntent2Crd != null ? this.RenderingIntent2Crd.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.RenderingIntent3Crd != null ? this.RenderingIntent3Crd.GetHashCode() : 0); + return hashCode; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs new file mode 100644 index 000000000..c14995748 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs @@ -0,0 +1,141 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// The type contains a one-dimensional table of double values. + /// + internal sealed class IccCurveTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public IccCurveTagDataEntry() + : this(new float[0], IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Gamma value + public IccCurveTagDataEntry(float gamma) + : this(new[] { gamma }, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Curve Data + public IccCurveTagDataEntry(float[] curveData) + : this(curveData, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Tag Signature + public IccCurveTagDataEntry(IccProfileTag tagSignature) + : this(new float[0], tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Gamma value + /// Tag Signature + public IccCurveTagDataEntry(float gamma, IccProfileTag tagSignature) + : this(new[] { gamma }, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Curve Data + /// Tag Signature + public IccCurveTagDataEntry(float[] curveData, IccProfileTag tagSignature) + : base(IccTypeSignature.Curve, tagSignature) + { + this.CurveData = curveData ?? new float[0]; + } + + /// + /// Gets the curve data + /// + public float[] CurveData { get; } + + /// + /// Gets the gamma value. + /// Only valid if is true + /// + public float Gamma => this.IsGamma ? this.CurveData[0] : 0; + + /// + /// Gets a value indicating whether the curve maps input directly to output + /// + public bool IsIdentityResponse => this.CurveData.Length == 0; + + /// + /// Gets a value indicating whether the curve is a gamma curve + /// + public bool IsGamma => this.CurveData.Length == 1; + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccCurveTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccCurveTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.CurveData.SequenceEqual(other.CurveData); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccCurveTagDataEntry && this.Equals((IccCurveTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.CurveData != null ? this.CurveData.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs new file mode 100644 index 000000000..57f04b11a --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs @@ -0,0 +1,120 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Text; + + /// + /// The dataType is a simple data structure that contains + /// either 7-bit ASCII or binary data, i.e. textType data or transparent bytes. + /// + internal sealed class IccDataTagDataEntry : IccTagDataEntry, IEquatable + { + private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// Initializes a new instance of the class. + /// + /// The raw data + public IccDataTagDataEntry(byte[] data) + : this(data, false, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw data + /// True if the given data is 7bit ASCII encoded text + public IccDataTagDataEntry(byte[] data, bool isAscii) + : this(data, isAscii, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw data + /// True if the given data is 7bit ASCII encoded text + /// Tag Signature + public IccDataTagDataEntry(byte[] data, bool isAscii, IccProfileTag tagSignature) + : base(IccTypeSignature.Data, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + this.IsAscii = isAscii; + } + + /// + /// Gets the raw Data + /// + public byte[] Data { get; } + + /// + /// Gets a value indicating whether the represents 7bit ASCII encoded text + /// + public bool IsAscii { get; } + + /// + /// Gets the decoded as 7bit ASCII. + /// If is false, returns null + /// + public string AsciiString => this.IsAscii ? AsciiEncoding.GetString(this.Data, 0, this.Data.Length) : null; + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccDataTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccDataTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data) && this.IsAscii == other.IsAscii; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccDataTagDataEntry && this.Equals((IccDataTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ this.IsAscii.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs new file mode 100644 index 000000000..7111913cb --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// This type is a representation of the time and date. + /// + internal sealed class IccDateTimeTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The DateTime value + public IccDateTimeTagDataEntry(DateTime value) + : this(value, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The DateTime value + /// Tag Signature + public IccDateTimeTagDataEntry(DateTime value, IccProfileTag tagSignature) + : base(IccTypeSignature.DateTime, tagSignature) + { + this.Value = value; + } + + /// + /// Gets the date and time value + /// + public DateTime Value { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccDateTimeTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccDateTimeTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Value.Equals(other.Value); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccDateTimeTagDataEntry && this.Equals((IccDateTimeTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ this.Value.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs new file mode 100644 index 000000000..c91347d23 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type represents an array of doubles (from 32bit fixed point values). + /// + internal sealed class IccFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccFix16ArrayTagDataEntry(float[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.S15Fixed16Array, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the array data + /// + public float[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccFix16ArrayTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccFix16ArrayTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccFix16ArrayTagDataEntry && this.Equals((IccFix16ArrayTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs new file mode 100644 index 000000000..45d7b3c50 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs @@ -0,0 +1,191 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// This structure represents a color transform using tables + /// with 16-bit precision. + /// + internal sealed class IccLut16TagDataEntry : IccTagDataEntry, IEquatable + { + private static readonly float[,] IdentityMatrix = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Lut16, tagSignature) + { + Guard.NotNull(matrix, nameof(matrix)); + Guard.NotNull(inputValues, nameof(inputValues)); + Guard.NotNull(clutValues, nameof(clutValues)); + Guard.NotNull(outputValues, nameof(outputValues)); + + bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); + + this.Matrix = this.CreateMatrix(matrix); + this.InputValues = inputValues; + this.ClutValues = clutValues; + this.OutputValues = outputValues; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount => this.InputValues.Length; + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount => this.OutputValues.Length; + + /// + /// Gets the conversion matrix + /// + public Matrix4x4 Matrix { get; } + + /// + /// Gets the input lookup table + /// + public IccLut[] InputValues { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the output lookup table + /// + public IccLut[] OutputValues { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccLut16TagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccLut16TagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Matrix.Equals(other.Matrix) + && this.InputValues.SequenceEqual(other.InputValues) + && this.ClutValues.Equals(other.ClutValues) + && this.OutputValues.SequenceEqual(other.OutputValues); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccLut16TagDataEntry && this.Equals((IccLut16TagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Matrix.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.InputValues != null ? this.InputValues.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.ClutValues != null ? this.ClutValues.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.OutputValues != null ? this.OutputValues.GetHashCode() : 0); + return hashCode; + } + } + + private Matrix4x4 CreateMatrix(float[,] matrix) + { + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs new file mode 100644 index 000000000..538cf0505 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs @@ -0,0 +1,194 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// This structure represents a color transform using tables + /// with 8-bit precision. + /// + internal sealed class IccLut8TagDataEntry : IccTagDataEntry, IEquatable + { + private static readonly float[,] IdentityMatrix = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Lut8, tagSignature) + { + Guard.NotNull(matrix, nameof(matrix)); + Guard.NotNull(inputValues, nameof(inputValues)); + Guard.NotNull(clutValues, nameof(clutValues)); + Guard.NotNull(outputValues, nameof(outputValues)); + + bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); + + this.Matrix = this.CreateMatrix(matrix); + this.InputValues = inputValues; + this.ClutValues = clutValues; + this.OutputValues = outputValues; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + + Guard.IsFalse(inputValues.Any(t => t.Values.Length != 256), nameof(inputValues), "Input lookup table has to have a length of 256"); + Guard.IsFalse(outputValues.Any(t => t.Values.Length != 256), nameof(outputValues), "Output lookup table has to have a length of 256"); + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount => this.InputValues.Length; + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount => this.OutputValues.Length; + + /// + /// Gets the conversion matrix + /// + public Matrix4x4 Matrix { get; } + + /// + /// Gets the input lookup table + /// + public IccLut[] InputValues { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the output lookup table + /// + public IccLut[] OutputValues { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccLut8TagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccLut8TagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Matrix.Equals(other.Matrix) + && this.InputValues.SequenceEqual(other.InputValues) + && this.ClutValues.Equals(other.ClutValues) + && this.OutputValues.SequenceEqual(other.OutputValues); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccLut8TagDataEntry && this.Equals((IccLut8TagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Matrix.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.InputValues != null ? this.InputValues.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.ClutValues != null ? this.ClutValues.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.OutputValues != null ? this.OutputValues.GetHashCode() : 0); + return hashCode; + } + } + + private Matrix4x4 CreateMatrix(float[,] matrix) + { + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs new file mode 100644 index 000000000..d440447cc --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -0,0 +1,321 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// This structure represents a color transform. + /// + internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + /// Tag Signature + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutAToB, tagSignature) + { + this.VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsAClutMMatrixB()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = 3; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsMMatrixB()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; + } + else if (this.IsAClutB()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = curveB.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) + { + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; + } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } + + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccLutAToBTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccLutAToBTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && this.EqualsCurve(this.CurveB, other.CurveB) + && this.EqualsCurve(this.CurveM, other.CurveM) + && this.EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccLutAToBTagDataEntry && this.Equals((IccLutAToBTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.InputChannelCount; + hashCode = (hashCode * 397) ^ this.OutputChannelCount; + hashCode = (hashCode * 397) ^ this.Matrix3x3.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Matrix3x1.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.ClutValues != null ? this.ClutValues.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.CurveB != null ? this.CurveB.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.CurveM != null ? this.CurveM.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.CurveA != null ? this.CurveA.GetHashCode() : 0); + return hashCode; + } + } + + private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves == null; + bool entryNull = entryCurves == null; + + if (thisNull && entryNull) + { + return true; + } + + if (entryNull) + { + return false; + } + + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsAClutMMatrixB() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsMMatrixB() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null; + } + + private bool IsAClutB() + { + return this.CurveB != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsB() + { + return this.CurveB != null; + } + + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) + { + bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); + } + } + + private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) + { + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); + } + + if (matrix3x3 != null) + { + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } + + private Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix == null) + { + return null; + } + + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix == null) + { + return null; + } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs new file mode 100644 index 000000000..9d63dd171 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -0,0 +1,321 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// This structure represents a color transform. + /// + internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A Curve + /// CLUT + /// M Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// B Curve + /// Tag Signature + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutBToA, tagSignature) + { + this.VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsBMatrixMClutA()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + + this.InputChannelCount = 3; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsBMatrixM()) + { + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; + } + else if (this.IsBClutA()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + + this.InputChannelCount = curveB.Length; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) + { + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; + } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } + + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccLutBToATagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccLutBToATagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && this.EqualsCurve(this.CurveB, other.CurveB) + && this.EqualsCurve(this.CurveM, other.CurveM) + && this.EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccLutBToATagDataEntry && this.Equals((IccLutBToATagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.InputChannelCount; + hashCode = (hashCode * 397) ^ this.OutputChannelCount; + hashCode = (hashCode * 397) ^ this.Matrix3x3.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Matrix3x1.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.ClutValues != null ? this.ClutValues.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.CurveB != null ? this.CurveB.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.CurveM != null ? this.CurveM.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.CurveA != null ? this.CurveA.GetHashCode() : 0); + return hashCode; + } + } + + private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves == null; + bool entryNull = entryCurves == null; + + if (thisNull && entryNull) + { + return true; + } + + if (entryNull) + { + return false; + } + + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsBMatrixMClutA() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsBMatrixM() + { + return this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null; + } + + private bool IsBClutA() + { + return this.CurveB != null + && this.ClutValues != null + && this.CurveA != null; + } + + private bool IsB() + { + return this.CurveB != null; + } + + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) + { + bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry)); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); + } + } + + private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) + { + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); + } + + if (matrix3x3 != null) + { + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } + + private Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix == null) + { + return null; + } + + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix == null) + { + return null; + } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs new file mode 100644 index 000000000..390a5ba54 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs @@ -0,0 +1,134 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// The measurementType information refers only to the internal + /// profile data and is meant to provide profile makers an alternative + /// to the default measurement specifications. + /// + internal sealed class IccMeasurementTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Observer + /// XYZ Backing values + /// Geometry + /// Flare + /// Illuminant + public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant) + : this(observer, xyzBacking, geometry, flare, illuminant, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Observer + /// XYZ Backing values + /// Geometry + /// Flare + /// Illuminant + /// Tag Signature + public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant, IccProfileTag tagSignature) + : base(IccTypeSignature.Measurement, tagSignature) + { + this.Observer = observer; + this.XyzBacking = xyzBacking; + this.Geometry = geometry; + this.Flare = flare; + this.Illuminant = illuminant; + } + + /// + /// Gets the observer + /// + public IccStandardObserver Observer { get; } + + /// + /// Gets the XYZ Backing values + /// + public Vector3 XyzBacking { get; } + + /// + /// Gets the geometry + /// + public IccMeasurementGeometry Geometry { get; } + + /// + /// Gets the flare + /// + public float Flare { get; } + + /// + /// Gets the illuminant + /// + public IccStandardIlluminant Illuminant { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccMeasurementTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccMeasurementTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Observer == other.Observer + && this.XyzBacking.Equals(other.XyzBacking) + && this.Geometry == other.Geometry + && this.Flare.Equals(other.Flare) + && this.Illuminant == other.Illuminant; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccMeasurementTagDataEntry && this.Equals((IccMeasurementTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.Observer; + hashCode = (hashCode * 397) ^ this.XyzBacking.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.Geometry; + hashCode = (hashCode * 397) ^ this.Flare.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.Illuminant; + return hashCode; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs new file mode 100644 index 000000000..573e77ed4 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This tag structure contains a set of records each referencing + /// a multilingual string associated with a profile. + /// + internal sealed class IccMultiLocalizedUnicodeTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Localized Text + public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts) + : this(texts, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Localized Text + /// Tag Signature + public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts, IccProfileTag tagSignature) + : base(IccTypeSignature.MultiLocalizedUnicode, tagSignature) + { + Guard.NotNull(texts, nameof(texts)); + this.Texts = texts; + } + + /// + /// Gets the localized texts + /// + public IccLocalizedString[] Texts { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccMultiLocalizedUnicodeTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccMultiLocalizedUnicodeTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Texts.SequenceEqual(other.Texts); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccMultiLocalizedUnicodeTagDataEntry && this.Equals((IccMultiLocalizedUnicodeTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Texts != null ? this.Texts.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs new file mode 100644 index 000000000..e3e6ef143 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs @@ -0,0 +1,115 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This structure represents a color transform, containing + /// a sequence of processing elements. + /// + internal sealed class IccMultiProcessElementsTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Processing elements + public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Processing elements + /// Tag Signature + public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.MultiProcessElements, tagSignature) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length > 0, nameof(data), $"{nameof(data)} must have at least one element"); + + this.InputChannelCount = data[0].InputChannelCount; + this.OutputChannelCount = data[0].OutputChannelCount; + this.Data = data; + + bool channelsNotSame = data.Any(t => t.InputChannelCount != this.InputChannelCount || t.OutputChannelCount != this.OutputChannelCount); + Guard.IsFalse(channelsNotSame, nameof(data), "The number of input and output channels are not the same for all elements"); + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the processing elements + /// + public IccMultiProcessElement[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccMultiProcessElementsTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccMultiProcessElementsTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccMultiProcessElementsTagDataEntry && this.Equals((IccMultiProcessElementsTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.InputChannelCount; + hashCode = (hashCode * 397) ^ this.OutputChannelCount; + hashCode = (hashCode * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs new file mode 100644 index 000000000..17b7213b2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs @@ -0,0 +1,182 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// The namedColor2Type is a count value and array of structures + /// that provide color coordinates for color names. + /// + internal sealed class IccNamedColor2TagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The named colors + public IccNamedColor2TagDataEntry(IccNamedColor[] colors) + : this(0, null, null, colors, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Prefix + /// Suffix + /// /// The named colors + public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors) + : this(0, prefix, suffix, colors, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Vendor specific flags + /// Prefix + /// Suffix + /// The named colors + public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors) + : this(vendorFlags, prefix, suffix, colors, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(IccNamedColor[] colors, IccProfileTag tagSignature) + : this(0, null, null, colors, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Prefix + /// Suffix + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) + : this(0, prefix, suffix, colors, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Vendor specific flags + /// Prefix + /// Suffix + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) + : base(IccTypeSignature.NamedColor2, tagSignature) + { + Guard.NotNull(colors, nameof(colors)); + + int coordinateCount = 0; + if (colors.Length > 0) + { + coordinateCount = colors[0].DeviceCoordinates?.Length ?? 0; + Guard.IsFalse(colors.Any(t => (t.DeviceCoordinates?.Length ?? 0) != coordinateCount), nameof(colors), "Device coordinate count must be the same for all colors"); + } + + this.VendorFlags = vendorFlags; + this.CoordinateCount = coordinateCount; + this.Prefix = prefix; + this.Suffix = suffix; + this.Colors = colors; + } + + /// + /// Gets the number of coordinates + /// + public int CoordinateCount { get; } + + /// + /// Gets the prefix + /// + public string Prefix { get; } + + /// + /// Gets the suffix + /// + public string Suffix { get; } + + /// + /// Gets the vendor specific flags + /// + public int VendorFlags { get; } + + /// + /// Gets the named colors + /// + public IccNamedColor[] Colors { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccNamedColor2TagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccNamedColor2TagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.CoordinateCount == other.CoordinateCount + && string.Equals(this.Prefix, other.Prefix) + && string.Equals(this.Suffix, other.Suffix) + && this.VendorFlags == other.VendorFlags + && this.Colors.SequenceEqual(other.Colors); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccNamedColor2TagDataEntry && this.Equals((IccNamedColor2TagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.CoordinateCount; + hashCode = (hashCode * 397) ^ (this.Prefix != null ? this.Prefix.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.Suffix != null ? this.Suffix.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ this.VendorFlags; + hashCode = (hashCode * 397) ^ (this.Colors != null ? this.Colors.GetHashCode() : 0); + return hashCode; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs new file mode 100644 index 000000000..4264c0b24 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// The parametricCurveType describes a one-dimensional curve by + /// specifying one of a predefined set of functions using the parameters. + /// + internal sealed class IccParametricCurveTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Curve + public IccParametricCurveTagDataEntry(IccParametricCurve curve) + : this(curve, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Curve + /// Tag Signature + public IccParametricCurveTagDataEntry(IccParametricCurve curve, IccProfileTag tagSignature) + : base(IccTypeSignature.ParametricCurve, tagSignature) + { + this.Curve = curve; + } + + /// + /// Gets the Curve + /// + public IccParametricCurve Curve { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccParametricCurveTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccParametricCurveTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Curve.Equals(other.Curve); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccParametricCurveTagDataEntry && this.Equals((IccParametricCurveTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Curve != null ? this.Curve.GetHashCode() : 0); + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs new file mode 100644 index 000000000..547483e23 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type is an array of structures, each of which contains information + /// from the header fields and tags from the original profiles which were + /// combined to create the final profile. + /// + internal sealed class IccProfileSequenceDescTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Profile Descriptions + public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions) + : this(descriptions, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Profile Descriptions + /// Tag Signature + public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature) + : base(IccTypeSignature.ProfileSequenceDesc, tagSignature) + { + Guard.NotNull(descriptions, nameof(descriptions)); + this.Descriptions = descriptions; + } + + /// + /// Gets the profile descriptions + /// + public IccProfileDescription[] Descriptions { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccProfileSequenceDescTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccProfileSequenceDescTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Descriptions.SequenceEqual(other.Descriptions); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccProfileSequenceDescTagDataEntry && this.Equals((IccProfileSequenceDescTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Descriptions != null ? this.Descriptions.GetHashCode() : 0); + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs new file mode 100644 index 000000000..1a1b02c0b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type is an array of structures, each of which contains information + /// for identification of a profile used in a sequence. + /// + internal sealed class IccProfileSequenceIdentifierTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Profile Identifiers + public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Profile Identifiers + /// Tag Signature + public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.ProfileSequenceIdentifier, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the profile identifiers + /// + public IccProfileSequenceIdentifier[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccProfileSequenceIdentifierTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccProfileSequenceIdentifierTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccProfileSequenceIdentifierTagDataEntry && this.Equals((IccProfileSequenceIdentifierTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs new file mode 100644 index 000000000..f4fc7fbd2 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs @@ -0,0 +1,108 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// The purpose of this tag type is to provide a mechanism to relate physical + /// colorant amounts with the normalized device codes produced by lut8Type, lut16Type, + /// lutAToBType, lutBToAType or multiProcessElementsType tags so that corrections can + /// be made for variation in the device without having to produce a new profile. + /// + internal sealed class IccResponseCurveSet16TagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Curves + public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves) + : this(curves, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Curves + /// Tag Signature + public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves, IccProfileTag tagSignature) + : base(IccTypeSignature.ResponseCurveSet16, tagSignature) + { + Guard.NotNull(curves, nameof(curves)); + Guard.IsTrue(curves.Length > 0, nameof(curves), $"{nameof(curves)} needs at least one element"); + + this.Curves = curves; + this.ChannelCount = (ushort)curves[0].ResponseArrays.Length; + + Guard.IsFalse(curves.Any(t => t.ResponseArrays.Length != this.ChannelCount), nameof(curves), "All curves need to have the same number of channels"); + } + + /// + /// Gets the number of channels + /// + public ushort ChannelCount { get; } + + /// + /// Gets the curves + /// + public IccResponseCurve[] Curves { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccResponseCurveSet16TagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccResponseCurveSet16TagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.ChannelCount == other.ChannelCount + && this.Curves.SequenceEqual(other.Curves); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccResponseCurveSet16TagDataEntry && this.Equals((IccResponseCurveSet16TagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.ChannelCount.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.Curves != null ? this.Curves.GetHashCode() : 0); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs new file mode 100644 index 000000000..8c3a2d83b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs @@ -0,0 +1,105 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type describes various screening parameters including + /// screen frequency, screening angle, and spot shape. + /// + internal sealed class IccScreeningTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Screening flags + /// Channel information + public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels) + : this(flags, channels, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Screening flags + /// Channel information + /// Tag Signature + public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels, IccProfileTag tagSignature) + : base(IccTypeSignature.Screening, tagSignature) + { + Guard.NotNull(channels, nameof(channels)); + + this.Flags = flags; + this.Channels = channels; + } + + /// + /// Gets the screening flags + /// + public IccScreeningFlag Flags { get; } + + /// + /// Gets the channel information + /// + public IccScreeningChannel[] Channels { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccScreeningTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccScreeningTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.Flags == other.Flags + && this.Channels.SequenceEqual(other.Channels); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccScreeningTagDataEntry && this.Equals((IccScreeningTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.Flags; + hashCode = (hashCode * 397) ^ (this.Channels != null ? this.Channels.GetHashCode() : 0); + return hashCode; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs new file mode 100644 index 000000000..9ce2c1b9a --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Typically this type is used for registered tags that can + /// be displayed on many development systems as a sequence of four characters. + /// + internal sealed class IccSignatureTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Signature + public IccSignatureTagDataEntry(string signatureData) + : this(signatureData, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Signature + /// Tag Signature + public IccSignatureTagDataEntry(string signatureData, IccProfileTag tagSignature) + : base(IccTypeSignature.Signature, tagSignature) + { + Guard.NotNull(signatureData, nameof(signatureData)); + this.SignatureData = signatureData; + } + + /// + /// Gets the Signature + /// + public string SignatureData { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccSignatureTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccSignatureTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && string.Equals(this.SignatureData, other.SignatureData); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccSignatureTagDataEntry && this.Equals((IccSignatureTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.SignatureData != null ? this.SignatureData.GetHashCode() : 0); + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs new file mode 100644 index 000000000..202220f93 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs @@ -0,0 +1,194 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + + /// + /// The TextDescriptionType contains three types of text description. + /// + internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// ASCII text + /// Unicode text + /// ScriptCode text + /// Unicode Language-Code + /// ScriptCode Code + public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode) + : this(ascii, unicode, scriptCode, unicodeLanguageCode, scriptCodeCode, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// ASCII text + /// Unicode text + /// ScriptCode text + /// Unicode Language-Code + /// ScriptCode Code + /// Tag Signature + public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode, IccProfileTag tagSignature) + : base(IccTypeSignature.TextDescription, tagSignature) + { + this.Ascii = ascii; + this.Unicode = unicode; + this.ScriptCode = scriptCode; + this.UnicodeLanguageCode = unicodeLanguageCode; + this.ScriptCodeCode = scriptCodeCode; + } + + /// + /// Gets the ASCII text + /// + public string Ascii { get; } + + /// + /// Gets the Unicode text + /// + public string Unicode { get; } + + /// + /// Gets the ScriptCode text + /// + public string ScriptCode { get; } + + /// + /// Gets the Unicode Language-Code + /// + public uint UnicodeLanguageCode { get; } + + /// + /// Gets the ScriptCode Code + /// + public ushort ScriptCodeCode { get; } + + /// + /// Performs an explicit conversion from + /// to . + /// + /// The entry to convert + /// The converted entry + public static explicit operator IccMultiLocalizedUnicodeTagDataEntry(IccTextDescriptionTagDataEntry textEntry) + { + if (textEntry == null) + { + return null; + } + + IccLocalizedString localString; + if (!string.IsNullOrEmpty(textEntry.Unicode)) + { + CultureInfo culture = GetCulture(textEntry.UnicodeLanguageCode); + localString = culture != null + ? new IccLocalizedString(culture, textEntry.Unicode) + : new IccLocalizedString(textEntry.Unicode); + } + else if (!string.IsNullOrEmpty(textEntry.Ascii)) + { + localString = new IccLocalizedString(textEntry.Ascii); + } + else if (!string.IsNullOrEmpty(textEntry.ScriptCode)) + { + localString = new IccLocalizedString(textEntry.ScriptCode); + } + else + { + localString = new IccLocalizedString(string.Empty); + } + + return new IccMultiLocalizedUnicodeTagDataEntry(new[] { localString }, textEntry.TagSignature); + + CultureInfo GetCulture(uint value) + { + if (value == 0) + { + return null; + } + + byte p1 = (byte)(value >> 24); + byte p2 = (byte)(value >> 16); + byte p3 = (byte)(value >> 8); + byte p4 = (byte)value; + + // Check if the values are [a-z]{2}[A-Z]{2} + if (p1 >= 0x61 && p1 <= 0x7A + && p2 >= 0x61 && p2 <= 0x7A + && p3 >= 0x41 && p3 <= 0x5A + && p4 >= 0x41 && p4 <= 0x5A) + { + string culture = new string(new[] { (char)p1, (char)p2, '-', (char)p3, (char)p4 }); + return new CultureInfo(culture); + } + + return null; + } + } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccTextDescriptionTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccTextDescriptionTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && string.Equals(this.Ascii, other.Ascii) + && string.Equals(this.Unicode, other.Unicode) + && string.Equals(this.ScriptCode, other.ScriptCode) + && this.UnicodeLanguageCode == other.UnicodeLanguageCode + && this.ScriptCodeCode == other.ScriptCodeCode; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccTextDescriptionTagDataEntry && this.Equals((IccTextDescriptionTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.Ascii != null ? this.Ascii.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.Unicode != null ? this.Unicode.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.ScriptCode != null ? this.ScriptCode.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (int)this.UnicodeLanguageCode; + hashCode = (hashCode * 397) ^ this.ScriptCodeCode.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs new file mode 100644 index 000000000..9e56a1cf8 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// This is a simple text structure that contains a text string. + /// + internal sealed class IccTextTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The Text + public IccTextTagDataEntry(string text) + : this(text, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Text + /// Tag Signature + public IccTextTagDataEntry(string text, IccProfileTag tagSignature) + : base(IccTypeSignature.Text, tagSignature) + { + Guard.NotNull(text, nameof(text)); + this.Text = text; + } + + /// + /// Gets the Text + /// + public string Text { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccTextTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccTextTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && string.Equals(this.Text, other.Text); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccTextTagDataEntry && this.Equals((IccTextTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Text != null ? this.Text.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs new file mode 100644 index 000000000..2b7b4044d --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type represents an array of doubles (from 32bit values). + /// + internal sealed class IccUFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUFix16ArrayTagDataEntry(float[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.U16Fixed16Array, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the array data + /// + public float[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccUFix16ArrayTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccUFix16ArrayTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccUFix16ArrayTagDataEntry && this.Equals((IccUFix16ArrayTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs new file mode 100644 index 000000000..da7725310 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type represents an array of unsigned shorts. + /// + internal sealed class IccUInt16ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt16ArrayTagDataEntry(ushort[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt16ArrayTagDataEntry(ushort[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt16Array, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the array data + /// + public ushort[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccUInt16ArrayTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccUInt16ArrayTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccUInt16ArrayTagDataEntry && this.Equals((IccUInt16ArrayTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs new file mode 100644 index 000000000..0dabdecc1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type represents an array of unsigned 32bit integers. + /// + internal sealed class IccUInt32ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt32ArrayTagDataEntry(uint[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt32ArrayTagDataEntry(uint[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt32Array, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the array data + /// + public uint[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccUInt32ArrayTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccUInt32ArrayTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccUInt32ArrayTagDataEntry && this.Equals((IccUInt32ArrayTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs new file mode 100644 index 000000000..d62a4a8a7 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type represents an array of unsigned 64bit integers. + /// + internal sealed class IccUInt64ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt64ArrayTagDataEntry(ulong[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt64Array, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the array data + /// + public ulong[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccUInt64ArrayTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccUInt64ArrayTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccUInt64ArrayTagDataEntry && this.Equals((IccUInt64ArrayTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs new file mode 100644 index 000000000..30363df9d --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type represents an array of bytes. + /// + internal sealed class IccUInt8ArrayTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The array data + public IccUInt8ArrayTagDataEntry(byte[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt8ArrayTagDataEntry(byte[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt8Array, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the array data + /// + public byte[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccUInt8ArrayTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccUInt8ArrayTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccUInt8ArrayTagDataEntry && this.Equals((IccUInt8ArrayTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs new file mode 100644 index 000000000..e668e5cfd --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs @@ -0,0 +1,117 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This type contains curves representing the under color removal and black generation + /// and a text string which is a general description of the method used for the UCR and BG. + /// + internal sealed class IccUcrBgTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// UCR (under color removal) curve values + /// BG (black generation) curve values + /// Description of the used UCR and BG method + public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description) + : this(ucrCurve, bgCurve, description, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// UCR (under color removal) curve values + /// BG (black generation) curve values + /// Description of the used UCR and BG method + /// Tag Signature + public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description, IccProfileTag tagSignature) + : base(IccTypeSignature.UcrBg, tagSignature) + { + Guard.NotNull(ucrCurve, nameof(ucrCurve)); + Guard.NotNull(bgCurve, nameof(bgCurve)); + Guard.NotNull(description, nameof(description)); + + this.UcrCurve = ucrCurve; + this.BgCurve = bgCurve; + this.Description = description; + } + + /// + /// Gets the UCR (under color removal) curve values + /// + public ushort[] UcrCurve { get; } + + /// + /// Gets the BG (black generation) curve values + /// + public ushort[] BgCurve { get; } + + /// + /// Gets a description of the used UCR and BG method + /// + public string Description { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccUcrBgTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccUcrBgTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.UcrCurve.SequenceEqual(other.UcrCurve) + && this.BgCurve.SequenceEqual(other.BgCurve) + && string.Equals(this.Description, other.Description); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccUcrBgTagDataEntry && this.Equals((IccUcrBgTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ (this.UcrCurve != null ? this.UcrCurve.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.BgCurve != null ? this.BgCurve.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.Description != null ? this.Description.GetHashCode() : 0); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs new file mode 100644 index 000000000..79549b7c8 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// This tag stores data of an unknown tag data entry + /// + internal sealed class IccUnknownTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The raw data of the entry + public IccUnknownTagDataEntry(byte[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw data of the entry + /// Tag Signature + public IccUnknownTagDataEntry(byte[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.Unknown, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the raw data of the entry + /// + public byte[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccUnknownTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccUnknownTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && this.Data.SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccUnknownTagDataEntry && this.Equals((IccUnknownTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (base.GetHashCode() * 397) ^ (this.Data != null ? this.Data.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs new file mode 100644 index 000000000..5f7406ef1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// This type represents a set of viewing condition parameters. + /// + internal sealed class IccViewingConditionsTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// XYZ values of Illuminant + /// XYZ values of Surrounding + /// Illuminant + public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant) + : this(illuminantXyz, surroundXyz, illuminant, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// XYZ values of Illuminant + /// XYZ values of Surrounding + /// Illuminant + /// Tag Signature + public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant, IccProfileTag tagSignature) + : base(IccTypeSignature.ViewingConditions, tagSignature) + { + this.IlluminantXyz = illuminantXyz; + this.SurroundXyz = surroundXyz; + this.Illuminant = illuminant; + } + + /// + /// Gets the XYZ values of Illuminant + /// + public Vector3 IlluminantXyz { get; } + + /// + /// Gets the XYZ values of Surrounding + /// + public Vector3 SurroundXyz { get; } + + /// + /// Gets the illuminant + /// + public IccStandardIlluminant Illuminant { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + var entry = other as IccViewingConditionsTagDataEntry; + return entry != null && this.Equals(entry); + } + + /// + public bool Equals(IccViewingConditionsTagDataEntry other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && this.IlluminantXyz.Equals(other.IlluminantXyz) + && this.SurroundXyz.Equals(other.SurroundXyz) + && this.Illuminant == other.Illuminant; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccViewingConditionsTagDataEntry && this.Equals((IccViewingConditionsTagDataEntry)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ this.IlluminantXyz.GetHashCode(); + hashCode = (hashCode * 397) ^ this.SurroundXyz.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.Illuminant; + return hashCode; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs new file mode 100644 index 000000000..77f9ff706 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// The XYZType contains an array of XYZ values. + /// + internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The XYZ numbers + public IccXyzTagDataEntry(Vector3[] data) + : this(data, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The XYZ numbers + /// Tag Signature + public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.Xyz, tagSignature) + { + Guard.NotNull(data, nameof(data)); + this.Data = data; + } + + /// + /// Gets the XYZ numbers + /// + public Vector3[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + if (base.Equals(other) && other is IccXyzTagDataEntry entry) + { + return this.Data.SequenceEqual(entry.Data); + } + + return false; + } + + /// + public bool Equals(IccXyzTagDataEntry other) + { + return this.Equals((IccTagDataEntry)other); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs new file mode 100644 index 000000000..d527d4052 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs @@ -0,0 +1,205 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// Color Lookup Table + /// + internal sealed class IccClut : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + /// The data type of this CLUT + public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); + + this.Values = values; + this.DataType = type; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + public IccClut(ushort[][] values, byte[] gridPointCount) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); + + const float Max = ushort.MaxValue; + + this.Values = new float[values.Length][]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = new float[values[i].Length]; + for (int j = 0; j < values[i].Length; j++) + { + this.Values[i][j] = values[i][j] / Max; + } + } + + this.DataType = IccClutDataType.UInt16; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + public IccClut(byte[][] values, byte[] gridPointCount) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); + + const float Max = byte.MaxValue; + + this.Values = new float[values.Length][]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = new float[values[i].Length]; + for (int j = 0; j < values[i].Length; j++) + { + this.Values[i][j] = values[i][j] / Max; + } + } + + this.DataType = IccClutDataType.UInt8; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Gets the values that make up this table + /// + public float[][] Values { get; } + + /// + /// Gets or sets the CLUT data type (important when writing a profile) + /// + public IccClutDataType DataType { get; set; } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the number of grid points per input channel + /// + public byte[] GridPointCount { get; } + + /// + public bool Equals(IccClut other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.EqualsValuesArray(other) + && this.DataType == other.DataType + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.GridPointCount.SequenceEqual(other.GridPointCount); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccClut && this.Equals((IccClut)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Values != null ? this.Values.GetHashCode() : 0; + hashCode = (hashCode * 397) ^ (int)this.DataType; + hashCode = (hashCode * 397) ^ this.InputChannelCount; + hashCode = (hashCode * 397) ^ this.OutputChannelCount; + hashCode = (hashCode * 397) ^ (this.GridPointCount != null ? this.GridPointCount.GetHashCode() : 0); + return hashCode; + } + } + + private bool EqualsValuesArray(IccClut other) + { + if (this.Values.Length != other.Values.Length) + { + return false; + } + + for (int i = 0; i < this.Values.Length; i++) + { + if (!this.Values[i].SequenceEqual(other.Values[i])) + { + return false; + } + } + + return true; + } + + private void CheckValues() + { + Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount)); + Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount)); + + bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount); + Guard.IsFalse(isLengthDifferent, nameof(this.Values), "The number of output values varies"); + + int length = 0; + for (int i = 0; i < this.InputChannelCount; i++) + { + length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount); + } + + length /= this.InputChannelCount; + + Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points"); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs new file mode 100644 index 000000000..bd8955075 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs @@ -0,0 +1,125 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Entry of ICC colorant table + /// + internal struct IccColorantTableEntry : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Name of the colorant + public IccColorantTableEntry(string name) + : this(name, 0, 0, 0) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// Name of the colorant + /// First PCS value + /// Second PCS value + /// Third PCS value + public IccColorantTableEntry(string name, ushort pcs1, ushort pcs2, ushort pcs3) + { + Guard.NotNull(name, nameof(name)); + + this.Name = name; + this.Pcs1 = pcs1; + this.Pcs2 = pcs2; + this.Pcs3 = pcs3; + } + + /// + /// Gets the colorant name + /// + public string Name { get; } + + /// + /// Gets the first PCS value + /// + public ushort Pcs1 { get; } + + /// + /// Gets the second PCS value + /// + public ushort Pcs2 { get; } + + /// + /// Gets the third PCS value + /// + public ushort Pcs3 { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccColorantTableEntry left, IccColorantTableEntry right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccColorantTableEntry left, IccColorantTableEntry right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object other) + { + return (other is IccColorantTableEntry) && this.Equals((IccColorantTableEntry)other); + } + + /// + public bool Equals(IccColorantTableEntry other) + { + return this.Name == other.Name + && this.Pcs1 == other.Pcs1 + && this.Pcs2 == other.Pcs2 + && this.Pcs3 == other.Pcs3; + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Name.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Pcs1.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Pcs2.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Pcs3.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + return $"{this.Name}: {this.Pcs1}; {this.Pcs2}; {this.Pcs3}"; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs new file mode 100644 index 000000000..0e2772963 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + + /// + /// A string with a specific locale + /// + internal sealed class IccLocalizedString : IEquatable + { + /// + /// Initializes a new instance of the class. + /// The culture will be + /// + /// The text value of this string + public IccLocalizedString(string text) + : this(CultureInfo.CurrentCulture, text) + { + } + + /// + /// Initializes a new instance of the class. + /// The culture will be + /// + /// The culture of this string + /// The text value of this string + public IccLocalizedString(CultureInfo culture, string text) + { + Guard.NotNull(culture, nameof(culture)); + Guard.NotNull(text, nameof(text)); + + this.Culture = culture; + this.Text = text; + } + + /// + /// Gets the actual text value + /// + public string Text { get; } + + /// + /// Gets the culture of the text + /// + public CultureInfo Culture { get; } + + /// + public bool Equals(IccLocalizedString other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Culture.Equals(other.Culture) + && this.Text == other.Text; + } + + /// + public override string ToString() + { + return $"{this.Culture.Name}: {this.Text}"; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs new file mode 100644 index 000000000..9b86e1113 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// Lookup Table + /// + internal sealed class IccLut : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The LUT values + public IccLut(float[] values) + { + Guard.NotNull(values, nameof(values)); + this.Values = values; + } + + /// + /// Initializes a new instance of the class. + /// + /// The LUT values + public IccLut(ushort[] values) + { + Guard.NotNull(values, nameof(values)); + + const float max = ushort.MaxValue; + + this.Values = new float[values.Length]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = values[i] / max; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The LUT values + public IccLut(byte[] values) + { + Guard.NotNull(values, nameof(values)); + + const float max = byte.MaxValue; + + this.Values = new float[values.Length]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = values[i] / max; + } + } + + /// + /// Gets the values that make up this table + /// + public float[] Values { get; } + + /// + public bool Equals(IccLut other) + { + if (other == null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Values.SequenceEqual(other.Values); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs new file mode 100644 index 000000000..5d5161568 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// A specific color with a name + /// + internal struct IccNamedColor : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Name of the color + /// Coordinates of the color in the profiles PCS + /// Coordinates of the color in the profiles Device-Space + public IccNamedColor(string name, ushort[] pcsCoordinates, ushort[] deviceCoordinates) + { + Guard.NotNull(name, nameof(name)); + Guard.NotNull(pcsCoordinates, nameof(pcsCoordinates)); + Guard.IsTrue(pcsCoordinates.Length == 3, nameof(pcsCoordinates), "Must have a length of 3"); + + this.Name = name; + this.PcsCoordinates = pcsCoordinates; + this.DeviceCoordinates = deviceCoordinates; + } + + /// + /// Gets the name of the color + /// + public string Name { get; } + + /// + /// Gets the coordinates of the color in the profiles PCS + /// + public ushort[] PcsCoordinates { get; } + + /// + /// Gets the coordinates of the color in the profiles Device-Space + /// + public ushort[] DeviceCoordinates { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccNamedColor left, IccNamedColor right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccNamedColor left, IccNamedColor right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object other) + { + return (other is IccNamedColor) && this.Equals((IccNamedColor)other); + } + + /// + public bool Equals(IccNamedColor other) + { + return this.Name == other.Name + && this.PcsCoordinates.SequenceEqual(other.PcsCoordinates) + && this.DeviceCoordinates.SequenceEqual(other.DeviceCoordinates); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Name.GetHashCode(); + hashCode = (hashCode * 397) ^ this.PcsCoordinates.GetHashCode(); + hashCode = (hashCode * 397) ^ this.DeviceCoordinates.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + return this.Name; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs new file mode 100644 index 000000000..768aead0e --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Position of an object within an ICC profile + /// + internal struct IccPositionNumber : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Offset in bytes + /// Size in bytes + public IccPositionNumber(uint offset, uint size) + { + this.Offset = offset; + this.Size = size; + } + + /// + /// Gets the offset in bytes + /// + public uint Offset { get; } + + /// + /// Gets the size in bytes + /// + public uint Size { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccPositionNumber left, IccPositionNumber right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccPositionNumber left, IccPositionNumber right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object other) + { + return (other is IccPositionNumber) && this.Equals((IccPositionNumber)other); + } + + /// + public bool Equals(IccPositionNumber other) + { + return this.Offset == other.Offset + && this.Size == other.Size; + } + + /// + public override int GetHashCode() + { + return unchecked((int)(this.Offset ^ this.Size)); + } + + /// + public override string ToString() + { + return $"{this.Offset}; {this.Size}"; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs new file mode 100644 index 000000000..4f744631d --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs @@ -0,0 +1,126 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// ICC Profile description + /// + internal sealed class IccProfileDescription : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Device Manufacturer + /// Device Model + /// Device Attributes + /// Technology Information + /// Device Manufacturer Info + /// Device Model Info + public IccProfileDescription( + uint deviceManufacturer, + uint deviceModel, + IccDeviceAttribute deviceAttributes, + IccProfileTag technologyInformation, + IccLocalizedString[] deviceManufacturerInfo, + IccLocalizedString[] deviceModelInfo) + { + Guard.NotNull(deviceManufacturerInfo, nameof(deviceManufacturerInfo)); + Guard.NotNull(deviceModelInfo, nameof(deviceModelInfo)); + + this.DeviceManufacturer = deviceManufacturer; + this.DeviceModel = deviceModel; + this.DeviceAttributes = deviceAttributes; + this.TechnologyInformation = technologyInformation; + this.DeviceManufacturerInfo = deviceManufacturerInfo; + this.DeviceModelInfo = deviceModelInfo; + } + + /// + /// Gets the device manufacturer + /// + public uint DeviceManufacturer { get; } + + /// + /// Gets the device model + /// + public uint DeviceModel { get; } + + /// + /// Gets the device attributes + /// + public IccDeviceAttribute DeviceAttributes { get; } + + /// + /// Gets the technology information + /// + public IccProfileTag TechnologyInformation { get; } + + /// + /// Gets the device manufacturer info + /// + public IccLocalizedString[] DeviceManufacturerInfo { get; } + + /// + /// Gets the device model info + /// + public IccLocalizedString[] DeviceModelInfo { get; } + + /// + public bool Equals(IccProfileDescription other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.DeviceManufacturer == other.DeviceManufacturer + && this.DeviceModel == other.DeviceModel + && this.DeviceAttributes == other.DeviceAttributes + && this.TechnologyInformation == other.TechnologyInformation + && this.DeviceManufacturerInfo.SequenceEqual(other.DeviceManufacturerInfo) + && this.DeviceModelInfo.SequenceEqual(other.DeviceModelInfo); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccProfileDescription && this.Equals((IccProfileDescription)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = (int)this.DeviceManufacturer; + hashCode = (hashCode * 397) ^ (int)this.DeviceModel; + hashCode = (hashCode * 397) ^ this.DeviceAttributes.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.TechnologyInformation; + hashCode = (hashCode * 397) ^ (this.DeviceManufacturerInfo != null ? this.DeviceManufacturerInfo.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.DeviceModelInfo != null ? this.DeviceModelInfo.GetHashCode() : 0); + return hashCode; + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs new file mode 100644 index 000000000..fc4760d3f --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs @@ -0,0 +1,138 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// ICC Profile ID + /// + public struct IccProfileId : IEquatable + { + /// + /// A profile ID with all values set to zero + /// + public static readonly IccProfileId Zero = new IccProfileId(0, 0, 0, 0); + + /// + /// Initializes a new instance of the struct. + /// + /// Part 1 of the ID + /// Part 2 of the ID + /// Part 3 of the ID + /// Part 4 of the ID + public IccProfileId(uint p1, uint p2, uint p3, uint p4) + { + this.Part1 = p1; + this.Part2 = p2; + this.Part3 = p3; + this.Part4 = p4; + } + + /// + /// Gets the first part of the ID + /// + public uint Part1 { get; } + + /// + /// Gets the second part of the ID + /// + public uint Part2 { get; } + + /// + /// Gets the third part of the ID + /// + public uint Part3 { get; } + + /// + /// Gets the fourth part of the ID + /// + public uint Part4 { get; } + + /// + /// Gets a value indicating whether the ID is set or just consists of zeros + /// + public bool IsSet + { + get + { + return this.Part1 != 0 + && this.Part2 != 0 + && this.Part3 != 0 + && this.Part4 != 0; + } + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccProfileId left, IccProfileId right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccProfileId left, IccProfileId right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object other) + { + return (other is IccProfileId) && this.Equals((IccProfileId)other); + } + + /// + public bool Equals(IccProfileId other) + { + return this.Part1 == other.Part1 + && this.Part2 == other.Part2 + && this.Part3 == other.Part3 + && this.Part4 == other.Part4; + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Part1.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Part2.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Part3.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Part4.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + return $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}"; + } + + private static string ToHex(uint value) + { + return value.ToString("X").PadLeft(8, '0'); + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs new file mode 100644 index 000000000..65507bf6b --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Linq; + + /// + /// Description of a profile within a sequence + /// + internal sealed class IccProfileSequenceIdentifier : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// ID of the profile + /// Description of the profile + public IccProfileSequenceIdentifier(IccProfileId id, IccLocalizedString[] description) + { + Guard.NotNull(description, nameof(description)); + + this.Id = id; + this.Description = description; + } + + /// + /// Gets the ID of the profile + /// + public IccProfileId Id { get; } + + /// + /// Gets the description of the profile + /// + public IccLocalizedString[] Description { get; } + + /// + public bool Equals(IccProfileSequenceIdentifier other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Id.Equals(other.Id) && this.Description.SequenceEqual(other.Description); + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj is IccProfileSequenceIdentifier && this.Equals((IccProfileSequenceIdentifier)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (this.Id.GetHashCode() * 397) ^ (this.Description != null ? this.Description.GetHashCode() : 0); + } + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs new file mode 100644 index 000000000..5c58aa1b1 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Associates a normalized device code with a measurement value + /// + internal struct IccResponseNumber : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Device Code + /// Measurement Value + public IccResponseNumber(ushort deviceCode, float measurementValue) + { + this.DeviceCode = deviceCode; + this.MeasurementValue = measurementValue; + } + + /// + /// Gets the device code + /// + public ushort DeviceCode { get; } + + /// + /// Gets the measurement value + /// + public float MeasurementValue { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccResponseNumber left, IccResponseNumber right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccResponseNumber left, IccResponseNumber right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object other) + { + return (other is IccResponseNumber) && this.Equals((IccResponseNumber)other); + } + + /// + public bool Equals(IccResponseNumber other) + { + return this.DeviceCode == other.DeviceCode + && this.MeasurementValue == other.MeasurementValue; + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.DeviceCode.GetHashCode(); + hashCode = (hashCode * 397) ^ this.MeasurementValue.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + return $"Code: {this.DeviceCode}; Value: {this.MeasurementValue}"; + } + } +} diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs new file mode 100644 index 000000000..ac0e89bb0 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs @@ -0,0 +1,105 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A single channel of a + /// + internal struct IccScreeningChannel : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Screen frequency + /// Angle in degrees + /// Spot shape + public IccScreeningChannel(float frequency, float angle, IccScreeningSpotType spotShape) + { + this.Frequency = frequency; + this.Angle = angle; + this.SpotShape = spotShape; + } + + /// + /// Gets the screen frequency + /// + public float Frequency { get; } + + /// + /// Gets the angle in degrees + /// + public float Angle { get; } + + /// + /// Gets the spot shape + /// + public IccScreeningSpotType SpotShape { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccScreeningChannel left, IccScreeningChannel right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccScreeningChannel left, IccScreeningChannel right) + { + return !left.Equals(right); + } + + /// + public bool Equals(IccScreeningChannel other) + { + return this.Frequency.Equals(other.Frequency) + && this.Angle.Equals(other.Angle) + && this.SpotShape == other.SpotShape; + } + + /// + public override bool Equals(object obj) + { + return obj is IccScreeningChannel && this.Equals((IccScreeningChannel)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Frequency.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Angle.GetHashCode(); + hashCode = (hashCode * 397) ^ (int)this.SpotShape; + return hashCode; + } + } + + /// + public override string ToString() + { + return $"{this.Frequency}Hz; {this.Angle}°; {this.SpotShape}"; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs new file mode 100644 index 000000000..793756999 --- /dev/null +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs @@ -0,0 +1,105 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// Entry of ICC tag table + /// + internal struct IccTagTableEntry : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Signature of the tag + /// Offset of entry in bytes + /// Size of entry in bytes + public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize) + { + this.Signature = signature; + this.Offset = offset; + this.DataSize = dataSize; + } + + /// + /// Gets the signature of the tag + /// + public IccProfileTag Signature { get; } + + /// + /// Gets the offset of entry in bytes + /// + public uint Offset { get; } + + /// + /// Gets the size of entry in bytes + /// + public uint DataSize { get; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object other) + { + return (other is IccTagTableEntry) && this.Equals((IccTagTableEntry)other); + } + + /// + public bool Equals(IccTagTableEntry other) + { + return this.Signature == other.Signature + && this.Offset == other.Offset + && this.DataSize == other.DataSize; + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Signature.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Offset.GetHashCode(); + hashCode = (hashCode * 397) ^ this.DataSize.GetHashCode(); + return hashCode; + } + } + + /// + public override string ToString() + { + return $"{this.Signature} (Offset: {this.Offset}; Size: {this.DataSize})"; + } + } +} diff --git a/src/ImageSharp/Numerics/Point.cs b/src/ImageSharp/Numerics/Point.cs index 3cd47659c..8d523895f 100644 --- a/src/ImageSharp/Numerics/Point.cs +++ b/src/ImageSharp/Numerics/Point.cs @@ -137,7 +137,7 @@ namespace ImageSharp /// The rotation public static Matrix3x2 CreateRotation(Point origin, float degrees) { - float radians = ImageMaths.DegreesToRadians(degrees); + float radians = MathF.DegreeToRadian(degrees); return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y)); } @@ -173,8 +173,8 @@ namespace ImageSharp /// The rotation public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) { - float radiansX = ImageMaths.DegreesToRadians(degreesX); - float radiansY = ImageMaths.DegreesToRadians(degreesY); + float radiansX = MathF.DegreeToRadian(degreesX); + float radiansY = MathF.DegreeToRadian(degreesY); return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y)); } diff --git a/src/ImageSharp/PixelFormats/Rgba32.ColorspaceTransforms.cs b/src/ImageSharp/PixelFormats/Rgba32.ColorspaceTransforms.cs deleted file mode 100644 index 1dc2292b1..000000000 --- a/src/ImageSharp/PixelFormats/Rgba32.ColorspaceTransforms.cs +++ /dev/null @@ -1,283 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Numerics; - using Colors.Spaces; - - /// - /// Provides implicit colorspace transformation. - /// - public partial struct Rgba32 - { - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Rgba32(Bgra32 color) - { - return new Rgba32(color.R, color.G, color.B, color.A); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Rgba32(Cmyk cmykColor) - { - float r = (1 - cmykColor.C) * (1 - cmykColor.K); - float g = (1 - cmykColor.M) * (1 - cmykColor.K); - float b = (1 - cmykColor.Y) * (1 - cmykColor.K); - return new Rgba32(r, g, b, 1); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Rgba32(YCbCr color) - { - float y = color.Y; - float cb = color.Cb - 128; - float cr = color.Cr - 128; - - byte r = (byte)(y + (1.402F * cr)).Clamp(0, 255); - byte g = (byte)(y - (0.34414F * cb) - (0.71414F * cr)).Clamp(0, 255); - byte b = (byte)(y + (1.772F * cb)).Clamp(0, 255); - - return new Rgba32(r, g, b); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Rgba32(CieXyz color) - { - float x = color.X / 100F; - float y = color.Y / 100F; - float z = color.Z / 100F; - - // Then XYZ to RGB (multiplication by 100 was done above already) - float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); - float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); - float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); - - Vector4 vector = new Vector4(r, g, b, 1).Compress(); - return new Rgba32(vector); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Rgba32(Hsv color) - { - float s = color.S; - float v = color.V; - - if (MathF.Abs(s) < Constants.Epsilon) - { - return new Rgba32(v, v, v, 1); - } - - float h = (MathF.Abs(color.H - 360) < Constants.Epsilon) ? 0 : color.H / 60; - int i = (int)Math.Truncate(h); - float f = h - i; - - float p = v * (1.0F - s); - float q = v * (1.0F - (s * f)); - float t = v * (1.0F - (s * (1.0F - 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 Rgba32(r, g, b, 1); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Rgba32(Hsl color) - { - float rangedH = color.H / 360F; - float r = 0; - float g = 0; - float b = 0; - float s = color.S; - float l = color.L; - - if (MathF.Abs(l) > Constants.Epsilon) - { - if (MathF.Abs(s) < Constants.Epsilon) - { - r = g = b = l; - } - else - { - float temp2 = (l < 0.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 Rgba32(r, g, b, 1); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Rgba32(CieLab cieLabColor) - { - // First convert back to XYZ... - float y = (cieLabColor.L + 16F) / 116F; - float x = (cieLabColor.A / 500F) + y; - float z = y - (cieLabColor.B / 200F); - - float x3 = x * x * x; - float y3 = y * y * y; - float z3 = z * z * z; - - x = x3 > 0.008856F ? x3 : (x - 0.137931F) / 7.787F; - y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F); - z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F; - - x *= 0.95047F; - z *= 1.08883F; - - // Then XYZ to RGB (multiplication by 100 was done above already) - float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); - float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); - float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); - - return new Rgba32(new Vector4(r, g, b, 1F).Compress()); - } - - /// - /// Gets the color component from the given values. - /// - /// The first value. - /// The second value. - /// The third value. - /// - /// The . - /// - private static float GetColorComponent(float first, float second, float third) - { - third = MoveIntoRange(third); - if (third < 0.1666667F) - { - return first + ((second - first) * 6.0f * third); - } - - if (third < 0.5) - { - return second; - } - - if (third < 0.6666667F) - { - return first + ((second - first) * (0.6666667F - third) * 6.0f); - } - - return first; - } - - /// - /// Moves the specific value within the acceptable range for - /// conversion. - /// Used for converting colors to this type. - /// - /// The value to shift. - /// - /// The . - /// - private static float MoveIntoRange(float value) - { - if (value < 0.0) - { - value += 1.0f; - } - else if (value > 1.0) - { - value -= 1.0f; - } - - return value; - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 5cd67f053..a2fa1ddf3 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -83,25 +83,22 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel color = sourcePixels[offsetX, offsetY]; - - // Any channel will do since it's Grayscale. - sourcePixels[offsetX, offsetY] = color.ToVector4().X >= threshold ? upper : lower; - } - }); - } + ref TPixel color = ref row[x - startX]; + + // Any channel will do since it's Grayscale. + color = color.ToVector4().X >= threshold ? upper : lower; + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index af2d9f760..47811f0ec 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -85,18 +85,17 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) + for (int y = minY; y < maxY; y++) { - for (int y = minY; y < maxY; y++) + int offsetY = y - startY; + Span row = source.GetRowSpan(offsetY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel sourceColor = sourcePixels[offsetX, offsetY]; - TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; - this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); - } + int offsetX = x - startX; + TPixel sourceColor = row[offsetX]; + TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(source, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); } } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs index c4d71d9af..898389777 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -93,21 +93,17 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) + byte[] bytes = new byte[4]; + for (int y = minY; y < maxY; y++) { - for (int y = minY; y < maxY; y++) - { - int offsetY = y - startY; - byte[] bytes = ArrayPool.Shared.Rent(4); - - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel sourceColor = sourcePixels[offsetX, offsetY]; - this.Dither.Dither(sourcePixels, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); - } + int offsetY = y - startY; + Span row = source.GetRowSpan(offsetY); - ArrayPool.Shared.Return(bytes); + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TPixel sourceColor = row[offsetX]; + this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); } } } diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs index cfe50150f..49af2667d 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Processing.Processors { using System; using System.Numerics; - using System.Runtime.CompilerServices; using System.Threading.Tasks; using ImageSharp.PixelFormats; @@ -61,39 +60,23 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - int offsetY = y - startY; + Span row = source.GetRowSpan(y - startY); + for (int x = minX; x < maxX; x++) { - int offsetX = x - startX; - sourcePixels[offsetX, offsetY] = this.ApplyMatrix(sourcePixels[offsetX, offsetY], matrix, compand); - } - }); - } - } + ref TPixel pixel = ref row[x - startX]; + var vector = pixel.ToVector4(); - /// - /// Applies the color matrix against the given color. - /// - /// The source color. - /// The matrix. - /// Whether to compand the color during processing. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TPixel ApplyMatrix(TPixel color, Matrix4x4 matrix, bool compand) - { - Vector4 vector = color.ToVector4(); + if (compand) + { + vector = vector.Expand(); + } - if (compand) - { - vector = vector.Expand(); + vector = Vector4.Transform(vector, matrix); + pixel.PackFromVector4(compand ? vector.Compress() : vector); + } + }); } - - vector = Vector4.Transform(vector, matrix); - TPixel packed = default(TPixel); - packed.PackFromVector4(compand ? vector.Compress() : vector); - return packed; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs index 8995663a3..f28683977 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs @@ -34,13 +34,13 @@ namespace ImageSharp.Processing.Processors this.Angle = angle; - float radians = ImageMaths.DegreesToRadians(angle); - double cosradians = Math.Cos(radians); - double sinradians = Math.Sin(radians); + float radians = MathF.DegreeToRadian(angle); + float cosradians = MathF.Cos(radians); + float sinradians = MathF.Sin(radians); - float lumR = .213f; - float lumG = .715f; - float lumB = .072f; + float lumR = .213F; + float lumG = .715F; + float lumB = .072F; float oneMinusLumR = 1 - lumR; float oneMinusLumG = 1 - lumG; @@ -51,15 +51,15 @@ namespace ImageSharp.Processing.Processors // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx Matrix4x4 matrix4X4 = new Matrix4x4() { - M11 = (float)(lumR + (cosradians * oneMinusLumR) - (sinradians * lumR)), - M12 = (float)(lumR - (cosradians * lumR) - (sinradians * 0.143)), - M13 = (float)(lumR - (cosradians * lumR) - (sinradians * oneMinusLumR)), - M21 = (float)(lumG - (cosradians * lumG) - (sinradians * lumG)), - M22 = (float)(lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140)), - M23 = (float)(lumG - (cosradians * lumG) + (sinradians * lumG)), - M31 = (float)(lumB - (cosradians * lumB) + (sinradians * oneMinusLumB)), - M32 = (float)(lumB - (cosradians * lumB) - (sinradians * 0.283)), - M33 = (float)(lumB + (cosradians * oneMinusLumB) + (sinradians * lumB)), + M11 = lumR + (cosradians * oneMinusLumR) - (sinradians * lumR), + M12 = lumR - (cosradians * lumR) - (sinradians * 0.143F), + M13 = lumR - (cosradians * lumR) - (sinradians * oneMinusLumR), + M21 = lumG - (cosradians * lumG) - (sinradians * lumG), + M22 = lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140F), + M23 = lumG - (cosradians * lumG) + (sinradians * lumG), + M31 = lumB - (cosradians * lumB) + (sinradians * oneMinusLumB), + M32 = lumB - (cosradians * lumB) - (sinradians * 0.283F), + M33 = lumB + (cosradians * oneMinusLumB) + (sinradians * lumB), M44 = 1 }; diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 29086b53f..b6b56adb3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Processing.Processors { + using System; using System.Numerics; using System.Threading.Tasks; @@ -56,10 +57,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -67,6 +67,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float rX = 0; @@ -83,6 +86,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelXWidth; fx++) { @@ -90,8 +94,7 @@ namespace ImageSharp.Processing.Processors int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); if (fy < kernelXHeight) { @@ -115,9 +118,8 @@ namespace ImageSharp.Processing.Processors float green = MathF.Sqrt((gX * gX) + (gY * gY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY)); - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index e39104793..efc00b08f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor firstPassPixels = new PixelAccessor(width, height)) + using (var firstPassPixels = new PixelAccessor(width, height)) using (PixelAccessor sourcePixels = source.Lock()) { this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds, this.KernelX); @@ -84,9 +84,11 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { - Vector4 destination = default(Vector4); + var destination = default(Vector4); // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelHeight; fy++) @@ -95,6 +97,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span row = sourcePixels.GetRowSpan(offsetY); for (int fx = 0; fx < kernelWidth; fx++) { @@ -103,14 +106,13 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = row[offsetX].ToVector4(); destination += kernel[fy, fx] * currentColor; } } - TPixel packed = default(TPixel); - packed.PackFromVector4(destination); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 17d5e3243..06607c87a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -46,10 +46,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -57,6 +56,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float red = 0; @@ -70,6 +72,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelLength; fx++) { @@ -78,7 +81,7 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; @@ -87,9 +90,8 @@ namespace ImageSharp.Processing.Processors } } - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); diff --git a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs index 5e7310e32..a3894f8d3 100644 --- a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs @@ -61,26 +61,22 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 alphaVector = new Vector4(1, 1, 1, this.Value); + var alphaVector = new Vector4(1, 1, 1, this.Value); - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel packed = default(TPixel); - packed.PackFromVector4(sourcePixels[offsetX, offsetY].ToVector4() * alphaVector); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + ref TPixel pixel = ref row[x - startX]; + pixel.PackFromVector4(pixel.ToVector4() * alphaVector); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index cc95f15fc..153719191 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Processing.Processors { using System; - using System.Numerics; using System.Threading.Tasks; using ImageSharp.Memory; @@ -64,9 +63,8 @@ namespace ImageSharp.Processing.Processors int width = maxX - minX; - using (Buffer colors = new Buffer(width)) - using (Buffer amount = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var colors = new Buffer(width)) + using (var amount = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -81,11 +79,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - int offsetY = y - startY; + Span destination = source.GetRowSpan(y - startY).Slice(minX - startX, width); - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); - - // this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one + // This switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one blender.Blend(destination, colors, destination, amount); }); } diff --git a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs index f9f1585ea..121d25d1e 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs @@ -63,31 +63,26 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); - // TODO: Check this with other formats. - Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); - Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); - vector = new Vector4(transformed, vector.W); + for (int x = minX; x < maxX; x++) + { + ref TPixel pixel = ref row[x - startX]; - TPixel packed = default(TPixel); - packed.PackFromVector4(vector.Compress()); + // TODO: Check this with other formats. + Vector4 vector = pixel.ToVector4().Expand(); + Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); + vector = new Vector4(transformed, vector.W); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + pixel.PackFromVector4(vector.Compress()); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs index 8308c57e2..1daead6e5 100644 --- a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs @@ -45,8 +45,8 @@ namespace ImageSharp.Processing.Processors int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); - Vector4 shiftVector = new Vector4(.5F, .5F, .5F, 1); + var contrastVector = new Vector4(contrast, contrast, contrast, 1); + var shiftVector = new Vector4(.5F, .5F, .5F, 1); // Align start/end positions. int minX = Math.Max(0, startX); @@ -65,29 +65,26 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; + ref TPixel pixel = ref row[x - startX]; - Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); - vector -= shiftVector; - vector *= contrastVector; - vector += shiftVector; - TPixel packed = default(TPixel); - packed.PackFromVector4(vector.Compress()); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + Vector4 vector = pixel.ToVector4().Expand(); + vector -= shiftVector; + vector *= contrastVector; + vector += shiftVector; + + pixel.PackFromVector4(vector.Compress()); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs index a0348970e..dc0b13442 100644 --- a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs @@ -44,27 +44,24 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z); + ref TPixel pixel = ref row[x - startX]; - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(vector, color.W)); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + var vector = pixel.ToVector4(); + Vector3 vector3 = inverseVector - new Vector3(vector.X, vector.Y, vector.Z); + + pixel.PackFromVector4(new Vector4(vector3, vector.W)); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index 1f06924af..a43f77a1c 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -69,10 +69,9 @@ namespace ImageSharp.Processing.Processors startX = 0; } - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( minY, @@ -80,6 +79,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { int maxIntensity = 0; @@ -107,6 +109,8 @@ namespace ImageSharp.Processing.Processors break; } + Span sourceOffsetRow = source.GetRowSpan(offsetY); + for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; @@ -121,13 +125,13 @@ namespace ImageSharp.Processing.Processors if (offsetX < maxX) { // ReSharper disable once AccessToDisposedClosure - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + var vector = sourceOffsetRow[offsetX].ToVector4(); - float sourceRed = color.X; - float sourceBlue = color.Z; - float sourceGreen = color.Y; + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; - int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); intensityBin[currentIntensity] += 1; blueBin[currentIntensity] += sourceBlue; @@ -146,9 +150,8 @@ namespace ImageSharp.Processing.Processors float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } } }); diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index 0287eaab8..ba6bb5c9d 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -66,47 +66,45 @@ namespace ImageSharp.Processing.Processors // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.ForEach( - range, - this.ParallelOptions, - y => + Parallel.ForEach( + range, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + int offsetPy = offset; + + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) { - int offsetY = y - startY; - int offsetPy = offset; + offsetPy--; + } - for (int x = minX; x < maxX; x += size) - { - int offsetX = x - startX; - int offsetPx = offset; + Span row = source.GetRowSpan(offsetY + offsetPy); - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) - { - offsetPy--; - } + for (int x = minX; x < maxX; x += size) + { + int offsetX = x - startX; + int offsetPx = offset; - while (x + offsetPx >= maxX) - { - offsetPx--; - } + while (x + offsetPx >= maxX) + { + offsetPx--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - TPixel pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + // Get the pixel color in the centre of the soon to be pixelated area. + TPixel pixel = row[offsetX + offsetPx]; - // For each pixel in the pixelate size, set it to the centre color. - for (int l = offsetY; l < offsetY + size && l < maxY; l++) + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) + { + for (int k = offsetX; k < offsetX + size && k < maxX; k++) { - for (int k = offsetX; k < offsetX + size && k < maxX; k++) - { - sourcePixels[k, l] = pixel; - } + source[k, l] = pixel; } } - }); - } + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 9de91c47b..23fea94e9 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -52,7 +52,7 @@ namespace ImageSharp.Processing.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel glowColor = this.GlowColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + var centre = Rectangle.Center(sourceRectangle).ToVector2(); float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; // Align start/end positions. @@ -73,8 +73,7 @@ namespace ImageSharp.Processing.Processors } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var rowColors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -82,26 +81,26 @@ namespace ImageSharp.Processing.Processors } Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - using (Buffer amounts = new Buffer(width)) - { - int offsetY = y - startY; - int offsetX = minX - startX; - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); - } + minY, + maxY, + this.ParallelOptions, + y => + { + using (var amounts = new Buffer(width)) + { + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + Span destination = source.GetRowSpan(offsetY).Slice(offsetX, width); - this.blender.Blend(destination, destination, rowColors, amounts); - } - }); + this.blender.Blend(destination, destination, rowColors, amounts); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index be431b07d..4dfa41989 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -58,7 +58,7 @@ namespace ImageSharp.Processing.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel vignetteColor = this.VignetteColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + var centre = Rectangle.Center(sourceRectangle).ToVector2(); float rX = this.RadiusX > 0 ? MathF.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; float rY = this.RadiusY > 0 ? MathF.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); @@ -81,8 +81,7 @@ namespace ImageSharp.Processing.Processors } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var rowColors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -95,7 +94,7 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - using (Buffer amounts = new Buffer(width)) + using (var amounts = new Buffer(width)) { int offsetY = y - startY; int offsetX = minX - startX; @@ -105,7 +104,7 @@ namespace ImageSharp.Processing.Processors amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + Span destination = source.GetRowSpan(offsetY).Slice(offsetX, width); this.blender.Blend(destination, destination, rowColors, amounts); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index b67ef5bf1..ade5fa830 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Processing.Processors { using System; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -44,22 +44,18 @@ namespace ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - using (PixelAccessor targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) + using (var targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - targetPixels[x - minX, y - minY] = sourcePixels[x, y]; - } - }); - } + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(minX, y); + Span targetRow = targetPixels.GetRowSpan(y - minY); + SpanHelper.Copy(sourceRow, targetRow, maxX - minX); + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index 2faf77905..d4303c455 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Threading.Tasks; + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -57,24 +58,23 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - halfHeight, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - int newY = height - y - 1; - targetPixels[x, y] = sourcePixels[x, newY]; - targetPixels[x, newY] = sourcePixels[x, y]; - } - }); - } + Parallel.For( + 0, + halfHeight, + this.ParallelOptions, + y => + { + int newY = height - y - 1; + Span sourceRow = source.GetRowSpan(y); + Span altSourceRow = source.GetRowSpan(newY); + Span targetRow = targetPixels.GetRowSpan(y); + Span altTargetRow = targetPixels.GetRowSpan(newY); + + sourceRow.CopyTo(altTargetRow); + altSourceRow.CopyTo(targetRow); + }); source.SwapPixelsBuffers(targetPixels); } @@ -91,24 +91,24 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < halfWidth; x++) { - for (int x = 0; x < halfWidth; x++) - { - int newX = width - x - 1; - targetPixels[x, y] = sourcePixels[newX, y]; - targetPixels[newX, y] = sourcePixels[x, y]; - } - }); - } + int newX = width - x - 1; + targetRow[x] = sourceRow[newX]; + targetRow[newX] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs index 3135551f8..4cc03d864 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Processing.Processors { - using System; using System.Numerics; using ImageSharp.PixelFormats; @@ -45,8 +44,8 @@ namespace ImageSharp.Processing.Processors /// protected Matrix3x2 GetCenteredMatrix(ImageBase source, Matrix3x2 matrix) { - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); + var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); return (translationToTargetCenter * matrix) * translateToSourceCenter; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index d49f37803..b5266c9bd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp.Processing.Processors { using System; diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index e2f77d812..757b0889a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -6,9 +6,7 @@ namespace ImageSharp.Processing.Processors { using System; - using System.Buffers; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using ImageSharp.PixelFormats; @@ -90,7 +88,7 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = MathF.Ceiling(scale * sampler.Radius); - WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); + var result = new WeightsBuffer(sourceSize, destinationSize); for (int i = 0; i < destinationSize; i++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 61a64f60f..68ff1397d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -73,26 +73,24 @@ namespace ImageSharp.Processing.Processors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)(((y - startY) * heightFactor) + sourceY); + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Y coordinates of source points + Span sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY)); + Span targetRow = targetPixels.GetRowSpan(y); - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; - } - }); - } + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; + } + }); // Break out now. source.SwapPixelsBuffers(targetPixels); @@ -106,10 +104,9 @@ namespace ImageSharp.Processing.Processors // are the upper and lower bounds of the source rectangle. // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - using (Buffer2D firstPassPixels = new Buffer2D(width, source.Height)) + using (var firstPassPixels = new Buffer2D(width, source.Height)) { firstPassPixels.Clear(); @@ -120,21 +117,18 @@ namespace ImageSharp.Processing.Processors y => { // TODO: Without Parallel.For() this buffer object could be reused: - using (Buffer tempRowBuffer = new Buffer(sourcePixels.Width)) + using (var tempRowBuffer = new Buffer(source.Width)) { - Span sourceRow = sourcePixels.GetRowSpan(y); - - PixelOperations.Instance.ToVector4( - sourceRow, - tempRowBuffer, - sourceRow.Length); + Span firstPassRow = firstPassPixels.GetRowSpan(y); + Span sourceRow = source.GetRowSpan(y); + PixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); + firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); } } else @@ -142,7 +136,7 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); + firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); } } } @@ -157,6 +151,7 @@ namespace ImageSharp.Processing.Processors { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + Span targetRow = targetPixels.GetRowSpan(y); if (this.Compand) { @@ -165,9 +160,9 @@ namespace ImageSharp.Processing.Processors // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); destination = destination.Compress(); - TPixel d = default(TPixel); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } } else @@ -177,9 +172,8 @@ namespace ImageSharp.Processing.Processors // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); - TPixel d = default(TPixel); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index fc5d29b06..43dbb53ea 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + var transformedPoint = Point.Rotate(new Point(x, y), matrix); + + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { - Point transformedPoint = Point.Rotate(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } + targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } - }); - } + } + }); source.SwapPixelsBuffers(targetPixels); } @@ -128,7 +128,7 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(height, width)) + using (var targetPixels = new PixelAccessor(height, width)) { using (PixelAccessor sourcePixels = source.Lock()) { @@ -161,24 +161,22 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(height - y - 1); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - int newX = width - x - 1; - int newY = height - y - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } + targetRow[width - x - 1] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } @@ -193,23 +191,21 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(height, width)) + using (var targetPixels = new PixelAccessor(height, width)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + int newX = height - y - 1; + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - targetPixels[newX, x] = sourcePixels[x, y]; - } - }); - } + targetPixels[newX, x] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 40ea6a94e..7807d0dfc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + var transformedPoint = Point.Skew(new Point(x, y), matrix); + + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { - Point transformedPoint = Point.Skew(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } + targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } - }); - } + } + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index 40bce74c3..e19df4cfa 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Quantizers /// /// Maximum allowed color depth /// - private int colors; + private byte colors; /// /// The reduced image palette @@ -58,7 +58,7 @@ namespace ImageSharp.Quantizers /// public override QuantizedImage Quantize(ImageBase image, int maxColors) { - this.colors = maxColors.Clamp(1, 255); + this.colors = (byte)maxColors.Clamp(1, 255); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); this.palette = null; @@ -66,7 +66,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. @@ -78,11 +78,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. @@ -121,7 +123,7 @@ namespace ImageSharp.Quantizers /// protected override TPixel[] GetPalette() { - return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1))); + return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1))); } /// @@ -141,6 +143,12 @@ namespace ImageSharp.Quantizers return this.GetClosestPixel(pixel, this.palette, this.colorMap); } + pixel.ToXyzwBytes(this.pixelBuffer, 0); + if (this.pixelBuffer[3] == 0) + { + return this.colors; + } + return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); } @@ -490,7 +498,7 @@ namespace ImageSharp.Quantizers byte b = (this.blue / this.pixelCount).ToByte(); // And set the color of the palette entry - TPixel pixel = default(TPixel); + var pixel = default(TPixel); pixel.PackFromBytes(r, g, b, 255); palette[index] = pixel; diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index 7e07da6c3..cf3ff94ee 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -51,7 +51,7 @@ namespace ImageSharp.Quantizers for (int i = 0; i < constants.Length; i++) { constants[i].ToXyzwBytes(this.pixelBuffer, 0); - TPixel packed = default(TPixel); + var packed = default(TPixel); packed.PackFromBytes(this.pixelBuffer[0], this.pixelBuffer[1], this.pixelBuffer[2], this.pixelBuffer[3]); safe[i] = packed; } @@ -72,7 +72,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. @@ -84,11 +84,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. diff --git a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs index 48f33f98b..2e3ea7a54 100644 --- a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Quantizers { + using System; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; @@ -54,35 +55,30 @@ namespace ImageSharp.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; - TPixel[] colorPalette; - using (PixelAccessor pixels = image.Lock()) + // Call the FirstPass function if not a single pass algorithm. + // For something like an Octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) { - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(pixels, width, height); - } + this.FirstPass(image, width, height); + } - // Collect the palette. Required before the second pass runs. - colorPalette = this.GetPalette(); + // Collect the palette. Required before the second pass runs. + TPixel[] colorPalette = this.GetPalette(); - if (this.Dither) - { - // We clone the image as we don't want to alter the original. - using (Image clone = new Image(image)) - using (PixelAccessor clonedPixels = clone.Lock()) - { - this.SecondPass(clonedPixels, quantizedPixels, width, height); - } - } - else + if (this.Dither) + { + // We clone the image as we don't want to alter the original. + using (var clone = new Image(image)) { - this.SecondPass(pixels, quantizedPixels, width, height); + this.SecondPass(clone, quantizedPixels, width, height); } } + else + { + this.SecondPass(image, quantizedPixels, width, height); + } return new QuantizedImage(width, height, colorPalette, quantizedPixels); } @@ -93,16 +89,18 @@ namespace ImageSharp.Quantizers /// The source data /// The width in pixels of the image. /// The height in pixels of the image. - protected virtual void FirstPass(PixelAccessor source, int width, int height) + protected virtual void FirstPass(ImageBase source, int width, int height) { // Loop through each row for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Now I have the pixel, call the FirstPassQuantize function... - this.InitialQuantizePixel(source[x, y]); + this.InitialQuantizePixel(row[x]); } } } @@ -114,7 +112,7 @@ namespace ImageSharp.Quantizers /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image - protected abstract void SecondPass(PixelAccessor source, byte[] output, int width, int height); + protected abstract void SecondPass(ImageBase source, byte[] output, int width, int height); /// /// Override this to process the pixel in the first pass of the algorithm @@ -155,7 +153,7 @@ namespace ImageSharp.Quantizers // Not found - loop through the palette and find the nearest match. byte colorIndex = 0; float leastDistance = int.MaxValue; - Vector4 vector = pixel.ToVector4(); + var vector = pixel.ToVector4(); for (int index = 0; index < colorPalette.Length; index++) { diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index fb63c9dcd..aecca771c 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -183,7 +183,7 @@ namespace ImageSharp.Quantizers float b = Volume(this.colorCube[k], this.vmb) / weight; float a = Volume(this.colorCube[k], this.vma) / weight; - TPixel color = default(TPixel); + var color = default(TPixel); color.PackFromVector4(new Vector4(r, g, b, a) / 255F); this.palette[k] = color; } @@ -221,7 +221,7 @@ namespace ImageSharp.Quantizers } /// - protected override void FirstPass(PixelAccessor source, int width, int height) + protected override void FirstPass(ImageBase source, int width, int height) { // Build up the 3-D color histogram // Loop through each row @@ -240,7 +240,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. @@ -252,11 +252,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs new file mode 100644 index 000000000..b29fdc25b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -0,0 +1,34 @@ +namespace ImageSharp.Benchmarks.Color +{ + using BenchmarkDotNet.Attributes; + + using Colourful; + using Colourful.Conversion; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + public class ColorspaceCieXyzToCieLabConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + + + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToLab(XYZColor).L; + } + + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToCieLab(CieXyz).L; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs new file mode 100644 index 000000000..3e7d60972 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -0,0 +1,34 @@ +namespace ImageSharp.Benchmarks.Color +{ + using BenchmarkDotNet.Attributes; + + using Colourful; + using Colourful.Conversion; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + public class ColorspaceCieXyzToHunterLabConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + + + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToHunterLab(XYZColor).L; + } + + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToHunterLab(CieXyz).L; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs new file mode 100644 index 000000000..f472dd292 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -0,0 +1,34 @@ +namespace ImageSharp.Benchmarks.Color +{ + using BenchmarkDotNet.Attributes; + + using Colourful; + using Colourful.Conversion; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + public class ColorspaceCieXyzToLmsConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + + + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToLMS(XYZColor).L; + } + + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToLms(CieXyz).L; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs new file mode 100644 index 000000000..eeea86c6e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -0,0 +1,34 @@ +namespace ImageSharp.Benchmarks.Color +{ + using BenchmarkDotNet.Attributes; + + using Colourful; + using Colourful.Conversion; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + public class ColorspaceCieXyzToRgbConvert + { + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); + + + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.ToRGB(XYZColor).R; + } + + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToRgb(CieXyz).R; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs new file mode 100644 index 000000000..21d040e5c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -0,0 +1,34 @@ +namespace ImageSharp.Benchmarks.Color +{ + using BenchmarkDotNet.Attributes; + + using Colourful; + using Colourful.Conversion; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + public class RgbWorkingSpaceAdapt + { + private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb); + + private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717, RGBWorkingSpaces.WideGamutRGB); + + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter { TargetRGBWorkingSpace = RGBWorkingSpaces.sRGB }; + + + [Benchmark(Baseline = true, Description = "Colourful Adapt")] + public RGBColor ColourfulConvert() + { + return ColourfulConverter.Adapt(RGBColor); + } + + [Benchmark(Description = "ImageSharp Adapt")] + internal Rgb ColorSpaceConvert() + { + return ColorSpaceConverter.Adapt(Rgb); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs index 5b5d14750..1d4ed1193 100644 --- a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs +++ b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs @@ -5,19 +5,20 @@ namespace ImageSharp.Benchmarks.Image { + using System; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; - using ImageSharp.PixelFormats; + using ImageSharp.Memory; public class CopyPixels : BenchmarkBase { - [Benchmark(Description = "Copy by Pixel")] - public Rgba32 CopyByPixel() + [Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")] + public Rgba32 CopyByPixelAccesor() { - using (Image source = new Image(1024, 768)) - using (Image target = new Image(1024, 768)) + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) { using (PixelAccessor sourcePixels = source.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -38,5 +39,81 @@ namespace ImageSharp.Benchmarks.Image } } } + + [Benchmark(Description = "PixelAccessor Copy by Span")] + public Rgba32 CopyByPixelAccesorSpan() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + Span sourceRow = sourcePixels.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < source.Width; x++) + { + targetRow[x] = sourceRow[x]; + } + }); + + return targetPixels[0, 0]; + } + } + } + + [Benchmark(Description = "Copy by indexer")] + public Rgba32 Copy() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + for (int x = 0; x < source.Width; x++) + { + target[x, y] = source[x, y]; + } + }); + + return target[0, 0]; + } + } + + [Benchmark(Description = "Copy by Span")] + public Rgba32 CopySpan() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = target.GetRowSpan(y); + + for (int x = 0; x < source.Width; x++) + { + targetRow[x] = sourceRow[x]; + } + }); + + return target[0, 0]; + } + } } } diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index b2070c0de..763ede521 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -8,6 +8,7 @@ + diff --git a/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs b/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs deleted file mode 100644 index 4b45d0ab4..000000000 --- a/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs +++ /dev/null @@ -1,494 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System; - using System.Diagnostics.CodeAnalysis; - using ImageSharp.Colors.Spaces; - using ImageSharp.PixelFormats; - using Xunit; - - /// - /// Test conversion between the various color structs. - /// - /// - /// Output values have been compared with - /// and for accuracy. - /// - public class ColorConversionTests - { - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToYCbCr() - { - // White - Rgba32 color = Rgba32.White; - YCbCr yCbCr = color; - - Assert.Equal(255, yCbCr.Y); - Assert.Equal(128, yCbCr.Cb); - Assert.Equal(128, yCbCr.Cr); - - // Black - Rgba32 color2 = Rgba32.Black; - YCbCr yCbCr2 = color2; - Assert.Equal(0, yCbCr2.Y); - Assert.Equal(128, yCbCr2.Cb); - Assert.Equal(128, yCbCr2.Cr); - - // Gray - Rgba32 color3 = Rgba32.Gray; - YCbCr yCbCr3 = color3; - Assert.Equal(128, yCbCr3.Y); - Assert.Equal(128, yCbCr3.Cb); - Assert.Equal(128, yCbCr3.Cr); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void YCbCrToColor() - { - // White - YCbCr yCbCr = new YCbCr(255, 128, 128); - Rgba32 color = yCbCr; - - Assert.Equal(255, color.R); - Assert.Equal(255, color.G); - Assert.Equal(255, color.B); - Assert.Equal(255, color.A); - - // Black - YCbCr yCbCr2 = new YCbCr(0, 128, 128); - Rgba32 color2 = yCbCr2; - - Assert.Equal(0, color2.R); - Assert.Equal(0, color2.G); - Assert.Equal(0, color2.B); - Assert.Equal(255, color2.A); - - // Gray - YCbCr yCbCr3 = new YCbCr(128, 128, 128); - Rgba32 color3 = yCbCr3; - - Assert.Equal(128, color3.R); - Assert.Equal(128, color3.G); - Assert.Equal(128, color3.B); - Assert.Equal(255, color3.A); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void ColorToCieXyz() - { - // White - Rgba32 color = Rgba32.White; - CieXyz ciexyz = color; - - Assert.Equal(95.05f, ciexyz.X, 3); - Assert.Equal(100.0f, ciexyz.Y, 3); - Assert.Equal(108.900f, ciexyz.Z, 3); - - // Black - Rgba32 color2 = Rgba32.Black; - CieXyz ciexyz2 = color2; - Assert.Equal(0, ciexyz2.X, 3); - Assert.Equal(0, ciexyz2.Y, 3); - Assert.Equal(0, ciexyz2.Z, 3); - - // Gray - Rgba32 color3 = Rgba32.Gray; - CieXyz ciexyz3 = color3; - Assert.Equal(20.518, ciexyz3.X, 3); - Assert.Equal(21.586, ciexyz3.Y, 3); - Assert.Equal(23.507, ciexyz3.Z, 3); - - // Cyan - Rgba32 color4 = Rgba32.Cyan; - CieXyz ciexyz4 = color4; - Assert.Equal(53.810f, ciexyz4.X, 3); - Assert.Equal(78.740f, ciexyz4.Y, 3); - Assert.Equal(106.970f, ciexyz4.Z, 3); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void CieXyzToColor() - { - // Dark moderate pink. - CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f); - Rgba32 color = ciexyz; - - Assert.Equal(128, color.R); - Assert.Equal(64, color.G); - Assert.Equal(106, color.B); - - // Ochre - CieXyz ciexyz2 = new CieXyz(31.787f, 26.147f, 4.885f); - Rgba32 color2 = ciexyz2; - - Assert.Equal(204, color2.R); - Assert.Equal(119, color2.G); - Assert.Equal(34, color2.B); - - // Black - CieXyz ciexyz3 = new CieXyz(0, 0, 0); - Rgba32 color3 = ciexyz3; - - Assert.Equal(0, color3.R); - Assert.Equal(0, color3.G); - Assert.Equal(0, color3.B); - - //// Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieXyz ciexyz4 = color4; - // Assert.Equal(color4, (Color)ciexyz4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsv() - { - // Black - Rgba32 b = Rgba32.Black; - Hsv h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.V, 1); - - // White - Rgba32 color = Rgba32.White; - Hsv hsv = color; - - Assert.Equal(0f, hsv.H, 1); - Assert.Equal(0f, hsv.S, 1); - Assert.Equal(1f, hsv.V, 1); - - // Dark moderate pink. - Rgba32 color2 = new Rgba32(128, 64, 106); - Hsv hsv2 = color2; - - Assert.Equal(320.6f, hsv2.H, 1); - Assert.Equal(0.5f, hsv2.S, 1); - Assert.Equal(0.502f, hsv2.V, 2); - - // Ochre. - Rgba32 color3 = new Rgba32(204, 119, 34); - Hsv hsv3 = color3; - - Assert.Equal(30f, hsv3.H, 1); - Assert.Equal(0.833f, hsv3.S, 3); - Assert.Equal(0.8f, hsv3.V, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HsvToColor() - { - // Dark moderate pink. - Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f); - Rgba32 color = hsv; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - Hsv hsv2 = new Hsv(30, 0.833f, 0.8f); - Rgba32 color2 = hsv2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // White - Hsv hsv3 = new Hsv(0, 0, 1); - Rgba32 color3 = hsv3; - - Assert.Equal(color3.B, 255); - Assert.Equal(color3.G, 255); - Assert.Equal(color3.R, 255); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsv hsv4 = color4; - // Assert.Equal(color4, (Color)hsv4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsl() - { - // Black - Rgba32 b = Rgba32.Black; - Hsl h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.L, 1); - - // White - Rgba32 color = Rgba32.White; - Hsl hsl = color; - - Assert.Equal(0f, hsl.H, 1); - Assert.Equal(0f, hsl.S, 1); - Assert.Equal(1f, hsl.L, 1); - - // Dark moderate pink. - Rgba32 color2 = new Rgba32(128, 64, 106); - Hsl hsl2 = color2; - - Assert.Equal(320.6f, hsl2.H, 1); - Assert.Equal(0.33f, hsl2.S, 1); - Assert.Equal(0.376f, hsl2.L, 2); - - // Ochre. - Rgba32 color3 = new Rgba32(204, 119, 34); - Hsl hsl3 = color3; - - Assert.Equal(30f, hsl3.H, 1); - Assert.Equal(0.714f, hsl3.S, 3); - Assert.Equal(0.467f, hsl3.L, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HslToColor() - { - // Dark moderate pink. - Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f); - Rgba32 color = hsl; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - Hsl hsl2 = new Hsl(30, 0.714f, 0.467f); - Rgba32 color2 = hsl2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // White - Hsl hsl3 = new Hsl(0, 0, 1); - Rgba32 color3 = hsl3; - - Assert.Equal(color3.R, 255); - Assert.Equal(color3.G, 255); - Assert.Equal(color3.B, 255); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsl hsl4 = color4; - // Assert.Equal(color4, (Color)hsl4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToCmyk() - { - // White - Rgba32 color = Rgba32.White; - Cmyk cmyk = color; - - Assert.Equal(0, cmyk.C, 1); - Assert.Equal(0, cmyk.M, 1); - Assert.Equal(0, cmyk.Y, 1); - Assert.Equal(0, cmyk.K, 1); - - // Black - Rgba32 color2 = Rgba32.Black; - Cmyk cmyk2 = color2; - Assert.Equal(0, cmyk2.C, 1); - Assert.Equal(0, cmyk2.M, 1); - Assert.Equal(0, cmyk2.Y, 1); - Assert.Equal(1, cmyk2.K, 1); - - // Gray - Rgba32 color3 = Rgba32.Gray; - Cmyk cmyk3 = color3; - Assert.Equal(0f, cmyk3.C, 1); - Assert.Equal(0f, cmyk3.M, 1); - Assert.Equal(0f, cmyk3.Y, 1); - Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters. - - // Cyan - Rgba32 color4 = Rgba32.Cyan; - Cmyk cmyk4 = color4; - Assert.Equal(1, cmyk4.C, 1); - Assert.Equal(0f, cmyk4.M, 1); - Assert.Equal(0f, cmyk4.Y, 1); - Assert.Equal(0f, cmyk4.K, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void CmykToColor() - { - // Dark moderate pink. - Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f); - Rgba32 color = cmyk; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f); - Rgba32 color2 = cmyk2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // White - Cmyk cmyk3 = new Cmyk(0, 0, 0, 0); - Rgba32 color3 = cmyk3; - - Assert.Equal(color3.R, 255); - Assert.Equal(color3.G, 255); - Assert.Equal(color3.B, 255); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Cmyk cmyk4 = color4; - // Assert.Equal(color4, (Color)cmyk4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - /// - [Fact] - public void ColorToCieLab() - { - // White - Rgba32 color = Rgba32.White; - CieLab cielab = color; - - Assert.Equal(100, cielab.L, 3); - Assert.Equal(0.005, cielab.A, 3); - Assert.Equal(-0.010, cielab.B, 3); - - // Black - Rgba32 color2 = Rgba32.Black; - CieLab cielab2 = color2; - Assert.Equal(0, cielab2.L, 3); - Assert.Equal(0, cielab2.A, 3); - Assert.Equal(0, cielab2.B, 3); - - // Gray - Rgba32 color3 = Rgba32.Gray; - CieLab cielab3 = color3; - Assert.Equal(53.585, cielab3.L, 3); - Assert.Equal(0.003, cielab3.A, 3); - Assert.Equal(-0.006, cielab3.B, 3); - - // Cyan - Rgba32 color4 = Rgba32.Cyan; - CieLab cielab4 = color4; - Assert.Equal(91.117, cielab4.L, 3); - Assert.Equal(-48.080, cielab4.A, 3); - Assert.Equal(-14.138, cielab4.B, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - [Fact] - public void CieLabToColor() - { - // Dark moderate pink. - CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f); - Rgba32 color = cielab; - - Assert.Equal(color.R, 128); - Assert.Equal(color.G, 64); - Assert.Equal(color.B, 106); - - // Ochre - CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f); - Rgba32 color2 = cielab2; - - Assert.Equal(color2.R, 204); - Assert.Equal(color2.G, 119); - Assert.Equal(color2.B, 34); - - // Black - CieLab cielab3 = new CieLab(0, 0, 0); - Rgba32 color3 = cielab3; - - Assert.Equal(color3.R, 0); - Assert.Equal(color3.G, 0); - Assert.Equal(color3.B, 0); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieLab cielab4 = color4; - // Assert.Equal(color4, (Color)cielab4); - //} - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs index f2113852f..7e5af9297 100644 --- a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs @@ -5,16 +5,14 @@ namespace ImageSharp.Tests { - using System; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; - using ImageSharp.Colors.Spaces; using ImageSharp.PixelFormats; using Xunit; + public class ColorDefinitionTests { public static IEnumerable ColorNames => typeof(NamedColors).GetTypeInfo().GetFields().Select(x => new[] { x.Name }); @@ -37,4 +35,4 @@ namespace ImageSharp.Tests Assert.Equal(expected, actual); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs index efec4ea38..2967e7c86 100644 --- a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Tests.Colors { using System; using System.Numerics; - using ImageSharp.Colors.Spaces; + using ImageSharp.PixelFormats; using Xunit; @@ -41,38 +41,6 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.One * 0x7FFF), typeof(Short4) }, }; - public static readonly TheoryData EqualityDataColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), new Bgra32(0, 0, 0), typeof(Bgra32) }, - { new Bgra32(0, 0, 0, 0), new Bgra32(0, 0, 0, 0), typeof(Bgra32) }, - { new Bgra32(100, 100, 0, 0), new Bgra32(100, 100, 0, 0), typeof(Bgra32) }, - { new Bgra32(255, 255, 255), new Bgra32(255, 255, 255), typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab) }, - { new CieLab(1f, 1f, 1f), new CieLab(1f, 1f, 1f), typeof(CieLab) }, - { new CieLab(0f, -100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, - { new CieLab(0f, 100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 100f), new CieLab(0f, -100f, 100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 50f), new CieLab(0f, -100f, 50f), typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380f), typeof(CieXyz) }, - { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 780f, 780f), typeof(CieXyz) }, - { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 780f, 780f), typeof(CieXyz) }, - { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 20f, 60f), typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk) }, - { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 1f, 1f), typeof(Cmyk) }, - { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 10f, 10f), typeof(Cmyk) }, - { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, .1f, .2f), typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl) }, - { new Hsl(360f, 1f, 1f), new Hsl(360f, 1f, 1f), typeof(Hsl) }, - { new Hsl(100f, .5f, .1f), new Hsl(100f, .5f, .1f), typeof(Hsl) }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv) }, - { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv) }, - { new Hsv(100f, .5f, .1f), new Hsv(100f, .5f, .1f), typeof(Hsv) }, - { new YCbCr(0, 0, 0), new YCbCr(0, 0, 0), typeof(YCbCr) }, - { new YCbCr(255, 255, 255), new YCbCr(255, 255, 255), typeof(YCbCr) }, - { new YCbCr(100, 100, 0), new YCbCr(100, 100, 0), typeof(YCbCr) }, - }; - public static readonly TheoryData NotEqualityDataNulls = new TheoryData() { @@ -97,18 +65,6 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), null, typeof(Short4) }, }; - public static readonly TheoryData NotEqualityDataNullsColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), null, typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), null, typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), null, typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), null, typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), null, typeof(Hsl) }, - { new Hsv(360f, 1f, 1f), null, typeof(Hsv) }, - { new YCbCr(0, 0, 0), null, typeof(YCbCr) }, - }; - public static readonly TheoryData NotEqualityDataDifferentObjects = new TheoryData() { @@ -118,16 +74,6 @@ namespace ImageSharp.Tests.Colors { new Rgba1010102(Vector4.One), new Bgra5551(Vector4.Zero), null }, }; - public static readonly TheoryData NotEqualityDataDifferentObjectsColorSpaces = - new TheoryData() - { - // Valid objects of different types but not equal - { new Bgra32(0, 0, 0), new CieLab(0f, 0f, 0f), null }, - { new CieXyz(380f, 380f, 380f), new Cmyk(0f, 0f, 0f, 0f), null }, - { new Hsl(0f, 0f, 0f), new Hsv(360f, 1f, 1f), null }, - { new YCbCr(0, 0, 0), new Hsv(360f, 1f, 1f), null }, - }; - public static readonly TheoryData NotEqualityData = new TheoryData() { @@ -153,92 +99,8 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.Zero), typeof(Short4) }, }; - public static readonly TheoryData NotEqualityDataColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), new Bgra32(0, 1, 0), typeof(Bgra32) }, - { new Bgra32(0, 0, 0, 0), new Bgra32(0, 1, 0, 0), typeof(Bgra32) }, - { new Bgra32(100, 100, 0, 0), new Bgra32(100, 0, 0, 0), typeof(Bgra32) }, - { new Bgra32(255, 255, 255), new Bgra32(255, 0, 255), typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 1f, 0f), typeof(CieLab) }, - { new CieLab(1f, 1f, 1f), new CieLab(1f, 0f, 1f), typeof(CieLab) }, - { new CieLab(0f, -100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, - { new CieLab(0f, 100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 100f), new CieLab(0f, 100f, 100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 50f), new CieLab(0f, 100f, 20f), typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 0f, 380f), typeof(CieXyz) }, - { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 0f, 780f), typeof(CieXyz) }, - { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 0f, 780f), typeof(CieXyz) }, - { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 0f, 60f), typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 1f, 0f, 0f), typeof(Cmyk) }, - { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 0f, 1f), typeof(Cmyk) }, - { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 0f, 10f), typeof(Cmyk) }, - { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, 5f, .2f), typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 5f, 0f), typeof(Hsl) }, - { new Hsl(360f, 1f, 1f), new Hsl(360f, .5f, 1f), typeof(Hsl) }, - { new Hsl(100f, .5f, .1f), new Hsl(100f, 9f, .1f), typeof(Hsl) }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 1f, 0f), typeof(Hsv) }, - { new Hsv(360f, 1f, 1f), new Hsv(0f, 1f, 1f), typeof(Hsv) }, - { new Hsv(100f, .5f, .1f), new Hsv(2f, .5f, .1f), typeof(Hsv) }, - { new YCbCr(0, 0, 0), new YCbCr(0, 1, 0), typeof(YCbCr) }, - { new YCbCr(255, 255, 255), new YCbCr(255, 0, 255), typeof(YCbCr) }, - { new YCbCr(100, 100, 0), new YCbCr(100, 20, 0), typeof(YCbCr) }, - }; - - public static readonly TheoryData AlmostEqualsData = - new TheoryData() - { - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), 0f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .0001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .0005f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, .001f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, .0001f), typeof(CieLab), .0001f }, - { new CieLab(0f, 0f, 0f), new CieLab(.0005f, 0f, 0f), typeof(CieLab), .0005f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380f), typeof(CieXyz), 0f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380.001f, 380f, 380f), typeof(CieXyz), .01f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380.001f, 380f), typeof(CieXyz), .01f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380.001f), typeof(CieXyz), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk), 0f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.001f, 0f, 0f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.001f, 0f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.001f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.001f), typeof(Cmyk), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl), 0f }, - { new Hsl(0f, 0f, 0f), new Hsl(0.001f, 0f, 0f), typeof(Hsl), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.001f, 0f), typeof(Hsl), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.001f), typeof(Hsl), .01f }, - { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv), 0f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv), 0f }, - { new Hsv(0f, 0f, 0f), new Hsv(0.001f, 0f, 0f), typeof(Hsv), .01f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.001f, 0f), typeof(Hsv), .01f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.001f), typeof(Hsv), .01f }, - }; - - public static readonly TheoryData AlmostNotEqualsData = - new TheoryData() - { - { new CieLab(0f, 0f, 0f), new CieLab(0.1f, 0f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0.1f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0.1f), typeof(CieLab), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380.1f, 380f, 380f), typeof(CieXyz), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380.1f, 380f), typeof(CieXyz), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380.1f), typeof(CieXyz), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.1f, 0f, 0f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.1f, 0f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.1f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.1f), typeof(Cmyk), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0.1f, 0f, 0f), typeof(Hsl), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.1f, 0f), typeof(Hsl), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.1f), typeof(Hsl), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0.1f, 0f, 0f), typeof(Hsv), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.1f, 0f), typeof(Hsv), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.1f), typeof(Hsv), .001f }, - }; - [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void Equality(object first, object second, Type type) { // Act @@ -250,11 +112,8 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataNulls))] - [MemberData(nameof(NotEqualityDataNullsColorSpaces))] [MemberData(nameof(NotEqualityDataDifferentObjects))] - [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEquality(object first, object second, Type type) { // Act @@ -266,7 +125,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void HashCodeEqual(object first, object second, Type type) { // Act @@ -278,7 +136,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataDifferentObjects))] - [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] public void HashCodeNotEqual(object first, object second, Type type) { // Act @@ -290,7 +147,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityObject(object first, object second, Type type) { // Arrange @@ -309,7 +165,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityObject(object first, object second, Type type) { // Arrange @@ -328,7 +183,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityOperator(object first, object second, Type type) { // Arrange @@ -347,7 +201,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityOperator(object first, object second, Type type) { // Arrange @@ -363,41 +216,5 @@ namespace ImageSharp.Tests.Colors // Assert Assert.True(notEqual); } - - [Theory] - [MemberData(nameof(AlmostEqualsData))] - public void AlmostEquals(object first, object second, Type type, float precision) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // Assert - Assert.True(almostEqual); - } - - [Theory] - [MemberData(nameof(AlmostNotEqualsData))] - public void AlmostNotEquals(object first, object second, Type type, float precision) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // Assert - Assert.False(almostEqual); - } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs new file mode 100644 index 000000000..f2d8c92d2 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs @@ -0,0 +1,76 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLabAndCieLchConversionTests + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, float b) + { + // Arrange + CieLch input = new CieLch(l, c, h); + + // Act + CieLab output = Converter.ToCieLab(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(a, output.A, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + public void Convert_Lab_to_LCHab(float l, float a, float b, float l2, float c, float h) + { + // Arrange + CieLab input = new CieLab(l, a, b); + + // Act + CieLch output = Converter.ToCieLch(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(c, output.C, FloatRoundingComparer); + Assert.Equal(h, output.H, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs new file mode 100644 index 000000000..d263bbe18 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs @@ -0,0 +1,77 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLuvAndCieLchuvuvConversionTests + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_Lchuv_to_Luv(float l, float c, float h, float l2, float u, float v) + { + // Arrange + CieLchuv input = new CieLchuv(l, c, h); + + // Act + CieLuv output = Converter.ToCieLuv(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(u, output.U, FloatRoundingComparer); + Assert.Equal(v, output.V, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + [InlineData(37.3511, 24.1720, 16.0684, 37.3511, 29.0255, 33.6141)] + public void Convert_Luv_to_LCHuv(float l, float u, float v, float l2, float c, float h) + { + // Arrange + CieLuv input = new CieLuv(l, u, v); + + // Act + CieLchuv output = Converter.ToCieLchuv(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(c, output.C, FloatRoundingComparer); + Assert.Equal(h, output.H, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs new file mode 100644 index 000000000..9bd8b17c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs @@ -0,0 +1,72 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieLabConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 431.0345, 0, 0.95047, 0, 0)] + [InlineData(100, -431.0345, 172.4138, 0, 1, 0)] + [InlineData(0, 0, -172.4138, 0, 0, 1.08883)] + [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)] + [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)] + [InlineData(10, -400, 20, 0, 0.011260, 0)] + public void Convert_Lab_to_Xyz(float l, float a, float b, float x, float y, float z) + { + // Arrange + CieLab input = new CieLab(l, a, b, Illuminants.D65); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0, 431.0345, 0)] + [InlineData(0, 1, 0, 100, -431.0345, 172.4138)] + [InlineData(0, 0, 1.08883, 0, 0, -172.4138)] + [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)] + public void Convert_Xyz_to_Lab(float x, float y, float z, float l, float a, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieLab output = converter.ToCieLab(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(a, output.A, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs new file mode 100644 index 000000000..5589e9753 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs @@ -0,0 +1,71 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieLuvConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 100, 50, 0, 0, 0)] + [InlineData(0.1, 100, 50, 0.000493, 0.000111, 0)] + [InlineData(70.0000, 86.3525, 2.8240, 0.569310, 0.407494, 0.365843)] + [InlineData(10.0000, -1.2345, -10.0000, 0.012191, 0.011260, 0.025939)] + [InlineData(100, 0, 0, 0.950470, 1.000000, 1.088830)] + [InlineData(1, 1, 1, 0.001255, 0.001107, 0.000137)] + public void Convert_Luv_to_Xyz(float l, float u, float v, float x, float y, float z) + { + // Arrange + CieLuv input = new CieLuv(l, u, v, Illuminants.D65); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.000493, 0.000111, 0, 0.1003, 0.9332, -0.0070)] + [InlineData(0.569310, 0.407494, 0.365843, 70.0000, 86.3524, 2.8240)] + [InlineData(0.012191, 0.011260, 0.025939, 9.9998, -1.2343, -9.9999)] + [InlineData(0.950470, 1.000000, 1.088830, 100, 0, 0)] + [InlineData(0.001255, 0.001107, 0.000137, 0.9999, 0.9998, 1.0004)] + public void Convert_Xyz_to_Luv(float x, float y, float z, float l, float u, float v) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieLuv output = converter.ToCieLuv(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(u, output.U, FloatRoundingComparer); + Assert.Equal(v, output.V, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs new file mode 100644 index 000000000..9b441f452 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs @@ -0,0 +1,61 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieXyyConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)] + public void Convert_xyY_to_XYZ(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + // Arrange + CieXyy input = new CieXyy(x, y, yl); + + // Act + CieXyz output = Converter.ToCieXyz(input); + + // Assert + Assert.Equal(xyzX, output.X, FloatRoundingComparer); + Assert.Equal(xyzY, output.Y, FloatRoundingComparer); + Assert.Equal(xyzZ, output.Z, FloatRoundingComparer); + } + + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)] + public void Convert_XYZ_to_xyY(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + // Arrange + CieXyz input = new CieXyz(xyzX, xyzY, xyzZ); + + // Act + CieXyy output = Converter.ToCieXyy(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(yl, output.Yl, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs new file mode 100644 index 000000000..5ad224eaf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs @@ -0,0 +1,83 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndHunterLabConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.98074, 1, 1.18232)] // C white point is HunterLab 100, 0, 0 + public void Convert_HunterLab_to_Xyz(float l, float a, float b, float x, float y, float z) + { + // Arrange + HunterLab input = new HunterLab(l, a, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.C }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] // D65 white point is HunerLab 100, 0, 0 (adaptation to C performed) + public void Convert_HunterLab_to_Xyz_D65(float l, float a, float b, float x, float y, float z) + { + // Arrange + HunterLab input = new HunterLab(l, a, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] // D65 white point is HunterLab 100, 0, 0 (adaptation to C performed) + public void Convert_Xyz_D65_to_HunterLab(float x, float y, float z, float l, float a, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; + + // Act + HunterLab output = converter.ToHunterLab(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(a, output.A, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs new file mode 100644 index 000000000..868d42975 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs @@ -0,0 +1,69 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using original colorful library. + /// + public class CieXyzAndLmsConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(5); + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)] + [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)] + [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)] + [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)] + public void Convert_Lms_to_CieXyz(float l, float m, float s, float x, float y, float z) + { + // Arrange + Lms input = new Lms(l, m, s); + ColorSpaceConverter converter = new ColorSpaceConverter(); + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)] + [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)] + [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)] + [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)] + public void Convert_CieXyz_to_Lms(float x, float y, float z, float l, float m, float s) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter(); + + // Act + Lms output = converter.ToLms(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(m, output.M, FloatRoundingComparer); + Assert.Equal(s, output.S, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs new file mode 100644 index 000000000..02095b31f --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs @@ -0,0 +1,150 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + using ImageSharp.ColorSpaces.Conversion.Implementation.Lms; + + using Xunit; + + /// + /// Tests methods. + /// Test data generated using: + /// + /// + /// + public class ColorConverterAdaptTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.206162, 0.260277, 0.746717, 0.220000, 0.130000, 0.780000)] + public void Adapt_RGB_WideGamutRGB_To_sRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + Rgb input = new Rgb(r1, g1, b1, RgbWorkingSpaces.WideGamutRgb); + Rgb expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.SRgb); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + // Action + Rgb output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); + Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); + Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.220000, 0.130000, 0.780000, 0.206162, 0.260277, 0.746717)] + public void Adapt_RGB_SRGB_To_WideGamutRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + Rgb input = new Rgb(r1, g1, b1, RgbWorkingSpaces.SRgb); + Rgb expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.WideGamutRgb); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.WideGamutRgb }; + + // Action + Rgb output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); + Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); + Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] + public void Adapt_Lab_D50_To_D65(float l1, float a1, float b1, float l2, float a2, float b2) + { + // Arrange + CieLab input = new CieLab(l1, a1, b1, Illuminants.D65); + CieLab expectedOutput = new CieLab(l2, a2, b2); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetLabWhitePoint = Illuminants.D50 }; + + // Action + CieLab output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.L, output.L, FloatRoundingComparer); + Assert.Equal(expectedOutput.A, output.A, FloatRoundingComparer); + Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] + public void Adapt_Xyz_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); + Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); + Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_CieXyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); + Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); + Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_Xyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); + Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); + Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs b/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs new file mode 100644 index 000000000..2a5462b40 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs @@ -0,0 +1,310 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Colorspaces +{ + using System; + using System.Numerics; + using Xunit; + + using ImageSharp.ColorSpaces; + + /// + /// Test implementations of IEquatable and IAlmostEquatable in our colorspaces + /// + public class ColorSpaceEqualityTests + { + public static readonly TheoryData EmptyData = + new TheoryData + { + CieLab.Empty, + CieLch.Empty, + CieLchuv.Empty, + CieLuv.Empty, + CieXyz.Empty, + CieXyy.Empty, + Hsl.Empty, + Hsl.Empty, + HunterLab.Empty, + Lms.Empty, + LinearRgb.Empty, + Rgb.Empty, + YCbCr.Empty + }; + + public static readonly TheoryData EqualityData = + new TheoryData + { + { new CieLab(Vector3.One), new CieLab(Vector3.One), typeof(CieLab) }, + { new CieLch(Vector3.One), new CieLch(Vector3.One), typeof(CieLch) }, + { new CieLchuv(Vector3.One), new CieLchuv(Vector3.One), typeof(CieLchuv) }, + { new CieLuv(Vector3.One), new CieLuv(Vector3.One), typeof(CieLuv) }, + { new CieXyz(Vector3.One), new CieXyz(Vector3.One), typeof(CieXyz) }, + { new CieXyy(Vector3.One), new CieXyy(Vector3.One), typeof(CieXyy) }, + { new HunterLab(Vector3.One), new HunterLab(Vector3.One), typeof(HunterLab) }, + { new Lms(Vector3.One), new Lms(Vector3.One), typeof(Lms) }, + { new LinearRgb(Vector3.One), new LinearRgb(Vector3.One), typeof(LinearRgb) }, + { new Rgb(Vector3.One), new Rgb(Vector3.One), typeof(Rgb) }, + { new Hsl(Vector3.One), new Hsl(Vector3.One), typeof(Hsl) }, + { new Hsv(Vector3.One), new Hsv(Vector3.One), typeof(Hsv) }, + { new YCbCr(Vector3.One), new YCbCr(Vector3.One), typeof(YCbCr) }, + }; + + public static readonly TheoryData NotEqualityDataNulls = + new TheoryData + { + // Valid object against null + { new CieLab(Vector3.One), null, typeof(CieLab) }, + { new CieLch(Vector3.One), null, typeof(CieLch) }, + { new CieLchuv(Vector3.One), null, typeof(CieLchuv) }, + { new CieLuv(Vector3.One), null, typeof(CieLuv) }, + { new CieXyz(Vector3.One), null, typeof(CieXyz) }, + { new CieXyy(Vector3.One), null, typeof(CieXyy) }, + { new HunterLab(Vector3.One), null, typeof(HunterLab) }, + { new Lms(Vector3.One), null, typeof(Lms) }, + { new LinearRgb(Vector3.One), null, typeof(LinearRgb) }, + { new Rgb(Vector3.One), null, typeof(Rgb) }, + { new Hsl(Vector3.One), null, typeof(Hsl) }, + { new Hsv(Vector3.One), null, typeof(Hsv) }, + { new YCbCr(Vector3.One), null, typeof(YCbCr) }, + }; + + public static readonly TheoryData NotEqualityDataDifferentObjects = + new TheoryData + { + // Valid objects of different types but not equal + { new CieLab(Vector3.One), new CieLch(Vector3.Zero), null }, + { new CieLuv(Vector3.One), new CieLchuv(Vector3.Zero), null }, + { new CieXyz(Vector3.One), new HunterLab(Vector3.Zero), null }, + { new Rgb(Vector3.One), new LinearRgb(Vector3.Zero), null }, + { new Rgb(Vector3.One), new Lms(Vector3.Zero), null }, + { new Cmyk(Vector4.One), new Hsl(Vector3.Zero), null }, + { new YCbCr(Vector3.One), new CieXyy(Vector3.Zero), null }, + { new Hsv(Vector3.One), new Hsl(Vector3.Zero), null }, + }; + + public static readonly TheoryData NotEqualityData = + new TheoryData + { + // Valid objects of the same type but not equal + { new CieLab(Vector3.One), new CieLab(Vector3.Zero), typeof(CieLab) }, + { new CieLch(Vector3.One), new CieLch(Vector3.Zero), typeof(CieLch) }, + { new CieLchuv(Vector3.One), new CieLchuv(Vector3.Zero), typeof(CieLchuv) }, + { new CieLuv(Vector3.One), new CieLuv(Vector3.Zero), typeof(CieLuv) }, + { new CieXyz(Vector3.One), new CieXyz(Vector3.Zero), typeof(CieXyz) }, + { new CieXyy(Vector3.One), new CieXyy(Vector3.Zero), typeof(CieXyy) }, + { new HunterLab(Vector3.One), new HunterLab(Vector3.Zero), typeof(HunterLab) }, + { new Lms(Vector3.One), new Lms(Vector3.Zero), typeof(Lms) }, + { new LinearRgb(Vector3.One), new LinearRgb(Vector3.Zero), typeof(LinearRgb) }, + { new Rgb(Vector3.One), new Rgb(Vector3.Zero), typeof(Rgb) }, + { new Cmyk(Vector4.One), new Cmyk(Vector4.Zero), typeof(Cmyk) }, + { new Hsl(Vector3.One), new Hsl(Vector3.Zero), typeof(Hsl) }, + { new Hsv(Vector3.One), new Hsv(Vector3.Zero), typeof(Hsv) }, + { new YCbCr(Vector3.One), new YCbCr(Vector3.Zero), typeof(YCbCr) }, + }; + + public static readonly TheoryData AlmostEqualsData = + new TheoryData + { + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), 0F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0005F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, .001F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, .0001F), typeof(CieLab), .0001F }, + { new CieLab(0F, 0F, 0F), new CieLab(.0005F, 0F, 0F), typeof(CieLab), .0005F }, + { new CieLch(0F, 0F, 0F), new CieLch(0F, .001F, 0F), typeof(CieLch), .001F }, + { new CieLchuv(0F, 0F, 0F), new CieLchuv(0F, .001F, 0F), typeof(CieLchuv), .001F }, + { new CieLuv(0F, 0F, 0F), new CieLuv(0F, .001F, 0F), typeof(CieLuv), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380F), typeof(CieXyz), 0F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380.001F, 380F, 380F), typeof(CieXyz), .01F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.001F, 380F), typeof(CieXyz), .01F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.001F), typeof(CieXyz), .01F }, + { new Cmyk(1, 1, 1, 1), new Cmyk(1, 1, 1, .99F), typeof(Cmyk), .01F }, + { new YCbCr(255F, 128F, 128F), new YCbCr(255F, 128F, 128.001F), typeof(YCbCr), .01F }, + { new Hsv(0F, 0F, 0F), new Hsv(0F, 0F, 0F), typeof(Hsv), 0F }, + { new Hsl(0F, 0F, 0F), new Hsl(0F, 0F, 0F), typeof(Hsl), 0F }, + }; + + public static readonly TheoryData AlmostNotEqualsData = + new TheoryData + { + { new CieLab(0F, 0F, 0F), new CieLab(0.1F, 0F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0.1F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0.1F), typeof(CieLab), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380.1F, 380F, 380F), typeof(CieXyz), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.1F, 380F), typeof(CieXyz), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.1F), typeof(CieXyz), .001F }, + }; + + [Theory] + [MemberData(nameof(EmptyData))] + public void Equality(IColorVector color) + { + // Act + bool equal = color.Vector.Equals(Vector3.Zero); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(EqualityData))] + public void Equality(object first, object second, Type type) + { + // Act + bool equal = first.Equals(second); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(NotEqualityDataNulls))] + [MemberData(nameof(NotEqualityDataDifferentObjects))] + [MemberData(nameof(NotEqualityData))] + public void NotEquality(object first, object second, Type type) + { + // Act + bool equal = first.Equals(second); + + // Assert + Assert.False(equal); + } + + [Theory] + [MemberData(nameof(EqualityData))] + public void HashCodeEqual(object first, object second, Type type) + { + // Act + bool equal = first.GetHashCode() == second.GetHashCode(); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(NotEqualityDataDifferentObjects))] + public void HashCodeNotEqual(object first, object second, Type type) + { + // Act + bool equal = first.GetHashCode() == second.GetHashCode(); + + // Assert + Assert.False(equal); + } + + [Theory] + [MemberData(nameof(EqualityData))] + public void EqualityObject(object first, object second, Type type) + { + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) + dynamic firstObject = Convert.ChangeType(first, type); + dynamic secondObject = Convert.ChangeType(second, type); + + // Act + dynamic equal = firstObject.Equals(secondObject); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(NotEqualityData))] + public void NotEqualityObject(object first, object second, Type type) + { + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) + dynamic firstObject = Convert.ChangeType(first, type); + dynamic secondObject = Convert.ChangeType(second, type); + + // Act + dynamic equal = firstObject.Equals(secondObject); + + // Assert + Assert.False(equal); + } + + // TODO:Disabled due to RuntypeBinder errors while structs are internal + //[Theory] + //[MemberData(nameof(EqualityData))] + //public void EqualityOperator(object first, object second, Type type) + //{ + // // Arrange + // // Cast to the known object types, this is so that we can hit the + // // equality operator on the concrete type, otherwise it goes to the + // // default "object" one :) + // dynamic firstObject = Convert.ChangeType(first, type); + // dynamic secondObject = Convert.ChangeType(second, type); + + // // Act + // dynamic equal = firstObject == secondObject; + + // // Assert + // Assert.True(equal); + //} + + [Theory] + [MemberData(nameof(NotEqualityData))] + public void NotEqualityOperator(object first, object second, Type type) + { + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) + dynamic firstObject = Convert.ChangeType(first, type); + dynamic secondObject = Convert.ChangeType(second, type); + + // Act + dynamic notEqual = firstObject != secondObject; + + // Assert + Assert.True(notEqual); + } + + // TODO:Disabled due to RuntypeBinder errors while structs are internal + //[Theory] + //[MemberData(nameof(AlmostEqualsData))] + //public void AlmostEquals(object first, object second, Type type, float precision) + //{ + // // Arrange + // // Cast to the known object types, this is so that we can hit the + // // equality operator on the concrete type, otherwise it goes to the + // // default "object" one :) + // dynamic firstObject = Convert.ChangeType(first, type); + // dynamic secondObject = Convert.ChangeType(second, type); + + // // Act + // dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); + + // // Assert + // Assert.True(almostEqual); + //} + + // TODO:Disabled due to RuntypeBinder errors while structs are internal + //[Theory] + //[MemberData(nameof(AlmostNotEqualsData))] + //public void AlmostNotEquals(object first, object second, Type type, float precision) + //{ + // // Arrange + // // Cast to the known object types, this is so that we can hit the + // // equality operator on the concrete type, otherwise it goes to the + // // default "object" one :) + // dynamic firstObject = Convert.ChangeType(first, type); + // dynamic secondObject = Convert.ChangeType(second, type); + + // // Act + // dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); + + // // Assert + // Assert.False(almostEqual); + //} + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs new file mode 100644 index 000000000..7a4520a57 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs @@ -0,0 +1,127 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class RgbAndCieXyzConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(6); + + /// + /// Tests conversion from () + /// to (default sRGB working space). + /// + [Theory] + [InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] + [InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] + [InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] + [InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] + public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + // Act + Rgb output = converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion + /// from () + /// to (default sRGB working space). + /// + [Theory] + [InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] + [InlineData(0, 1.000000, 0, 0, 1, 0)] + [InlineData(0.950470, 0, 0, 1, 0, 0.254967)] + [InlineData(0, 0, 1.088830, 0, 0.235458, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] + public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + // Act + Rgb output = converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] + [InlineData(0, 1, 0, 0.385065, 0.716879, 0.0971045)] + [InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] + [InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] + public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z) + { + // Arrange + Rgb input = new Rgb(r, g, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] + [InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] + [InlineData(0, 0, 1, 0.1804375, 0.072175, 0.950304)] + [InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] + public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z) + { + // Arrange + Rgb input = new Rgb(r, g, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs new file mode 100644 index 000000000..8fe64e915 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs @@ -0,0 +1,68 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + /// + public class RgbAndCmykConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(1, 1, 1, 1, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 1, 1, 1)] + [InlineData(0, 0.84, 0.037, 0.365, 0.635, 0.1016, 0.6115)] + public void Convert_Cmyk_To_Rgb(float c, float m, float y, float k, float r, float g, float b) + { + // Arrange + Cmyk input = new Cmyk(c, m, y, k); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(1, 1, 1, 0, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.635, 0.1016, 0.6115, 0, 0.84, 0.037, 0.365)] + public void Convert_Rgb_To_Cmyk(float r, float g, float b, float c, float m, float y, float k) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + Cmyk output = Converter.ToCmyk(input); + + // Assert + Assert.Equal(c, output.C, FloatRoundingComparer); + Assert.Equal(m, output.M, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(k, output.K, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs new file mode 100644 index 000000000..fa02f887f --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs @@ -0,0 +1,72 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + /// + public class RgbAndHslConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 1, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 1, 1)] + [InlineData(0, 1, .5F, 1, 0, 0)] + [InlineData(120, 1, .5F, 0, 1, 0)] + [InlineData(240, 1, .5F, 0, 0, 1)] + public void Convert_Hsl_To_Rgb(float h, float s, float l, float r, float g, float b) + { + // Arrange + Hsl input = new Hsl(h, s, l); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, .5F)] + [InlineData(0, 1, 0, 120, 1, .5F)] + [InlineData(0, 0, 1, 240, 1, .5F)] + public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + Hsl output = Converter.ToHsl(input); + + // Assert + Assert.Equal(h, output.H, FloatRoundingComparer); + Assert.Equal(s, output.S, FloatRoundingComparer); + Assert.Equal(l, output.L, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs new file mode 100644 index 000000000..f8d8c773a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs @@ -0,0 +1,71 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class RgbAndHsvConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 0, 0)] + [InlineData(0, 1, 1, 1, 0, 0)] + [InlineData(120, 1, 1, 0, 1, 0)] + [InlineData(240, 1, 1, 0, 0, 1)] + public void Convert_Hsv_To_Rgb(float h, float s, float v, float r, float g, float b) + { + // Arrange + Hsv input = new Hsv(h, s, v); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, 1)] + [InlineData(0, 1, 0, 120, 1, 1)] + [InlineData(0, 0, 1, 240, 1, 1)] + public void Convert_Rgb_To_Hsv(float r, float g, float b, float h, float s, float v) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + Hsv output = Converter.ToHsv(input); + + // Assert + Assert.Equal(h, output.H, FloatRoundingComparer); + Assert.Equal(s, output.S, FloatRoundingComparer); + Assert.Equal(v, output.V, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs new file mode 100644 index 000000000..3f741ea3d --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs @@ -0,0 +1,66 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated mathematically + /// + public class RgbAndYCbCrConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(255, 128, 128, 1, 1, 1)] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(128, 128, 128, 0.502, 0.502, 0.502)] + public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b) + { + // Arrange + YCbCr input = new YCbCr(y, cb, cr); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(1, 1, 1, 255, 128, 128)] + [InlineData(0.5, 0.5, 0.5, 127.5, 128, 128)] + [InlineData(1, 0, 0, 76.245, 84.972, 255)] + public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + YCbCr output = Converter.ToYCbCr(input); + + // Assert + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(cb, output.Cb, FloatRoundingComparer); + Assert.Equal(cr, output.Cr, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs index 1291160b2..21e86d434 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -44,7 +44,7 @@ namespace ImageSharp.Tests int max = isRawData ? PixelDataPool.CalculateMaxArrayLength() : PixelDataPool.CalculateMaxArrayLength(); - Assert.Equal(max < int.MaxValue, !isRawData); + Assert.Equal(max > 1024 * 1024, !isRawData); } [Fact] diff --git a/tests/ImageSharp.Tests/Helpers/MathFTests.cs b/tests/ImageSharp.Tests/Helpers/MathFTests.cs index 7f3fb77d0..62a971f9f 100644 --- a/tests/ImageSharp.Tests/Helpers/MathFTests.cs +++ b/tests/ImageSharp.Tests/Helpers/MathFTests.cs @@ -18,12 +18,24 @@ Assert.Equal(MathF.Ceiling(0.3333F), (float)Math.Ceiling(0.3333F)); } + [Fact] + public void MathF_Cos_Is_Equal() + { + Assert.Equal(MathF.Cos(0.3333F), (float)Math.Cos(0.3333F)); + } + [Fact] public void MathF_Abs_Is_Equal() { Assert.Equal(MathF.Abs(-0.3333F), (float)Math.Abs(-0.3333F)); } + [Fact] + public void MathF_Atan2_Is_Equal() + { + Assert.Equal(MathF.Atan2(1.2345F, 1.2345F), (float)Math.Atan2(1.2345F, 1.2345F)); + } + [Fact] public void MathF_Exp_Is_Equal() { @@ -54,16 +66,56 @@ Assert.Equal(MathF.Pow(1.2345F, 5.4321F), (float)Math.Pow(1.2345F, 5.4321F)); } + [Fact] + public void MathF_Round_Is_Equal() + { + Assert.Equal(MathF.Round(1.2345F), (float)Math.Round(1.2345F)); + } + + [Fact] + public void MathF_Round_With_Midpoint_Is_Equal() + { + Assert.Equal(MathF.Round(1.2345F, MidpointRounding.AwayFromZero), (float)Math.Round(1.2345F, MidpointRounding.AwayFromZero)); + } + [Fact] public void MathF_Sin_Is_Equal() { Assert.Equal(MathF.Sin(1.2345F), (float)Math.Sin(1.2345F)); } + [Fact] + public void MathF_SinC_Is_Equal() + { + float f = 1.2345F; + float expected = 1F; + if (Math.Abs(f) > Constants.Epsilon) + { + f *= (float)Math.PI; + float sinC = (float)Math.Sin(f) / f; + + expected = Math.Abs(sinC) < Constants.Epsilon ? 0F : sinC; + } + + Assert.Equal(MathF.SinC(1.2345F), expected); + } + [Fact] public void MathF_Sqrt_Is_Equal() { Assert.Equal(MathF.Sqrt(2F), (float)Math.Sqrt(2F)); } + + [Fact] + public void Convert_Degree_To_Radian() + { + Assert.Equal((float)(Math.PI / 2D), MathF.DegreeToRadian(90F), new FloatRoundingComparer(6)); + } + + [Fact] + public void Convert_Radian_To_Degree() + { + Assert.Equal(60F, MathF.RadianToDegree((float)(Math.PI / 3D)), new FloatRoundingComparer(5)); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 4cdf529e6..7a5b8ffc5 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -526,6 +526,48 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, this.decoderOptions)); } + [Fact] + public void LoadFromPixelData_Pixels() + { + var img = Image.LoadPixelData(new Rgba32[] { + Rgba32.Black, Rgba32.White, + Rgba32.White, Rgba32.Black, + }, 2, 2); + + Assert.NotNull(img); + using (var px = img.Lock()) + { + Assert.Equal(Rgba32.Black, px[0, 0]); + Assert.Equal(Rgba32.White, px[0, 1]); + + Assert.Equal(Rgba32.White, px[1, 0]); + Assert.Equal(Rgba32.Black, px[1, 1]); + + } + } + + [Fact] + public void LoadFromPixelData_Bytes() + { + var img = Image.LoadPixelData(new byte[] { + 0,0,0,255, // 0,0 + 255,255,255,255, // 0,1 + 255,255,255,255, // 1,0 + 0,0,0,255, // 1,1 + }, 2, 2); + + Assert.NotNull(img); + using (var px = img.Lock()) + { + Assert.Equal(Rgba32.Black, px[0, 0]); + Assert.Equal(Rgba32.White, px[0, 1]); + + Assert.Equal(Rgba32.White, px[1, 0]); + Assert.Equal(Rgba32.Black, px[1, 1]); + + } + } + public void Dispose() { // clean up the global object; diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index c2529340d..3ec2210c5 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -16,6 +16,7 @@ + diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs new file mode 100644 index 000000000..2bde12543 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) + { + IccDataReader reader = CreateReader(data); + + IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) + { + IccDataReader reader = CreateReader(data); + + IccResponseCurve output = reader.ReadResponseCurve(channelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) + { + IccDataReader reader = CreateReader(data); + + IccParametricCurve output = reader.ReadParametricCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) + { + IccDataReader reader = CreateReader(data); + + IccCurveSegment output = reader.ReadCurveSegment(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) + { + IccDataReader reader = CreateReader(data); + + IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) + { + IccDataReader reader = CreateReader(data); + + IccSampledCurveElement output = reader.ReadSampledCurveElement(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs new file mode 100644 index 000000000..52c67ba53 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut8(byte[] data, IccLut expected) + { + IccDataReader reader = CreateReader(data); + + IccLut output = reader.ReadLut8(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut16(byte[] data, IccLut expected, int count) + { + IccDataReader reader = CreateReader(data); + + IccLut output = reader.ReadLut16(count); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs new file mode 100644 index 000000000..9d148ec94 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) + { + IccDataReader reader = CreateReader(data); + + float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) + { + IccDataReader reader = CreateReader(data); + + float[] output = reader.ReadMatrix(yCount, isSingle); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs new file mode 100644 index 000000000..c02ef40e3 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiProcessElement output = reader.ReadMultiProcessElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs new file mode 100644 index 000000000..e3593bfa9 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs @@ -0,0 +1,129 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using System.Numerics; + using Xunit; + + public class IccDataReaderNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadDateTime(byte[] data, DateTime expected) + { + IccDataReader reader = CreateReader(data); + + DateTime output = reader.ReadDateTime(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadVersionNumber(byte[] data, Version expected) + { + IccDataReader reader = CreateReader(data); + + Version output = reader.ReadVersionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadXyzNumber(byte[] data, Vector3 expected) + { + IccDataReader reader = CreateReader(data); + + Vector3 output = reader.ReadXyzNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileId(byte[] data, IccProfileId expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileId output = reader.ReadProfileId(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) + { + IccDataReader reader = CreateReader(data); + + IccPositionNumber output = reader.ReadPositionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) + { + IccDataReader reader = CreateReader(data); + + IccResponseNumber output = reader.ReadResponseNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) + { + IccDataReader reader = CreateReader(data); + + IccNamedColor output = reader.ReadNamedColor(coordinateCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileDescription output = reader.ReadProfileDescription(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantTableEntry output = reader.ReadColorantTableEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) + { + IccDataReader reader = CreateReader(data); + + IccScreeningChannel output = reader.ReadScreeningChannel(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs new file mode 100644 index 000000000..b9b0c6655 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using Xunit; + + public class IccDataReaderPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadAsciiString(byte[] textBytes, int length, string expected) + { + IccDataReader reader = CreateReader(textBytes); + + string output = reader.ReadAsciiString(length); + + Assert.Equal(expected, output); + } + + [Fact] + public void ReadAsciiStringWithNegativeLenghtThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadAsciiString(-1)); + } + + [Fact] + public void ReadUnicodeStringWithNegativeLenghtThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadUnicodeString(-1)); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadUFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadU1Fix15(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadU1Fix15(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix8(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadUFix8(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs new file mode 100644 index 000000000..9a2455f0e --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs @@ -0,0 +1,380 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderTagDataEntryTests + { + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceIdentifierTagDataEntry(byte[] data, IccProfileSequenceIdentifierTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs new file mode 100644 index 000000000..635ce6168 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using Xunit; + + public class IccDataReaderTests + { + [Fact] + public void ConstructorThrowsNullException() + { + Assert.Throws(() => new IccDataReader(null)); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs new file mode 100644 index 000000000..c04401f6d --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteOneDimensionalCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteParametricCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveSegment(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFormulaCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteSampledCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs new file mode 100644 index 000000000..4fcc55d01 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs new file mode 100644 index 000000000..61b5d57ff --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System.Numerics; + + using ImageSharp.Memory; + + using Xunit; + + public class IccDataWriterMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_Fast2DArrayTestData), MemberType = typeof(IccTestDataMatrix))] + internal void WriteMatrix2D_Fast2DArray(byte[] expected, int xCount, int yCount, bool isSingle, Fast2DArray data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs new file mode 100644 index 000000000..e3bc37574 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveSetProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrixProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClutProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs new file mode 100644 index 000000000..ae8345805 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs @@ -0,0 +1,127 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using System.Numerics; + using Xunit; + + public class IccDataWriterNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteDateTime(byte[] expected, DateTime data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDateTime(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteVersionNumber(byte[] expected, Version data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteVersionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteXyzNumber(byte[] expected, Vector3 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteXyzNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileId(byte[] expected, IccProfileId data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileId(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WritePositionNumber(byte[] expected, IccPositionNumber data) + { + IccDataWriter writer = CreateWriter(); + + writer.WritePositionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteNamedColor(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileDescription(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteScreeningChannel(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs new file mode 100644 index 000000000..8d0cd32ab --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs @@ -0,0 +1,122 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using Xunit; + + public class IccDataWriterPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiString(byte[] expected, string data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data, length, ensureNullTerminator); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Fact] + public void WriteAsciiStringWithNullWritesEmpty() + { + IccDataWriter writer = CreateWriter(); + + int count = writer.WriteAsciiString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(new byte[0], output); + } + + [Fact] + public void WriteAsciiStringWithNegativeLenghtThrowsArgumentException() + { + IccDataWriter writer = CreateWriter(); + + Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); + } + + [Fact] + public void WriteUnicodeStringWithNullWritesEmpty() + { + IccDataWriter writer = CreateWriter(); + + int count = writer.WriteUnicodeString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(new byte[0], output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteFix16(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix16(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteU1Fix15(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteU1Fix15(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix8(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs new file mode 100644 index 000000000..affe9835e --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs @@ -0,0 +1,413 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterTagDataEntryTests + { + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUnknownTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteChromaticityTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteColorantOrderTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteColorantTableTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDataTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDateTimeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut8TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLutAtoBTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLutBtoATagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMeasurementTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiLocalizedUnicodeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiProcessElementsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteNamedColor2TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteParametricCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileSequenceDescTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileSequenceIdentifierTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseCurveSet16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteSignatureTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteTextTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt32ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt64ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt8ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteViewingConditionsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteXyzTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteTextDescriptionTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCrdInfoTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteScreeningTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUcrBgTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs new file mode 100644 index 000000000..5239d7cd5 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterTests + { + [Fact] + public void WriteEmpty() + { + IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(4); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[4], output); + } + + [Theory] + [InlineData(1, 4)] + [InlineData(4, 4)] + public void WritePadding(int writePosition, int expectedLength) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(writePosition); + writer.WritePadding(); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[expectedLength], output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt8(byte[] data, byte[] expected) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt16(byte[] expected, ushort[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt16(byte[] expected, short[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt32(byte[] expected, uint[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt32(byte[] expected, int[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt64(byte[] expected, ulong[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs new file mode 100644 index 000000000..0db64c47f --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccReaderTests + { + [Fact] + public void ReadProfile() + { + IccReader reader = CreateReader(); + + IccProfile output = reader.Read(IccTestDataProfiles.Header_Random_Array); + + Assert.Equal(0, output.Entries.Count); + Assert.NotNull(output.Header); + + IccProfileHeader header = output.Header; + IccProfileHeader expected = IccTestDataProfiles.Header_Random_Read; + Assert.Equal(header.Class, expected.Class); + Assert.Equal(header.CmmType, expected.CmmType); + Assert.Equal(header.CreationDate, expected.CreationDate); + Assert.Equal(header.CreatorSignature, expected.CreatorSignature); + Assert.Equal(header.DataColorSpace, expected.DataColorSpace); + Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); + Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); + Assert.Equal(header.DeviceModel, expected.DeviceModel); + Assert.Equal(header.FileSignature, expected.FileSignature); + Assert.Equal(header.Flags, expected.Flags); + Assert.Equal(header.Id, expected.Id); + Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); + Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); + Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); + Assert.Equal(header.RenderingIntent, expected.RenderingIntent); + Assert.Equal(header.Size, expected.Size); + Assert.Equal(header.Version, expected.Version); + } + + private IccReader CreateReader() + { + return new IccReader(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs new file mode 100644 index 000000000..6192e6eae --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccWriterTests + { + [Fact] + public void WriteProfile() + { + IccWriter writer = CreateWriter(); + + IccProfile profile = new IccProfile() + { + Header = IccTestDataProfiles.Header_Random_Write + }; + byte[] output = writer.Write(profile); + + Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); + } + + private IccWriter CreateWriter() + { + return new IccWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs index e40e3a205..401ac916b 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(AlphaValues))] - public void ImageShouldApplyAlphaFilter(int value) + public void ImageShouldApplyAlphaFilter(float value) { string path = this.CreateOutputDirectory("Alpha"); @@ -39,7 +39,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(AlphaValues))] - public void ImageShouldApplyAlphaFilterInBox(int value) + public void ImageShouldApplyAlphaFilterInBox(float value) { string path = this.CreateOutputDirectory("Alpha"); diff --git a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs index 9a7d87854..2e82191ec 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs @@ -25,9 +25,9 @@ namespace ImageSharp.Tests { image.Grayscale(value); byte[] data = new byte[3]; - foreach (TPixel p in image.Pixels) + for (int i = 0; i < image.Pixels.Length; i++) { - p.ToXyzBytes(data, 0); + image.Pixels[i].ToXyzBytes(data, 0); Assert.Equal(data[0], data[1]); Assert.Equal(data[1], data[2]); } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs new file mode 100644 index 000000000..a14433f1e --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs @@ -0,0 +1,146 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataArray + { + #region Byte + + public static readonly byte[] UInt8 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly object[][] UInt8TestData = + { + new object[] { UInt8, UInt8 } + }; + + #endregion + + #region UInt16 + + public static readonly ushort[] UInt16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_0, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9 + ); + + public static readonly object[][] UInt16TestData = + { + new object[] { UInt16_Arr, UInt16_Val } + }; + + #endregion + + #region Int16 + + public static readonly short[] Int16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int16_0, + IccTestDataPrimitives.Int16_1, + IccTestDataPrimitives.Int16_2, + IccTestDataPrimitives.Int16_3, + IccTestDataPrimitives.Int16_4, + IccTestDataPrimitives.Int16_5, + IccTestDataPrimitives.Int16_6, + IccTestDataPrimitives.Int16_7, + IccTestDataPrimitives.Int16_8, + IccTestDataPrimitives.Int16_9 + ); + + public static readonly object[][] Int16TestData = + { + new object[] { Int16_Arr, Int16_Val } + }; + + #endregion + + #region UInt32 + + public static readonly uint[] UInt32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt32_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_0, + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt32_4, + IccTestDataPrimitives.UInt32_5, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.UInt32_7, + IccTestDataPrimitives.UInt32_8, + IccTestDataPrimitives.UInt32_9 + ); + + public static readonly object[][] UInt32TestData = + { + new object[] { UInt32_Arr, UInt32_Val } + }; + + #endregion + + #region Int32 + + public static readonly int[] Int32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int32_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int32_0, + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.Int32_2, + IccTestDataPrimitives.Int32_3, + IccTestDataPrimitives.Int32_4, + IccTestDataPrimitives.Int32_5, + IccTestDataPrimitives.Int32_6, + IccTestDataPrimitives.Int32_7, + IccTestDataPrimitives.Int32_8, + IccTestDataPrimitives.Int32_9 + ); + + public static readonly object[][] Int32TestData = + { + new object[] { Int32_Arr, Int32_Val } + }; + + #endregion + + #region UInt64 + + public static readonly ulong[] UInt64_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt64_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt64_0, + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3, + IccTestDataPrimitives.UInt64_4, + IccTestDataPrimitives.UInt64_5, + IccTestDataPrimitives.UInt64_6, + IccTestDataPrimitives.UInt64_7, + IccTestDataPrimitives.UInt64_8, + IccTestDataPrimitives.UInt64_9 + ); + + public static readonly object[][] UInt64TestData = + { + new object[] { UInt64_Arr, UInt64_Val } + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs new file mode 100644 index 000000000..c8d517aac --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -0,0 +1,386 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Numerics; + + internal static class IccTestDataCurves + { + #region Response + + /// + /// Channels: 3 + /// + public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve + ( + IccCurveMeasurementEncodings.StatusA, + new Vector3[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccTestDataNonPrimitives.XyzNumber_ValVar3, + }, + new IccResponseNumber[][] + { + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val1, IccTestDataNonPrimitives.ResponseNumber_Val2 }, + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val3, IccTestDataNonPrimitives.ResponseNumber_Val4 }, + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val5, IccTestDataNonPrimitives.ResponseNumber_Val6 }, + } + ); + + /// + /// Channels: 3 + /// + public static readonly byte[] Response_Grad = ArrayHelper.Concat + ( + new byte[] { 0x53, 0x74, 0x61, 0x41 }, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_2, + + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataNonPrimitives.XyzNumber_Var3, + + IccTestDataNonPrimitives.ResponseNumber_1, + IccTestDataNonPrimitives.ResponseNumber_2, + + IccTestDataNonPrimitives.ResponseNumber_3, + IccTestDataNonPrimitives.ResponseNumber_4, + + IccTestDataNonPrimitives.ResponseNumber_5, + IccTestDataNonPrimitives.ResponseNumber_6 + ); + + public static readonly object[][] ResponseCurveTestData = + { + new object[] { Response_Grad, Response_ValGrad, 3 }, + }; + + #endregion + + #region Parametric + + public static readonly IccParametricCurve Parametric_ValVar1 = new IccParametricCurve(1); + public static readonly IccParametricCurve Parametric_ValVar2 = new IccParametricCurve(1, 2, 3); + public static readonly IccParametricCurve Parametric_ValVar3 = new IccParametricCurve(1, 2, 3, 4); + public static readonly IccParametricCurve Parametric_ValVar4 = new IccParametricCurve(1, 2, 3, 4, 5); + public static readonly IccParametricCurve Parametric_ValVar5 = new IccParametricCurve(1, 2, 3, 4, 5, 6, 7); + + public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1 + ); + + public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3 + ); + + public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4 + ); + + public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5 + ); + + public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x04, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Fix16_7 + ); + + public static readonly object[][] ParametricCurveTestData = + { + new object[] { Parametric_Var1, Parametric_ValVar1 }, + new object[] { Parametric_Var2, Parametric_ValVar2 }, + new object[] { Parametric_Var3, Parametric_ValVar3 }, + new object[] { Parametric_Var4, Parametric_ValVar4 }, + new object[] { Parametric_Var5, Parametric_ValVar5 }, + }; + + #endregion + + #region Formula Segment + + public static readonly IccFormulaCurveElement Formula_ValVar1 = new IccFormulaCurveElement(IccFormulaCurveType.Type1, 1, 2, 3, 4, 0, 0); + public static readonly IccFormulaCurveElement Formula_ValVar2 = new IccFormulaCurveElement(IccFormulaCurveType.Type2, 1, 2, 3, 4, 5, 0); + public static readonly IccFormulaCurveElement Formula_ValVar3 = new IccFormulaCurveElement(IccFormulaCurveType.Type3, 0, 2, 3, 4, 5, 6); + + public static readonly byte[] Formula_Var1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4 + ); + + public static readonly byte[] Formula_Var2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5 + ); + + public static readonly byte[] Formula_Var3 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6 + ); + + public static readonly object[][] FormulaCurveSegmentTestData = + { + new object[] { Formula_Var1, Formula_ValVar1 }, + new object[] { Formula_Var2, Formula_ValVar2 }, + new object[] { Formula_Var3, Formula_ValVar3 }, + }; + + #endregion + + #region Sampled Segment + + public static readonly IccSampledCurveElement Sampled_ValGrad1 = new IccSampledCurveElement(new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + public static readonly IccSampledCurveElement Sampled_ValGrad2 = new IccSampledCurveElement(new float[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 }); + + public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_9, + + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9 + ); + + public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_9, + + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_1 + ); + + public static readonly object[][] SampledCurveSegmentTestData = + { + new object[] { Sampled_Grad1, Sampled_ValGrad1 }, + new object[] { Sampled_Grad2, Sampled_ValGrad2 }, + }; + + #endregion + + #region Segment + + public static readonly IccCurveSegment Segment_ValFormula1 = Formula_ValVar1; + public static readonly IccCurveSegment Segment_ValFormula2 = Formula_ValVar2; + public static readonly IccCurveSegment Segment_ValFormula3 = Formula_ValVar3; + public static readonly IccCurveSegment Segment_ValSampled1 = Sampled_ValGrad1; + public static readonly IccCurveSegment Segment_ValSampled2 = Sampled_ValGrad2; + + public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var1 + ); + + public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var2 + ); + + public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var3 + ); + + public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat + ( + new byte[] + { + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad1 + ); + + public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat + ( + new byte[] + { + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad2 + ); + + public static readonly object[][] CurveSegmentTestData = + { + new object[] { Segment_Formula1, Segment_ValFormula1 }, + new object[] { Segment_Formula2, Segment_ValFormula2 }, + new object[] { Segment_Formula3, Segment_ValFormula3 }, + new object[] { Segment_Sampled1, Segment_ValSampled1 }, + new object[] { Segment_Sampled2, Segment_ValSampled2 }, + }; + + #endregion + + #region One Dimensional + + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValFormula1, Segment_ValFormula2, Segment_ValFormula3 } + ); + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula2 = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValFormula3, Segment_ValFormula2, Segment_ValFormula1 } + ); + public static readonly IccOneDimensionalCurve OneDimensional_ValSampled = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 } + ); + + public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula1, + Segment_Formula2, + Segment_Formula3 + ); + + public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula3, + Segment_Formula2, + Segment_Formula1 + ); + + public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Sampled1, + Segment_Sampled2, + Segment_Sampled1 + ); + + public static readonly object[][] OneDimensionalCurveTestData = + { + new object[] { OneDimensional_Formula1, OneDimensional_ValFormula1 }, + new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, + new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs new file mode 100644 index 000000000..cc2dfc6e9 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -0,0 +1,254 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataLut + { + #region LUT8 + + public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); + public static readonly byte[] LUT8_Grad = CreateLUT8(); + + private static IccLut CreateLUT8Val() + { + float[] result = new float[256]; + for (int i = 0; i < 256; i++) { result[i] = i / 255f; } + return new IccLut(result); + } + + private static byte[] CreateLUT8() + { + byte[] result = new byte[256]; + for (int i = 0; i < 256; i++) { result[i] = (byte)i; } + return result; + } + + public static readonly object[][] Lut8TestData = + { + new object[] { LUT8_Grad, LUT8_ValGrad }, + }; + + #endregion + + #region LUT16 + + public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] + { + 1f / ushort.MaxValue, + 2f / ushort.MaxValue, + 3f / ushort.MaxValue, + 4f / ushort.MaxValue, + 5f / ushort.MaxValue, + 6f / ushort.MaxValue, + 7f / ushort.MaxValue, + 8f / ushort.MaxValue, + 9f / ushort.MaxValue, + 32768f / ushort.MaxValue, + 1f + }); + + public static readonly byte[] LUT16_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9, + IccTestDataPrimitives.UInt16_32768, + IccTestDataPrimitives.UInt16_Max + ); + + public static readonly object[][] Lut16TestData = + { + new object[] { LUT16_Grad, LUT16_ValGrad, 11 }, + }; + + #endregion + + #region CLUT8 + + public static readonly IccClut CLUT8_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, + new float[] { 4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue }, + new float[] { 7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue }, + + new float[] { 10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue }, + new float[] { 13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue }, + new float[] { 16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue }, + + new float[] { 19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue }, + new float[] { 22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue }, + new float[] { 25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue }, + }, + new byte[] { 3, 3 }, IccClutDataType.UInt8 + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT8_Grad = + { + 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, + + 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, + + 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, + }; + + public static readonly object[][] Clut8TestData = + { + new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUT16 + + public static readonly IccClut CLUT16_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, + new float[] { 4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue }, + new float[] { 7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue }, + + new float[] { 10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue }, + new float[] { 13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue }, + new float[] { 16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue }, + + new float[] { 19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue }, + new float[] { 22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue }, + new float[] { 25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue }, + }, + new byte[] { 3, 3 }, IccClutDataType.UInt16 + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT16_Grad = + { + 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, + + 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, + + 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, + 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B, + }; + + public static readonly object[][] Clut16TestData = + { + new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUTf32 + + public static readonly IccClut CLUTf32_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + }, + new byte[] { 3, 3 }, IccClutDataType.Float + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUTf32_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, + + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, + + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9 + ); + + public static readonly object[][] ClutF32TestData = + { + new object[] { CLUTf32_Grad, CLUTf32_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUT + + public static readonly IccClut CLUT_Val8 = CLUT8_ValGrad; + public static readonly IccClut CLUT_Val16 = CLUT16_ValGrad; + public static readonly IccClut CLUT_Valf32 = CLUTf32_ValGrad; + + public static readonly byte[] CLUT_8 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[4] { 0x01, 0x00, 0x00, 0x00 }, + CLUT8_Grad + ); + + public static readonly byte[] CLUT_16 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[4] { 0x02, 0x00, 0x00, 0x00 }, + CLUT16_Grad + ); + + public static readonly byte[] CLUT_f32 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + CLUTf32_Grad + ); + + public static readonly object[][] ClutTestData = + { + new object[] { CLUT_8, CLUT_Val8, 2, 3, false }, + new object[] { CLUT_16, CLUT_Val16, 2, 3, false }, + new object[] { CLUT_f32, CLUT_Valf32, 2, 3, true }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs new file mode 100644 index 000000000..78e493829 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -0,0 +1,177 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System.Numerics; + +namespace ImageSharp.Tests +{ + using ImageSharp.Memory; + + internal static class IccTestDataMatrix + { + #region 2D + + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValGrad = + { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + }; + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValIdentity = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + }; + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValGrad = new Matrix4x4(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 1); + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValIdentity = Matrix4x4.Identity; + + /// + /// 3x3 Matrix + /// + public static readonly Fast2DArray Single_Fast2DArray_ValGrad = new Fast2DArray(Single_2DArray_ValGrad); + + /// + /// 3x3 Matrix + /// + public static readonly Fast2DArray Single_Fast2DArray_ValIdentity = new Fast2DArray(Single_2DArray_ValIdentity); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_7, + + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Fix16_8, + + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Fix16_9 + ); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_0, + + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_0, + + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_1 + ); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_7, + + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_8, + + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_9 + ); + + public static readonly object[][] Matrix2D_FloatArrayTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_2DArray_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_2DArray_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_2DArray_ValGrad }, + }; + + public static readonly object[][] Matrix2D_Fast2DArrayTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_Fast2DArray_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_Fast2DArray_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_Fast2DArray_ValGrad }, + }; + + public static readonly object[][] Matrix2D_Matrix4x4TestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_Matrix4x4_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_Matrix4x4_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_Matrix4x4_ValGrad }, + }; + + #endregion + + #region 1D + + /// + /// 3x1 Matrix + /// + public static readonly float[] Single_1DArray_ValGrad = { 1, 4, 7 }; + /// + /// 3x1 Matrix + /// + public static readonly Vector3 Single_Vector3_ValGrad = new Vector3(1, 4, 7); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_7 + ); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_7 + ); + + public static readonly object[][] Matrix1D_ArrayTestData = + { + new object[] { Fix16_1D_Grad, 3, false, Single_1DArray_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_1DArray_ValGrad }, + }; + + public static readonly object[][] Matrix1D_Vector3TestData = + { + new object[] { Fix16_1D_Grad, 3, false, Single_Vector3_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_Vector3_ValGrad }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs new file mode 100644 index 000000000..49e9550df --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -0,0 +1,157 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataMultiProcessElement + { + #region CurveSet + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccCurveSetProcessElement CurvePE_ValGrad = new IccCurveSetProcessElement(new IccOneDimensionalCurve[] + { + IccTestDataCurves.OneDimensional_ValFormula1, + IccTestDataCurves.OneDimensional_ValFormula2, + IccTestDataCurves.OneDimensional_ValFormula1 + }); + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat + ( + IccTestDataCurves.OneDimensional_Formula1, + IccTestDataCurves.OneDimensional_Formula2, + IccTestDataCurves.OneDimensional_Formula1 + ); + + public static readonly object[][] CurveSetTestData = + { + new object[] { CurvePE_Grad, CurvePE_ValGrad, 3, 3 }, + }; + + #endregion + + #region Matrix + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement + ( + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad + ); + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat + ( + IccTestDataMatrix.Single_2D_Grad, + IccTestDataMatrix.Single_1D_Grad + ); + + public static readonly object[][] MatrixTestData = + { + new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, + }; + + + #endregion + + #region CLUT + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// + public static readonly IccClutProcessElement CLUTPE_ValGrad = new IccClutProcessElement(IccTestDataLut.CLUT_Valf32); + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// + public static readonly byte[] CLUTPE_Grad = IccTestDataLut.CLUT_f32; + + public static readonly object[][] ClutTestData = + { + new object[] { CLUTPE_Grad, CLUTPE_ValGrad, 2, 3 }, + }; + + #endregion + + #region MultiProcessElement + + public static readonly IccMultiProcessElement MPE_ValMatrix = MatrixPE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValCLUT = CLUTPE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValCurve = CurvePE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValbACS = new IccBAcsProcessElement(3, 3); + public static readonly IccMultiProcessElement MPE_ValeACS = new IccEAcsProcessElement(3, 3); + + public static readonly byte[] MPE_Matrix = ArrayHelper.Concat + ( + new byte[] + { + 0x6D, 0x61, 0x74, 0x66, + 0x00, 0x03, + 0x00, 0x03, + }, + MatrixPE_Grad + ); + + public static readonly byte[] MPE_CLUT = ArrayHelper.Concat + ( + new byte[] + { + 0x63, 0x6C, 0x75, 0x74, + 0x00, 0x02, + 0x00, 0x03, + }, + CLUTPE_Grad + ); + + public static readonly byte[] MPE_Curve = ArrayHelper.Concat + ( + new byte[] + { + 0x6D, 0x66, 0x6C, 0x74, + 0x00, 0x03, + 0x00, 0x03, + }, + CurvePE_Grad + ); + + public static readonly byte[] MPE_bACS = + { + 0x62, 0x41, 0x43, 0x53, + 0x00, 0x03, + 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] MPE_eACS = + { + 0x65, 0x41, 0x43, 0x53, + 0x00, 0x03, + 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly object[][] MultiProcessElementTestData = + { + new object[] { MPE_Matrix, MPE_ValMatrix }, + new object[] { MPE_CLUT, MPE_ValCLUT }, + new object[] { MPE_Curve, MPE_ValCurve }, + new object[] { MPE_bACS, MPE_ValbACS }, + new object[] { MPE_eACS, MPE_ValeACS }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs new file mode 100644 index 000000000..fdbcb3127 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -0,0 +1,412 @@ +namespace ImageSharp.Tests +{ + using System; + using System.Globalization; + using System.Numerics; + + internal static class IccTestDataNonPrimitives + { + #region DateTime + + public static readonly DateTime DateTime_ValMin = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); + public static readonly DateTime DateTime_ValMax = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc); + public static readonly DateTime DateTime_ValRand1 = new DateTime(1990, 11, 26, 3, 19, 47, DateTimeKind.Utc); + + public static readonly byte[] DateTime_Min = + { + 0x00, 0x01, // Year 1 + 0x00, 0x01, // Month 1 + 0x00, 0x01, // Day 1 + 0x00, 0x00, // Hour 0 + 0x00, 0x00, // Minute 0 + 0x00, 0x00, // Second 0 + }; + + public static readonly byte[] DateTime_Max = + { + 0x27, 0x0F, // Year 9999 + 0x00, 0x0C, // Month 12 + 0x00, 0x1F, // Day 31 + 0x00, 0x17, // Hour 23 + 0x00, 0x3B, // Minute 59 + 0x00, 0x3B, // Second 59 + }; + + public static readonly byte[] DateTime_Invalid = + { + 0xFF, 0xFF, // Year 65535 + 0x00, 0x0E, // Month 14 + 0x00, 0x21, // Day 33 + 0x00, 0x19, // Hour 25 + 0x00, 0x3D, // Minute 61 + 0x00, 0x3D, // Second 61 + }; + + public static readonly byte[] DateTime_Rand1 = + { + 0x07, 0xC6, // Year 1990 + 0x00, 0x0B, // Month 11 + 0x00, 0x1A, // Day 26 + 0x00, 0x03, // Hour 3 + 0x00, 0x13, // Minute 19 + 0x00, 0x2F, // Second 47 + }; + + public static readonly object[][] DateTimeTestData = + { + new object[] { DateTime_Min, DateTime_ValMin }, + new object[] { DateTime_Max, DateTime_ValMax }, + new object[] { DateTime_Rand1, DateTime_ValRand1 }, + }; + + #endregion + + #region VersionNumber + + public static readonly Version VersionNumber_ValMin = new Version(0, 0, 0); + public static readonly Version VersionNumber_Val211 = new Version(2, 1, 1); + public static readonly Version VersionNumber_Val430 = new Version(4, 3, 0); + public static readonly Version VersionNumber_ValMax = new Version(255, 15, 15); + + public static readonly byte[] VersionNumber_Min = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_211 = { 0x02, 0x11, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_430 = { 0x04, 0x30, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_Max = { 0xFF, 0xFF, 0x00, 0x00 }; + + public static readonly object[][] VersionNumberTestData = + { + new object[] { VersionNumber_Min, VersionNumber_ValMin }, + new object[] { VersionNumber_211, VersionNumber_Val211 }, + new object[] { VersionNumber_430, VersionNumber_Val430 }, + new object[] { VersionNumber_Max, VersionNumber_ValMax }, + }; + + #endregion + + #region XyzNumber + + public static readonly Vector3 XyzNumber_ValMin = new Vector3(IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin); + public static readonly Vector3 XyzNumber_Val0 = new Vector3(0, 0, 0); + public static readonly Vector3 XyzNumber_Val1 = new Vector3(1, 1, 1); + public static readonly Vector3 XyzNumber_ValVar1 = new Vector3(1, 2, 3); + public static readonly Vector3 XyzNumber_ValVar2 = new Vector3(4, 5, 6); + public static readonly Vector3 XyzNumber_ValVar3 = new Vector3(7, 8, 9); + public static readonly Vector3 XyzNumber_ValMax = new Vector3(IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] XyzNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] XyzNumber_0 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0); + public static readonly byte[] XyzNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] XyzNumber_Var1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] XyzNumber_Var2 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] XyzNumber_Var3 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_7, IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] XyzNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] XyzNumberTestData = + { + new object[] { XyzNumber_Min, XyzNumber_ValMin }, + new object[] { XyzNumber_0, XyzNumber_Val0 }, + new object[] { XyzNumber_Var1, XyzNumber_ValVar1 }, + new object[] { XyzNumber_Max, XyzNumber_ValMax }, + }; + + #endregion + + #region ProfileId + + public static readonly IccProfileId ProfileId_ValMin = new IccProfileId(0, 0, 0, 0); + public static readonly IccProfileId ProfileId_ValRand = new IccProfileId(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2, IccTestDataPrimitives.UInt32_ValRand3, IccTestDataPrimitives.UInt32_ValRand4); + public static readonly IccProfileId ProfileId_ValMax = new IccProfileId(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); + + public static readonly byte[] ProfileId_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] ProfileId_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2, IccTestDataPrimitives.UInt32_Rand3, IccTestDataPrimitives.UInt32_Rand4); + public static readonly byte[] ProfileId_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + + public static readonly object[][] ProfileIdTestData = + { + new object[] { ProfileId_Min, ProfileId_ValMin }, + new object[] { ProfileId_Rand, ProfileId_ValRand }, + new object[] { ProfileId_Max, ProfileId_ValMax }, + }; + + #endregion + + #region PositionNumber + + public static readonly IccPositionNumber PositionNumber_ValMin = new IccPositionNumber(0, 0); + public static readonly IccPositionNumber PositionNumber_ValRand = new IccPositionNumber(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2); + public static readonly IccPositionNumber PositionNumber_ValMax = new IccPositionNumber(uint.MaxValue, uint.MaxValue); + + public static readonly byte[] PositionNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] PositionNumber_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2); + public static readonly byte[] PositionNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + + public static readonly object[][] PositionNumberTestData = + { + new object[] { PositionNumber_Min, PositionNumber_ValMin }, + new object[] { PositionNumber_Rand, PositionNumber_ValRand }, + new object[] { PositionNumber_Max, PositionNumber_ValMax }, + }; + + #endregion + + #region ResponseNumber + + public static readonly IccResponseNumber ResponseNumber_ValMin = new IccResponseNumber(0, IccTestDataPrimitives.Fix16_ValMin); + public static readonly IccResponseNumber ResponseNumber_Val1 = new IccResponseNumber(1, 1); + public static readonly IccResponseNumber ResponseNumber_Val2 = new IccResponseNumber(2, 2); + public static readonly IccResponseNumber ResponseNumber_Val3 = new IccResponseNumber(3, 3); + public static readonly IccResponseNumber ResponseNumber_Val4 = new IccResponseNumber(4, 4); + public static readonly IccResponseNumber ResponseNumber_Val5 = new IccResponseNumber(5, 5); + public static readonly IccResponseNumber ResponseNumber_Val6 = new IccResponseNumber(6, 6); + public static readonly IccResponseNumber ResponseNumber_Val7 = new IccResponseNumber(7, 7); + public static readonly IccResponseNumber ResponseNumber_Val8 = new IccResponseNumber(8, 8); + public static readonly IccResponseNumber ResponseNumber_Val9 = new IccResponseNumber(9, 9); + public static readonly IccResponseNumber ResponseNumber_ValMax = new IccResponseNumber(ushort.MaxValue, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] ResponseNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] ResponseNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] ResponseNumber_2 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.Fix16_2); + public static readonly byte[] ResponseNumber_3 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] ResponseNumber_4 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.Fix16_4); + public static readonly byte[] ResponseNumber_5 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_5, IccTestDataPrimitives.Fix16_5); + public static readonly byte[] ResponseNumber_6 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] ResponseNumber_7 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.Fix16_7); + public static readonly byte[] ResponseNumber_8 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.Fix16_8); + public static readonly byte[] ResponseNumber_9 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] ResponseNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] ResponseNumberTestData = + { + new object[] { ResponseNumber_Min, ResponseNumber_ValMin }, + new object[] { ResponseNumber_1, ResponseNumber_Val1 }, + new object[] { ResponseNumber_4, ResponseNumber_Val4 }, + new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, + }; + + #endregion + + #region NamedColor + + public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor + ( + ArrayHelper.Fill('A', 31), + new ushort[] { 0, 0, 0 }, + new ushort[] { 0, 0, 0 } + ); + public static readonly IccNamedColor NamedColor_ValRand = new IccNamedColor + ( + ArrayHelper.Fill('5', 31), + new ushort[] { 10794, 10794, 10794 }, + new ushort[] { 17219, 17219, 17219, 17219, 17219 } + ); + public static readonly IccNamedColor NamedColor_ValMax = new IccNamedColor + ( + ArrayHelper.Fill('4', 31), + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue }, + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue } + ); + + public static readonly byte[] NamedColor_Min = CreateNamedColor(3, 0x41, 0x00, 0x00); + public static readonly byte[] NamedColor_Rand = CreateNamedColor(5, 0x35, 42, 67); + public static readonly byte[] NamedColor_Max = CreateNamedColor(4, 0x34, 0xFF, 0xFF); + + private static byte[] CreateNamedColor(int devCoordCount, byte name, byte PCS, byte device) + { + byte[] data = new byte[32 + 6 + devCoordCount * 2]; + for (int i = 0; i < data.Length; i++) + { + if (i < 31) { data[i] = name; } // Name + else if (i == 31) { data[i] = 0x00; } // Name null terminator + else if (i < 32 + 6) { data[i] = PCS; } // PCS Coordinates + else { data[i] = device; } // Device Coordinates + } + return data; + } + + public static readonly object[][] NamedColorTestData = + { + new object[] { NamedColor_Min, NamedColor_ValMin, 3u }, + new object[] { NamedColor_Rand, NamedColor_ValRand, 5u }, + new object[] { NamedColor_Max, NamedColor_ValMax, 4u }, + }; + + #endregion + + #region ProfileDescription + + private static readonly CultureInfo CultureEnUs = new CultureInfo("en-US"); + private static readonly CultureInfo CultureDeAT = new CultureInfo("de-AT"); + + private static readonly IccLocalizedString LocalizedString_Rand1 = new IccLocalizedString(CultureEnUs, IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2 = new IccLocalizedString(CultureDeAT, IccTestDataPrimitives.Unicode_ValRand3); + + private static readonly IccLocalizedString[] LocalizedString_RandArr1 = new IccLocalizedString[] + { + LocalizedString_Rand1, + LocalizedString_Rand2, + }; + private static readonly IccLocalizedString[] LocalizedString_RandArr2 = new IccLocalizedString[] + { + LocalizedString_Rand2, + LocalizedString_Rand1, + }; + + private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); + private static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { (byte)'d', (byte)'e', (byte)'A', (byte)'T' }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry + ( + IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), + 1701729619, 2 + ); + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x07 }, // 7 + IccTestDataPrimitives.Unicode_Rand2, + new byte[] { 0x00, 0x00 }, // Null terminator + + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + ArrayHelper.Fill((byte)0x41, 66), + new byte[] { 0x00 } // Null terminator + ); + + public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription + ( + 1, 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + MultiLocalizedUnicode_Val.Texts, + MultiLocalizedUnicode_Val.Texts + ); + + public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription + ( + 1, 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + new IccLocalizedString[] { LocalizedString_Rand1 }, + new IccLocalizedString[] { LocalizedString_Rand1 } + ); + + public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + + new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + MultiLocalizedUnicode_Arr, + new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + MultiLocalizedUnicode_Arr + ); + + public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + TextDescription_Arr1, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + TextDescription_Arr1 + ); + + public static readonly object[][] ProfileDescriptionReadTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + new object[] { ProfileDescription_Rand2, ProfileDescription_ValRand2 }, + }; + + public static readonly object[][] ProfileDescriptionWriteTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + }; + + #endregion + + #region ColorantTableEntry + + public static readonly IccColorantTableEntry ColorantTableEntry_ValRand1 = new IccColorantTableEntry(ArrayHelper.Fill('A', 31), 1, 2, 3); + public static readonly IccColorantTableEntry ColorantTableEntry_ValRand2 = new IccColorantTableEntry(ArrayHelper.Fill('4', 31), 4, 5, 6); + + public static readonly byte[] ColorantTableEntry_Rand1 = ArrayHelper.Concat + ( + ArrayHelper.Fill((byte)0x41, 31), + new byte[1], // null terminator + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat + ( + ArrayHelper.Fill((byte)0x34, 31), + new byte[1], // null terminator + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6 + ); + + public static readonly object[][] ColorantTableEntryTestData = + { + new object[] { ColorantTableEntry_Rand1, ColorantTableEntry_ValRand1 }, + new object[] { ColorantTableEntry_Rand2, ColorantTableEntry_ValRand2 }, + }; + + #endregion + + #region ScreeningChannel + + public static readonly IccScreeningChannel ScreeningChannel_ValRand1 = new IccScreeningChannel(4, 6, IccScreeningSpotType.Cross); + public static readonly IccScreeningChannel ScreeningChannel_ValRand2 = new IccScreeningChannel(8, 5, IccScreeningSpotType.Diamond); + + public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Int32_7 + ); + + public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_8, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Int32_3 + ); + + public static readonly object[][] ScreeningChannelTestData = + { + new object[] { ScreeningChannel_Rand1, ScreeningChannel_ValRand1 }, + new object[] { ScreeningChannel_Rand2, ScreeningChannel_ValRand2 }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs new file mode 100644 index 000000000..fcfa2d0d7 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs @@ -0,0 +1,332 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataPrimitives + { + #region UInt16 + + public static readonly byte[] UInt16_0 = { 0x00, 0x00 }; + public static readonly byte[] UInt16_1 = { 0x00, 0x01 }; + public static readonly byte[] UInt16_2 = { 0x00, 0x02 }; + public static readonly byte[] UInt16_3 = { 0x00, 0x03 }; + public static readonly byte[] UInt16_4 = { 0x00, 0x04 }; + public static readonly byte[] UInt16_5 = { 0x00, 0x05 }; + public static readonly byte[] UInt16_6 = { 0x00, 0x06 }; + public static readonly byte[] UInt16_7 = { 0x00, 0x07 }; + public static readonly byte[] UInt16_8 = { 0x00, 0x08 }; + public static readonly byte[] UInt16_9 = { 0x00, 0x09 }; + public static readonly byte[] UInt16_32768 = { 0x80, 0x00 }; + public static readonly byte[] UInt16_Max = { 0xFF, 0xFF }; + + #endregion + + #region Int16 + + public static readonly byte[] Int16_Min = { 0x80, 0x00 }; + public static readonly byte[] Int16_0 = { 0x00, 0x00 }; + public static readonly byte[] Int16_1 = { 0x00, 0x01 }; + public static readonly byte[] Int16_2 = { 0x00, 0x02 }; + public static readonly byte[] Int16_3 = { 0x00, 0x03 }; + public static readonly byte[] Int16_4 = { 0x00, 0x04 }; + public static readonly byte[] Int16_5 = { 0x00, 0x05 }; + public static readonly byte[] Int16_6 = { 0x00, 0x06 }; + public static readonly byte[] Int16_7 = { 0x00, 0x07 }; + public static readonly byte[] Int16_8 = { 0x00, 0x08 }; + public static readonly byte[] Int16_9 = { 0x00, 0x09 }; + public static readonly byte[] Int16_Max = { 0x7F, 0xFF }; + + #endregion + + #region UInt32 + + public static readonly byte[] UInt32_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UInt32_1 = { 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] UInt32_2 = { 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] UInt32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt32_8 = { 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] UInt32_9 = { 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] UInt32_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; + + + public static readonly uint UInt32_ValRand1 = 1749014123; + public static readonly uint UInt32_ValRand2 = 3870560989; + public static readonly uint UInt32_ValRand3 = 1050090334; + public static readonly uint UInt32_ValRand4 = 3550252874; + + public static readonly byte[] UInt32_Rand1 = { 0x68, 0x3F, 0xD6, 0x6B }; + public static readonly byte[] UInt32_Rand2 = { 0xE6, 0xB4, 0x12, 0xDD }; + public static readonly byte[] UInt32_Rand3 = { 0x3E, 0x97, 0x1B, 0x5E }; + public static readonly byte[] UInt32_Rand4 = { 0xD3, 0x9C, 0x8F, 0x4A }; + + #endregion + + #region Int32 + + public static readonly byte[] Int32_Min = { 0x80, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int32_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int32_1 = { 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] Int32_2 = { 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int32_8 = { 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] Int32_9 = { 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] Int32_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region UInt64 + + public static readonly byte[] UInt64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UInt64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] UInt64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] UInt64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] UInt64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] UInt64_Max = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Int64 + + public static readonly byte[] Int64_Min = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] Int64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] Int64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] Int64_Max = { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Single + + public static readonly byte[] Single_Min = { 0xFF, 0x7F, 0xFF, 0xFF }; + public static readonly byte[] Single_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_1 = { 0x3F, 0x80, 0x00, 0x00 }; + public static readonly byte[] Single_2 = { 0x40, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_3 = { 0x40, 0x40, 0x00, 0x00 }; + public static readonly byte[] Single_4 = { 0x40, 0x80, 0x00, 0x00 }; + public static readonly byte[] Single_5 = { 0x40, 0xA0, 0x00, 0x00 }; + public static readonly byte[] Single_6 = { 0x40, 0xC0, 0x00, 0x00 }; + public static readonly byte[] Single_7 = { 0x40, 0xE0, 0x00, 0x00 }; + public static readonly byte[] Single_8 = { 0x41, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_9 = { 0x41, 0x10, 0x00, 0x00 }; + public static readonly byte[] Single_Max = { 0x7F, 0x7F, 0xFF, 0xFF }; + + #endregion + + #region Double + + public static readonly byte[] Double_Min = { 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + public static readonly byte[] Double_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Double_1 = { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Double_Max = { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Fix16 + + public const float Fix16_ValMin = short.MinValue; + public const float Fix16_ValMax = short.MaxValue + 65535f / 65536f; + + public static readonly byte[] Fix16_Min = { 0x80, 0x00, 0x00, 0x00 }; + public static readonly byte[] Fix16_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Fix16_1 = { 0x00, 0x01, 0x00, 0x00 }; + public static readonly byte[] Fix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] Fix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] Fix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] Fix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] Fix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] Fix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] Fix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] Fix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] Fix16_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] Fix16TestData = + { + new object[] { Fix16_Min, Fix16_ValMin }, + new object[] { Fix16_0, 0 }, + new object[] { Fix16_4, 4 }, + new object[] { Fix16_Max, Fix16_ValMax }, + }; + + #endregion + + #region UFix16 + + public const float UFix16_ValMin = 0; + public const float UFix16_ValMax = ushort.MaxValue + 65535f / 65536f; + + public static readonly byte[] UFix16_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UFix16_1 = { 0x00, 0x01, 0x00, 0x00 }; + public static readonly byte[] UFix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] UFix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] UFix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] UFix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] UFix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] UFix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] UFix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] UFix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] UFix16_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] UFix16TestData = + { + new object[] { UFix16_0, 0 }, + new object[] { UFix16_4, 4 }, + new object[] { UFix16_Max, UFix16_ValMax }, + }; + + #endregion + + #region U1Fix15 + + public const float U1Fix15_ValMin = 0; + public const float U1Fix15_ValMax = 1f + 32767f / 32768f; + + public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; + public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; + public static readonly byte[] U1Fix15_Max = { 0xFF, 0xFF }; + + public static readonly object[][] U1Fix15TestData = + { + new object[] { U1Fix15_0, 0 }, + new object[] { U1Fix15_1, 1 }, + new object[] { U1Fix15_Max, U1Fix15_ValMax }, + }; + + #endregion + + #region UFix8 + + public const float UFix8_ValMin = 0; + public const float UFix8_ValMax = byte.MaxValue + 255f / 256f; + + public static readonly byte[] UFix8_0 = { 0x00, 0x00 }; + public static readonly byte[] UFix8_1 = { 0x01, 0x00 }; + public static readonly byte[] UFix8_2 = { 0x02, 0x00 }; + public static readonly byte[] UFix8_3 = { 0x03, 0x00 }; + public static readonly byte[] UFix8_4 = { 0x04, 0x00 }; + public static readonly byte[] UFix8_5 = { 0x05, 0x00 }; + public static readonly byte[] UFix8_6 = { 0x06, 0x00 }; + public static readonly byte[] UFix8_7 = { 0x07, 0x00 }; + public static readonly byte[] UFix8_8 = { 0x08, 0x00 }; + public static readonly byte[] UFix8_9 = { 0x09, 0x00 }; + public static readonly byte[] UFix8_Max = { 0xFF, 0xFF }; + + public static readonly object[][] UFix8TestData = + { + new object[] { UFix8_0, 0 }, + new object[] { UFix8_4, 4 }, + new object[] { UFix8_Max, UFix8_ValMax }, + }; + + #endregion + + #region ASCII String + + public const string Ascii_ValRand = "aBcdEf1234"; + public const string Ascii_ValRand1 = "Ecf3a"; + public const string Ascii_ValRand2 = "2Bd4c"; + public const string Ascii_ValRand3 = "cad14"; + public const string Ascii_ValRand4 = "fd4E1"; + public const string Ascii_ValRandLength4 = "aBcd"; + public const string Ascii_ValNullRand = "aBcd\0Ef\0123"; + + public static readonly byte[] Ascii_Rand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52 }; + public static readonly byte[] Ascii_Rand1 = { 69, 99, 102, 51, 97 }; + public static readonly byte[] Ascii_Rand2 = { 50, 66, 100, 52, 99 }; + public static readonly byte[] Ascii_Rand3 = { 99, 97, 100, 49, 52 }; + public static readonly byte[] Ascii_Rand4 = { 102, 100, 52, 69, 49 }; + public static readonly byte[] Ascii_RandLength4 = { 97, 66, 99, 100 }; + public static readonly byte[] Ascii_PaddedRand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52, 0, 0, 0, 0 }; + public static readonly byte[] Ascii_NullRand = { 97, 66, 99, 100, 0, 69, 102, 0, 49, 50, 51 }; + + public const int Ascii_Rand_Length = 10; + public const int Ascii_PaddedRand_Length = 14; + public const int Ascii_NullRand_Length = 11; + public const int Ascii_NullRand_LengthNoNull = 4; + + public static readonly object[][] AsciiTestData = + { + new object[] { Ascii_Rand, Ascii_Rand_Length, Ascii_ValRand }, + new object[] { Ascii_Rand, 4, Ascii_ValRandLength4 }, + new object[] { Ascii_NullRand, Ascii_NullRand_LengthNoNull, Ascii_ValRandLength4 }, + }; + + public static readonly object[][] AsciiWriteTestData = + { + new object[] { Ascii_Rand, Ascii_ValRand }, + new object[] { Ascii_NullRand, Ascii_ValNullRand }, + }; + + public static readonly object[][] AsciiPaddingTestData = + { + new object[] { Ascii_PaddedRand, Ascii_PaddedRand_Length, Ascii_ValRand, true }, + new object[] { Ascii_RandLength4, 4, Ascii_ValRand, false }, + }; + + #endregion + + #region Unicode String + + public const string Unicode_ValRand1 = ".6Abäñ$€β𐐷𤭢"; + public const string Unicode_ValRand2 = ".6Abäñ"; + public const string Unicode_ValRand3 = "$€β𐐷𤭢"; + + public static readonly byte[] Unicode_Rand1 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; + + public static readonly byte[] Unicode_Rand2 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + }; + + public static readonly byte[] Unicode_Rand3 = + { + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs new file mode 100644 index 000000000..20fff50a8 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System; +using System.Numerics; + +namespace ImageSharp.Tests +{ + internal static class IccTestDataProfiles + { + public static readonly IccProfileHeader Header_Random_Write = new IccProfileHeader + { + Class = IccProfileClass.DisplayDevice, + CmmType = "abcd", + CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), + CreatorSignature = "dcba", + DataColorSpace = IccColorSpaceType.Rgb, + DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, + DeviceManufacturer = 123456789u, + DeviceModel = 987654321u, + FileSignature = "ijkl", // should be overwritten to "acsp" + Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, + Id = new IccProfileId(1, 2, 3, 4), // should be overwritten + PcsIlluminant = new Vector3(4, 5, 6), + PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, + Size = 562, // should be overwritten + Version = new Version(4, 3, 0), + }; + + public static readonly IccProfileHeader Header_Random_Read = new IccProfileHeader + { + Class = IccProfileClass.DisplayDevice, + CmmType = "abcd", + CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), + CreatorSignature = "dcba", + DataColorSpace = IccColorSpaceType.Rgb, + DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, + DeviceManufacturer = 123456789u, + DeviceModel = 987654321u, + FileSignature = "acsp", + Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, +#if !NETSTANDARD1_1 + Id = new IccProfileId(2931428592, 418415738, 3086756963, 2237536530), +#else + Id = IccProfileId.Zero, +#endif + PcsIlluminant = new Vector3(4, 5, 6), + PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, + Size = 132, + Version = new Version(4, 3, 0), + }; + + public static readonly byte[] Header_Random_Array = + { + 0x00, 0x00, 0x00, 0x84, // Size (132) + 0x61, 0x62, 0x63, 0x64, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x00, 0x00, 0x00, 0x03, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + +#if !NETSTANDARD1_1 + 0xAE, 0xBA, 0x0C, 0xF0, 0x18, 0xF0, 0x84, 0x7A, 0xB7, 0xFC, 0x2C, 0x63, 0x85, 0x5E, 0x19, 0x12, // Id +#else + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Id +#endif + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Nr of tag table entries (0) + 0x00, 0x00, 0x00, 0x00, + }; + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs new file mode 100644 index 000000000..769ec3a01 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -0,0 +1,1004 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Globalization; + using System.Numerics; + + internal static class IccTestDataTagDataEntry + { + #region TagDataEntry Header + + public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; + public static readonly byte[] TagDataEntryHeader_UnknownArr = + { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly IccTypeSignature TagDataEntryHeader_MultiLocalizedUnicodeVal = IccTypeSignature.MultiLocalizedUnicode; + public static readonly byte[] TagDataEntryHeader_MultiLocalizedUnicodeArr = + { + 0x6D, 0x6C, 0x75, 0x63, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly IccTypeSignature TagDataEntryHeader_CurveVal = IccTypeSignature.Curve; + public static readonly byte[] TagDataEntryHeader_CurveArr = + { + 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly object[][] TagDataEntryHeaderTestData = + { + new object[] { TagDataEntryHeader_UnknownArr, TagDataEntryHeader_UnknownVal }, + new object[] { TagDataEntryHeader_MultiLocalizedUnicodeArr, TagDataEntryHeader_MultiLocalizedUnicodeVal }, + new object[] { TagDataEntryHeader_CurveArr, TagDataEntryHeader_CurveVal }, + }; + + #endregion + + #region UnknownTagDataEntry + + public static readonly IccUnknownTagDataEntry Unknown_Val = new IccUnknownTagDataEntry(new byte[] { 0x00, 0x01, 0x02, 0x03 }); + public static readonly byte[] Unknown_Arr = { 0x00, 0x01, 0x02, 0x03 }; + + public static readonly object[][] UnknownTagDataEntryTestData = + { + new object[] { Unknown_Arr, Unknown_Val, 12u }, + }; + + #endregion + + #region ChromaticityTagDataEntry + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val1 = new IccChromaticityTagDataEntry(IccColorantEncoding.ItuRBt709_2); + public static readonly byte[] Chromaticity_Arr1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_1, + + new byte[] { 0x00, 0x00, 0xA3, 0xD7 }, // 0.640 + new byte[] { 0x00, 0x00, 0x54, 0x7B }, // 0.330 + + new byte[] { 0x00, 0x00, 0x4C, 0xCD }, // 0.300 + new byte[] { 0x00, 0x00, 0x99, 0x9A }, // 0.600 + + new byte[] { 0x00, 0x00, 0x26, 0x66 }, // 0.150 + new byte[] { 0x00, 0x00, 0x0F, 0x5C } // 0.060 + ); + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry + ( + new double[][] + { + new double[] { 1, 2 }, + new double[] { 3, 4 }, + } + ); + public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_0, + + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UFix16_2, + + IccTestDataPrimitives.UFix16_3, + IccTestDataPrimitives.UFix16_4 + ); + + /// + /// : channel count must be 3 for any enum other than + /// + public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_1 + ); + + /// + /// : invalid enum value + /// + public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_9 + ); + + public static readonly object[][] ChromaticityTagDataEntryTestData = + { + new object[] { Chromaticity_Arr1, Chromaticity_Val1 }, + new object[] { Chromaticity_Arr2, Chromaticity_Val2 }, + }; + + #endregion + + #region ColorantOrderTagDataEntry + + public static readonly IccColorantOrderTagDataEntry ColorantOrder_Val = new IccColorantOrderTagDataEntry(new byte[] { 0x00, 0x01, 0x02 }); + public static readonly byte[] ColorantOrder_Arr = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_3, new byte[] { 0x00, 0x01, 0x02 }); + + public static readonly object[][] ColorantOrderTagDataEntryTestData = + { + new object[] { ColorantOrder_Arr, ColorantOrder_Val }, + }; + + #endregion + + #region ColorantTableTagDataEntry + + public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry + ( + new IccColorantTableEntry[] + { + IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, + IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 + } + ); + public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ColorantTableEntry_Rand1, + IccTestDataNonPrimitives.ColorantTableEntry_Rand2 + ); + + public static readonly object[][] ColorantTableTagDataEntryTestData = + { + new object[] { ColorantTable_Arr, ColorantTable_Val }, + }; + + #endregion + + #region CurveTagDataEntry + + public static readonly IccCurveTagDataEntry Curve_Val_0 = new IccCurveTagDataEntry(); + public static readonly byte[] Curve_Arr_0 = IccTestDataPrimitives.UInt32_0; + + public static readonly IccCurveTagDataEntry Curve_Val_1 = new IccCurveTagDataEntry(1f); + public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UFix8_1 + ); + + public static readonly IccCurveTagDataEntry Curve_Val_2 = new IccCurveTagDataEntry(new float[] { 1 / 65535f, 2 / 65535f, 3 / 65535f }); + public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly object[][] CurveTagDataEntryTestData = + { + new object[] { Curve_Arr_0, Curve_Val_0 }, + new object[] { Curve_Arr_1, Curve_Val_1 }, + new object[] { Curve_Arr_2, Curve_Val_2 }, + }; + + #endregion + + #region DataTagDataEntry + + public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry + ( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + false + ); + public static readonly byte[] Data_ArrNoASCII = + { + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x04 + }; + + public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry + ( + new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, + true + ); + public static readonly byte[] Data_ArrASCII = + { + 0x00, 0x00, 0x00, 0x01, + (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' + }; + + public static readonly object[][] DataTagDataEntryTestData = + { + new object[] { Data_ArrNoASCII, Data_ValNoASCII, 16u }, + new object[] { Data_ArrASCII, Data_ValASCII, 17u }, + }; + + #endregion + + #region DateTimeTagDataEntry + + public static readonly IccDateTimeTagDataEntry DateTime_Val = new IccDateTimeTagDataEntry(IccTestDataNonPrimitives.DateTime_ValRand1); + public static readonly byte[] DateTime_Arr = IccTestDataNonPrimitives.DateTime_Rand1; + + public static readonly object[][] DateTimeTagDataEntryTestData = + { + new object[] { DateTime_Arr, DateTime_Val }, + }; + + #endregion + + #region Lut16TagDataEntry + + public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry + ( + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad }, + IccTestDataLut.CLUT16_ValGrad, + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad } + ); + public static readonly byte[] Lut16_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x03, 0x00 }, + IccTestDataMatrix.Fix16_2D_Identity, + new byte[] { 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length, 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length }, + + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad, + + IccTestDataLut.CLUT16_Grad, + + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad + ); + + public static readonly object[][] Lut16TagDataEntryTestData = + { + new object[] { Lut16_Arr, Lut16_Val }, + }; + + #endregion + + #region Lut8TagDataEntry + + public static readonly IccLut8TagDataEntry Lut8_Val = new IccLut8TagDataEntry + ( + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad }, + IccTestDataLut.CLUT8_ValGrad, + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad } + ); + public static readonly byte[] Lut8_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x03, 0x00 }, + IccTestDataMatrix.Fix16_2D_Identity, + + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad, + + IccTestDataLut.CLUT8_Grad, + + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad + ); + + public static readonly object[][] Lut8TagDataEntryTestData = + { + new object[] { Lut8_Arr, Lut8_Val }, + }; + + #endregion + + #region LutAToBTagDataEntry + + private static readonly byte[] CurveFull_0 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_0 + ); + private static readonly byte[] CurveFull_1 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_1 + ); + private static readonly byte[] CurveFull_2 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_2 + ); + + public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry + ( + new IccCurveTagDataEntry[] + { + Curve_Val_0, + Curve_Val_1, + Curve_Val_2, + }, + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad, + new IccCurveTagDataEntry[] + { + Curve_Val_1, + Curve_Val_2, + Curve_Val_0, + }, + IccTestDataLut.CLUT_Val16, + new IccCurveTagDataEntry[] + { + Curve_Val_2, + Curve_Val_1, + } + ); + public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 + new byte[] { 0x00, 0x00, 0x00, 0x50 }, // matrix: 80 + new byte[] { 0x00, 0x00, 0x00, 0x80 }, // m: 128 + new byte[] { 0x00, 0x00, 0x00, 0xB0 }, // clut: 176 + new byte[] { 0x00, 0x00, 0x00, 0xFC }, // a: 252 + + // B + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // Matrix + IccTestDataMatrix.Fix16_2D_Grad, // 36 bytes + IccTestDataMatrix.Fix16_1D_Grad, // 12 bytes + + // M + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0, // 12 bytes + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 } // Padding + ); + + public static readonly object[][] LutAToBTagDataEntryTestData = + { + new object[] { LutAToB_Arr, LutAToB_Val }, + }; + + #endregion + + #region LutBToATagDataEntry + + public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry + ( + new IccCurveTagDataEntry[] + { + Curve_Val_0, + Curve_Val_1, + }, + null, + null, + null, + IccTestDataLut.CLUT_Val16, + new IccCurveTagDataEntry[] + { + Curve_Val_2, + Curve_Val_1, + Curve_Val_0, + } + ); + public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // matrix: 0 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // m: 0 + new byte[] { 0x00, 0x00, 0x00, 0x3C }, // clut: 60 + new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 + + // B + CurveFull_0, //12 bytes + CurveFull_1, //14 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0 // 12 bytes + ); + + public static readonly object[][] LutBToATagDataEntryTestData = + { + new object[] { LutBToA_Arr, LutBToA_Val }, + }; + + #endregion + + #region MeasurementTagDataEntry + + public static readonly IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry + ( + IccStandardObserver.Cie1931Observer, IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccMeasurementGeometry.Degree0ToDOrDTo0, 1f, IccStandardIlluminant.D50 + ); + public static readonly byte[] Measurement_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UInt32_1 + ); + + public static readonly object[][] MeasurementTagDataEntryTestData = + { + new object[] { Measurement_Arr, Measurement_Val }, + }; + + #endregion + + #region MultiLocalizedUnicodeTagDataEntry + + private static readonly IccLocalizedString LocalizedString_Rand1 = new IccLocalizedString(new CultureInfo("en-US"), IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2 = new IccLocalizedString(new CultureInfo("de-DE"), IccTestDataPrimitives.Unicode_ValRand3); + + private static readonly IccLocalizedString[] LocalizedString_RandArr1 = new IccLocalizedString[] + { + LocalizedString_Rand1, + LocalizedString_Rand2, + }; + private static readonly IccLocalizedString[] LocalizedString_RandArr2 = new IccLocalizedString[] + { + LocalizedString_Rand2, + LocalizedString_Rand1, + }; + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); + public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData = + { + new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, + }; + + #endregion + + #region MultiProcessElementsTagDataEntry + + public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry + ( + new IccMultiProcessElement[] + { + IccTestDataMultiProcessElement.MPE_ValCLUT, + IccTestDataMultiProcessElement.MPE_ValCLUT, + } + ); + public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt32_2, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // 32 + new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 + + new byte[] { 0x00, 0x00, 0x00, 0xA4 }, // 164 + new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 + + IccTestDataMultiProcessElement.MPE_CLUT, + IccTestDataMultiProcessElement.MPE_CLUT + ); + + public static readonly object[][] MultiProcessElementsTagDataEntryTestData = + { + new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, + }; + + #endregion + + #region NamedColor2TagDataEntry + + public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry + ( + 16909060, + ArrayHelper.Fill('A', 31), ArrayHelper.Fill('4', 31), + new IccNamedColor[] + { + IccTestDataNonPrimitives.NamedColor_ValMin, + IccTestDataNonPrimitives.NamedColor_ValMin + } + ); + public static readonly byte[] NamedColor2_Arr = ArrayHelper.Concat + ( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + ArrayHelper.Fill((byte)0x41, 31), + new byte[] { 0x00 }, + ArrayHelper.Fill((byte)0x34, 31), + new byte[] { 0x00 }, + IccTestDataNonPrimitives.NamedColor_Min, + IccTestDataNonPrimitives.NamedColor_Min + ); + + public static readonly object[][] NamedColor2TagDataEntryTestData = + { + new object[] { NamedColor2_Arr, NamedColor2_Val }, + }; + + #endregion + + #region ParametricCurveTagDataEntry + + public static readonly IccParametricCurveTagDataEntry ParametricCurve_Val = new IccParametricCurveTagDataEntry(IccTestDataCurves.Parametric_ValVar1); + public static readonly byte[] ParametricCurve_Arr = IccTestDataCurves.Parametric_Var1; + + public static readonly object[][] ParametricCurveTagDataEntryTestData = + { + new object[] { ParametricCurve_Arr, ParametricCurve_Val }, + }; + + #endregion + + #region ProfileSequenceDescTagDataEntry + + public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry + ( + new IccProfileDescription[] + { + IccTestDataNonPrimitives.ProfileDescription_ValRand1, + IccTestDataNonPrimitives.ProfileDescription_ValRand1 + } + ); + public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ProfileDescription_Rand1, + IccTestDataNonPrimitives.ProfileDescription_Rand1 + ); + + public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = + { + new object[] { ProfileSequenceDesc_Arr, ProfileSequenceDesc_Val }, + }; + + #endregion + + #region ProfileSequenceIdentifierTagDataEntry + + public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new IccProfileSequenceIdentifierTagDataEntry + ( + new IccProfileSequenceIdentifier[] + { + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr1), + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr1), + } + ); + public static readonly byte[] ProfileSequenceIdentifier_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + + new byte[] { 0x00, 0x00, 0x00, 0x1C }, // 28 + new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 + + new byte[] { 0x00, 0x00, 0x00, 0x70 }, // 112 + new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 + + IccTestDataNonPrimitives.ProfileId_Rand, // 16 bytes + TagDataEntryHeader_MultiLocalizedUnicodeArr, // 8 bytes + MultiLocalizedUnicode_Arr, // 58 bytes + new byte[] { 0x00, 0x00 }, // 2 bytes (padding) + + IccTestDataNonPrimitives.ProfileId_Rand, + TagDataEntryHeader_MultiLocalizedUnicodeArr, + MultiLocalizedUnicode_Arr, + new byte[] { 0x00, 0x00 } + ); + + public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = + { + new object[] { ProfileSequenceIdentifier_Arr, ProfileSequenceIdentifier_Val }, + }; + + #endregion + + #region ResponseCurveSet16TagDataEntry + + public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry + ( + new IccResponseCurve[] + { + IccTestDataCurves.Response_ValGrad, + IccTestDataCurves.Response_ValGrad, + } + ); + public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_2, + + new byte[] { 0x00, 0x00, 0x00, 0x14 }, // 20 + new byte[] { 0x00, 0x00, 0x00, 0x6C }, // 108 + + IccTestDataCurves.Response_Grad, // 88 bytes + IccTestDataCurves.Response_Grad // 88 bytes + ); + + public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = + { + new object[] { ResponseCurveSet16_Arr, ResponseCurveSet16_Val }, + }; + + #endregion + + #region Fix16ArrayTagDataEntry + + public static readonly IccFix16ArrayTagDataEntry Fix16Array_Val = new IccFix16ArrayTagDataEntry(new float[] { 1 / 256f, 2 / 256f, 3 / 256f }); + public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3 + ); + + public static readonly object[][] Fix16ArrayTagDataEntryTestData = + { + new object[] { Fix16Array_Arr, Fix16Array_Val, 20u }, + }; + + #endregion + + #region SignatureTagDataEntry + + public static readonly IccSignatureTagDataEntry Signature_Val = new IccSignatureTagDataEntry("ABCD"); + public static readonly byte[] Signature_Arr = { 0x41, 0x42, 0x43, 0x44, }; + + public static readonly object[][] SignatureTagDataEntryTestData = + { + new object[] { Signature_Arr, Signature_Val }, + }; + + #endregion + + #region TextTagDataEntry + + public static readonly IccTextTagDataEntry Text_Val = new IccTextTagDataEntry("ABCD"); + public static readonly byte[] Text_Arr = { 0x41, 0x42, 0x43, 0x44 }; + + public static readonly object[][] TextTagDataEntryTestData = + { + new object[] { Text_Arr, Text_Val, 12u }, + }; + + #endregion + + #region UFix16ArrayTagDataEntry + + public static readonly IccUFix16ArrayTagDataEntry UFix16Array_Val = new IccUFix16ArrayTagDataEntry(new float[] { 1, 2, 3 }); + public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UFix16_2, + IccTestDataPrimitives.UFix16_3 + ); + + public static readonly object[][] UFix16ArrayTagDataEntryTestData = + { + new object[] { UFix16Array_Arr, UFix16Array_Val, 20u }, + }; + + #endregion + + #region UInt16ArrayTagDataEntry + + public static readonly IccUInt16ArrayTagDataEntry UInt16Array_Val = new IccUInt16ArrayTagDataEntry(new ushort[] { 1, 2, 3 }); + public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly object[][] UInt16ArrayTagDataEntryTestData = + { + new object[] { UInt16Array_Arr, UInt16Array_Val, 14u }, + }; + + #endregion + + #region UInt32ArrayTagDataEntry + + public static readonly IccUInt32ArrayTagDataEntry UInt32Array_Val = new IccUInt32ArrayTagDataEntry(new uint[] { 1, 2, 3 }); + public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3 + ); + + public static readonly object[][] UInt32ArrayTagDataEntryTestData = + { + new object[] { UInt32Array_Arr, UInt32Array_Val, 20u }, + }; + + #endregion + + #region UInt64ArrayTagDataEntry + + public static readonly IccUInt64ArrayTagDataEntry UInt64Array_Val = new IccUInt64ArrayTagDataEntry(new ulong[] { 1, 2, 3 }); + public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3 + ); + + public static readonly object[][] UInt64ArrayTagDataEntryTestData = + { + new object[] { UInt64Array_Arr, UInt64Array_Val, 32u }, + }; + + #endregion + + #region UInt8ArrayTagDataEntry + + public static readonly IccUInt8ArrayTagDataEntry UInt8Array_Val = new IccUInt8ArrayTagDataEntry(new byte[] { 1, 2, 3 }); + public static readonly byte[] UInt8Array_Arr = { 1, 2, 3 }; + + public static readonly object[][] UInt8ArrayTagDataEntryTestData = + { + new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, + }; + + #endregion + + #region ViewingConditionsTagDataEntry + + public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry + ( + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccStandardIlluminant.D50 + ); + public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat + ( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataPrimitives.UInt32_1 + ); + + public static readonly object[][] ViewingConditionsTagDataEntryTestData = + { + new object[] { ViewingConditions_Arr, ViewingConditions_Val }, + }; + + #endregion + + #region XYZTagDataEntry + + public static readonly IccXyzTagDataEntry XYZ_Val = new IccXyzTagDataEntry(new Vector3[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccTestDataNonPrimitives.XyzNumber_ValVar3, + }); + public static readonly byte[] XYZ_Arr = ArrayHelper.Concat + ( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataNonPrimitives.XyzNumber_Var3 + ); + + public static readonly object[][] XYZTagDataEntryTestData = + { + new object[] { XYZ_Arr, XYZ_Val, 44u }, + }; + + #endregion + + #region TextDescriptionTagDataEntry + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry + ( + IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), + 1701729619, 2 + ); + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + IccTestDataPrimitives.Unicode_Rand1, + new byte[] { 0x00, 0x00 }, // Null terminator + + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + ArrayHelper.Fill((byte)0x41, 66), + new byte[] { 0x00 } // Null terminator + ); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val2 = new IccTextDescriptionTagDataEntry(IccTestDataPrimitives.Ascii_ValRand, null, null, 0, 0); + public static readonly byte[] TextDescription_Arr2 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + IccTestDataPrimitives.UInt32_0, + IccTestDataPrimitives.UInt32_0, + + new byte[] { 0x00, 0x00, 0x00 }, // 0, 0 + ArrayHelper.Fill((byte)0x00, 67) + ); + + public static readonly object[][] TextDescriptionTagDataEntryTestData = + { + new object[] { TextDescription_Arr1, TextDescription_Val1 }, + new object[] { TextDescription_Arr2, TextDescription_Val2 }, + }; + + #endregion + + #region CrdInfoTagDataEntry + + public static readonly IccCrdInfoTagDataEntry CrdInfo_Val = new IccCrdInfoTagDataEntry( + IccTestDataPrimitives.Ascii_ValRand4, + IccTestDataPrimitives.Ascii_ValRand1, + IccTestDataPrimitives.Ascii_ValRand2, + IccTestDataPrimitives.Ascii_ValRand3, + IccTestDataPrimitives.Ascii_ValRand4 + ); + public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand1, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand2, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand3, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 } + ); + + public static readonly object[][] CrdInfoTagDataEntryTestData = + { + new object[] { CrdInfo_Arr, CrdInfo_Val }, + }; + + #endregion + + #region ScreeningTagDataEntry + + public static readonly IccScreeningTagDataEntry Screening_Val = new IccScreeningTagDataEntry( + IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, + new IccScreeningChannel[] + { + IccTestDataNonPrimitives.ScreeningChannel_ValRand1, + IccTestDataNonPrimitives.ScreeningChannel_ValRand2, + } + ); + public static readonly byte[] Screening_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ScreeningChannel_Rand1, + IccTestDataNonPrimitives.ScreeningChannel_Rand2 + ); + + public static readonly object[][] ScreeningTagDataEntryTestData = + { + new object[] { Screening_Arr, Screening_Val }, + }; + + #endregion + + #region UcrBgTagDataEntry + + public static readonly IccUcrBgTagDataEntry UcrBg_Val = new IccUcrBgTagDataEntry( + new ushort[] { 3, 4, 6 }, + new ushort[] { 9, 7, 2, 5 }, + IccTestDataPrimitives.Ascii_ValRand + ); + public static readonly byte[] UcrBg_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_6, + + IccTestDataPrimitives.UInt32_4, + IccTestDataPrimitives.UInt16_9, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_5, + + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0 } + ); + + public static readonly object[][] UcrBgTagDataEntryTestData = + { + new object[] { UcrBg_Arr, UcrBg_Val, 41 }, + }; + + #endregion + + #region TagDataEntry + + public static readonly IccTagDataEntry TagDataEntry_CurveVal = Curve_Val_2; + public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_2, + new byte[] { 0x00, 0x00 } // padding + ); + + public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; + public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat + ( + TagDataEntryHeader_MultiLocalizedUnicodeArr, + MultiLocalizedUnicode_Arr, + new byte[] { 0x00, 0x00 } // padding + ); + + public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new IccTagTableEntry + ( + IccProfileTag.Unknown, 0, + (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2 + ); + + public static readonly IccTagTableEntry TagDataEntry_CurveTable = new IccTagTableEntry + ( + IccProfileTag.Unknown, 0, + (uint)TagDataEntry_CurveArr.Length - 2 + ); + + public static readonly object[][] TagDataEntryTestData = + { + new object[] { TagDataEntry_CurveArr, TagDataEntry_CurveVal }, + new object[] { TagDataEntry_MultiLocalizedUnicodeArr, TagDataEntry_MultiLocalizedUnicodeVal }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs new file mode 100644 index 000000000..d3b4da9b9 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Linq; + + public static class ArrayHelper + { + /// + /// Concatenates multiple arrays of the same type into one + /// + /// The array type + /// The arrays to concatenate. The order is kept + /// The concatenated array + public static T[] Concat(params T[][] arrs) + { + T[] result = new T[arrs.Sum(t => t.Length)]; + int offset = 0; + for (int i = 0; i < arrs.Length; i++) + { + arrs[i].CopyTo(result, offset); + offset += arrs[i].Length; + } + return result; + } + + /// + /// Creates an array filled with the given value + /// + /// The array type + /// The value to fill the array with + /// The wanted length of the array + /// The created array filled with the given value + public static T[] Fill(T value, int length) + { + T[] result = new T[length]; + for (int i = 0; i < length; i++) + { + result[i] = value; + } + return result; + } + + /// + /// Creates a string from a character with a given length + /// + /// The character to fill the string with + /// The wanted length of the string + /// The filled string + public static string Fill(char value, int length) + { + return "".PadRight(length, value); + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs b/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs new file mode 100644 index 000000000..dd3ef3a4f --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs @@ -0,0 +1,64 @@ +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Numerics; + + /// + /// Allows the comparison of single-precision floating point values by precision. + /// + public struct FloatRoundingComparer : IEqualityComparer, IEqualityComparer + { + /// + /// Initializes a new instance of the struct. + /// + /// The number of decimal places (valid values: 0-7) + public FloatRoundingComparer(int precision) + { + Guard.MustBeBetweenOrEqualTo(precision, 0, 7, nameof(precision)); + this.Precision = precision; + } + + /// + /// Gets the number of decimal places (valid values: 0-7) + /// + public int Precision { get; } + + /// + public bool Equals(float x, float y) + { + float xp = (float)Math.Round(x, this.Precision, MidpointRounding.AwayFromZero); + float yp = (float)Math.Round(y, this.Precision, MidpointRounding.AwayFromZero); + + return Comparer.Default.Compare(xp, yp) == 0; + } + + /// + public bool Equals(Vector4 x, Vector4 y) + { + return Equals(x.X, y.X) && Equals(x.Y, y.Y) && Equals(x.Z, y.Z) && Equals(x.W, y.W); + } + + /// + public int GetHashCode(float obj) + { + unchecked + { + int hashCode = obj.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Precision.GetHashCode(); + return hashCode; + } + } + + /// + public int GetHashCode(Vector4 obj) + { + unchecked + { + int hashCode = obj.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Precision.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file