From 16179b32113db9d1e5bba9013a8ec739ed00c00f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 27 Feb 2021 18:40:40 +0000 Subject: [PATCH] Copy original PR changes. --- .../Icc/Calculators/ClutCalculator.cs | 105 +++++++++++ .../Icc/Calculators/ColorTrcCalculator.cs | 54 ++++++ .../CurveCalculator.CalculationType.cs | 15 ++ .../Icc/Calculators/CurveCalculator.cs | 56 ++++++ .../Icc/Calculators/GrayTrcCalculator.cs | 25 +++ .../Icc/Calculators/ISingleCalculator.cs | 18 ++ .../Icc/Calculators/IVector4Calculator.cs | 20 ++ .../LutABCalculator.CalculationType.cs | 19 ++ .../Icc/Calculators/LutABCalculator.cs | 135 ++++++++++++++ .../Icc/Calculators/LutCalculator.cs | 73 ++++++++ .../Icc/Calculators/LutEntryCalculator.cs | 75 ++++++++ .../Icc/Calculators/MatrixCalculator.cs | 27 +++ .../Calculators/ParametricCurveCalculator.cs | 168 +++++++++++++++++ .../Icc/Calculators/TrcCalculator.cs | 47 +++++ .../Icc/IccConverterBase.Checks.cs | 173 ++++++++++++++++++ .../Icc/IccConverterBase.ConversionMethod.cs | 67 +++++++ .../Icc/IccConverterbase.Conversions.cs | 150 +++++++++++++++ .../Implementation/Icc/IccConverterbase.cs | 37 ++++ .../Icc/IccDataToDataConverter.cs | 22 +++ .../Icc/IccDataToPcsConverter.cs | 22 +++ .../Icc/IccPcsToDataConverter.cs | 22 +++ .../Icc/IccPcsToPcsConverter.cs | 22 +++ .../ICC/DataWriter/IccDataWriter.Matrix.cs | 27 --- .../Profiles/ICC/Enums/IccFormulaCurveType.cs | 2 +- .../Exceptions/InvalidIccProfileException.cs | 4 +- .../Icc/Calculators/ClutCalculatorTests.cs | 27 +++ .../Icc/Calculators/CurveCalculatorTests.cs | 26 +++ .../Icc/Calculators/LutABCalculatorTests.cs | 38 ++++ .../Icc/Calculators/LutCalculatorTests.cs | 25 +++ .../Calculators/LutEntryCalculatorTests.cs | 38 ++++ .../Icc/Calculators/MatrixCalculatorTests.cs | 26 +++ .../ParametricCurveCalculatorTests.cs | 26 +++ .../Icc/Calculators/TrcCalculatorTests.cs | 27 +++ .../Conversion/IccConversionData.Clut.cs | 163 +++++++++++++++++ .../Conversion/IccConversionData.Lut.cs | 30 +++ .../Conversion/IccConversionData.LutAB.cs | 44 +++++ .../Conversion/IccConversionData.LutEntry.cs | 65 +++++++ .../Conversion/IccConversionData.Matrix.cs | 45 +++++ .../IccConversionData.MultiProcessElement.cs | 76 ++++++++ .../Conversion/IccConversionData.Trc.cs | 77 ++++++++ 40 files changed, 2088 insertions(+), 30 deletions(-) create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ClutCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ColorTrcCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.CalculationType.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/GrayTrcCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ISingleCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/IVector4Calculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.CalculationType.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutEntryCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/TrcCalculator.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.ConversionMethod.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.Conversions.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToDataConverter.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToPcsConverter.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToDataConverter.cs create mode 100644 src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToPcsConverter.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ClutCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/CurveCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutABCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutEntryCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/MatrixCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ParametricCurveCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/TrcCalculatorTests.cs create mode 100644 tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Clut.cs create mode 100644 tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Lut.cs create mode 100644 tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutAB.cs create mode 100644 tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutEntry.cs create mode 100644 tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Matrix.cs create mode 100644 tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.MultiProcessElement.cs create mode 100644 tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Trc.cs diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ClutCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ClutCalculator.cs new file mode 100644 index 0000000000..7f2b355cd8 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ClutCalculator.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class ClutCalculator : IVector4Calculator + { + private int inputCount; + private int outputCount; + private float[][] lut; + private byte[] gridPointCount; + private int[] indexFactor; + private int nodeCount; + + public ClutCalculator(IccClut clut) + { + Guard.NotNull(clut, nameof(clut)); + + this.inputCount = clut.InputChannelCount; + this.outputCount = clut.OutputChannelCount; + this.lut = clut.Values; + this.gridPointCount = clut.GridPointCount; + this.indexFactor = this.CalculateIndexFactor(clut.InputChannelCount, clut.GridPointCount); + this.nodeCount = (int)Math.Pow(2, clut.InputChannelCount); + } + + public unsafe Vector4 Calculate(Vector4 value) + { + value = Vector4.Clamp(value, Vector4.Zero, Vector4.One); + + Vector4 result = default; + this.Interpolate((float*)&value, this.inputCount, (float*)&result, this.outputCount); + + return result; + } + + private int[] CalculateIndexFactor(int inputCount, byte[] gridPointCount) + { + int[] factors = new int[inputCount]; + int gpc = 1; + for (int j = inputCount - 1; j >= 0; j--) + { + factors[j] = gpc * (gridPointCount[j] - 1); + gpc *= gridPointCount[j]; + } + + return factors; + } + + private unsafe void Interpolate(float* values, int valueLength, float* result, int resultLength) + { + float[][] nodes = new float[this.nodeCount][]; + for (int i = 0; i < nodes.Length; i++) + { + int index = 0; + for (int j = 0; j < valueLength; j++) + { + float fraction = 1f / (this.gridPointCount[j] - 1); + int position = (int)(values[j] / fraction) + ((i >> j) & 1); + index += (int)((this.indexFactor[j] * (position * fraction)) + 0.5f); + } + + nodes[i] = this.lut[index]; + } + + Span factors = stackalloc float[this.nodeCount]; + for (int i = 0; i < factors.Length; i++) + { + float factor = 1; + for (int j = 0; j < valueLength; j++) + { + float fraction = 1f / (this.gridPointCount[j] - 1); + int position = (int)(values[j] / fraction); + + float low = position * fraction; + float high = (position + 1) * fraction; + float percentage = (high - values[j]) / (high - low); + + if (((i >> j) & 1) == 1) + { + factor *= percentage; + } + else + { + factor *= 1 - percentage; + } + } + + factors[i] = factor; + } + + for (int i = 0; i < resultLength; i++) + { + for (int j = 0; j < nodes.Length; j++) + { + result[i] += nodes[j][i] * factors[j]; + } + } + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ColorTrcCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ColorTrcCalculator.cs new file mode 100644 index 0000000000..1eacaf3feb --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ColorTrcCalculator.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class ColorTrcCalculator : IVector4Calculator + { + private TrcCalculator curveCalculator; + private Matrix4x4 matrix; + private bool toPcs; + + public ColorTrcCalculator( + IccXyzTagDataEntry redMatrixColumn, + IccXyzTagDataEntry greenMatrixColumn, + IccXyzTagDataEntry blueMatrixColumn, + IccTagDataEntry redTrc, + IccTagDataEntry greenTrc, + IccTagDataEntry blueTrc, + bool toPcs) + { + this.toPcs = toPcs; + this.curveCalculator = new TrcCalculator(new IccTagDataEntry[] { redTrc, greenTrc, blueTrc }, !toPcs); + + Vector3 mr = redMatrixColumn.Data[0]; + Vector3 mg = greenMatrixColumn.Data[0]; + Vector3 mb = blueMatrixColumn.Data[0]; + this.matrix = new Matrix4x4(mr.X, mr.Y, mr.Z, 0, mg.X, mg.Y, mg.Z, 0, mb.X, mb.Y, mb.Z, 0, 0, 0, 0, 1); + + if (!toPcs) + { + Matrix4x4.Invert(this.matrix, out this.matrix); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Calculate(Vector4 value) + { + if (this.toPcs) + { + value = this.curveCalculator.Calculate(value); + return Vector4.Transform(value, this.matrix); + } + else + { + value = Vector4.Transform(value, this.matrix); + return this.curveCalculator.Calculate(value); + } + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.CalculationType.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.CalculationType.cs new file mode 100644 index 0000000000..1d892a1863 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.CalculationType.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal partial class CurveCalculator + { + private enum CalculationType + { + Identity, + Gamma, + Lut, + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.cs new file mode 100644 index 0000000000..ea8c014368 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/CurveCalculator.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal partial class CurveCalculator : ISingleCalculator + { + private LutCalculator lutCalculator; + private float gamma; + private CalculationType type; + + public CurveCalculator(IccCurveTagDataEntry entry, bool inverted) + { + if (entry.IsIdentityResponse) + { + this.type = CalculationType.Identity; + } + else if (entry.IsGamma) + { + this.gamma = entry.Gamma; + if (inverted) + { + this.gamma = 1f / this.gamma; + } + + this.type = CalculationType.Gamma; + } + else + { + this.lutCalculator = new LutCalculator(entry.CurveData, inverted); + this.type = CalculationType.Lut; + } + } + + public float Calculate(float value) + { + switch (this.type) + { + case CalculationType.Identity: + return value; + + case CalculationType.Gamma: + return MathF.Pow(value, this.gamma); + + case CalculationType.Lut: + return this.lutCalculator.Calculate(value); + + default: + throw new InvalidOperationException("Invalid calculation type"); + } + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/GrayTrcCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/GrayTrcCalculator.cs new file mode 100644 index 0000000000..01dae3ecba --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/GrayTrcCalculator.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class GrayTrcCalculator : IVector4Calculator + { + private TrcCalculator calculator; + + public GrayTrcCalculator(IccTagDataEntry grayTrc, bool toPcs) + { + this.calculator = new TrcCalculator(new IccTagDataEntry[] { grayTrc }, !toPcs); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Calculate(Vector4 value) + { + return this.calculator.Calculate(value); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ISingleCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ISingleCalculator.cs new file mode 100644 index 0000000000..923da13bd8 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ISingleCalculator.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Represents an ICC calculator with a single floating point value and result + /// + internal interface ISingleCalculator + { + /// + /// Calculates a result from the given value + /// + /// The input value + /// The calculated result + float Calculate(float value); + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/IVector4Calculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/IVector4Calculator.cs new file mode 100644 index 0000000000..e931c38d81 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/IVector4Calculator.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Represents an ICC calculator with values and results + /// + internal interface IVector4Calculator + { + /// + /// Calculates a result from the given values + /// + /// The input values + /// The calculated result + Vector4 Calculate(Vector4 value); + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.CalculationType.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.CalculationType.cs new file mode 100644 index 0000000000..5f613270ad --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.CalculationType.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal partial class LutABCalculator + { + private enum CalculationType + { + AtoB = 1 << 3, + BtoA = 1 << 4, + + SingleCurve = 1, + CurveMatrix = 2, + CurveClut = 3, + Full = 4, + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.cs new file mode 100644 index 0000000000..d203a65989 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutABCalculator.cs @@ -0,0 +1,135 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal partial class LutABCalculator : IVector4Calculator + { + private CalculationType type; + private TrcCalculator curveACalculator; + private TrcCalculator curveBCalculator; + private TrcCalculator curveMCalculator; + private MatrixCalculator matrixCalculator; + private ClutCalculator clutCalculator; + + public LutABCalculator(IccLutAToBTagDataEntry entry) + { + Guard.NotNull(entry, nameof(entry)); + this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); + this.type |= CalculationType.AtoB; + } + + public LutABCalculator(IccLutBToATagDataEntry entry) + { + Guard.NotNull(entry, nameof(entry)); + this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); + this.type |= CalculationType.BtoA; + } + + public Vector4 Calculate(Vector4 value) + { + switch (this.type) + { + case CalculationType.Full | CalculationType.AtoB: + value = this.curveACalculator.Calculate(value); + value = this.clutCalculator.Calculate(value); + value = this.curveMCalculator.Calculate(value); + value = this.matrixCalculator.Calculate(value); + return this.curveBCalculator.Calculate(value); + + case CalculationType.Full | CalculationType.BtoA: + value = this.curveBCalculator.Calculate(value); + value = this.matrixCalculator.Calculate(value); + value = this.curveMCalculator.Calculate(value); + value = this.clutCalculator.Calculate(value); + return this.curveACalculator.Calculate(value); + + case CalculationType.CurveClut | CalculationType.AtoB: + value = this.curveACalculator.Calculate(value); + value = this.clutCalculator.Calculate(value); + return this.curveBCalculator.Calculate(value); + + case CalculationType.CurveClut | CalculationType.BtoA: + value = this.curveBCalculator.Calculate(value); + value = this.clutCalculator.Calculate(value); + return this.curveACalculator.Calculate(value); + + case CalculationType.CurveMatrix | CalculationType.AtoB: + value = this.curveMCalculator.Calculate(value); + value = this.matrixCalculator.Calculate(value); + return this.curveBCalculator.Calculate(value); + + case CalculationType.CurveMatrix | CalculationType.BtoA: + value = this.curveBCalculator.Calculate(value); + value = this.matrixCalculator.Calculate(value); + return this.curveMCalculator.Calculate(value); + + case CalculationType.SingleCurve | CalculationType.AtoB: + case CalculationType.SingleCurve | CalculationType.BtoA: + return this.curveBCalculator.Calculate(value); + + default: + throw new InvalidOperationException("Invalid calculation type"); + } + } + + private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagDataEntry[] curveM, Vector3? matrix3x1, Matrix4x4? matrix3x3, IccClut clut) + { + bool hasACurve = curveA != null; + bool hasBCurve = curveB != null; + bool hasMCurve = curveM != null; + bool hasMatrix = matrix3x1 != null && matrix3x3 != null; + bool hasClut = clut != null; + + if (hasBCurve && hasMatrix && hasMCurve && hasClut && hasACurve) + { + this.type = CalculationType.Full; + } + else if (hasBCurve && hasClut && hasACurve) + { + this.type = CalculationType.CurveClut; + } + else if (hasBCurve && hasMatrix && hasMCurve) + { + this.type = CalculationType.CurveMatrix; + } + else if (hasBCurve) + { + this.type = CalculationType.SingleCurve; + } + else + { + throw new InvalidIccProfileException("AToB or BToA tag has an invalid configuration"); + } + + if (hasACurve) + { + this.curveACalculator = new TrcCalculator(curveA, false); + } + + if (hasBCurve) + { + this.curveBCalculator = new TrcCalculator(curveB, false); + } + + if (hasMCurve) + { + this.curveMCalculator = new TrcCalculator(curveM, false); + } + + if (hasMatrix) + { + this.matrixCalculator = new MatrixCalculator(matrix3x3.Value, matrix3x1.Value); + } + + if (hasClut) + { + this.clutCalculator = new ClutCalculator(clut); + } + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutCalculator.cs new file mode 100644 index 0000000000..10b5023aaf --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutCalculator.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class LutCalculator : ISingleCalculator + { + private float[] lut; + private bool inverse; + + public LutCalculator(float[] lut, bool inverse) + { + Guard.NotNull(lut, nameof(lut)); + + this.lut = lut; + this.inverse = inverse; + } + + public float Calculate(float value) + { + if (this.inverse) + { + return this.LookupInverse(value); + } + else + { + return this.Lookup(value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float Lookup(float value) + { + float factor = value * (this.lut.Length - 1); + int index = (int)factor; + float low = this.lut[index]; + float high = this.lut[index + 1]; + return low + ((high - low) * (factor - index)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float LookupInverse(float value) + { + int index = Array.BinarySearch(this.lut, value); + if (index >= 0) + { + return index / (float)(this.lut.Length - 1); + } + + index = ~index; + if (index == 0) + { + return 0; + } + else if (index == this.lut.Length) + { + return 1; + } + + float high = this.lut[index]; + float low = this.lut[index - 1]; + + float valuePercent = (value - low) / (high - low); + float lutRange = 1 / (float)(this.lut.Length - 1); + float lutLow = (index - 1) / (float)(this.lut.Length - 1); + + return lutLow + (valuePercent * lutRange); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutEntryCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutEntryCalculator.cs new file mode 100644 index 0000000000..a2cc7c6645 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/LutEntryCalculator.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class LutEntryCalculator : IVector4Calculator + { + private LutCalculator[] inputCurve; + private LutCalculator[] outputCurve; + private ClutCalculator clutCalculator; + private Matrix4x4 matrix; + private bool doTransform; + + public LutEntryCalculator(IccLut8TagDataEntry lut) + { + Guard.NotNull(lut, nameof(lut)); + this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix); + } + + public LutEntryCalculator(IccLut16TagDataEntry lut) + { + Guard.NotNull(lut, nameof(lut)); + this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix); + } + + public Vector4 Calculate(Vector4 value) + { + if (this.doTransform) + { + value = Vector4.Transform(value, this.matrix); + } + + value = this.CalculateLut(this.inputCurve, value); + value = this.clutCalculator.Calculate(value); + return this.CalculateLut(this.outputCurve, value); + } + + private unsafe Vector4 CalculateLut(LutCalculator[] lut, Vector4 value) + { + value = Vector4.Clamp(value, Vector4.Zero, Vector4.One); + + float* valuePointer = (float*)&value; + for (int i = 0; i < lut.Length; i++) + { + valuePointer[i] = lut[i].Calculate(valuePointer[i]); + } + + return value; + } + + private void Init(IccLut[] inputCurve, IccLut[] outputCurve, IccClut clut, Matrix4x4 matrix) + { + this.inputCurve = this.InitLut(inputCurve); + this.outputCurve = this.InitLut(outputCurve); + this.clutCalculator = new ClutCalculator(clut); + this.matrix = matrix; + + this.doTransform = !matrix.IsIdentity && inputCurve.Length == 3; + } + + private LutCalculator[] InitLut(IccLut[] curves) + { + var calculators = new LutCalculator[curves.Length]; + for (int i = 0; i < curves.Length; i++) + { + calculators[i] = new LutCalculator(curves[i].Values, false); + } + + return calculators; + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs new file mode 100644 index 0000000000..a672347cc9 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class MatrixCalculator : IVector4Calculator + { + private Matrix4x4 matrix2D; + private Vector4 matrix1D; + + public MatrixCalculator(Matrix4x4 matrix3x3, Vector3 matrix3x1) + { + this.matrix2D = matrix3x3; + this.matrix1D = new Vector4(matrix3x1, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Calculate(Vector4 value) + { + var transformed = Vector4.Transform(value, this.matrix2D); + return Vector4.Add(this.matrix1D, transformed); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs new file mode 100644 index 0000000000..a53b32477d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class ParametricCurveCalculator : ISingleCalculator + { + private IccParametricCurve curve; + private IccParametricCurveType type; + private const IccParametricCurveType InvertedFlag = (IccParametricCurveType)(1 << 3); + + public ParametricCurveCalculator(IccParametricCurveTagDataEntry entry, bool inverted) + { + Guard.NotNull(entry, nameof(entry)); + this.curve = entry.Curve; + this.type = entry.Curve.Type; + + if (inverted) + { + this.type |= InvertedFlag; + } + } + + public float Calculate(float value) + { + switch (this.type) + { + case IccParametricCurveType.Type1: + return this.CalculateGamma(value); + case IccParametricCurveType.Cie122_1996: + return this.CalculateCie122(value); + case IccParametricCurveType.Iec61966_3: + return this.CalculateIec61966(value); + case IccParametricCurveType.SRgb: + return this.CalculateSRgb(value); + case IccParametricCurveType.Type5: + return this.CalculateType5(value); + + case IccParametricCurveType.Type1 | InvertedFlag: + return this.CalculateInvertedGamma(value); + case IccParametricCurveType.Cie122_1996 | InvertedFlag: + return this.CalculateInvertedCie122(value); + case IccParametricCurveType.Iec61966_3 | InvertedFlag: + return this.CalculateInvertedIec61966(value); + case IccParametricCurveType.SRgb | InvertedFlag: + return this.CalculateInvertedSRgb(value); + case IccParametricCurveType.Type5 | InvertedFlag: + return this.CalculateInvertedType5(value); + + default: + throw new InvalidIccProfileException("ParametricCurve"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateGamma(float value) + { + return MathF.Pow(value, this.curve.G); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateCie122(float value) + { + if (value >= -this.curve.B / this.curve.A) + { + return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G); + } + else + { + return 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateIec61966(float value) + { + if (value >= -this.curve.B / this.curve.A) + { + return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.C; + } + else + { + return this.curve.C; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateSRgb(float value) + { + if (value >= this.curve.D) + { + return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G); + } + else + { + return this.curve.C * value; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateType5(float value) + { + if (value >= this.curve.D) + { + return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.E; + } + else + { + return (this.curve.C * value) + this.curve.F; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateInvertedGamma(float value) + { + return MathF.Pow(value, 1 / this.curve.G); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateInvertedCie122(float value) + { + return (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateInvertedIec61966(float value) + { + if (value >= this.curve.C) + { + return (MathF.Pow(value - this.curve.C, 1 / this.curve.G) - this.curve.B) / this.curve.A; + } + else + { + return -this.curve.B / this.curve.A; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateInvertedSRgb(float value) + { + if (value >= MathF.Pow((this.curve.A * this.curve.D) + this.curve.B, this.curve.G)) + { + return (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; + } + else + { + return value / this.curve.C; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float CalculateInvertedType5(float value) + { + if (value >= (this.curve.C * this.curve.D) + this.curve.F) + { + return (MathF.Pow(value - this.curve.E, 1 / this.curve.G) - this.curve.B) / this.curve.A; + } + else + { + return (value - this.curve.F) / this.curve.C; + } + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/TrcCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/TrcCalculator.cs new file mode 100644 index 0000000000..2eacf8f3e5 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/TrcCalculator.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + internal class TrcCalculator : IVector4Calculator + { + private ISingleCalculator[] calculators; + + public TrcCalculator(IccTagDataEntry[] entries, bool inverted) + { + Guard.NotNull(entries, nameof(entries)); + + this.calculators = new ISingleCalculator[entries.Length]; + for (int i = 0; i < entries.Length; i++) + { + switch (entries[i]) + { + case IccCurveTagDataEntry curve: + this.calculators[i] = new CurveCalculator(curve, inverted); + break; + + case IccParametricCurveTagDataEntry parametricCurve: + this.calculators[i] = new ParametricCurveCalculator(parametricCurve, inverted); + break; + + default: + throw new InvalidIccProfileException("Invalid Entry."); + } + } + } + + public unsafe Vector4 Calculate(Vector4 value) + { + float* valuePointer = (float*)&value; + for (int i = 0; i < this.calculators.Length; i++) + { + valuePointer[i] = this.calculators[i].Calculate(valuePointer[i]); + } + + return value; + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs new file mode 100644 index 0000000000..42f6e45802 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Linq; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal abstract partial class IccConverterBase + { + private ConversionMethod GetConversionMethod(IccProfile profile, IccRenderingIntent renderingIntent) + { + switch (profile.Header.Class) + { + case IccProfileClass.InputDevice: + case IccProfileClass.DisplayDevice: + case IccProfileClass.OutputDevice: + case IccProfileClass.ColorSpace: + return this.CheckMethod1(profile, renderingIntent); + + case IccProfileClass.DeviceLink: + case IccProfileClass.Abstract: + return this.CheckMethod2(profile); + + default: + return ConversionMethod.Invalid; + } + } + + private ConversionMethod CheckMethod1(IccProfile profile, IccRenderingIntent renderingIntent) + { + ConversionMethod method = ConversionMethod.Invalid; + + method = this.CheckMethodD(profile, renderingIntent); + if (method != ConversionMethod.Invalid) + { + return method; + } + + method = this.CheckMethodA(profile, renderingIntent); + if (method != ConversionMethod.Invalid) + { + return method; + } + + method = this.CheckMethodA0(profile); + if (method != ConversionMethod.Invalid) + { + return method; + } + + method = this.CheckMethodTrc(profile); + if (method != ConversionMethod.Invalid) + { + return method; + } + + return ConversionMethod.Invalid; + } + + private ConversionMethod CheckMethodD(IccProfile profile, IccRenderingIntent renderingIntent) + { + if ((this.HasTag(profile, IccProfileTag.DToB0) || this.HasTag(profile, IccProfileTag.BToD0)) + && renderingIntent == IccRenderingIntent.Perceptual) + { + return ConversionMethod.D0; + } + + if ((this.HasTag(profile, IccProfileTag.DToB1) || this.HasTag(profile, IccProfileTag.BToD1)) + && renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) + { + return ConversionMethod.D1; + } + + if ((this.HasTag(profile, IccProfileTag.DToB2) || this.HasTag(profile, IccProfileTag.BToD2)) + && renderingIntent == IccRenderingIntent.Saturation) + { + return ConversionMethod.D2; + } + + if ((this.HasTag(profile, IccProfileTag.DToB3) || this.HasTag(profile, IccProfileTag.BToD3)) + && renderingIntent == IccRenderingIntent.AbsoluteColorimetric) + { + return ConversionMethod.D3; + } + + return ConversionMethod.Invalid; + } + + private ConversionMethod CheckMethodA(IccProfile profile, IccRenderingIntent renderingIntent) + { + if ((this.HasTag(profile, IccProfileTag.AToB0) || this.HasTag(profile, IccProfileTag.BToA0)) + && renderingIntent == IccRenderingIntent.Perceptual) + { + return ConversionMethod.A0; + } + + if ((this.HasTag(profile, IccProfileTag.AToB1) || this.HasTag(profile, IccProfileTag.BToA1)) + && renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) + { + return ConversionMethod.A1; + } + + if ((this.HasTag(profile, IccProfileTag.AToB2) || this.HasTag(profile, IccProfileTag.BToA2)) + && renderingIntent == IccRenderingIntent.Saturation) + { + return ConversionMethod.A2; + } + + return ConversionMethod.Invalid; + } + + private ConversionMethod CheckMethodA0(IccProfile profile) + { + bool valid = this.HasTag(profile, IccProfileTag.AToB0) || this.HasTag(profile, IccProfileTag.BToA0); + return valid ? ConversionMethod.A0 : ConversionMethod.Invalid; + } + + private ConversionMethod CheckMethodTrc(IccProfile profile) + { + if (this.HasTag(profile, IccProfileTag.RedMatrixColumn) + && this.HasTag(profile, IccProfileTag.GreenMatrixColumn) + && this.HasTag(profile, IccProfileTag.BlueMatrixColumn) + && this.HasTag(profile, IccProfileTag.RedTrc) + && this.HasTag(profile, IccProfileTag.GreenTrc) + && this.HasTag(profile, IccProfileTag.BlueTrc)) + { + return ConversionMethod.ColorTrc; + } + + if (this.HasTag(profile, IccProfileTag.GrayTrc)) + { + return ConversionMethod.GrayTrc; + } + + return ConversionMethod.Invalid; + } + + private ConversionMethod CheckMethod2(IccProfile profile) + { + if (this.HasTag(profile, IccProfileTag.DToB0) || this.HasTag(profile, IccProfileTag.BToD0)) + { + return ConversionMethod.D0; + } + + if (this.HasTag(profile, IccProfileTag.AToB0) || this.HasTag(profile, IccProfileTag.AToB0)) + { + return ConversionMethod.A0; + } + + return ConversionMethod.Invalid; + } + + private bool HasTag(IccProfile profile, IccProfileTag tag) + { + return profile.Entries.Any(t => t.TagSignature == tag); + } + + private IccTagDataEntry GetTag(IccProfile profile, IccProfileTag tag) + { + return profile.Entries.FirstOrDefault(t => t.TagSignature == tag); + } + + private T GetTag(IccProfile profile, IccProfileTag tag) + where T : IccTagDataEntry + { + return profile.Entries.OfType().FirstOrDefault(t => t.TagSignature == tag); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.ConversionMethod.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.ConversionMethod.cs new file mode 100644 index 0000000000..727dd1a682 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.ConversionMethod.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal abstract partial class IccConverterBase + { + /// + /// Conversion methods with ICC profiles + /// + private enum ConversionMethod + { + /// + /// Conversion using anything but Multi Process Elements with perceptual rendering intent + /// + A0, + + /// + /// Conversion using anything but Multi Process Elements with relative colorimetric rendering intent + /// + A1, + + /// + /// Conversion using anything but Multi Process Elements with saturation rendering intent + /// + A2, + + /// + /// Conversion using Multi Process Elements with perceptual rendering intent + /// + D0, + + /// + /// Conversion using Multi Process Elements with relative colorimetric rendering intent + /// + D1, + + /// + /// Conversion using Multi Process Elements with saturation rendering intent + /// + D2, + + /// + /// Conversion using Multi Process Elements with absolute colorimetric rendering intent + /// + D3, + + /// + /// Conversion of more than one channel using tone reproduction curves + /// + ColorTrc, + + /// + /// Conversion of exactly one channel using a tone reproduction curve + /// + GrayTrc, + + /// + /// No valid conversion method available or found + /// + Invalid, + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.Conversions.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.Conversions.cs new file mode 100644 index 0000000000..5391f13049 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.Conversions.cs @@ -0,0 +1,150 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal abstract partial class IccConverterBase + { + private IVector4Calculator calculator; + + /// + /// Checks the profile for available conversion methods and gathers all the informations necessary for it + /// + /// The profile to use for the conversion + /// True if the conversion is to the Profile Connection Space + /// The wanted rendering intent. Can be ignored if not available + protected void Init(IccProfile profile, bool toPcs, IccRenderingIntent renderingIntent) + { + ConversionMethod method = this.GetConversionMethod(profile, renderingIntent); + switch (method) + { + case ConversionMethod.D0: + this.calculator = toPcs ? + this.InitD(profile, IccProfileTag.DToB0) : + this.InitD(profile, IccProfileTag.BToD0); + break; + + case ConversionMethod.D1: + this.calculator = toPcs ? + this.InitD(profile, IccProfileTag.DToB1) : + this.InitD(profile, IccProfileTag.BToD1); + break; + + case ConversionMethod.D2: + this.calculator = toPcs ? + this.InitD(profile, IccProfileTag.DToB2) : + this.InitD(profile, IccProfileTag.BToD2); + break; + + case ConversionMethod.D3: + this.calculator = toPcs ? + this.InitD(profile, IccProfileTag.DToB3) : + this.InitD(profile, IccProfileTag.BToD3); + break; + + case ConversionMethod.A0: + this.calculator = toPcs ? + this.InitA(profile, IccProfileTag.AToB0) : + this.InitA(profile, IccProfileTag.BToA0); + break; + + case ConversionMethod.A1: + this.calculator = toPcs ? + this.InitA(profile, IccProfileTag.AToB1) : + this.InitA(profile, IccProfileTag.BToA1); + break; + + case ConversionMethod.A2: + this.calculator = toPcs ? + this.InitA(profile, IccProfileTag.AToB2) : + this.InitA(profile, IccProfileTag.BToA2); + break; + + case ConversionMethod.ColorTrc: + this.calculator = this.InitColorTrc(profile, toPcs); + break; + + case ConversionMethod.GrayTrc: + this.calculator = this.InitGrayTrc(profile, toPcs); + break; + + case ConversionMethod.Invalid: + default: + throw new InvalidIccProfileException("Invalid conversion method."); + } + } + + private IVector4Calculator InitA(IccProfile profile, IccProfileTag tag) + { + IccTagDataEntry entry = this.GetTag(profile, tag); + switch (entry) + { + case IccLut8TagDataEntry lut8: + return new LutEntryCalculator(lut8); + case IccLut16TagDataEntry lut16: + return new LutEntryCalculator(lut16); + case IccLutAToBTagDataEntry lutAtoB: + return new LutABCalculator(lutAtoB); + case IccLutBToATagDataEntry lutBtoA: + return new LutABCalculator(lutBtoA); + + default: + throw new InvalidIccProfileException("Invalid entry."); + } + } + + private IVector4Calculator InitD(IccProfile profile, IccProfileTag tag) + { + IccMultiProcessElementsTagDataEntry entry = this.GetTag(profile, tag); + if (entry == null) + { + throw new InvalidIccProfileException("Entry is null."); + } + + throw new NotImplementedException("Multi process elements are not supported"); + } + + private IVector4Calculator InitColorTrc(IccProfile profile, bool toPcs) + { + IccXyzTagDataEntry redMatrixColumn = this.GetTag(profile, IccProfileTag.RedMatrixColumn); + IccXyzTagDataEntry greenMatrixColumn = this.GetTag(profile, IccProfileTag.GreenMatrixColumn); + IccXyzTagDataEntry blueMatrixColumn = this.GetTag(profile, IccProfileTag.BlueMatrixColumn); + + IccTagDataEntry redTrc = this.GetTag(profile, IccProfileTag.RedTrc); + IccTagDataEntry greenTrc = this.GetTag(profile, IccProfileTag.GreenTrc); + IccTagDataEntry blueTrc = this.GetTag(profile, IccProfileTag.BlueTrc); + + if (redMatrixColumn == null || + greenMatrixColumn == null || + blueMatrixColumn == null || + redTrc == null || + greenTrc == null || + blueTrc == null) + { + throw new InvalidIccProfileException("Missing matrix column or channel."); + } + + return new ColorTrcCalculator( + redMatrixColumn, + greenMatrixColumn, + blueMatrixColumn, + redTrc, + greenTrc, + blueTrc, + toPcs); + } + + private IVector4Calculator InitGrayTrc(IccProfile profile, bool toPcs) + { + IccTagDataEntry entry = this.GetTag(profile, IccProfileTag.GrayTrc); + return new GrayTrcCalculator(entry, toPcs); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs new file mode 100644 index 0000000000..53605fb045 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal abstract partial class IccConverterBase + { + /// + /// Initializes a new instance of the class. + /// + /// The ICC profile to use for the conversions + /// True if the conversion is to the profile connection space (PCS); False if the conversion is to the data space + protected IccConverterBase(IccProfile profile, bool toPcs) + { + Guard.NotNull(profile, nameof(profile)); + this.Init(profile, toPcs, profile.Header.RenderingIntent); + } + + /// + /// Converts colors with the initially provided ICC profile + /// + /// The value to convert + /// The converted value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Calculate(Vector4 value) + { + return this.calculator.Calculate(value); + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToDataConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToDataConverter.cs new file mode 100644 index 0000000000..2a911068f5 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToDataConverter.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal class IccDataToDataConverter : IccConverterBase + { + /// + /// Initializes a new instance of the class. + /// + /// The ICC profile to use for the conversions + public IccDataToDataConverter(IccProfile profile) + : base(profile, true) // toPCS is true because in this case the PCS space is also a data space + { + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToPcsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToPcsConverter.cs new file mode 100644 index 0000000000..ae274936b2 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccDataToPcsConverter.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal class IccDataToPcsConverter : IccConverterBase + { + /// + /// Initializes a new instance of the class. + /// + /// The ICC profile to use for the conversions + public IccDataToPcsConverter(IccProfile profile) + : base(profile, true) + { + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToDataConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToDataConverter.cs new file mode 100644 index 0000000000..c61642635b --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToDataConverter.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal class IccPcsToDataConverter : IccConverterBase + { + /// + /// Initializes a new instance of the class. + /// + /// The ICC profile to use for the conversions + public IccPcsToDataConverter(IccProfile profile) + : base(profile, false) + { + } + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToPcsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToPcsConverter.cs new file mode 100644 index 0000000000..8cbeeff70d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccPcsToPcsConverter.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc +{ + /// + /// Color converter for ICC profiles + /// + internal class IccPcsToPcsConverter : IccConverterBase + { + /// + /// Initializes a new instance of the class. + /// + /// The ICC profile to use for the conversions + public IccPcsToPcsConverter(IccProfile profile) + : base(profile, true) + { + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs index aa28d25aae..eb6aa875b7 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -79,33 +79,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc 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 /// diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs index 4372353213..561becf9c8 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc Type1 = 0, /// - /// Type 1: Y = a * log10 (b * X^γ + c) + d + /// Type 2: Y = a * log10 (b * X^γ + c) + d /// Type2 = 1, diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs index cb08d116d1..ebf749174a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -30,4 +30,4 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ClutCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ClutCalculatorTests.cs new file mode 100644 index 0000000000..a03bdceaf6 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ClutCalculatorTests.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class ClutCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataClut.ClutConversionTestData), MemberType = typeof(IccConversionDataClut))] + internal void ClutCalculator_WithClut_ReturnsResult(IccClut lut, Vector4 input, Vector4 expected) + { + var calculator = new ClutCalculator(lut); + + Vector4 result = calculator.Calculate(input); + + VectorAssert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/CurveCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/CurveCalculatorTests.cs new file mode 100644 index 0000000000..800a752e4c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/CurveCalculatorTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class CurveCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataTrc.CurveConversionTestData), MemberType = typeof(IccConversionDataTrc))] + internal void CurveCalculator_WithCurveEntry_ReturnsResult(IccCurveTagDataEntry curve, bool inverted, float input, float expected) + { + var calculator = new CurveCalculator(curve, inverted); + + float result = calculator.Calculate(input); + + Assert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutABCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutABCalculatorTests.cs new file mode 100644 index 0000000000..0581d88718 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutABCalculatorTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class LutABCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataLutAB.LutAToBConversionTestData), MemberType = typeof(IccConversionDataLutAB))] + internal void LutABCalculator_WithLutAToB_ReturnsResult(IccLutAToBTagDataEntry lut, Vector4 input, Vector4 expected) + { + var calculator = new LutABCalculator(lut); + + Vector4 result = calculator.Calculate(input); + + VectorAssert.Equal(expected, result, 4); + } + + [Theory] + [MemberData(nameof(IccConversionDataLutAB.LutBToAConversionTestData), MemberType = typeof(IccConversionDataLutAB))] + internal void LutABCalculator_WithLutBToA_ReturnsResult(IccLutBToATagDataEntry lut, Vector4 input, Vector4 expected) + { + var calculator = new LutABCalculator(lut); + + Vector4 result = calculator.Calculate(input); + + VectorAssert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutCalculatorTests.cs new file mode 100644 index 0000000000..c86d2a563d --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutCalculatorTests.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class LutCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataLut.LutConversionTestData), MemberType = typeof(IccConversionDataLut))] + internal void LutCalculator_WithLut_ReturnsResult(float[] lut, bool inverted, float input, float expected) + { + var calculator = new LutCalculator(lut, inverted); + + float result = calculator.Calculate(input); + + Assert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutEntryCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutEntryCalculatorTests.cs new file mode 100644 index 0000000000..9cda497954 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/LutEntryCalculatorTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class LutEntryCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataLutEntry.Lut8ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] + internal void LutEntryCalculator_WithLut8_ReturnsResult(IccLut8TagDataEntry lut, Vector4 input, Vector4 expected) + { + var calculator = new LutEntryCalculator(lut); + + Vector4 result = calculator.Calculate(input); + + VectorAssert.Equal(expected, result, 4); + } + + [Theory] + [MemberData(nameof(IccConversionDataLutEntry.Lut16ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] + internal void LutEntryCalculator_WithLut16_ReturnsResult(IccLut16TagDataEntry lut, Vector4 input, Vector4 expected) + { + var calculator = new LutEntryCalculator(lut); + + Vector4 result = calculator.Calculate(input); + + VectorAssert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/MatrixCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/MatrixCalculatorTests.cs new file mode 100644 index 0000000000..d1c1512812 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/MatrixCalculatorTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class MatrixCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataMatrix.MatrixConversionTestData), MemberType = typeof(IccConversionDataMatrix))] + internal void MatrixCalculator_WithMatrix_ReturnsResult(Matrix4x4 matrix2D, Vector3 matrix1D, Vector4 input, Vector4 expected) + { + var calculator = new MatrixCalculator(matrix2D, matrix1D); + + Vector4 result = calculator.Calculate(input); + + VectorAssert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ParametricCurveCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ParametricCurveCalculatorTests.cs new file mode 100644 index 0000000000..2e7f942641 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/ParametricCurveCalculatorTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class ParametricCurveCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataTrc.ParametricCurveConversionTestData), MemberType = typeof(IccConversionDataTrc))] + internal void ParametricCurveCalculator_WithCurveEntry_ReturnsResult(IccParametricCurveTagDataEntry curve, bool inverted, float input, float expected) + { + var calculator = new ParametricCurveCalculator(curve, inverted); + + float result = calculator.Calculate(input); + + Assert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/TrcCalculatorTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/TrcCalculatorTests.cs new file mode 100644 index 0000000000..d2d2da9e79 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/Calculators/TrcCalculatorTests.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators +{ + /// + /// Tests ICC + /// + public class TrcCalculatorTests + { + [Theory] + [MemberData(nameof(IccConversionDataTrc.TrcArrayConversionTestData), MemberType = typeof(IccConversionDataTrc))] + internal void TrcCalculator_WithCurvesArray_ReturnsResult(IccTagDataEntry[] entries, bool inverted, Vector4 input, Vector4 expected) + { + var calculator = new TrcCalculator(entries, inverted); + + Vector4 result = calculator.Calculate(input); + + VectorAssert.Equal(expected, result, 4); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Clut.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Clut.cs new file mode 100644 index 0000000000..deb327164b --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Clut.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc +{ + public class IccConversionDataClut + { + internal static IccClut Clut3x2 = new IccClut( + new float[][] + { + new float[] { 0.1f, 0.1f }, + new float[] { 0.2f, 0.2f }, + new float[] { 0.3f, 0.3f }, + + new float[] { 0.11f, 0.11f }, + new float[] { 0.21f, 0.21f }, + new float[] { 0.31f, 0.31f }, + + new float[] { 0.12f, 0.12f }, + new float[] { 0.22f, 0.22f }, + new float[] { 0.32f, 0.32f }, + + new float[] { 0.13f, 0.13f }, + new float[] { 0.23f, 0.23f }, + new float[] { 0.33f, 0.33f }, + + new float[] { 0.14f, 0.14f }, + new float[] { 0.24f, 0.24f }, + new float[] { 0.34f, 0.34f }, + + new float[] { 0.15f, 0.15f }, + new float[] { 0.25f, 0.25f }, + new float[] { 0.35f, 0.35f }, + + new float[] { 0.16f, 0.16f }, + new float[] { 0.26f, 0.26f }, + new float[] { 0.36f, 0.36f }, + + new float[] { 0.17f, 0.17f }, + new float[] { 0.27f, 0.27f }, + new float[] { 0.37f, 0.37f }, + + new float[] { 0.18f, 0.18f }, + new float[] { 0.28f, 0.28f }, + new float[] { 0.38f, 0.38f }, + }, + new byte[] { 3, 3, 3 }, + IccClutDataType.Float); + + internal static IccClut Clut3x1 = new IccClut( + new float[][] + { + new float[] { 0.10f }, + new float[] { 0.20f }, + new float[] { 0.30f }, + + new float[] { 0.11f }, + new float[] { 0.21f }, + new float[] { 0.31f }, + + new float[] { 0.12f }, + new float[] { 0.22f }, + new float[] { 0.32f }, + + new float[] { 0.13f }, + new float[] { 0.23f }, + new float[] { 0.33f }, + + new float[] { 0.14f }, + new float[] { 0.24f }, + new float[] { 0.34f }, + + new float[] { 0.15f }, + new float[] { 0.25f }, + new float[] { 0.35f }, + + new float[] { 0.16f }, + new float[] { 0.26f }, + new float[] { 0.36f }, + + new float[] { 0.17f }, + new float[] { 0.27f }, + new float[] { 0.37f }, + + new float[] { 0.18f }, + new float[] { 0.28f }, + new float[] { 0.38f }, + }, + new byte[] { 3, 3, 3 }, + IccClutDataType.Float); + + internal static IccClut Clut2x2 = new IccClut( + new float[][] + { + new float[] { 0.1f, 0.9f }, + new float[] { 0.2f, 0.8f }, + new float[] { 0.3f, 0.7f }, + + new float[] { 0.4f, 0.6f }, + new float[] { 0.5f, 0.5f }, + new float[] { 0.6f, 0.4f }, + + new float[] { 0.7f, 0.3f }, + new float[] { 0.8f, 0.2f }, + new float[] { 0.9f, 0.1f }, + }, + new byte[] { 3, 3 }, + IccClutDataType.Float); + + internal static IccClut Clut2x1 = new IccClut( + new float[][] + { + new float[] { 0.1f }, + new float[] { 0.2f }, + new float[] { 0.3f }, + + new float[] { 0.4f }, + new float[] { 0.5f }, + new float[] { 0.6f }, + + new float[] { 0.7f }, + new float[] { 0.8f }, + new float[] { 0.9f }, + }, + new byte[] { 3, 3 }, + IccClutDataType.Float); + + internal static IccClut Clut1x2 = new IccClut( + new float[][] + { + new float[] { 0f, 0.5f }, + new float[] { 0.25f, 0.75f, }, + new float[] { 0.5f, 1f }, + }, + new byte[] { 3 }, + IccClutDataType.Float); + + internal static IccClut Clut1x1 = new IccClut( + new float[][] + { + new float[] { 0f }, + new float[] { 0.5f }, + new float[] { 1f }, + }, + new byte[] { 3 }, + IccClutDataType.Float); + + public static object[][] ClutConversionTestData = + { + new object[] { Clut3x2, new Vector4(0.75f, 0.75f, 0.75f, 0), new Vector4(0.31f, 0.31f, 0, 0) }, + new object[] { Clut3x1, new Vector4(0.2f, 0.6f, 0.8f, 0), new Vector4(0.276f, 0, 0, 0) }, + new object[] { Clut3x1, new Vector4(0.75f, 0.75f, 0.75f, 0), new Vector4(0.31f, 0, 0, 0) }, + new object[] { Clut2x2, new Vector4(0.2f, 0.6f, 0, 0), new Vector4(0.46f, 0.54f, 0, 0) }, + new object[] { Clut2x2, new Vector4(0.25f, 0.75f, 0, 0), new Vector4(0.4f, 0.6f, 0, 0) }, + new object[] { Clut2x1, new Vector4(0.25f, 0.75f, 0, 0), new Vector4(0.4f, 0, 0, 0) }, + new object[] { Clut1x2, new Vector4(0.25f, 0, 0, 0), new Vector4(0.125f, 0.625f, 0, 0) }, + new object[] { Clut1x1, new Vector4(0.25f, 0, 0, 0), new Vector4(0.25f, 0, 0, 0) }, + }; + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Lut.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Lut.cs new file mode 100644 index 0000000000..0c96b2777c --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Lut.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc +{ + public class IccConversionDataLut + { + private static float[] LutEven = { 0, 0.5f, 1 }; + private static float[] LutUneven = { 0, 0.7f, 1 }; + + public static object[][] LutConversionTestData = + { + new object[] { LutEven, false, 0.5f, 0.5f }, + new object[] { LutEven, false, 0.25f, 0.25f }, + new object[] { LutEven, false, 0.75f, 0.75f }, + + new object[] { LutEven, true, 0.5f, 0.5f }, + new object[] { LutEven, true, 0.25f, 0.25f }, + new object[] { LutEven, true, 0.75f, 0.75f }, + + new object[] { LutUneven, false, 0.1, 0.14 }, + new object[] { LutUneven, false, 0.5, 0.7 }, + new object[] { LutUneven, false, 0.75, 0.85 }, + + new object[] { LutUneven, true, 0.14, 0.1 }, + new object[] { LutUneven, true, 0.7, 0.5 }, + new object[] { LutUneven, true, 0.85, 0.75 }, + }; + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutAB.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutAB.cs new file mode 100644 index 0000000000..f989117522 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutAB.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc +{ + public class IccConversionDataLutAB + { + private static IccLutAToBTagDataEntry lutAtoB_SingleCurve = new IccLutAToBTagDataEntry( + new IccTagDataEntry[] + { + IccConversionDataTrc.IdentityCurve, + IccConversionDataTrc.IdentityCurve, + IccConversionDataTrc.IdentityCurve + }, + null, null, null, null, null); + + // also need: + // # CurveM + matrix + // # CurveA + CLUT + CurveB + // # CurveA + CLUT + CurveM + Matrix + CurveB + + private static IccLutBToATagDataEntry lutBtoA_SingleCurve = new IccLutBToATagDataEntry( + new IccTagDataEntry[] + { + IccConversionDataTrc.IdentityCurve, + IccConversionDataTrc.IdentityCurve, + IccConversionDataTrc.IdentityCurve + }, + null, null, null, null, null); + + public static object[][] LutAToBConversionTestData = + { + new object[] { lutAtoB_SingleCurve, new Vector4(0.2f, 0.3f, 0.4f, 0), new Vector4(0.2f, 0.3f, 0.4f, 0) }, + }; + + public static object[][] LutBToAConversionTestData = + { + new object[] { lutBtoA_SingleCurve, new Vector4(0.2f, 0.3f, 0.4f, 0), new Vector4(0.2f, 0.3f, 0.4f, 0) }, + }; + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutEntry.cs new file mode 100644 index 0000000000..f676490f1d --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.LutEntry.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc +{ + public class IccConversionDataLutEntry + { + private static readonly IccLut Lut256 = CreateLut(256); + private static readonly IccLut Lut32 = CreateLut(32); + private static readonly IccLut LutIdentity = CreateIdentityLut(0, 1); + + private static readonly IccLut8TagDataEntry Lut8 = new IccLut8TagDataEntry( + new IccLut[] { Lut256, Lut256 }, + IccConversionDataClut.Clut2x1, + new IccLut[] { Lut256 }); + + private static readonly IccLut16TagDataEntry Lut16 = new IccLut16TagDataEntry( + new IccLut[] { Lut32, Lut32 }, + IccConversionDataClut.Clut2x1, + new IccLut[] { LutIdentity }); + + private static readonly IccLut8TagDataEntry Lut8Matrix = new IccLut8TagDataEntry( + IccConversionDataMatrix.Matrix3x3Random, + new IccLut[] { Lut256, Lut256, Lut256 }, + IccConversionDataClut.Clut3x1, + new IccLut[] { Lut256 }); + + private static readonly IccLut16TagDataEntry Lut16Matrix = new IccLut16TagDataEntry( + IccConversionDataMatrix.Matrix3x3Random, + new IccLut[] { Lut32, Lut32, Lut32 }, + IccConversionDataClut.Clut3x1, + new IccLut[] { LutIdentity }); + + private static IccLut CreateLut(int length) + { + float[] values = new float[length]; + for (int i = 0; i < values.Length; i++) + { + values[i] = 0.1f + (i / (float)length); + } + + return new IccLut(values); + } + + private static IccLut CreateIdentityLut(float min, float max) + { + return new IccLut(new float[] { min, max }); + } + + public static object[][] Lut8ConversionTestData = + { + new object[] { Lut8, new Vector4(0.2f, 0.3f, 0, 0), new Vector4(0.339762866f, 0, 0, 0) }, + new object[] { Lut8Matrix, new Vector4(0.21f, 0.31f, 0.41f, 0), new Vector4(0.431305826f, 0, 0, 0) }, + }; + + public static object[][] Lut16ConversionTestData = + { + new object[] { Lut16, new Vector4(0.2f, 0.3f, 0, 0), new Vector4(0.245625019f, 0, 0, 0) }, + new object[] { Lut16Matrix, new Vector4(0.21f, 0.31f, 0.41f, 0), new Vector4(0.336980581f, 0, 0, 0) }, + }; + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Matrix.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Matrix.cs new file mode 100644 index 0000000000..42b8f2bceb --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Matrix.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc +{ + public class IccConversionDataMatrix + { + public static float[,] Matrix3x3Random = { { 0.1f, 0.2f, 0.3f }, { 0.4f, 0.5f, 0.6f }, { 0.7f, 0.8f, 0.9f } }; + public static float[,] Matrix3x3Identity = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + public static object[][] MatrixConversionTestData = + { + new object[] { CreateMatrix(Matrix3x3Identity), Vector3.Zero, new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.5f, 0.5f, 0.5f, 0) }, + new object[] { CreateMatrix(Matrix3x3Identity), new Vector3(0.2f, 0.2f, 0.2f), new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.7f, 0.7f, 0.7f, 0) }, + new object[] { CreateMatrix(Matrix3x3Random), Vector3.Zero, new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.6f, 0.75f, 0.9f, 0) }, + new object[] { CreateMatrix(Matrix3x3Random), new Vector3(0.1f, 0.2f, 0.3f), new Vector4(0.5f, 0.5f, 0.5f, 0), new Vector4(0.7f, 0.95f, 1.2f, 0) }, + new object[] { CreateMatrix(Matrix3x3Random), Vector3.Zero, new Vector4(0.2f, 0.4f, 0.7f, 0), new Vector4(0.67f, 0.8f, 0.93f, 0) }, + new object[] { CreateMatrix(Matrix3x3Random), new Vector3(0.1f, 0.2f, 0.3f), new Vector4(0.2f, 0.4f, 0.7f, 0), new Vector4(0.77f, 1, 1.23f, 0) }, + }; + + private static 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/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.MultiProcessElement.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.MultiProcessElement.cs new file mode 100644 index 0000000000..696cec6d59 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.MultiProcessElement.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc +{ + public class IccConversionDataMultiProcessElement + { + private static IccMatrixProcessElement Matrix = new IccMatrixProcessElement(new float[,] + { + { 2, 4, 6 }, + { 3, 5, 7 }, + }, new float[] { 3, 4, 5 }); + + private static IccClut Clut = new IccClut(new float[][] + { + new float[] { 0.2f, 0.3f }, + new float[] { 0.4f, 0.5f }, + + new float[] { 0.21f, 0.31f }, + new float[] { 0.41f, 0.51f }, + + new float[] { 0.22f, 0.32f }, + new float[] { 0.42f, 0.52f }, + + new float[] { 0.23f, 0.33f }, + new float[] { 0.43f, 0.53f }, + }, new byte[] { 2, 2, 2 }, IccClutDataType.Float); + + private static IccFormulaCurveElement FormulaCurveElement1 = new IccFormulaCurveElement(IccFormulaCurveType.Type1, 2.2f, 0.7f, 0.2f, 0.3f, 0, 0); + private static IccFormulaCurveElement FormulaCurveElement2 = new IccFormulaCurveElement(IccFormulaCurveType.Type2, 2.2f, 0.9f, 0.9f, 0.02f, 0.1f, 0); + private static IccFormulaCurveElement FormulaCurveElement3 = new IccFormulaCurveElement(IccFormulaCurveType.Type3, 0, 0.9f, 0.9f, 1.02f, 0.1f, 0.02f); + + private static IccCurveSetProcessElement CurveSet1DFormula1 = Create1DSingleCurveSet(FormulaCurveElement1); + private static IccCurveSetProcessElement CurveSet1DFormula2 = Create1DSingleCurveSet(FormulaCurveElement2); + private static IccCurveSetProcessElement CurveSet1DFormula3 = Create1DSingleCurveSet(FormulaCurveElement3); + + private static IccCurveSetProcessElement CurveSet1DFormula1And2 = Create1DMultiCurveSet(new float[] { 0.5f }, FormulaCurveElement1, FormulaCurveElement2); + + private static IccClutProcessElement ClutElement = new IccClutProcessElement(Clut); + + private static IccCurveSetProcessElement Create1DSingleCurveSet(IccCurveSegment segment) + { + var curve = new IccOneDimensionalCurve(new float[0], new IccCurveSegment[] { segment }); + return new IccCurveSetProcessElement(new IccOneDimensionalCurve[] { curve }); + } + + private static IccCurveSetProcessElement Create1DMultiCurveSet(float[] breakPoints, params IccCurveSegment[] segments) + { + var curve = new IccOneDimensionalCurve(breakPoints, segments); + return new IccCurveSetProcessElement(new IccOneDimensionalCurve[] { curve }); + } + + + public static object[][] MpeCurveConversionTestData = + { + new object[] { CurveSet1DFormula1, new float[] { 0.51f }, new float[] { 0.575982451f } }, + new object[] { CurveSet1DFormula2, new float[] { 0.52f }, new float[] { -0.4684991f } }, + new object[] { CurveSet1DFormula3, new float[] { 0.53f }, new float[] { 0.86126f } }, + + new object[] { CurveSet1DFormula1And2, new float[] { 0.31f }, new float[] { 0.445982f } }, + new object[] { CurveSet1DFormula1And2, new float[] { 0.61f }, new float[] { -0.341274023f } }, + }; + + public static object[][] MpeMatrixConversionTestData = + { + new object[] { Matrix, new float[] { 2, 4 }, new float[] { 19, 32, 45 } } + }; + + public static object[][] MpeClutConversionTestData = + { + new object[] { ClutElement, new float[] { 0.5f, 0.5f, 0.5f }, new float[] { 0.5f, 0.5f } } + }; + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Trc.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Trc.cs new file mode 100644 index 0000000000..5bf745be42 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionData.Trc.cs @@ -0,0 +1,77 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc +{ + public class IccConversionDataTrc + { + internal static IccCurveTagDataEntry IdentityCurve = new IccCurveTagDataEntry(); + internal static IccCurveTagDataEntry Gamma2Curve = new IccCurveTagDataEntry(2); + internal static IccCurveTagDataEntry LutCurve = new IccCurveTagDataEntry(new float[] { 0, 0.7f, 1 }); + + internal static IccParametricCurveTagDataEntry ParamCurve1 = new IccParametricCurveTagDataEntry(new IccParametricCurve(2.2f)); + internal static IccParametricCurveTagDataEntry ParamCurve2 = new IccParametricCurveTagDataEntry(new IccParametricCurve(2.2f, 1.5f, -0.5f)); + internal static IccParametricCurveTagDataEntry ParamCurve3 = new IccParametricCurveTagDataEntry(new IccParametricCurve(2.2f, 1.5f, -0.5f, 0.3f)); + internal static IccParametricCurveTagDataEntry ParamCurve4 = new IccParametricCurveTagDataEntry(new IccParametricCurve(2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f)); + internal static IccParametricCurveTagDataEntry ParamCurve5 = new IccParametricCurveTagDataEntry(new IccParametricCurve(2.2f, 0.7f, 0.2f, 0.3f, 0.1f, 0.5f, 0.2f)); + + public static object[][] TrcArrayConversionTestData = + { + new object[] + { + new IccTagDataEntry[] { IdentityCurve, Gamma2Curve, ParamCurve1 }, + false, + new Vector4(2, 2, 0.5f, 0), + new Vector4(2, 4, 0.217637628f, 0), + }, + new object[] + { + new IccTagDataEntry[] { IdentityCurve, Gamma2Curve, ParamCurve1 }, + true, + new Vector4(1, 4, 0.217637628f, 0), + new Vector4(1, 2, 0.5f, 0), + }, + }; + + public static object[][] CurveConversionTestData = + { + new object[] { IdentityCurve, false, 2, 2 }, + new object[] { Gamma2Curve, false, 2, 4 }, + new object[] { LutCurve, false, 0.1, 0.14 }, + new object[] { LutCurve, false, 0.5, 0.7 }, + new object[] { LutCurve, false, 0.75, 0.85 }, + + new object[] { IdentityCurve, true, 2, 2 }, + new object[] { Gamma2Curve, true, 4, 2 }, + new object[] { LutCurve, true, 0.14, 0.1 }, + new object[] { LutCurve, true, 0.7, 0.5 }, + new object[] { LutCurve, true, 0.85, 0.75 }, + }; + + public static object[][] ParametricCurveConversionTestData = + { + new object[] { ParamCurve1, false, 0.5f, 0.217637628f }, + new object[] { ParamCurve2, false, 0.6f, 0.133208528f }, + new object[] { ParamCurve2, false, 0.21f, 0 }, + new object[] { ParamCurve3, false, 0.61f, 0.444446117f }, + new object[] { ParamCurve3, false, 0.22f, 0.3f }, + new object[] { ParamCurve4, false, 0.3f, 0.0732389539f }, + new object[] { ParamCurve4, false, 0.03f, 0.00232198136f }, + new object[] { ParamCurve5, false, 0.2f, 0.593165159f }, + new object[] { ParamCurve5, false, 0.05f, 0.215f }, + + new object[] { ParamCurve1, true, 0.217637628f, 0.5f }, + new object[] { ParamCurve2, true, 0.133208528f, 0.6f }, + new object[] { ParamCurve2, true, 0, 1 / 3f }, + new object[] { ParamCurve3, true, 0.444446117f, 0.61f }, + new object[] { ParamCurve3, true, 0.3f, 1 / 3f }, + new object[] { ParamCurve4, true, 0.0732389539f, 0.3f }, + new object[] { ParamCurve4, true, 0.00232198136f, 0.03f }, + new object[] { ParamCurve5, true, 0.593165159f, 0.2f }, + new object[] { ParamCurve5, true, 0.215f, 0.05f }, + }; + } +}