Browse Source

Merge pull request #2937 from SixLabors/bp/tiff-jpeg-cmyk

Add Full Support for JPEG Tiff PhotometricInterpretation.Separated
pull/2941/head
James Jackson-South 11 months ago
committed by GitHub
parent
commit
9badd817b3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs
  2. 118
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs
  3. 99
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs
  4. 99
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs
  5. 108
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs
  6. 153
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs
  7. 131
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs
  8. 131
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs
  9. 142
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs
  10. 6
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs
  11. 10
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs
  12. 77
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  13. 3
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  14. 3
      src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs
  15. 10
      src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs
  16. 18
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  17. 9
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  18. 8
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs
  19. 29
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs
  20. 5
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffOldJpegSpectralConverter{TPixel}.cs
  21. 6
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  22. 23
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs
  23. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  24. 25
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  25. 8
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  26. 1
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  27. 2
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  28. 32
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  29. 3
      tests/ImageSharp.Tests/TestImages.cs
  30. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png
  31. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png
  32. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png
  33. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png
  34. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png
  35. 3
      tests/Images/Input/Tiff/Cmyk-jpeg.tiff
  36. 3
      tests/Images/Input/Tiff/Cmyk-planar-jpg.tiff
  37. 3
      tests/Images/Input/Tiff/Issues/Issue2454_A.tif
  38. 3
      tests/Images/Input/Tiff/Issues/Issue2454_B.tif

1
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs

@ -75,6 +75,7 @@ internal abstract partial class JpegColorConverterBase
internal static void ConvertFromRgb(ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// TODO: This doesn't seem correct. We should be scaling to the maximum value here.
rLane.CopyTo(values.Component0);
gLane.CopyTo(values.Component1);
bLane.CopyTo(values.Component2);

118
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs

@ -0,0 +1,118 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
/// </summary>
internal sealed class TiffCmykScalar : JpegColorConverterScalar
{
public TiffCmykScalar(int precision)
: base(JpegColorSpace.TiffCmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(in values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{
float c = c0[i] * scale;
float m = c1[i] * scale;
float y = c2[i] * scale;
float k = 1 - (c3[i] * scale);
c0[i] = (1 - c) * k;
c1[i] = (1 - m) * k;
c2[i] = (1 - y) * k;
}
}
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> c = values.Component0;
Span<float> m = values.Component1;
Span<float> y = values.Component2;
Span<float> k = values.Component3;
for (int i = 0; i < c.Length; i++)
{
float ctmp = 255F - rLane[i];
float mtmp = 255F - gLane[i];
float ytmp = 255F - bLane[i];
float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp);
if (ktmp >= 255F)
{
ctmp = 0F;
mtmp = 0F;
ytmp = 0F;
}
else
{
float divisor = 1 / (255F - ktmp);
ctmp = (ctmp - ktmp) * divisor;
mtmp = (mtmp - ktmp) * divisor;
ytmp = (ytmp - ktmp) * divisor;
}
c[i] = ctmp * maxValue;
m[i] = mtmp * maxValue;
y[i] = ytmp * maxValue;
k[i] = ktmp;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

99
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs

@ -0,0 +1,99 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffCmykVector128 : JpegColorConverterVector128
{
public TiffCmykVector128(int precision)
: base(JpegColorSpace.TiffCmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> c1Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> c2Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> c3Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> y = ref Unsafe.Add(ref c2Base, i);
Vector128<float> k = Unsafe.Add(ref c3Base, i);
k = Vector128<float>.One - (k * scale);
c = (Vector128<float>.One - (c * scale)) * k;
m = (Vector128<float>.One - (m * scale)) * k;
y = (Vector128<float>.One - (y * scale)) * k;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> destC =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destM =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> destK =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector128<float> srcR =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector128<float> srcG =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector128<float> srcB =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));
Vector128<float> scale = Vector128.Create(maxValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector128<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector128<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector128<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector128<float> ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp));
Vector128<float> kMask = ~Vector128.Equals(ktmp, scale);
Vector128<float> divisor = Vector128<float>.One / (scale - ktmp);
ctmp = ((ctmp - ktmp) * divisor) & kMask;
mtmp = ((mtmp - ktmp) * divisor) & kMask;
ytmp = ((ytmp - ktmp) * divisor) & kMask;
Unsafe.Add(ref destC, i) = ctmp * scale;
Unsafe.Add(ref destM, i) = mtmp * scale;
Unsafe.Add(ref destY, i) = ytmp * scale;
Unsafe.Add(ref destK, i) = ktmp;
}
}
}
}

99
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs

@ -0,0 +1,99 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffCmykVector256 : JpegColorConverterVector256
{
public TiffCmykVector256(int precision)
: base(JpegColorSpace.TiffCmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Vector256<float>.One - (k * scale);
c = (Vector256<float>.One - (c * scale)) * k;
m = (Vector256<float>.One - (m * scale)) * k;
y = (Vector256<float>.One - (y * scale)) * k;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> destC =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destM =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> destK =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> srcR =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector256<float> srcG =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector256<float> srcB =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane));
Vector256<float> scale = Vector256.Create(maxValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector256<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector256<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector256<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector256<float> ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp));
Vector256<float> kMask = ~Vector256.Equals(ktmp, scale);
Vector256<float> divisor = Vector256<float>.One / (scale - ktmp);
ctmp = ((ctmp - ktmp) * divisor) & kMask;
mtmp = ((mtmp - ktmp) * divisor) & kMask;
ytmp = ((ytmp - ktmp) * divisor) & kMask;
Unsafe.Add(ref destC, i) = ctmp * scale;
Unsafe.Add(ref destM, i) = mtmp * scale;
Unsafe.Add(ref destY, i) = ytmp * scale;
Unsafe.Add(ref destK, i) = ktmp;
}
}
}
}

