Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;

internal partial class LutABCalculator
{
/// <summary>
/// Identifies the transform direction for the configured LUT calculator.
/// </summary>
private enum CalculationType
{
AtoB = 1 << 3,
BtoA = 1 << 4,
/// <summary>
/// Converts from device space to PCS using ICC <c>mAB</c> stage order.
/// </summary>
AtoB,

SingleCurve = 1,
CurveMatrix = 2,
CurveClut = 3,
Full = 4,
/// <summary>
/// Converts from PCS to device space using ICC <c>mBA</c> stage order.
/// </summary>
BtoA,
}
}
141 changes: 82 additions & 59 deletions src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,67 +17,106 @@ internal partial class LutABCalculator : IVector4Calculator
private MatrixCalculator matrixCalculator;
private ClutCalculator clutCalculator;

/// <summary>
/// Initializes a new instance of the <see cref="LutABCalculator"/> class for an ICC <c>mAB</c> transform.
/// </summary>
/// <param name="entry">The parsed A-to-B LUT entry.</param>
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;
this.type = CalculationType.AtoB;
}

/// <summary>
/// Initializes a new instance of the <see cref="LutABCalculator"/> class for an ICC <c>mBA</c> transform.
/// </summary>
/// <param name="entry">The parsed B-to-A LUT entry.</param>
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;
this.type = CalculationType.BtoA;
}

/// <summary>
/// Calculates the transformed value by applying the configured ICC LUT stages in specification order.
/// </summary>
/// <param name="value">The input value.</param>
/// <returns>The transformed value.</returns>
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);
case CalculationType.AtoB:
// ICC mAB order: A, CLUT, M, Matrix, B.
if (this.curveACalculator != null)
{
value = this.curveACalculator.Calculate(value);
}

if (this.clutCalculator != null)
{
value = this.clutCalculator.Calculate(value);
}

if (this.curveMCalculator != null)
{
value = this.curveMCalculator.Calculate(value);
}

if (this.matrixCalculator != null)
{
value = this.matrixCalculator.Calculate(value);
}

if (this.curveBCalculator != null)
{
value = this.curveBCalculator.Calculate(value);
}

return value;

case CalculationType.BtoA:
// ICC mBA order: B, Matrix, M, CLUT, A.
if (this.curveBCalculator != null)
{
value = this.curveBCalculator.Calculate(value);
}

if (this.matrixCalculator != null)
{
value = this.matrixCalculator.Calculate(value);
}

if (this.curveMCalculator != null)
{
value = this.curveMCalculator.Calculate(value);
}

if (this.clutCalculator != null)
{
value = this.clutCalculator.Calculate(value);
}

if (this.curveACalculator != null)
{
value = this.curveACalculator.Calculate(value);
}

return value;

default:
throw new InvalidOperationException("Invalid calculation type");
}
}

/// <summary>
/// Creates calculators for the processing stages present in the LUT entry.
/// </summary>
/// <remarks>
/// The tag entry classes already validate channel continuity, so this method only materializes the available stages.
/// </remarks>
private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagDataEntry[] curveM, Vector3? matrix3x1, Matrix4x4? matrix3x3, IccClut clut)
{
bool hasACurve = curveA != null;
Expand All @@ -86,26 +125,10 @@ private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagData
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");
}
Guard.IsTrue(
hasACurve || hasBCurve || hasMCurve || hasMatrix || hasClut,
"entry",
"AToB or BToA tag must contain at least one processing element");

if (hasACurve)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private static IVector4Calculator InitA(IccProfile profile, IccProfileTag tag)
IccLut16TagDataEntry lut16 => new LutEntryCalculator(lut16),
IccLutAToBTagDataEntry lutAtoB => new LutABCalculator(lutAtoB),
IccLutBToATagDataEntry lutBtoA => new LutABCalculator(lutBtoA),
_ => throw new InvalidIccProfileException("Invalid entry."),
_ => throw new InvalidIccProfileException($"Invalid entry {tag}."),
};

