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