108
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs

@ -0,0 +1,108 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffCmykVector512 : JpegColorConverterVector512
{
public TiffCmykVector512(int precision)
: base(JpegColorSpace.TiffCmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector512<float> c0Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> c1Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> c2Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector512<float> c3Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
// Used for the color conversion
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector512<float> c = ref Unsafe.Add(ref c0Base, i);
ref Vector512<float> m = ref Unsafe.Add(ref c1Base, i);
ref Vector512<float> y = ref Unsafe.Add(ref c2Base, i);
Vector512<float> k = Unsafe.Add(ref c3Base, i);
k = Vector512<float>.One - (k * scale);
c = (Vector512<float>.One - (c * scale)) * k;
m = (Vector512<float>.One - (m * scale)) * k;
y = (Vector512<float>.One - (y * scale)) * k;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> TiffCmykScalar.ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> TiffCmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane);
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector512<float> destC =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> destM =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> destY =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector512<float> destK =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector512<float> srcR =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector512<float> srcG =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector512<float> srcB =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane));
Vector512<float> scale = Vector512.Create(maxValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector512<float> ctmp = scale - Unsafe.Add(ref srcR, i);
Vector512<float> mtmp = scale - Unsafe.Add(ref srcG, i);
Vector512<float> ytmp = scale - Unsafe.Add(ref srcB, i);
Vector512<float> ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp));
Vector512<float> kMask = ~Vector512.Equals(ktmp, scale);
Vector512<float> divisor = Vector512<float>.One / (scale - ktmp);
ctmp = ((ctmp - ktmp) * divisor) & kMask;
mtmp = ((mtmp - ktmp) * divisor) & kMask;
ytmp = ((ytmp - ktmp) * divisor) & kMask;
Unsafe.Add(ref destC, i) = ctmp * scale;
Unsafe.Add(ref destM, i) = mtmp * scale;
Unsafe.Add(ref destY, i) = ytmp * scale;
Unsafe.Add(ref destK, i) = ktmp;
}
}
}
}

153
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs

@ -0,0 +1,153 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
/// </summary>
internal sealed class TiffYccKScalar : JpegColorConverterScalar
{
// Derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
internal const float BCbMult = 1.772f;
public TiffYccKScalar(int precision)
: base(JpegColorSpace.TiffYccK, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(in values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
float scale = 1F / maxValue;
halfValue *= scale;
for (int i = 0; i < values.Component0.Length; i++)
{
float y = c0[i] * scale;
float cb = (c1[i] * scale) - halfValue;
float cr = (c2[i] * scale) - halfValue;
float scaledK = 1 - (c3[i] * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
c0[i] = (y + (RCrMult * cr)) * scaledK;
c1[i] = (y - (GCbMult * cb) - (GCrMult * cr)) * scaledK;
c2[i] = (y + (BCbMult * cb)) * scaledK;
}
}
public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> y = values.Component0;
Span<float> cb = values.Component1;
Span<float> cr = values.Component2;
Span<float> k = values.Component3;
for (int i = 0; i < cr.Length; i++)
{
// Scale down to [0-1]
const float divisor = 1F / 255F;
float r = rLane[i] * divisor;
float g = gLane[i] * divisor;
float b = bLane[i] * divisor;
float ytmp;
float cbtmp;
float crtmp;
float ktmp = 1F - MathF.Max(r, MathF.Max(g, b));
if (ktmp >= 1F)
{
ytmp = 0F;
cbtmp = 0.5F;
crtmp = 0.5F;
ktmp = maxValue;
}
else
{
float kmask = 1F / (1F - ktmp);
r *= kmask;
g *= kmask;
b *= kmask;
// Scale to [0-maxValue]
ytmp = ((0.299f * r) + (0.587f * g) + (0.114f * b)) * maxValue;
cbtmp = halfValue - (((0.168736f * r) - (0.331264f * g) + (0.5f * b)) * maxValue);
crtmp = halfValue + (((0.5f * r) - (0.418688f * g) - (0.081312f * b)) * maxValue);
ktmp *= maxValue;
}
y[i] = ytmp;
cb[i] = cbtmp;
cr[i] = crtmp;
k[i] = ktmp;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
ColorProfileConverter converter = new();
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
// YccK is not a defined ICC color space — it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs.
// ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform.
// This transform assumes Rec.601 YCbCr coefficients and an inverted K channel.
//
// The YccK => Cmyk conversion is independent of any embedded ICC profile.
// Since the same RGB working space is used during conversion to and from XYZ,
// colorimetric accuracy is preserved.
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

131
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs

@ -0,0 +1,131 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffYccKVector128 : JpegColorConverterVector128
{
public TiffYccKVector128(int precision)
: base(JpegColorSpace.TiffYccK, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> c1Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> c2Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> c3Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector128<float> scale = Vector128.Create(1F / this.MaximumValue);
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue) * scale;
Vector128<float> rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
Vector128<float> gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
Vector128<float> gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
Vector128<float> bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector128<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector128<float> c2 = ref Unsafe.Add(ref c2Base, i);
ref Vector128<float> c3 = ref Unsafe.Add(ref c3Base, i);
Vector128<float> y = c0 * scale;
Vector128<float> cb = (c1 * scale) - chromaOffset;
Vector128<float> cr = (c2 * scale) - chromaOffset;
Vector128<float> scaledK = Vector128<float>.One - (c3 * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector128<float> r = Vector128_.MultiplyAdd(y, cr, rCrMult) * scaledK;
Vector128<float> g = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK;
Vector128<float> b = Vector128_.MultiplyAdd(y, cb, bCbMult) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector128<float> srcR =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector128<float> srcG =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector128<float> srcB =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(bLane));
ref Vector128<float> destY =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> destCb =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> destCr =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector128<float> destK =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector128<float> maxSourceValue = Vector128.Create(1 / 255F);
Vector128<float> maxSampleValue = Vector128.Create(this.MaximumValue);
Vector128<float> chromaOffset = Vector128.Create(this.HalfValue);
Vector128<float> f0299 = Vector128.Create(0.299f);
Vector128<float> f0587 = Vector128.Create(0.587f);
Vector128<float> f0114 = Vector128.Create(0.114f);
Vector128<float> fn0168736 = Vector128.Create(-0.168736f);
Vector128<float> fn0331264 = Vector128.Create(-0.331264f);
Vector128<float> fn0418688 = Vector128.Create(-0.418688f);
Vector128<float> fn0081312F = Vector128.Create(-0.081312F);
Vector128<float> f05 = Vector128.Create(0.5f);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector128<float> r = Unsafe.Add(ref srcR, i) * maxSourceValue;
Vector128<float> g = Unsafe.Add(ref srcG, i) * maxSourceValue;
Vector128<float> b = Unsafe.Add(ref srcB, i) * maxSourceValue;
Vector128<float> ktmp = Vector128<float>.One - Vector128.Max(r, Vector128.Min(g, b));
Vector128<float> kMask = ~Vector128.Equals(ktmp, Vector128<float>.One);
Vector128<float> divisor = Vector128<float>.One / (Vector128<float>.One - ktmp);
r = (r * divisor) & kMask;
g = (g * divisor) & kMask;
b = (b * divisor) & kMask;
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector128<float> y = Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f0114 * b, f0587, g), f0299, r);
Vector128<float> cb = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r);
Vector128<float> cr = chromaOffset + Vector128_.MultiplyAdd(Vector128_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r);
Unsafe.Add(ref destY, i) = y * maxSampleValue;
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue);
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue);
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue;
}
}
}
}

131
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs

@ -0,0 +1,131 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffYccKVector256 : JpegColorConverterVector256
{
public TiffYccKVector256(int precision)
: base(JpegColorSpace.TiffYccK, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector256<float> scale = Vector256.Create(1F / this.MaximumValue);
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue) * scale;
Vector256<float> rCrMult = Vector256.Create(YCbCrScalar.RCrMult);
Vector256<float> gCbMult = Vector256.Create(-YCbCrScalar.GCbMult);
Vector256<float> gCrMult = Vector256.Create(-YCbCrScalar.GCrMult);
Vector256<float> bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
ref Vector256<float> c3 = ref Unsafe.Add(ref c3Base, i);
Vector256<float> y = c0 * scale;
Vector256<float> cb = (c1 * scale) - chromaOffset;
Vector256<float> cr = (c2 * scale) - chromaOffset;
Vector256<float> scaledK = Vector256<float>.One - (c3 * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector256<float> r = Vector256_.MultiplyAdd(y, cr, rCrMult) * scaledK;
Vector256<float> g = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK;
Vector256<float> b = Vector256_.MultiplyAdd(y, cb, bCbMult) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector256<float> srcR =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector256<float> srcG =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector256<float> srcB =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(bLane));
ref Vector256<float> destY =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> destCb =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> destCr =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> destK =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector256<float> maxSourceValue = Vector256.Create(255F);
Vector256<float> maxSampleValue = Vector256.Create(this.MaximumValue);
Vector256<float> chromaOffset = Vector256.Create(this.HalfValue);
Vector256<float> f0299 = Vector256.Create(0.299f);
Vector256<float> f0587 = Vector256.Create(0.587f);
Vector256<float> f0114 = Vector256.Create(0.114f);
Vector256<float> fn0168736 = Vector256.Create(-0.168736f);
Vector256<float> fn0331264 = Vector256.Create(-0.331264f);
Vector256<float> fn0418688 = Vector256.Create(-0.418688f);
Vector256<float> fn0081312F = Vector256.Create(-0.081312F);
Vector256<float> f05 = Vector256.Create(0.5f);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector256<float> r = Unsafe.Add(ref srcR, i) / maxSourceValue;
Vector256<float> g = Unsafe.Add(ref srcG, i) / maxSourceValue;
Vector256<float> b = Unsafe.Add(ref srcB, i) / maxSourceValue;
Vector256<float> ktmp = Vector256<float>.One - Vector256.Max(r, Vector256.Min(g, b));
Vector256<float> kMask = ~Vector256.Equals(ktmp, Vector256<float>.One);
Vector256<float> divisor = Vector256<float>.One / (Vector256<float>.One - ktmp);
r = (r * divisor) & kMask;
g = (g * divisor) & kMask;
b = (b * divisor) & kMask;
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector256<float> y = Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f0114 * b, f0587, g), f0299, r);
Vector256<float> cb = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r);
Vector256<float> cr = chromaOffset + Vector256_.MultiplyAdd(Vector256_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r);
Unsafe.Add(ref destY, i) = y * maxSampleValue;
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue);
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue);
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue;
}
}
}
}

