mirror of https://github.com/SixLabors/ImageSharp
23 changed files with 295 additions and 308 deletions
@ -1,54 +1,53 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc |
|||
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; |
|||
|
|||
internal class ColorTrcCalculator : IVector4Calculator |
|||
{ |
|||
internal class ColorTrcCalculator : IVector4Calculator |
|||
{ |
|||
private TrcCalculator curveCalculator; |
|||
private Matrix4x4 matrix; |
|||
private bool toPcs; |
|||
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); |
|||
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); |
|||
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); |
|||
} |
|||
if (!toPcs) |
|||
{ |
|||
Matrix4x4.Invert(this.matrix, out this.matrix); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 Calculate(Vector4 value) |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 Calculate(Vector4 value) |
|||
{ |
|||
if (this.toPcs) |
|||
{ |
|||
value = this.curveCalculator.Calculate(value); |
|||
return Vector4.Transform(value, this.matrix); |
|||
} |
|||
else |
|||
{ |
|||
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); |
|||
} |
|||
value = Vector4.Transform(value, this.matrix); |
|||
return this.curveCalculator.Calculate(value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,173 +1,165 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc |
|||
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal abstract partial class IccConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal abstract partial class IccConverterBase |
|||
private static ConversionMethod GetConversionMethod(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
{ |
|||
private ConversionMethod GetConversionMethod(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
switch (profile.Header.Class) |
|||
{ |
|||
switch (profile.Header.Class) |
|||
{ |
|||
case IccProfileClass.InputDevice: |
|||
case IccProfileClass.DisplayDevice: |
|||
case IccProfileClass.OutputDevice: |
|||
case IccProfileClass.ColorSpace: |
|||
return this.CheckMethod1(profile, renderingIntent); |
|||
case IccProfileClass.InputDevice: |
|||
case IccProfileClass.DisplayDevice: |
|||
case IccProfileClass.OutputDevice: |
|||
case IccProfileClass.ColorSpace: |
|||
return CheckMethod1(profile, renderingIntent); |
|||
|
|||
case IccProfileClass.DeviceLink: |
|||
case IccProfileClass.Abstract: |
|||
return CheckMethod2(profile); |
|||
|
|||
default: |
|||
return ConversionMethod.Invalid; |
|||
} |
|||
} |
|||
|
|||
case IccProfileClass.DeviceLink: |
|||
case IccProfileClass.Abstract: |
|||
return this.CheckMethod2(profile); |
|||
private static ConversionMethod CheckMethod1(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
{ |
|||
ConversionMethod method = ConversionMethod.Invalid; |
|||
|
|||
default: |
|||
return ConversionMethod.Invalid; |
|||
} |
|||
method = CheckMethodD(profile, renderingIntent); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
return method; |
|||
} |
|||
|
|||
private ConversionMethod CheckMethod1(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
method = CheckMethodA(profile, renderingIntent); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
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; |
|||
return method; |
|||
} |
|||
|
|||
private ConversionMethod CheckMethodD(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
method = CheckMethodA0(profile); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
if ((this.HasTag(profile, IccProfileTag.DToB0) || this.HasTag(profile, IccProfileTag.BToD0)) |
|||
&& renderingIntent == IccRenderingIntent.Perceptual) |
|||
{ |
|||
return ConversionMethod.D0; |
|||
} |
|||
return method; |
|||
} |
|||
|
|||
if ((this.HasTag(profile, IccProfileTag.DToB1) || this.HasTag(profile, IccProfileTag.BToD1)) |
|||
&& renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) |
|||
{ |
|||
return ConversionMethod.D1; |
|||
} |
|||
method = CheckMethodTrc(profile); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
return method; |
|||
} |
|||
|
|||
if ((this.HasTag(profile, IccProfileTag.DToB2) || this.HasTag(profile, IccProfileTag.BToD2)) |
|||
&& renderingIntent == IccRenderingIntent.Saturation) |
|||
{ |
|||
return ConversionMethod.D2; |
|||
} |
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
if ((this.HasTag(profile, IccProfileTag.DToB3) || this.HasTag(profile, IccProfileTag.BToD3)) |
|||
&& renderingIntent == IccRenderingIntent.AbsoluteColorimetric) |
|||
{ |
|||
return ConversionMethod.D3; |
|||
} |
|||
private static ConversionMethod CheckMethodD(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
{ |
|||
if ((HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0)) |
|||
&& renderingIntent == IccRenderingIntent.Perceptual) |
|||
{ |
|||
return ConversionMethod.D0; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
if ((HasTag(profile, IccProfileTag.DToB1) || HasTag(profile, IccProfileTag.BToD1)) |
|||
&& renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) |
|||
{ |
|||
return ConversionMethod.D1; |
|||
} |
|||
|
|||
private ConversionMethod CheckMethodA(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
if ((HasTag(profile, IccProfileTag.DToB2) || HasTag(profile, IccProfileTag.BToD2)) |
|||
&& renderingIntent == IccRenderingIntent.Saturation) |
|||
{ |
|||
if ((this.HasTag(profile, IccProfileTag.AToB0) || this.HasTag(profile, IccProfileTag.BToA0)) |
|||
&& renderingIntent == IccRenderingIntent.Perceptual) |
|||
{ |
|||
return ConversionMethod.A0; |
|||
} |
|||
return ConversionMethod.D2; |
|||
} |
|||
|
|||
if ((this.HasTag(profile, IccProfileTag.AToB1) || this.HasTag(profile, IccProfileTag.BToA1)) |
|||
&& renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) |
|||
{ |
|||
return ConversionMethod.A1; |
|||
} |
|||
if ((HasTag(profile, IccProfileTag.DToB3) || HasTag(profile, IccProfileTag.BToD3)) |
|||
&& renderingIntent == IccRenderingIntent.AbsoluteColorimetric) |
|||
{ |
|||
return ConversionMethod.D3; |
|||
} |
|||
|
|||
if ((this.HasTag(profile, IccProfileTag.AToB2) || this.HasTag(profile, IccProfileTag.BToA2)) |
|||
&& renderingIntent == IccRenderingIntent.Saturation) |
|||
{ |
|||
return ConversionMethod.A2; |
|||
} |
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
private static ConversionMethod CheckMethodA(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
{ |
|||
if ((HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0)) |
|||
&& renderingIntent == IccRenderingIntent.Perceptual) |
|||
{ |
|||
return ConversionMethod.A0; |
|||
} |
|||
|
|||
private ConversionMethod CheckMethodA0(IccProfile profile) |
|||
if ((HasTag(profile, IccProfileTag.AToB1) || HasTag(profile, IccProfileTag.BToA1)) |
|||
&& renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) |
|||
{ |
|||
bool valid = this.HasTag(profile, IccProfileTag.AToB0) || this.HasTag(profile, IccProfileTag.BToA0); |
|||
return valid ? ConversionMethod.A0 : ConversionMethod.Invalid; |
|||
return ConversionMethod.A1; |
|||
} |
|||
|
|||
private ConversionMethod CheckMethodTrc(IccProfile profile) |
|||
if ((HasTag(profile, IccProfileTag.AToB2) || HasTag(profile, IccProfileTag.BToA2)) |
|||
&& renderingIntent == IccRenderingIntent.Saturation) |
|||
{ |
|||
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; |
|||
return ConversionMethod.A2; |
|||
} |
|||
|
|||
private ConversionMethod CheckMethod2(IccProfile profile) |
|||
{ |
|||
if (this.HasTag(profile, IccProfileTag.DToB0) || this.HasTag(profile, IccProfileTag.BToD0)) |
|||
{ |
|||
return ConversionMethod.D0; |
|||
} |
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
if (this.HasTag(profile, IccProfileTag.AToB0) || this.HasTag(profile, IccProfileTag.AToB0)) |
|||
{ |
|||
return ConversionMethod.A0; |
|||
} |
|||
private static ConversionMethod CheckMethodA0(IccProfile profile) |
|||
{ |
|||
bool valid = HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0); |
|||
return valid ? ConversionMethod.A0 : ConversionMethod.Invalid; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
private static ConversionMethod CheckMethodTrc(IccProfile profile) |
|||
{ |
|||
if (HasTag(profile, IccProfileTag.RedMatrixColumn) |
|||
&& HasTag(profile, IccProfileTag.GreenMatrixColumn) |
|||
&& HasTag(profile, IccProfileTag.BlueMatrixColumn) |
|||
&& HasTag(profile, IccProfileTag.RedTrc) |
|||
&& HasTag(profile, IccProfileTag.GreenTrc) |
|||
&& HasTag(profile, IccProfileTag.BlueTrc)) |
|||
{ |
|||
return ConversionMethod.ColorTrc; |
|||
} |
|||
|
|||
private bool HasTag(IccProfile profile, IccProfileTag tag) |
|||
if (HasTag(profile, IccProfileTag.GrayTrc)) |
|||
{ |
|||
return profile.Entries.Any(t => t.TagSignature == tag); |
|||
return ConversionMethod.GrayTrc; |
|||
} |
|||
|
|||
private IccTagDataEntry GetTag(IccProfile profile, IccProfileTag tag) |
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static ConversionMethod CheckMethod2(IccProfile profile) |
|||
{ |
|||
if (HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0)) |
|||
{ |
|||
return profile.Entries.FirstOrDefault(t => t.TagSignature == tag); |
|||
return ConversionMethod.D0; |
|||
} |
|||
|
|||
private T GetTag<T>(IccProfile profile, IccProfileTag tag) |
|||
where T : IccTagDataEntry |
|||
if (HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.AToB0)) |
|||
{ |
|||
return profile.Entries.OfType<T>().FirstOrDefault(t => t.TagSignature == tag); |
|||
return ConversionMethod.A0; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static bool HasTag(IccProfile profile, IccProfileTag tag) |
|||
=> profile.Entries.Any(t => t.TagSignature == tag); |
|||
|
|||
private static IccTagDataEntry GetTag(IccProfile profile, IccProfileTag tag) |
|||
=> profile.Entries.FirstOrDefault(t => t.TagSignature == tag); |
|||
|
|||
private static T GetTag<T>(IccProfile profile, IccProfileTag tag) |
|||
where T : IccTagDataEntry |
|||
=> profile.Entries.OfType<T>().FirstOrDefault(t => t.TagSignature == tag); |
|||
} |
|||
|
|||
@ -1,29 +1,27 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators |
|||
namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc.Calculators; |
|||
|
|||
/// <summary>
|
|||
/// Tests ICC <see cref="ClutCalculator"/>
|
|||
/// </summary>
|
|||
[Trait("Color", "Conversion")] |
|||
public class ClutCalculatorTests |
|||
{ |
|||
/// <summary>
|
|||
/// Tests ICC <see cref="ClutCalculator"/>
|
|||
/// </summary>
|
|||
[Trait("Color", "Conversion")] |
|||
public class ClutCalculatorTests |
|||
[Theory] |
|||
[MemberData(nameof(IccConversionDataClut.ClutConversionTestData), MemberType = typeof(IccConversionDataClut))] |
|||
internal void ClutCalculator_WithClut_ReturnsResult(IccClut lut, Vector4 input, Vector4 expected) |
|||
{ |
|||
[Theory] |
|||
[MemberData(nameof(IccConversionDataClut.ClutConversionTestData), MemberType = typeof(IccConversionDataClut))] |
|||
internal void ClutCalculator_WithClut_ReturnsResult(IccClut lut, Vector4 input, Vector4 expected) |
|||
{ |
|||
var calculator = new ClutCalculator(lut); |
|||
ClutCalculator calculator = new(lut); |
|||
|
|||
Vector4 result = calculator.Calculate(input); |
|||
Vector4 result = calculator.Calculate(input); |
|||
|
|||
VectorAssert.Equal(expected, result, 4); |
|||
} |
|||
VectorAssert.Equal(expected, result, 4); |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue