Browse Source

Change clut values from jagged array to flat array

pull/1567/head
Brian Popow 3 years ago
parent
commit
d6fbc01446
  1. 24
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ClutCalculator.cs
  2. 8
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.Conversions.cs
  3. 101
      src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs
  4. 126
      src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
  5. 59
      src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
  6. 2
      src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
  7. 14
      src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs
  8. 174
      tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataClut.cs
  9. 19
      tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs
  10. 63
      tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs

24
src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ClutCalculator.cs

@ -15,8 +15,7 @@ internal class ClutCalculator : IVector4Calculator
{
private readonly int inputCount;
private readonly int outputCount;
private readonly float[][] lut;
private readonly float[] lutFlat;
private readonly float[] lut;
private readonly byte[] gridPointCount;
private readonly byte[] maxGridPoint;
private readonly int[] indexFactor;
@ -69,17 +68,6 @@ internal class ClutCalculator : IVector4Calculator
}
this.indexFactor = this.CalculateIndexFactor();
// TODO: Using here a flat array instead of a jagged array to match the reference implementation.
// Maybe consider changing the clut values from jagged to a flat in IccClut to avoid this allocation.
this.lutFlat = new float[this.lut.Length * 3];
int offset = 0;
for (int i = 0; i < this.lut.Length; i++)
{
this.lutFlat[offset++] = this.lut[i][0];
this.lutFlat[offset++] = this.lut[i][1];
this.lutFlat[offset++] = this.lut[i][2];
}
}
public unsafe Vector4 Calculate(Vector4 value)
@ -209,7 +197,7 @@ internal class ClutCalculator : IVector4Calculator
float nu = (float)(1.0 - u);
int i;
Span<float> p = this.lutFlat.AsSpan((int)(ix * this.n001));
Span<float> p = this.lut.AsSpan((int)(ix * this.n001));
// Normalize grid units.
float dF0 = nu;
@ -259,7 +247,7 @@ internal class ClutCalculator : IVector4Calculator
float nu = (float)(1.0 - u);
int i;
Span<float> p = this.lutFlat.AsSpan((int)((ix * this.n001) + (iy * this.n010)));
Span<float> p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010)));
// Normalize grid units.
float dF0 = nt * nu;
@ -322,7 +310,7 @@ internal class ClutCalculator : IVector4Calculator
float nt = (float)(1.0 - t);
float nu = (float)(1.0 - u);
Span<float> p = this.lutFlat.AsSpan((int)((ix * this.n001) + (iy * this.n010) + (iz * this.n100)));
Span<float> p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010) + (iz * this.n100)));
// Normalize grid units
float dF0 = ns * nt * nu;
@ -401,7 +389,7 @@ internal class ClutCalculator : IVector4Calculator
float nu = (float)(1.0 - u);
float nv = (float)(1.0 - v);
Span<float> p = this.lutFlat.AsSpan((int)((iw * this.n001) + (ix * this.n010) + (iy * this.n100) + (iz * this.n1000)));
Span<float> p = this.lut.AsSpan((int)((iw * this.n001) + (ix * this.n010) + (iy * this.n100) + (iz * this.n1000)));
// Normalize grid units.
float[] dF = new float[16];
@ -458,7 +446,7 @@ internal class ClutCalculator : IVector4Calculator
index += (int)this.ig[i] * this.dimSize[i];
}
Span<float> p = this.lutFlat.AsSpan(index);
Span<float> p = this.lut.AsSpan(index);
float[] temp = new float[2];
bool nFlag = false;

8
src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.Conversions.cs

@ -13,11 +13,11 @@ internal abstract partial class IccConverterBase
private IVector4Calculator calculator;
/// <summary>
/// Checks the profile for available conversion methods and gathers all the informations necessary for it
/// Checks the profile for available conversion methods and gathers all the information's necessary for it.
/// </summary>
/// <param name="profile">The profile to use for the conversion</param>
/// <param name="toPcs">True if the conversion is to the Profile Connection Space</param>
/// <param name="renderingIntent">The wanted rendering intent. Can be ignored if not available</param>
/// <param name="profile">The profile to use for the conversion.</param>
/// <param name="toPcs">True if the conversion is to the Profile Connection Space.</param>
/// <param name="renderingIntent">The wanted rendering intent. Can be ignored if not available.</param>
/// <exception cref="InvalidIccProfileException">Invalid conversion method.</exception>
protected void Init(IccProfile profile, bool toPcs, IccRenderingIntent renderingIntent)
=> this.calculator = GetConversionMethod(profile, renderingIntent) switch

101
src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs

@ -4,24 +4,21 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
/// <summary>
/// Provides methods to read ICC data types
/// Provides methods to read ICC data types.
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads an 8bit lookup table
/// Reads an 8bit lookup table.
/// </summary>
/// <returns>The read LUT</returns>
public IccLut ReadLut8()
{
return new IccLut(this.ReadBytes(256));
}
/// <returns>The read LUT.</returns>
public IccLut ReadLut8() => new(this.ReadBytes(256));
/// <summary>
/// Reads a 16bit lookup table
/// Reads a 16bit lookup table.
/// </summary>
/// <param name="count">The number of entries</param>
/// <returns>The read LUT</returns>
/// <param name="count">The number of entries.</param>
/// <returns>The read LUT.</returns>
public IccLut ReadLut16(int count)
{
var values = new ushort[count];
@ -34,16 +31,16 @@ internal sealed partial class IccDataReader
}
/// <summary>
/// Reads a CLUT depending on type
/// Reads a CLUT depending on type.
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="inChannelCount">Input channel count.</param>
/// <param name="outChannelCount">Output channel count.</param>
/// <param name="isFloat">If true, it's read as CLUTf32,
/// else read as either CLUT8 or CLUT16 depending on embedded information</param>
/// <returns>The read CLUT</returns>
/// else read as either CLUT8 or CLUT16 depending on embedded information.</param>
/// <returns>The read CLUT.</returns>
public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat)
{
// Grid-points are always 16 bytes long but only 0-inChCount are used
// Grid-points are always 16 bytes long but only 0-inChCount are used.
var gridPointCount = new byte[inChannelCount];
Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount);
@ -67,12 +64,12 @@ internal sealed partial class IccDataReader
}
/// <summary>
/// Reads an 8 bit CLUT
/// Reads an 8 bit CLUT.
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUT8</returns>
/// <param name="inChannelCount">Input channel count.</param>
/// <param name="outChannelCount">Output channel count.</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel.</param>
/// <returns>The read CLUT8.</returns>
public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount)
{
int start = this.currentIndex;
@ -82,31 +79,25 @@ internal sealed partial class IccDataReader
length += (int)Math.Pow(gridPointCount[i], inChannelCount);
}
length /= inChannelCount;
const float Max = byte.MaxValue;
var values = new float[length][];
var values = new float[length];
for (int i = 0; i < length; i++)
{
values[i] = new float[outChannelCount];
for (int j = 0; j < outChannelCount; j++)
{
values[i][j] = this.data[this.currentIndex++] / Max;
}
values[i] = this.data[this.currentIndex++] / Max;
}
this.currentIndex = start + (length * outChannelCount);
return new IccClut(values, gridPointCount, IccClutDataType.UInt8);
this.currentIndex = start + length;
return new IccClut(values, gridPointCount, IccClutDataType.UInt8, outChannelCount);
}
/// <summary>
/// Reads a 16 bit CLUT
/// Reads a 16 bit CLUT.
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUT16</returns>
/// <param name="inChannelCount">Input channel count.</param>
/// <param name="outChannelCount">Output channel count.</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel.</param>
/// <returns>The read CLUT16.</returns>
public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount)
{
int start = this.currentIndex;
@ -116,31 +107,25 @@ internal sealed partial class IccDataReader
length += (int)Math.Pow(gridPointCount[i], inChannelCount);
}
length /= inChannelCount;
const float Max = ushort.MaxValue;
var values = new float[length][];
var values = new float[length];
for (int i = 0; i < length; i++)
{
values[i] = new float[outChannelCount];
for (int j = 0; j < outChannelCount; j++)
{
values[i][j] = this.ReadUInt16() / Max;
}
values[i] = this.ReadUInt16() / Max;
}
this.currentIndex = start + (length * outChannelCount * 2);
return new IccClut(values, gridPointCount, IccClutDataType.UInt16);
this.currentIndex = start + (length * 2);
return new IccClut(values, gridPointCount, IccClutDataType.UInt16, outChannelCount);
}
/// <summary>
/// Reads a 32bit floating point CLUT
/// Reads a 32bit floating point CLUT.
/// </summary>
/// <param name="inChCount">Input channel count</param>
/// <param name="outChCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUTf32</returns>
/// <param name="inChCount">Input channel count.</param>
/// <param name="outChCount">Output channel count.</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel.</param>
/// <returns>The read CLUTf32.</returns>
public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount)
{
int start = this.currentIndex;
@ -150,19 +135,13 @@ internal sealed partial class IccDataReader
length += (int)Math.Pow(gridPointCount[i], inChCount);
}
length /= inChCount;
var values = new float[length][];
var values = new float[length];
for (int i = 0; i < length; i++)
{
values[i] = new float[outChCount];
for (int j = 0; j < outChCount; j++)
{
values[i][j] = this.ReadSingle();
}
values[i] = this.ReadSingle();
}
this.currentIndex = start + (length * outChCount * 4);
return new IccClut(values, gridPointCount, IccClutDataType.Float);
this.currentIndex = start + (length * 4);
return new IccClut(values, gridPointCount, IccClutDataType.Float, outChCount);
}
}

126
src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs

@ -8,15 +8,15 @@ using System.Numerics;
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
/// <summary>
/// Provides methods to read ICC data types
/// Provides methods to read ICC data types.
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a tag data entry
/// Reads a tag data entry.
/// </summary>
/// <param name="info">The table entry with reading information</param>
/// <returns>the tag data entry</returns>
/// <param name="info">The table entry with reading information.</param>
/// <returns>The tag data entry.</returns>
public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info)
{
this.currentIndex = (int)info.Offset;
@ -101,7 +101,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads the header of a <see cref="IccTagDataEntry"/>
/// </summary>
/// <returns>The read signature</returns>
/// <returns>The read signature.</returns>
public IccTypeSignature ReadTagDataEntryHeader()
{
IccTypeSignature type = (IccTypeSignature)this.ReadUInt32();
@ -112,7 +112,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads the header of a <see cref="IccTagDataEntry"/> and checks if it's the expected value
/// </summary>
/// <param name="expected">expected value to check against</param>
/// <param name="expected">The expected value to check against.</param>
public void ReadCheckTagDataEntryHeader(IccTypeSignature expected)
{
IccTypeSignature type = this.ReadTagDataEntryHeader();
@ -125,8 +125,8 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccTagDataEntry"/> with an unknown <see cref="IccTypeSignature"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size)
{
int count = (int)size - 8; // 8 is the tag header size
@ -136,7 +136,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccChromaticityTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry()
{
ushort channelCount = this.ReadUInt16();
@ -150,7 +150,7 @@ internal sealed partial class IccDataReader
}
else
{
// The type is not know, so the values need be read
// The type is not know, so the values need be read.
double[][] values = new double[channelCount][];
for (int i = 0; i < channelCount; i++)
{
@ -164,7 +164,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccColorantOrderTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry()
{
uint colorantCount = this.ReadUInt32();
@ -175,7 +175,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccColorantTableTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry()
{
uint colorantCount = this.ReadUInt32();
@ -191,7 +191,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccCurveTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccCurveTagDataEntry ReadCurveTagDataEntry()
{
uint pointCount = this.ReadUInt32();
@ -220,7 +220,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccDataTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry</returns>
public IccDataTagDataEntry ReadDataTagDataEntry(uint size)
{
@ -238,16 +238,13 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccDateTimeTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry()
{
return new IccDateTimeTagDataEntry(this.ReadDateTime());
}
/// <returns>The read entry.</returns>
public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() => new IccDateTimeTagDataEntry(this.ReadDateTime());
/// <summary>
/// Reads a <see cref="IccLut16TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccLut16TagDataEntry ReadLut16TagDataEntry()
{
byte inChCount = this.data[this.AddIndex(1)];
@ -285,7 +282,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccLut8TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccLut8TagDataEntry ReadLut8TagDataEntry()
{
byte inChCount = this.data[this.AddIndex(1)];
@ -320,7 +317,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccLutAToBTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@ -379,7 +376,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccLutBToATagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccLutBToATagDataEntry ReadLutBtoATagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@ -438,21 +435,18 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccMeasurementTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry()
{
return new IccMeasurementTagDataEntry(
/// <returns>The read entry.</returns>
public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() => new(
observer: (IccStandardObserver)this.ReadUInt32(),
xyzBacking: this.ReadXyzNumber(),
geometry: (IccMeasurementGeometry)this.ReadUInt32(),
flare: this.ReadUFix16(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
}
/// <summary>
/// Reads a <see cref="IccMultiLocalizedUnicodeTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@ -517,7 +511,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccMultiProcessElementsTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry()
{
int start = this.currentIndex - 8;
@ -545,7 +539,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccNamedColor2TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry()
{
int vendorFlag = this.ReadInt32();
@ -567,15 +561,12 @@ internal sealed partial class IccDataReader
/// Reads a <see cref="IccParametricCurveTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry()
{
return new IccParametricCurveTagDataEntry(this.ReadParametricCurve());
}
public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() => new(this.ReadParametricCurve());
/// <summary>
/// Reads a <see cref="IccProfileSequenceDescTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry()
{
uint count = this.ReadUInt32();
@ -591,7 +582,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccProfileSequenceIdentifierTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@ -618,7 +609,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccResponseCurveSet16TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@ -644,8 +635,8 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
@ -661,27 +652,21 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccSignatureTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccSignatureTagDataEntry ReadSignatureTagDataEntry()
{
return new IccSignatureTagDataEntry(this.ReadAsciiString(4));
}
/// <returns>The read entry.</returns>
public IccSignatureTagDataEntry ReadSignatureTagDataEntry() => new(this.ReadAsciiString(4));
/// <summary>
/// Reads a <see cref="IccTextTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccTextTagDataEntry ReadTextTagDataEntry(uint size)
{
return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size
}
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccTextTagDataEntry ReadTextTagDataEntry(uint size) => new(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size
/// <summary>
/// Reads a <see cref="IccUFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
@ -697,8 +682,8 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccUInt16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 2;
@ -714,8 +699,8 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccUInt32ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
@ -731,8 +716,8 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccUInt64ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 8;
@ -748,8 +733,8 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccUInt8ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size)
{
int count = (int)size - 8; // 8 is the tag header size
@ -761,20 +746,17 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccViewingConditionsTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry()
{
return new IccViewingConditionsTagDataEntry(
/// <returns>The read entry.</returns>
public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() => new(
illuminantXyz: this.ReadXyzNumber(),
surroundXyz: this.ReadXyzNumber(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
}
/// <summary>
/// Reads a <see cref="IccXyzTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry.</returns>
public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size)
{
uint count = (size - 8) / 12;
@ -790,7 +772,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccTextDescriptionTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry()
{
string unicodeValue, scriptcodeValue;
@ -830,7 +812,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccTextDescriptionTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry()
{
uint productNameCount = this.ReadUInt32();
@ -854,7 +836,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccScreeningTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
/// <returns>The read entry.</returns>
public IccScreeningTagDataEntry ReadScreeningTagDataEntry()
{
var flags = (IccScreeningFlag)this.ReadInt32();
@ -871,7 +853,7 @@ internal sealed partial class IccDataReader
/// <summary>
/// Reads a <see cref="IccUcrBgTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <param name="size">The size of the entry in bytes.</param>
/// <returns>The read entry</returns>
public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size)
{

59
src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs

@ -4,15 +4,15 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
/// <content>
/// Provides methods to write ICC data types
/// Provides methods to write ICC data types.
/// </content>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes an 8bit lookup table
/// Writes an 8bit lookup table.
/// </summary>
/// <param name="value">The LUT to write</param>
/// <returns>The number of bytes written</returns>
/// <param name="value">The LUT to write.</param>
/// <returns>The number of bytes written.</returns>
public int WriteLut8(IccLut value)
{
foreach (float item in value.Values)
@ -24,10 +24,10 @@ internal sealed partial class IccDataWriter
}
/// <summary>
/// Writes an 16bit lookup table
/// Writes an 16bit lookup table.
/// </summary>
/// <param name="value">The LUT to write</param>
/// <returns>The number of bytes written</returns>
/// <param name="value">The LUT to write.</param>
/// <returns>The number of bytes written.</returns>
public int WriteLut16(IccLut value)
{
foreach (float item in value.Values)
@ -39,10 +39,10 @@ internal sealed partial class IccDataWriter
}
/// <summary>
/// Writes an color lookup table
/// Writes an color lookup table.
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
/// <param name="value">The CLUT to write.</param>
/// <returns>The number of bytes written.</returns>
public int WriteClut(IccClut value)
{
int count = this.WriteArray(value.GridPointCount);
@ -67,57 +67,48 @@ internal sealed partial class IccDataWriter
}
/// <summary>
/// Writes a 8bit color lookup table
/// Writes a 8bit color lookup table.
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
/// <param name="value">The CLUT to write.</param>
/// <returns>The number of bytes written.</returns>
public int WriteClut8(IccClut value)
{
int count = 0;
foreach (float[] inArray in value.Values)
foreach (float item in value.Values)
{
foreach (float item in inArray)
{
count += this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue));
}
count += this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue));
}
return count;
}
/// <summary>
/// Writes a 16bit color lookup table
/// Writes a 16bit color lookup table.
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
/// <param name="value">The CLUT to write.</param>
/// <returns>The number of bytes written.</returns>
public int WriteClut16(IccClut value)
{
int count = 0;
foreach (float[] inArray in value.Values)
foreach (float item in value.Values)
{
foreach (float item in inArray)
{
count += this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue));
}
count += this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue));
}
return count;
}
/// <summary>
/// Writes a 32bit float color lookup table
/// Writes a 32bit float color lookup table.
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
/// <param name="value">The CLUT to write.</param>
/// <returns>The number of bytes written.</returns>
public int WriteClutF32(IccClut value)
{
int count = 0;
foreach (float[] inArray in value.Values)
foreach (float item in value.Values)
{
foreach (float item in inArray)
{
count += this.WriteSingle(item);
}
count += this.WriteSingle(item);
}
return count;

2
src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs

@ -231,7 +231,7 @@ internal sealed partial class IccDataWriter
{
int count = this.WriteByte((byte)value.InputChannelCount);
count += this.WriteByte((byte)value.OutputChannelCount);
count += this.WriteByte((byte)value.ClutValues.Values[0].Length);
count += this.WriteByte((byte)value.ClutValues.OutputChannelCount);
count += this.WriteEmpty(1);
count += this.WriteMatrix(value.Matrix, false);

14
src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs

@ -14,7 +14,8 @@ internal sealed class IccClut : IEquatable<IccClut>
/// <param name="values">The CLUT values.</param>
/// <param name="gridPointCount">The gridpoint count.</param>
/// <param name="type">The data type of this CLUT.</param>
public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type)
/// <param name="outputChannelCount">The output channels count.</param>
public IccClut(float[] values, byte[] gridPointCount, IccClutDataType type, int outputChannelCount)
{
Guard.NotNull(values, nameof(values));
Guard.NotNull(gridPointCount, nameof(gridPointCount));
@ -22,7 +23,7 @@ internal sealed class IccClut : IEquatable<IccClut>
this.Values = values;
this.DataType = type;
this.InputChannelCount = gridPointCount.Length;
this.OutputChannelCount = values[0].Length;
this.OutputChannelCount = outputChannelCount;
this.GridPointCount = gridPointCount;
this.CheckValues();
}
@ -30,7 +31,7 @@ internal sealed class IccClut : IEquatable<IccClut>
/// <summary>
/// Gets the values that make up this table.
/// </summary>
public float[][] Values { get; }
public float[] Values { get; }
/// <summary>
/// Gets the CLUT data type (important when writing a profile).
@ -92,7 +93,7 @@ internal sealed class IccClut : IEquatable<IccClut>
for (int i = 0; i < this.Values.Length; i++)
{
if (!this.Values[i].AsSpan().SequenceEqual(other.Values[i]))
if (!this.Values.SequenceEqual(other.Values))
{
return false;
}
@ -106,17 +107,12 @@ internal sealed class IccClut : IEquatable<IccClut>
Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount));
Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount));
bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount);
Guard.IsFalse(isLengthDifferent, nameof(this.Values), "The number of output values varies");
int length = 0;
for (int i = 0; i < this.InputChannelCount; i++)
{
length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount);
}
length /= this.InputChannelCount;
Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points");
}
}

174
tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataClut.cs

@ -11,142 +11,148 @@ public class IccConversionDataClut
internal static IccClut Clut3x2 = new(
new[]
{
new[] { 0.1f, 0.1f },
new[] { 0.2f, 0.2f },
new[] { 0.3f, 0.3f },
0.1f, 0.1f,
0.2f, 0.2f,
0.3f, 0.3f,
new[] { 0.11f, 0.11f },
new[] { 0.21f, 0.21f },
new[] { 0.31f, 0.31f },
0.11f, 0.11f,
0.21f, 0.21f,
0.31f, 0.31f,
new[] { 0.12f, 0.12f },
new[] { 0.22f, 0.22f },
new[] { 0.32f, 0.32f },
0.12f, 0.12f,
0.22f, 0.22f,
0.32f, 0.32f,
new[] { 0.13f, 0.13f },
new[] { 0.23f, 0.23f },
new[] { 0.33f, 0.33f },
0.13f, 0.13f,
0.23f, 0.23f,
0.33f, 0.33f,
new[] { 0.14f, 0.14f },
new[] { 0.24f, 0.24f },
new[] { 0.34f, 0.34f },
0.14f, 0.14f,
0.24f, 0.24f,
0.34f, 0.34f,
new[] { 0.15f, 0.15f },
new[] { 0.25f, 0.25f },
new[] { 0.35f, 0.35f },
0.15f, 0.15f,
0.25f, 0.25f,
0.35f, 0.35f,
new[] { 0.16f, 0.16f },
new[] { 0.26f, 0.26f },
new[] { 0.36f, 0.36f },
0.16f, 0.16f,
0.26f, 0.26f,
0.36f, 0.36f,
new[] { 0.17f, 0.17f },
new[] { 0.27f, 0.27f },
new[] { 0.37f, 0.37f },
0.17f, 0.17f,
0.27f, 0.27f,
0.37f, 0.37f,
new[] { 0.18f, 0.18f },
new[] { 0.28f, 0.28f },
new[] { 0.38f, 0.38f },
0.18f, 0.18f,
0.28f, 0.28f,
0.38f, 0.38f,
},
new byte[] { 3, 3, 3 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 3);
internal static IccClut Clut3x1 = new(
new[]
{
new[] { 0.10f },
new[] { 0.20f },
new[] { 0.30f },
0.10f,
0.20f,
0.30f,
new[] { 0.11f },
new[] { 0.21f },
new[] { 0.31f },
0.11f,
0.21f,
0.31f,
new[] { 0.12f },
new[] { 0.22f },
new[] { 0.32f },
0.12f,
0.22f,
0.32f,
new[] { 0.13f },
new[] { 0.23f },
new[] { 0.33f },
0.13f,
0.23f,
0.33f,
new[] { 0.14f },
new[] { 0.24f },
new[] { 0.34f },
0.14f,
0.24f,
0.34f,
new[] { 0.15f },
new[] { 0.25f },
new[] { 0.35f },
0.15f,
0.25f,
0.35f,
new[] { 0.16f },
new[] { 0.26f },
new[] { 0.36f },
0.16f,
0.26f,
0.36f,
new[] { 0.17f },
new[] { 0.27f },
new[] { 0.37f },
0.17f,
0.27f,
0.37f,
new[] { 0.18f },
new[] { 0.28f },
new[] { 0.38f },
0.18f,
0.28f,
0.38f,
},
new byte[] { 3, 3, 3 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 3);
internal static IccClut Clut2x2 = new(
new[]
{
new[] { 0.1f, 0.9f },
new[] { 0.2f, 0.8f },
new[] { 0.3f, 0.7f },
0.1f, 0.9f,
0.2f, 0.8f,
0.3f, 0.7f,
new[] { 0.4f, 0.6f },
new[] { 0.5f, 0.5f },
new[] { 0.6f, 0.4f },
0.4f, 0.6f,
0.5f, 0.5f,
0.6f, 0.4f,
new[] { 0.7f, 0.3f },
new[] { 0.8f, 0.2f },
new[] { 0.9f, 0.1f },
0.7f, 0.3f,
0.8f, 0.2f,
0.9f, 0.1f,
},
new byte[] { 3, 3 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 3);
internal static IccClut Clut2x1 = new(
new[]
{
new[] { 0.1f },
new[] { 0.2f },
new[] { 0.3f },
0.1f,
0.2f,
0.3f,
new[] { 0.4f },
new[] { 0.5f },
new[] { 0.6f },
0.4f,
0.5f,
0.6f,
new[] { 0.7f },
new[] { 0.8f },
new[] { 0.9f },
0.7f,
0.8f,
0.9f,
},
new byte[] { 3, 3 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 3);
internal static IccClut Clut1x2 = new(
new[]
{
new[] { 0f, 0.5f },
new[] { 0.25f, 0.75f, },
new[] { 0.5f, 1f },
0f, 0.5f,
0.25f, 0.75f,
0.5f, 1f,
},
new byte[] { 3 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 3);
internal static IccClut Clut1x1 = new(
new[]
{
new[] { 0f },
new[] { 0.5f },
new[] { 1f },
0f,
0.5f,
1f,
},
new byte[] { 3 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 3);
public static object[][] ClutConversionTestData =
{

19
tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs

@ -18,20 +18,21 @@ public class IccConversionDataMultiProcessElement
private static readonly IccClut Clut = new(
new[]
{
new[] { 0.2f, 0.3f },
new[] { 0.4f, 0.5f },
0.2f, 0.3f,
0.4f, 0.2f,
new[] { 0.21f, 0.31f },
new[] { 0.41f, 0.51f },
0.21f, 0.31f,
0.41f, 0.51f,
new[] { 0.22f, 0.32f },
new[] { 0.42f, 0.52f },
0.22f, 0.32f,
0.42f, 0.52f,
new[] { 0.23f, 0.33f },
new[] { 0.43f, 0.53f },
0.23f, 0.33f,
0.43f, 0.53f,
},
new byte[] { 2, 2, 2 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 2);
private static readonly IccFormulaCurveElement FormulaCurveElement1 = new(IccFormulaCurveType.Type1, 2.2f, 0.7f, 0.2f, 0.3f, 0, 0);
private static readonly IccFormulaCurveElement FormulaCurveElement2 = new(IccFormulaCurveType.Type2, 2.2f, 0.9f, 0.9f, 0.02f, 0.1f, 0);

63
tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs

@ -73,20 +73,21 @@ internal static class IccTestDataLut
public static readonly IccClut Clut8ValGrad = new(
new[]
{
new[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue },
new[] { 4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue },
new[] { 7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue },
1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue,
4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue,
7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue,
new[] { 10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue },
new[] { 13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue },
new[] { 16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue },
10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue,
13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue,
16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue,
new[] { 19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue },
new[] { 22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue },
new[] { 25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue },
19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue,
22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue,
25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue,
},
new byte[] { 3, 3 },
IccClutDataType.UInt8);
IccClutDataType.UInt8,
outputChannelCount: 3);
/// <summary>
/// <para>Input Channel Count: 2</para>
@ -116,20 +117,21 @@ internal static class IccTestDataLut
public static readonly IccClut Clut16ValGrad = new(
new[]
{
new[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue },
new[] { 4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue },
new[] { 7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue },
1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue,
4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue,
7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue,
new[] { 10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue },
new[] { 13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue },
new[] { 16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue },
10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue,
13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue,
16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue,
new[] { 19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue },
new[] { 22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue },
new[] { 25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue },
19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue,
22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue,
25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue,
},
new byte[] { 3, 3 },
IccClutDataType.UInt16);
IccClutDataType.UInt16,
outputChannelCount: 3);
/// <summary>
/// <para>Input Channel Count: 2</para>
@ -159,20 +161,21 @@ internal static class IccTestDataLut
public static readonly IccClut CluTf32ValGrad = new(
new[]
{
new[] { 1f, 2f, 3f },
new[] { 4f, 5f, 6f },
new[] { 7f, 8f, 9f },
1f, 2f, 3f,
4f, 5f, 6f,
7f, 8f, 9f,
new[] { 1f, 2f, 3f },
new[] { 4f, 5f, 6f },
new[] { 7f, 8f, 9f },
1f, 2f, 3f,
4f, 5f, 6f,
7f, 8f, 9f,
new[] { 1f, 2f, 3f },
new[] { 4f, 5f, 6f },
new[] { 7f, 8f, 9f },
1f, 2f, 3f,
4f, 5f, 6f,
7f, 8f, 9f,
},
new byte[] { 3, 3 },
IccClutDataType.Float);
IccClutDataType.Float,
outputChannelCount: 3);
/// <summary>
/// <para>Input Channel Count: 2</para>

Loading…
Cancel
Save