142
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs

@ -0,0 +1,142 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
internal sealed class TiffYccKVector512 : JpegColorConverterVector512
{
public TiffYccKVector512(int precision)
: base(JpegColorSpace.TiffYccK, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector512<float> c0Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> c1Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> c2Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector512<float> c3Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector512<float> scale = Vector512.Create(1F / this.MaximumValue);
Vector512<float> chromaOffset = Vector512.Create(this.HalfValue) * scale;
Vector512<float> rCrMult = Vector512.Create(YCbCrScalar.RCrMult);
Vector512<float> gCbMult = Vector512.Create(-YCbCrScalar.GCbMult);
Vector512<float> gCrMult = Vector512.Create(-YCbCrScalar.GCrMult);
Vector512<float> bCbMult = Vector512.Create(YCbCrScalar.BCbMult);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector512<float> c0 = ref Unsafe.Add(ref c0Base, i);
ref Vector512<float> c1 = ref Unsafe.Add(ref c1Base, i);
ref Vector512<float> c2 = ref Unsafe.Add(ref c2Base, i);
ref Vector512<float> c3 = ref Unsafe.Add(ref c3Base, i);
Vector512<float> y = c0 * scale;
Vector512<float> cb = (c1 * scale) - chromaOffset;
Vector512<float> cr = (c2 * scale) - chromaOffset;
Vector512<float> scaledK = Vector512<float>.One - (c3 * scale);
// r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr);
// b = y + (1.772F * cb);
Vector512<float> r = Vector512_.MultiplyAdd(y, cr, rCrMult) * scaledK;
Vector512<float> g = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(y, cb, gCbMult), cr, gCrMult) * scaledK;
Vector512<float> b = Vector512_.MultiplyAdd(y, cb, bCbMult) * scaledK;
c0 = r;
c1 = g;
c2 = b;
}
}
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgbVectorized(in values, this.MaximumValue, this.HalfValue, rLane, gLane, bLane);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> TiffYccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> TiffYccKScalar.ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, float halfValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
ref Vector512<float> srcR =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(rLane));
ref Vector512<float> srcG =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(gLane));
ref Vector512<float> srcB =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(bLane));
ref Vector512<float> destY =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> destCb =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> destCr =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector512<float> destK =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component3));
Vector512<float> maxSourceValue = Vector512.Create(255F);
Vector512<float> maxSampleValue = Vector512.Create(maxValue);
Vector512<float> chromaOffset = Vector512.Create(halfValue);
Vector512<float> f0299 = Vector512.Create(0.299f);
Vector512<float> f0587 = Vector512.Create(0.587f);
Vector512<float> f0114 = Vector512.Create(0.114f);
Vector512<float> fn0168736 = Vector512.Create(-0.168736f);
Vector512<float> fn0331264 = Vector512.Create(-0.331264f);
Vector512<float> fn0418688 = Vector512.Create(-0.418688f);
Vector512<float> fn0081312F = Vector512.Create(-0.081312F);
Vector512<float> f05 = Vector512.Create(0.5f);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
Vector512<float> r = Unsafe.Add(ref srcR, i) / maxSourceValue;
Vector512<float> g = Unsafe.Add(ref srcG, i) / maxSourceValue;
Vector512<float> b = Unsafe.Add(ref srcB, i) / maxSourceValue;
Vector512<float> ktmp = Vector512<float>.One - Vector512.Max(r, Vector512.Min(g, b));
Vector512<float> kMask = ~Vector512.Equals(ktmp, Vector512<float>.One);
Vector512<float> divisor = Vector512<float>.One / (Vector512<float>.One - ktmp);
r = (r * divisor) & kMask;
g = (g * divisor) & kMask;
b = (b * divisor) & kMask;
// y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
// cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
// cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
Vector512<float> y = Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f0114 * b, f0587, g), f0299, r);
Vector512<float> cb = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(f05 * b, fn0331264, g), fn0168736, r);
Vector512<float> cr = chromaOffset + Vector512_.MultiplyAdd(Vector512_.MultiplyAdd(fn0081312F * b, fn0418688, g), f05, r);
Unsafe.Add(ref destY, i) = y * maxSampleValue;
Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue);
Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue);
Unsafe.Add(ref destK, i) = ktmp * maxSampleValue;
}
}
}
}

6
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs

@ -14,7 +14,7 @@ internal abstract partial class JpegColorConverterBase
{
internal sealed class YccKScalar : JpegColorConverterScalar
{
// derived from ITU-T Rec. T.871
// Derived from ITU-T Rec. T.871
internal const float RCrMult = 1.402f;
internal const float GCbMult = (float)(0.114 * 1.772 / 0.587);
internal const float GCrMult = (float)(0.299 * 1.402 / 0.587);
@ -27,7 +27,7 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgpInPlace(values, this.MaximumValue, this.HalfValue);
=> ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
@ -37,7 +37,7 @@ internal abstract partial class JpegColorConverterBase
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
public static void ConvertToRgpInPlace(in ComponentValues values, float maxValue, float halfValue)
public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue)
{
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;

10
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs

@ -82,7 +82,7 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> YccKScalar.ConvertToRgpInPlace(values, this.MaximumValue, this.HalfValue);
=> YccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
@ -138,12 +138,6 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
// rgb -> cmyk
CmykScalar.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
// cmyk -> ycck
YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
}
=> YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
}
}

77
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

@ -153,6 +153,39 @@ internal abstract partial class JpegColorConverterBase
}
}
public static void PackedNormalizeInterleave4(
ReadOnlySpan<float> xLane,
ReadOnlySpan<float> yLane,
ReadOnlySpan<float> zLane,
ReadOnlySpan<float> wLane,
Span<float> packed,
float maxValue)
{
DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4.");
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!");
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!");
DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!");
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed));
float scale = 1F / maxValue;
// TODO: Investigate SIMD version of this.
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane);
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane);
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane);
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane);
ref float packedRef = ref MemoryMarshal.GetReference(packed);
for (nuint i = 0; i < (nuint)xLane.Length; i++)
{
nuint baseIdx = i * 4;
Unsafe.Add(ref packedRef, baseIdx) = Unsafe.Add(ref xLaneRef, i) * scale;
Unsafe.Add(ref packedRef, baseIdx + 1) = Unsafe.Add(ref yLaneRef, i) * scale;
Unsafe.Add(ref packedRef, baseIdx + 2) = Unsafe.Add(ref zLaneRef, i) * scale;
Unsafe.Add(ref packedRef, baseIdx + 3) = Unsafe.Add(ref wLaneRef, i) * scale;
}
}
public static void PackedInvertNormalizeInterleave4(
ReadOnlySpan<float> xLane,
ReadOnlySpan<float> yLane,
@ -198,6 +231,8 @@ internal abstract partial class JpegColorConverterBase
GetCmykConverter(8),
GetGrayScaleConverter(8),
GetRgbConverter(8),
GetTiffCmykConverter(8),
GetTiffYccKConverter(8),
// 12-bit converters
GetYCbCrConverter(12),
@ -205,6 +240,8 @@ internal abstract partial class JpegColorConverterBase
GetCmykConverter(12),
GetGrayScaleConverter(12),
GetRgbConverter(12),
GetTiffCmykConverter(12),
GetTiffYccKConverter(12),
];
/// <summary>
@ -327,6 +364,46 @@ internal abstract partial class JpegColorConverterBase
return new RgbScalar(precision);
}
private static JpegColorConverterBase GetTiffCmykConverter(int precision)
{
if (JpegColorConverterVector512.IsSupported)
{
return new TiffCmykVector512(precision);
}
if (JpegColorConverterVector256.IsSupported)
{
return new TiffCmykVector256(precision);
}
if (JpegColorConverterVector128.IsSupported)
{
return new TiffCmykVector128(precision);
}
return new TiffCmykScalar(precision);
}
private static JpegColorConverterBase GetTiffYccKConverter(int precision)
{
if (JpegColorConverterVector512.IsSupported)
{
return new TiffYccKVector512(precision);
}
if (JpegColorConverterVector256.IsSupported)
{
return new TiffYccKVector256(precision);
}
if (JpegColorConverterVector128.IsSupported)
{
return new TiffYccKVector128(precision);
}
return new TiffYccKScalar(precision);
}
/// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.
/// </summary>

3
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -83,7 +83,8 @@ internal abstract class SpectralConverter
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
/// <param name="jpegData">The raw JPEG data.</param>
/// <returns>The color converter.</returns>
protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision);
protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData)
=> JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision);
/// <summary>
/// Calculates image size with optional scaling.

3
src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -109,6 +108,8 @@ internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
int y = yy - this.pixelRowCounter;
// Unpack TPixel to r/g/b planes
// TODO: The individual implementation code would be much easier here if
// we scaled to [0-1] before passing to the individual converters.
int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex);
Span<TPixel> sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex);
PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow);

10
src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs

@ -23,6 +23,16 @@ internal enum JpegColorSpace
/// </summary>
Cmyk,
/// <summary>
/// YccK color space with 4 components, used with tiff images, which use jpeg compression.
/// </summary>
TiffYccK,
/// <summary>
/// Cmyk color space with 4 components, used with tiff images, which use jpeg compression.
/// </summary>
TiffCmyk,
/// <summary>
/// Color space with 3 components.
/// </summary>

18
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -115,12 +115,14 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
/// Initializes a new instance of the <see cref="JpegDecoderCore"/> class.
/// </summary>
/// <param name="options">The decoder options.</param>
public JpegDecoderCore(JpegDecoderOptions options)
/// <param name="iccProfile">The ICC profile to use for color conversion.</param>
public JpegDecoderCore(JpegDecoderOptions options, IccProfile iccProfile = null)
: base(options.GeneralOptions)
{
this.resizeMode = options.ResizeMode;
this.configuration = options.GeneralOptions.Configuration;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
this.SetIccMetadata(iccProfile);
}
/// <summary>
@ -231,7 +233,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
/// <param name="scanDecoder">The scan decoder.</param>
public void LoadTables(byte[] tableBytes, IJpegScanDecoder scanDecoder)
{
this.Metadata = new ImageMetadata();
this.Metadata ??= new ImageMetadata();
this.QuantizationTables = new Block8x8F[4];
this.scanDecoder = scanDecoder;
if (tableBytes.Length < 4)
@ -314,7 +316,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
this.scanDecoder ??= new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
this.Metadata = new ImageMetadata();
this.Metadata ??= new ImageMetadata();
Span<byte> markerBuffer = stackalloc byte[2];
@ -678,6 +680,16 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
}
}
private void SetIccMetadata(IccProfile profile)
{
if (!this.skipMetadata && profile?.CheckIsValid() == true)
{
this.hasIcc = true;
this.Metadata ??= new ImageMetadata();
this.Metadata.IccProfile = profile;
}
}
/// <summary>
/// Initializes the IPTC profile.
/// </summary>