private static IVector4Calculator InitD(IccProfile profile, IccProfileTag tag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,82 +20,46 @@ internal sealed partial class IccDataReader
public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info)
{
this.currentIndex = (int)info.Offset;
switch (this.ReadTagDataEntryHeader())
{
case IccTypeSignature.Chromaticity:
return this.ReadChromaticityTagDataEntry();
case IccTypeSignature.ColorantOrder:
return this.ReadColorantOrderTagDataEntry();
case IccTypeSignature.ColorantTable:
return this.ReadColorantTableTagDataEntry();
case IccTypeSignature.Curve:
return this.ReadCurveTagDataEntry();
case IccTypeSignature.Data:
return this.ReadDataTagDataEntry(info.DataSize);
case IccTypeSignature.DateTime:
return this.ReadDateTimeTagDataEntry();
case IccTypeSignature.Lut16:
return this.ReadLut16TagDataEntry();
case IccTypeSignature.Lut8:
return this.ReadLut8TagDataEntry();
case IccTypeSignature.LutAToB:
return this.ReadLutAtoBTagDataEntry();
case IccTypeSignature.LutBToA:
return this.ReadLutBtoATagDataEntry();
case IccTypeSignature.Measurement:
return this.ReadMeasurementTagDataEntry();
case IccTypeSignature.MultiLocalizedUnicode:
return this.ReadMultiLocalizedUnicodeTagDataEntry();
case IccTypeSignature.MultiProcessElements:
return this.ReadMultiProcessElementsTagDataEntry();
case IccTypeSignature.NamedColor2:
return this.ReadNamedColor2TagDataEntry();
case IccTypeSignature.ParametricCurve:
return this.ReadParametricCurveTagDataEntry();
case IccTypeSignature.ProfileSequenceDesc:
return this.ReadProfileSequenceDescTagDataEntry();
case IccTypeSignature.ProfileSequenceIdentifier:
return this.ReadProfileSequenceIdentifierTagDataEntry();
case IccTypeSignature.ResponseCurveSet16:
return this.ReadResponseCurveSet16TagDataEntry();
case IccTypeSignature.S15Fixed16Array:
return this.ReadFix16ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.Signature:
return this.ReadSignatureTagDataEntry();
case IccTypeSignature.Text:
return this.ReadTextTagDataEntry(info.DataSize);
case IccTypeSignature.U16Fixed16Array:
return this.ReadUFix16ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt16Array:
return this.ReadUInt16ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt32Array:
return this.ReadUInt32ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt64Array:
return this.ReadUInt64ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.UInt8Array:
return this.ReadUInt8ArrayTagDataEntry(info.DataSize);
case IccTypeSignature.ViewingConditions:
return this.ReadViewingConditionsTagDataEntry();
case IccTypeSignature.Xyz:
return this.ReadXyzTagDataEntry(info.DataSize);
return this.ReadTagDataEntryHeader() switch
{
IccTypeSignature.Chromaticity => this.ReadChromaticityTagDataEntry(),
IccTypeSignature.ColorantOrder => this.ReadColorantOrderTagDataEntry(),
IccTypeSignature.ColorantTable => this.ReadColorantTableTagDataEntry(),
IccTypeSignature.Curve => this.ReadCurveTagDataEntry(),
IccTypeSignature.Data => this.ReadDataTagDataEntry(info.DataSize),
IccTypeSignature.DateTime => this.ReadDateTimeTagDataEntry(),
IccTypeSignature.Lut16 => this.ReadLut16TagDataEntry(),
IccTypeSignature.Lut8 => this.ReadLut8TagDataEntry(),
IccTypeSignature.LutAToB => this.ReadLutAtoBTagDataEntry(),
IccTypeSignature.LutBToA => this.ReadLutBtoATagDataEntry(),
IccTypeSignature.Measurement => this.ReadMeasurementTagDataEntry(),
IccTypeSignature.MultiLocalizedUnicode => this.ReadMultiLocalizedUnicodeTagDataEntry(),
IccTypeSignature.MultiProcessElements => this.ReadMultiProcessElementsTagDataEntry(),
IccTypeSignature.NamedColor2 => this.ReadNamedColor2TagDataEntry(),
IccTypeSignature.ParametricCurve => this.ReadParametricCurveTagDataEntry(),
IccTypeSignature.ProfileSequenceDesc => this.ReadProfileSequenceDescTagDataEntry(),
IccTypeSignature.ProfileSequenceIdentifier => this.ReadProfileSequenceIdentifierTagDataEntry(),
IccTypeSignature.ResponseCurveSet16 => this.ReadResponseCurveSet16TagDataEntry(),
IccTypeSignature.S15Fixed16Array => this.ReadFix16ArrayTagDataEntry(info.DataSize),
IccTypeSignature.Signature => this.ReadSignatureTagDataEntry(),
IccTypeSignature.Text => this.ReadTextTagDataEntry(info.DataSize),
IccTypeSignature.U16Fixed16Array => this.ReadUFix16ArrayTagDataEntry(info.DataSize),
IccTypeSignature.UInt16Array => this.ReadUInt16ArrayTagDataEntry(info.DataSize),
IccTypeSignature.UInt32Array => this.ReadUInt32ArrayTagDataEntry(info.DataSize),
IccTypeSignature.UInt64Array => this.ReadUInt64ArrayTagDataEntry(info.DataSize),
IccTypeSignature.UInt8Array => this.ReadUInt8ArrayTagDataEntry(info.DataSize),
IccTypeSignature.ViewingConditions => this.ReadViewingConditionsTagDataEntry(),
IccTypeSignature.Xyz => this.ReadXyzTagDataEntry(info.DataSize),

// V2 Types:
case IccTypeSignature.TextDescription:
return this.ReadTextDescriptionTagDataEntry();
case IccTypeSignature.CrdInfo:
return this.ReadCrdInfoTagDataEntry();
case IccTypeSignature.Screening:
return this.ReadScreeningTagDataEntry();
case IccTypeSignature.UcrBg:
return this.ReadUcrBgTagDataEntry(info.DataSize);
IccTypeSignature.TextDescription => this.ReadTextDescriptionTagDataEntry(),
IccTypeSignature.CrdInfo => this.ReadCrdInfoTagDataEntry(),
IccTypeSignature.Screening => this.ReadScreeningTagDataEntry(),
IccTypeSignature.UcrBg => this.ReadUcrBgTagDataEntry(info.DataSize),

// Unsupported or unknown
case IccTypeSignature.DeviceSettings:
case IccTypeSignature.NamedColor:
case IccTypeSignature.Unknown:
default:
return this.ReadUnknownTagDataEntry(info.DataSize);
}
_ => this.ReadUnknownTagDataEntry(info.DataSize),
};
}