9
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
@ -22,6 +23,8 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
private readonly TiffPhotometricInterpretation photometricInterpretation;
private readonly ImageFrameMetadata metadata;
/// <summary>
/// Initializes a new instance of the <see cref="JpegTiffCompression"/> class.
/// </summary>
@ -29,6 +32,7 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="metadata">The image frame metadata.</param>
/// <param name="jpegTables">The JPEG tables containing the quantization and/or Huffman tables.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public JpegTiffCompression(
@ -36,11 +40,13 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
MemoryAllocator memoryAllocator,
int width,
int bitsPerPixel,
ImageFrameMetadata metadata,
byte[] jpegTables,
TiffPhotometricInterpretation photometricInterpretation)
: base(memoryAllocator, width, bitsPerPixel)
{
this.options = options;
this.metadata = metadata;
this.jpegTables = jpegTables;
this.photometricInterpretation = photometricInterpretation;
}
@ -61,7 +67,7 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
private void DecodeJpegData(BufferedReadStream stream, Span<byte> buffer, CancellationToken cancellationToken)
{
using JpegDecoderCore jpegDecoder = new(this.options);
using JpegDecoderCore jpegDecoder = new(this.options, this.metadata.IccProfile);
Configuration configuration = this.options.GeneralOptions.Configuration;
switch (this.photometricInterpretation)
{
@ -85,6 +91,7 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
case TiffPhotometricInterpretation.YCbCr:
case TiffPhotometricInterpretation.Rgb:
case TiffPhotometricInterpretation.Separated:
{
using SpectralConverter<Rgb24> spectralConverter = new TiffJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);
HuffmanScanDecoder scanDecoder = new(stream, spectralConverter, cancellationToken);

8
src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
@ -17,6 +18,8 @@ internal sealed class OldJpegTiffCompression : TiffBaseDecompressor
private readonly uint startOfImageMarker;
private readonly ImageFrameMetadata metadata;
private readonly TiffPhotometricInterpretation photometricInterpretation;
public OldJpegTiffCompression(
@ -24,12 +27,14 @@ internal sealed class OldJpegTiffCompression : TiffBaseDecompressor
MemoryAllocator memoryAllocator,
int width,
int bitsPerPixel,
ImageFrameMetadata metadata,
uint startOfImageMarker,
TiffPhotometricInterpretation photometricInterpretation)
: base(memoryAllocator, width, bitsPerPixel)
{
this.options = options;
this.startOfImageMarker = startOfImageMarker;
this.metadata = metadata;
this.photometricInterpretation = photometricInterpretation;
}
@ -47,7 +52,7 @@ internal sealed class OldJpegTiffCompression : TiffBaseDecompressor
private void DecodeJpegData(BufferedReadStream stream, Span<byte> buffer, CancellationToken cancellationToken)
{
using JpegDecoderCore jpegDecoder = new(this.options);
using JpegDecoderCore jpegDecoder = new(this.options, this.metadata.IccProfile);
Configuration configuration = this.options.GeneralOptions.Configuration;
switch (this.photometricInterpretation)
{
@ -71,6 +76,7 @@ internal sealed class OldJpegTiffCompression : TiffBaseDecompressor
case TiffPhotometricInterpretation.YCbCr:
case TiffPhotometricInterpretation.Rgb:
case TiffPhotometricInterpretation.Separated:
{
using SpectralConverter<Rgb24> spectralConverter = new TiffOldJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);

29
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs

@ -31,19 +31,30 @@ internal sealed class TiffJpegSpectralConverter<TPixel> : SpectralConverter<TPix
/// <inheritdoc/>
protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData)
{
JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation);
JpegColorSpace colorSpace = GetJpegColorSpace(this.photometricInterpretation, jpegData);
return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision);
}
/// <summary>
/// This converter must be used only for RGB and YCbCr color spaces for performance reasons.
/// Photometric interpretation Rgb and YCbCr will be mapped to RGB colorspace, which means the jpeg decompression will leave the data as is (no color conversion).
/// The color conversion will be done after the decompression. For Separated/CMYK/YCCK, the jpeg color converter will handle the color conversion,
/// since the jpeg color converter needs to return RGB data and cannot return 4 component data.
/// For grayscale images <see cref="GrayJpegSpectralConverter{TPixel}"/> must be used.
/// </summary>
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation)
=> interpretation switch
{
TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB,
TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB,
_ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"),
};
/// <param name="interpretation">
/// The <see cref="TiffPhotometricInterpretation"/> to convert to a <see cref="JpegColorSpace"/>.
/// </param>
/// <param name="data">
/// The <see cref="IRawJpegData"/> containing the color space information.
/// </param>
/// <exception cref="InvalidImageContentException">
/// Thrown when the <paramref name="interpretation"/> is not supported for JPEG encoding.
/// </exception>
private static JpegColorSpace GetJpegColorSpace(TiffPhotometricInterpretation interpretation, IRawJpegData data) => interpretation switch
{
TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB,
TiffPhotometricInterpretation.Separated => data.ColorSpace == JpegColorSpace.Ycck ? JpegColorSpace.TiffYccK : JpegColorSpace.TiffCmyk,
TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB, // TODO: Why doesn't this use the YCbCr color space?
_ => throw new InvalidImageContentException($"Invalid TIFF photometric interpretation for JPEG encoding: {interpretation}"),
};
}

5
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffOldJpegSpectralConverter{TPixel}.cs

@ -30,15 +30,16 @@ internal sealed class TiffOldJpegSpectralConverter<TPixel> : SpectralConverter<T
/// <inheritdoc/>
protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData)
{
JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation);
JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation, jpegData);
return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision);
}
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation)
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation, IRawJpegData data)
=> interpretation switch
{
// Like libtiff: Always treat the pixel data as YCbCr when the data is compressed with old jpeg compression.
TiffPhotometricInterpretation.Rgb => JpegColorSpace.YCbCr,
TiffPhotometricInterpretation.Separated => data.ColorSpace == JpegColorSpace.Ycck ? JpegColorSpace.TiffYccK : JpegColorSpace.TiffCmyk,
TiffPhotometricInterpretation.YCbCr => JpegColorSpace.YCbCr,
_ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"),
};

6
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression;
@ -17,6 +18,7 @@ internal static class TiffDecompressorsFactory
TiffPhotometricInterpretation photometricInterpretation,
int width,
int bitsPerPixel,
ImageFrameMetadata metadata,
TiffColorType colorType,
TiffPredictor predictor,
FaxCompressionOptions faxOptions,
@ -62,11 +64,11 @@ internal static class TiffDecompressorsFactory
case TiffDecoderCompressionType.Jpeg:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new JpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation);
return new JpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, metadata, jpegTables, photometricInterpretation);
case TiffDecoderCompressionType.OldJpeg:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new OldJpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, oldJpegStartOfImageMarker, photometricInterpretation);
return new OldJpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, metadata, oldJpegStartOfImageMarker, photometricInterpretation);
case TiffDecoderCompressionType.Webp:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");

23
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -13,10 +15,31 @@ internal class CmykTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
private static readonly ColorProfileConverter ColorProfileConverter = new();
private const float Inv255 = 1f / 255f;
private readonly TiffDecoderCompressionType compression;
public CmykTiffColor(TiffDecoderCompressionType compression) => this.compression = compression;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
int offset = 0;
if (this.compression == TiffDecoderCompressionType.Jpeg)
{
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
pixelRow[x] = TPixel.FromVector4(new Vector4(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, 1.0f));
offset += 3;
}
}
return;
}
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -19,6 +20,7 @@ internal static class TiffColorDecoderFactory<TPixel>
Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients,
ushort[] ycbcrSubSampling,
TiffDecoderCompressionType compression,
ByteOrder byteOrder)
{
switch (colorType)
@ -410,7 +412,7 @@ internal static class TiffColorDecoderFactory<TPixel>
&& bitsPerSample.Channel1 == 8
&& bitsPerSample.Channel0 == 8,
"bitsPerSample");
return new CmykTiffColor<TPixel>();
return new CmykTiffColor<TPixel>(compression);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());

25
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -11,6 +11,7 @@ using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff;
@ -280,6 +281,12 @@ internal class TiffDecoderCore : ImageDecoderCore
if (!this.skipMetadata)
{
imageFrameMetaData.ExifProfile = tags;
// We resolve the ICC profile early so that we can use it for color conversion if needed.
if (tags.TryGetValue(ExifTag.IccProfile, out IExifValue<byte[]> iccProfileBytes))
{
imageFrameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value);
}
}
TiffFrameMetadata tiffMetadata = TiffFrameMetadata.Parse(tags);
@ -438,7 +445,7 @@ internal class TiffDecoderCore : ImageDecoderCore
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
}
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel);
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel, frame.Metadata);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = this.CreatePlanarColorDecoder<TPixel>();
for (int i = 0; i < stripsPerPlane; i++)
@ -507,7 +514,7 @@ internal class TiffDecoderCore : ImageDecoderCore
Span<byte> stripBufferSpan = stripBuffer.GetSpan();
Buffer2D<TPixel> pixels = frame.PixelBuffer;
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel);
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(width, bitsPerPixel, frame.Metadata);
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
@ -578,7 +585,7 @@ internal class TiffDecoderCore : ImageDecoderCore
tilesBuffers[i] = this.memoryAllocator.Allocate<byte>(uncompressedTilesSize, AllocationOptions.Clean);
}
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel, frame.Metadata);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = this.CreatePlanarColorDecoder<TPixel>();
int tileIndex = 0;
@ -679,7 +686,7 @@ internal class TiffDecoderCore : ImageDecoderCore
using IMemoryOwner<byte> tileBuffer = this.memoryAllocator.Allocate<byte>(bytesPerTileRow * tileLength, AllocationOptions.Clean);
Span<byte> tileBufferSpan = tileBuffer.GetSpan();
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel, true, tileWidth, tileLength);
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel, frame.Metadata, true, tileWidth, tileLength);
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();
int tileIndex = 0;
@ -733,6 +740,7 @@ internal class TiffDecoderCore : ImageDecoderCore
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,
this.YcbcrSubSampling,
this.CompressionType,
this.byteOrder);
private TiffBasePlanarColorDecoder<TPixel> CreatePlanarColorDecoder<TPixel>()
@ -747,7 +755,13 @@ internal class TiffDecoderCore : ImageDecoderCore
this.YcbcrSubSampling,
this.byteOrder);
private TiffBaseDecompressor CreateDecompressor<TPixel>(int frameWidth, int bitsPerPixel, bool isTiled = false, int tileWidth = 0, int tileHeight = 0)
private TiffBaseDecompressor CreateDecompressor<TPixel>(
int frameWidth,
int bitsPerPixel,
ImageFrameMetadata metadata,
bool isTiled = false,
int tileWidth = 0,
int tileHeight = 0)
where TPixel : unmanaged, IPixel<TPixel> =>
TiffDecompressorsFactory.Create(
this.Options,
@ -756,6 +770,7 @@ internal class TiffDecoderCore : ImageDecoderCore
this.PhotometricInterpretation,
frameWidth,
bitsPerPixel,
metadata,
this.ColorType,
this.Predictor,
this.FaxCompressionOptions,

8
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

@ -5,7 +5,6 @@
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@ -29,6 +28,8 @@ internal static class TiffDecoderMetadataCreator
{
for (int i = 0; i < frames.Count; i++)
{
// ICC profile data has already been resolved in the frame metadata,
// as it is required for color conversion.
ImageFrameMetadata frameMetaData = frames[i];
if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes))
{
@ -39,11 +40,6 @@ internal static class TiffDecoderMetadataCreator
{
frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value);
}
if (frameMetaData.ExifProfile.TryGetValue(ExifTag.IccProfile, out IExifValue<byte[]> iccProfileBytes))
{
frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value);
}
}
}