/// <summary>
Expand Down Expand Up @@ -477,7 +441,7 @@ public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntr

return new IccMultiLocalizedUnicodeTagDataEntry(text);

CultureInfo ReadCulture(string language, string country)
static CultureInfo ReadCulture(string language, string country)
{
if (string.IsNullOrWhiteSpace(language))
{
Expand Down
6 changes: 3 additions & 3 deletions src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ public static IccProfileId CalculateHash(byte[] data)
// need to copy some values because they need to be zero for the hashing
Span<byte> temp = stackalloc byte[24];
data.AsSpan(profileFlagPos, 4).CopyTo(temp);
data.AsSpan(renderingIntentPos, 4).CopyTo(temp.Slice(4));
data.AsSpan(profileIdPos, 16).CopyTo(temp.Slice(8));
data.AsSpan(renderingIntentPos, 4).CopyTo(temp[4..]);
data.AsSpan(profileIdPos, 16).CopyTo(temp[8..]);

try
{
Expand All @@ -131,7 +131,7 @@ public static IccProfileId CalculateHash(byte[] data)
}
finally
{
temp.Slice(0, 4).CopyTo(data.AsSpan(profileFlagPos));
temp[..4].CopyTo(data.AsSpan(profileFlagPos));
temp.Slice(4, 4).CopyTo(data.AsSpan(renderingIntentPos));
temp.Slice(8, 16).CopyTo(data.AsSpan(profileIdPos));
}
Expand Down
Loading
Loading