1
src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs

@ -3,7 +3,6 @@
using System.Buffers.Binary;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using SixLabors.ImageSharp.Metadata.Profiles.IPTC;

2
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -210,6 +210,8 @@ public partial class PixelOperations<TPixel>
{
GuardUnpackIntoRgbPlanes(redChannel, greenChannel, blueChannel, source);
// TODO: This can be much faster.
// Convert to Rgba32 first using pixel operations then use the R, G, B properties.
int count = source.Length;
ref float r = ref MemoryMarshal.GetReference(redChannel);

32
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -341,16 +341,46 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Cmyk, PixelTypes.Rgba32)]
[WithFile(CmykLzwPredictor, PixelTypes.Rgba32)]
[WithFile(CmykJpeg, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Cmyk<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong
// converting the pixel data from Magick.NET to our format with CMYK?
using Image<TPixel> image = provider.GetImage();
using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance);
image.DebugSave(provider);
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
[Theory]
[WithFile(Issues2454_A, PixelTypes.Rgba32)]
[WithFile(Issues2454_B, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_YccK<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance);
image.DebugSave(provider);
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
[Theory]
[WithFile(Issues2454_A, PixelTypes.Rgba32)]
[WithFile(Issues2454_B, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_YccK_ICC<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new()
{
ColorProfileHandling = ColorProfileHandling.Convert,
};
using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance, options);
image.DebugSave(provider);
// Linux reports a 0.0000% difference, so we use a tolerant comparer here.
image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.0001F), provider);
}
[Theory]
[WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)]

3
tests/ImageSharp.Tests/TestImages.cs

@ -1122,6 +1122,7 @@ public static class TestImages
public const string Cmyk = "Tiff/Cmyk.tiff";
public const string Cmyk64BitDeflate = "Tiff/cmyk_deflate_64bit.tiff";
public const string CmykLzwPredictor = "Tiff/Cmyk-lzw-predictor.tiff";
public const string CmykJpeg = "Tiff/Cmyk-jpeg.tiff";
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";
@ -1129,6 +1130,8 @@ public static class TestImages
public const string Issues2149 = "Tiff/Issues/Group4CompressionWithStrips.tiff";
public const string Issues2255 = "Tiff/Issues/Issue2255.png";
public const string Issues2435 = "Tiff/Issues/Issue2435.tiff";
public const string Issues2454_A = "Tiff/Issues/Issue2454_A.tif";
public const string Issues2454_B = "Tiff/Issues/Issue2454_B.tif";
public const string Issues2587 = "Tiff/Issues/Issue2587.tiff";
public const string Issues2679 = "Tiff/Issues/Issue2679.tiff";
public const string JpegCompressedGray0000539558 = "Tiff/Issues/JpegCompressedGray-0000539558.tiff";

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f68db78d765a7f36570cd7b57a1f06cfca24c3b4916d0692a4aa051209ec327
size 616

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4f77673028643af0ac02a8f6a1e2db14052177e3401c369391a8ff7e943770c
size 7679254

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e616895c21fd8b19a216e8a3ef4968bd413589b5875efdac29860f019a710527
size 7517284

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d7911e059049c427229136479740fd62e2e09907549ec3e1421a6a60da6167cc
size 7840892

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:291f2033a7b4cfc10fb3301283c167b3fbc288bc173c95b21bc726bf076865af
size 7649213

3
tests/Images/Input/Tiff/Cmyk-jpeg.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abb923e457acc31a7f18c46a7d58fc5a42f5c3d197236403921e3ee623fa4fac
size 2046

3
tests/Images/Input/Tiff/Cmyk-planar-jpg.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abb923e457acc31a7f18c46a7d58fc5a42f5c3d197236403921e3ee623fa4fac
size 2046

3
tests/Images/Input/Tiff/Issues/Issue2454_A.tif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:868fbf7fc7a61bc6b1226160c8dc3bb1faebd8d4a2a6fe9494962f3fbe3a7fdc
size 5024256

3
tests/Images/Input/Tiff/Issues/Issue2454_B.tif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:867851192f540742ba1481f503834f8aa77caa03ac59f8204d098bf940b0bb3a
size 4387646
Loading…
Cancel
Save