Browse Source

Merge branch 'master' into webp

pull/1552/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
ec7c253742
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      src/ImageSharp/Common/Helpers/ExifResolutionValues.cs
  2. 25
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  3. 41
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  4. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  5. 144
      src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs
  6. 194
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  7. 6
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  8. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  9. 66
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  10. 6
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  11. 121
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  12. 81
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  13. 5
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  14. 69
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  15. 65
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  16. 61
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  17. 24
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs
  18. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  19. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  20. 53
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  21. 72
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  22. 82
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  23. 80
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  24. 73
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  25. 71
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  26. 30
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs
  27. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  28. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  29. 28
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs
  30. 74
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  31. 59
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  32. 61
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  33. 65
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  34. 60
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  35. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs
  36. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  37. 8
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  38. 73
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  39. 61
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  40. 160
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  41. 102
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  42. 10
      src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
  43. 16
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  44. 16
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  45. 4
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  46. 3
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  47. 6
      src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
  48. 4
      src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
  49. 4
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
  50. 91
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  51. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  52. 39
      tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs
  53. 42
      tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs
  54. 1
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  55. 2
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs
  56. 2
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs
  57. 83
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  58. 114
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
  59. 179
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs
  60. 100
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  61. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  62. 20
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  63. 23
      tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
  64. 24
      tests/ImageSharp.Tests/TestImages.cs
  65. 3
      tests/Images/Input/Jpg/baseline/forest_bridge.jpg
  66. 3
      tests/Images/Input/Jpg/progressive/winter.jpg
  67. 3
      tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff
  68. 3
      tests/Images/Input/Tiff/flower-minisblack-24.tiff
  69. 3
      tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff
  70. 3
      tests/Images/Input/Tiff/flower-minisblack-32.tiff
  71. 3
      tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff
  72. 3
      tests/Images/Input/Tiff/flower-miniswhite-16.tiff
  73. 3
      tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff
  74. 3
      tests/Images/Input/Tiff/flower-miniswhite-32.tiff
  75. 3
      tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff
  76. 3
      tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff
  77. 3
      tests/Images/Input/Tiff/flower-rgb-contig-24.tiff
  78. 3
      tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff
  79. 3
      tests/Images/Input/Tiff/flower-rgb-contig-32.tiff
  80. 3
      tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff
  81. 3
      tests/Images/Input/Tiff/flower-rgb-planar-08-15strips.tiff
  82. 3
      tests/Images/Input/Tiff/flower-rgb-planar-08-6strips.tiff
  83. 3
      tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff
  84. 3
      tests/Images/Input/Tiff/flower-rgb-planar-24.tiff
  85. 3
      tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff
  86. 3
      tests/Images/Input/Tiff/flower-rgb-planar-32.tiff
  87. 3
      tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff

21
src/ImageSharp/Common/Helpers/ExifResolutionValues.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Common.Helpers
{
internal readonly struct ExifResolutionValues
{
public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution)
{
this.ResolutionUnit = resolutionUnit;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public ushort ResolutionUnit { get; }
public double? HorizontalResolution { get; }
public double? VerticalResolution { get; }
}
}

25
src/ImageSharp/Common/Helpers/UnitConverter.cs

@ -98,14 +98,14 @@ namespace SixLabors.ImageSharp.Common.Helpers
}
/// <summary>
/// Sets the exif profile resolution values.
/// Gets the exif profile resolution values.
/// </summary>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="unit">The resolution unit.</param>
/// <param name="horizontal">The horizontal resolution value.</param>
/// <param name="vertical">The vertical resolution value.</param>
/// <returns><see cref="ExifResolutionValues"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical)
public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical)
{
switch (unit)
{
@ -115,9 +115,9 @@ namespace SixLabors.ImageSharp.Common.Helpers
break;
case PixelResolutionUnit.PixelsPerMeter:
{
unit = PixelResolutionUnit.PixelsPerCentimeter;
horizontal = UnitConverter.MeterToCm(horizontal);
vertical = UnitConverter.MeterToCm(vertical);
unit = PixelResolutionUnit.PixelsPerCentimeter;
horizontal = MeterToCm(horizontal);
vertical = MeterToCm(vertical);
}
break;
@ -126,18 +126,13 @@ namespace SixLabors.ImageSharp.Common.Helpers
break;
}
exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1));
ushort exifUnit = (ushort)(unit + 1);
if (unit == PixelResolutionUnit.AspectRatio)
{
exifProfile.RemoveValue(ExifTag.XResolution);
exifProfile.RemoveValue(ExifTag.YResolution);
}
else
{
exifProfile.SetValue(ExifTag.XResolution, new Rational(horizontal));
exifProfile.SetValue(ExifTag.YResolution, new Rational(vertical));
return new ExifResolutionValues(exifUnit, null, null);
}
return new ExifResolutionValues(exifUnit, horizontal, vertical);
}
}
}

41
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -830,5 +830,46 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
d.V7R.W = this.V7R.W;
}
}
/// <summary>
/// Compares entire 8x8 block to a single scalar value.
/// </summary>
/// <param name="value">Value to compare to.</param>
public bool EqualsToScalar(int value)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
var targetVector = Vector256.Create(value);
ref Vector256<float> blockStride = ref this.V0;
for (int i = 0; i < RowCount; i++)
{
Vector256<int> areEqual = Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref this.V0, i)), targetVector);
if (Avx2.MoveMask(areEqual.AsByte()) != equalityMask)
{
return false;
}
}
return true;
}
#endif
{
ref float scalars = ref Unsafe.As<Block8x8F, float>(ref this);
for (int i = 0; i < Size; i++)
{
if ((int)Unsafe.Add(ref scalars, i) != value)
{
return false;
}
}
return true;
}
}
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (quantizationTableIndex > 3)
{
JpegThrowHelper.ThrowBadQuantizationTable();
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex);
}
this.QuantizationTableIndex = quantizationTableIndex;

144
src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs

@ -1,144 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Provides methods to evaluate the quality of an image.
/// Ported from <see href="https://github.com/ImageMagick/ImageMagick/blob/f362c02083d27211b913c6e44794f0ac6edaf2bd/coders/jpeg.c#L855"/>
/// </summary>
internal static class QualityEvaluator
{
private static readonly int[] Hash = new int[101]
{
1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
0
};
private static readonly int[] Sums = new int[101]
{
32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946,
23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998,
16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702,
12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208,
9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458,
8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788,
6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128,
4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509,
3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
1666, 1483, 1297, 1109, 927, 735, 554, 375, 201,
128, 0
};
private static readonly int[] Hash1 = new int[101]
{
510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
0
};
private static readonly int[] Sums1 = new int[101]
{
16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859,
12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679,
9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823,
6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086,
4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092,
3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396,
3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727,
2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068,
1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398,
1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
667, 592, 518, 441, 369, 292, 221, 151, 86,
64, 0
};
/// <summary>
/// Returns an estimated quality of the image based on the quantization tables.
/// </summary>
/// <param name="quantizationTables">The quantization tables.</param>
/// <returns>The <see cref="int"/>.</returns>
public static int EstimateQuality(Block8x8F[] quantizationTables)
{
int quality = 75;
float sum = 0;
for (int i = 0; i < quantizationTables.Length; i++)
{
ref Block8x8F qTable = ref quantizationTables[i];
if (!qTable.Equals(default))
{
for (int j = 0; j < Block8x8F.Size; j++)
{
sum += qTable[j];
}
}
}
ref Block8x8F qTable0 = ref quantizationTables[0];
ref Block8x8F qTable1 = ref quantizationTables[1];
if (!qTable0.Equals(default))
{
if (!qTable1.Equals(default))
{
quality = (int)(qTable0[2]
+ qTable0[53]
+ qTable1[0]
+ qTable1[Block8x8F.Size - 1]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash[i] && sum < Sums[i])
{
continue;
}
if (((quality <= Hash[i]) && (sum <= Sums[i])) || (i >= 50))
{
return i + 1;
}
}
}
else
{
quality = (int)(qTable0[2] + qTable0[53]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash1[i] && sum < Sums1[i])
{
continue;
}
if (((quality <= Hash1[i]) && (sum <= Sums1[i])) || (i >= 50))
{
return i + 1;
}
}
}
}
return quality;
}
}
}

194
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -0,0 +1,194 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Provides methods and properties related to jpeg quantization.
/// </summary>
internal static class Quantization
{
/// <summary>
/// Upper bound (inclusive) for jpeg quality setting.
/// </summary>
public const int MaxQualityFactor = 100;
/// <summary>
/// Lower bound (inclusive) for jpeg quality setting.
/// </summary>
public const int MinQualityFactor = 1;
/// <summary>
/// Default JPEG quality for both luminance and chominance tables.
/// </summary>
public const int DefaultQualityFactor = 75;
/// <summary>
/// Represents lowest quality setting which can be estimated with enough confidence.
/// Any quality below it results in a highly compressed jpeg image
/// which shouldn't use standard itu quantization tables for re-encoding.
/// </summary>
public const int QualityEstimationConfidenceLowerThreshold = 25;
/// <summary>
/// Represents highest quality setting which can be estimated with enough confidence.
/// </summary>
public const int QualityEstimationConfidenceUpperThreshold = 98;
/// <summary>
/// Gets the unscaled luminance quantization table in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter.
/// The values are derived from ITU section K.1 after converting from natural to
/// zig-zag order.
/// </summary>
// The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
public static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[]
{
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
100, 120, 92, 101, 103, 99,
};
/// <summary>
/// Gets the unscaled chrominance quantization table in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter.
/// The values are derived from ITU section K.1 after converting from natural to
/// zig-zag order.
/// </summary>
// The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
public static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[]
{
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
};
/// Ported from JPEGsnoop:
/// https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694
/// <summary>
/// Estimates jpeg quality based on quantization table in zig-zag order.
/// </summary>
/// <remarks>
/// This technically can be used with any given table but internal decoder code uses ITU spec tables:
/// <see cref="UnscaledQuant_Luminance"/> and <see cref="UnscaledQuant_Chrominance"/>.
/// </remarks>
/// <param name="table">Input quantization table.</param>
/// <param name="target">Quantization to estimate against.</param>
/// <returns>Estimated quality</returns>
public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target)
{
// This method can be SIMD'ified if standard table is injected as Block8x8F.
// Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8.
double comparePercent;
double sumPercent = 0;
// Corner case - all 1's => 100 quality
// It would fail to deduce using algorithm below without this check
if (table.EqualsToScalar(1))
{
// While this is a 100% to be 100 quality, any given table can be scaled to all 1's.
// According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically.
// Quality=100 shouldn't be used in usual use case.
return 100;
}
int quality;
for (int i = 0; i < Block8x8F.Size; i++)
{
float coeff = table[i];
int coeffInteger = (int)coeff;
// Coefficients are actually int16 casted to float numbers so there's no truncating error.
if (coeffInteger != 0)
{
comparePercent = 100.0 * (table[i] / target[i]);
}
else
{
// No 'valid' quantization table should contain zero at any position
// while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage.
// Not sure what to do here, we can't throw as this technically correct
// but this will screw up the encoder.
comparePercent = 999.99;
}
sumPercent += comparePercent;
}
// Perform some statistical analysis of the quality factor
// to determine the likelihood of the current quantization
// table being a scaled version of the "standard" tables.
// If the variance is high, it is unlikely to be the case.
sumPercent /= 64.0;
// Generate the equivalent IJQ "quality" factor
if (sumPercent <= 100.0)
{
quality = (int)Math.Round((200 - sumPercent) / 2);
}
else
{
quality = (int)Math.Round(5000.0 / sumPercent);
}
return quality;
}
/// <summary>
/// Estimates jpeg quality based on quantization table in zig-zag order.
/// </summary>
/// <param name="luminanceTable">Luminance quantization table.</param>
/// <returns>Estimated quality</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable)
=> EstimateQuality(ref luminanceTable, UnscaledQuant_Luminance);
/// <summary>
/// Estimates jpeg quality based on quantization table in zig-zag order.
/// </summary>
/// <param name="chrominanceTable">Chrominance quantization table.</param>
/// <returns>Estimated quality</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable)
=> EstimateQuality(ref chrominanceTable, UnscaledQuant_Chrominance);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int QualityToScale(int quality)
{
DebugGuard.MustBeBetweenOrEqualTo(quality, MinQualityFactor, MaxQualityFactor, nameof(quality));
return quality < 50 ? (5000 / quality) : (200 - (quality * 2));
}
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
{
Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = ((unscaledTable[j] * scale) + 50) / 100;
table[j] = Numerics.Clamp(x, 1, 255);
}
return table;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleLuminanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), UnscaledQuant_Luminance);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleChrominanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), UnscaledQuant_Chrominance);
}
}

6
src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs

@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
internal interface IJpegEncoderOptions
{
/// <summary>
/// Gets the quality, that will be used to encode the image. Quality
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// Defaults to <value>75</value>.
/// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value>
int? Quality { get; }
public int? Quality { get; set; }
/// <summary>
/// Gets the subsample ration, that will be used to encode the image.

2
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -4,8 +4,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg

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

@ -699,81 +699,95 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception>
private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining)
{
JpegMetadata jpegMetadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance);
while (remaining > 0)
{
bool done = false;
remaining--;
// 1 byte: quantization table spec
// bit 0..3: table index (0..3)
// bit 4..7: table precision (0 = 8 bit, 1 = 16 bit)
int quantizationTableSpec = stream.ReadByte();
int tableIndex = quantizationTableSpec & 15;
int tablePrecision = quantizationTableSpec >> 4;
// Max index. 4 Tables max.
// Validate:
if (tableIndex > 3)
{
JpegThrowHelper.ThrowBadQuantizationTable();
JpegThrowHelper.ThrowBadQuantizationTableIndex(tableIndex);
}
switch (quantizationTableSpec >> 4)
remaining--;
// Decoding single 8x8 table
ref Block8x8F table = ref this.QuantizationTables[tableIndex];
switch (tablePrecision)
{
// 8 bit values
case 0:
{
// 8 bit values
// Validate: 8 bit table needs exactly 64 bytes
if (remaining < 64)
{
done = true;
break;
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining);
}
stream.Read(this.temp, 0, 64);
remaining -= 64;
ref Block8x8F table = ref this.QuantizationTables[tableIndex];
for (int j = 0; j < 64; j++)
{
table[j] = this.temp[j];
}
break;
}
break;
// 16 bit values
case 1:
{
// 16 bit values
// Validate: 16 bit table needs exactly 128 bytes
if (remaining < 128)
{
done = true;
break;
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining);
}
stream.Read(this.temp, 0, 128);
remaining -= 128;
ref Block8x8F table = ref this.QuantizationTables[tableIndex];
for (int j = 0; j < 64; j++)
{
table[j] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1];
}
}
break;
break;
}
// Unknown precision - error
default:
{
JpegThrowHelper.ThrowBadQuantizationTable();
JpegThrowHelper.ThrowBadQuantizationTablePrecision(tablePrecision);
break;
}
}
if (done)
// Estimating quality
switch (tableIndex)
{
break;
}
}
// luminance table
case 0:
{
jpegMetadata.LuminanceQuality = Quantization.EstimateLuminanceQuality(ref table);
break;
}
if (remaining != 0)
{
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining);
// chrominance table
case 1:
{
jpegMetadata.ChrominanceQuality = Quantization.EstimateChrominanceQuality(ref table);
break;
}
}
}
this.Metadata.GetFormatMetadata(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables);
}
/// <summary>

6
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -13,11 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{
/// <summary>
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// Defaults to <value>75</value>.
/// </summary>
/// <inheritdoc/>
public int? Quality { get; set; }
/// <summary>

121
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -64,44 +64,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.colorType = options.ColorType;
}
/// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter.
/// The values are derived from section K.1 after converting from natural to
/// zig-zag order.
/// </summary>
// The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[]
{
// Luminance.
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
100, 120, 92, 101, 103, 99,
};
/// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter.
/// The values are derived from section K.1 after converting from natural to
/// zig-zag order.
/// </summary>
// The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[]
{
// Chrominance.
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
};
/// <summary>
/// Encode writes the image to the jpeg baseline format with the given options.
/// </summary>
@ -124,35 +86,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream = stream;
ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
// Compute number of components based on color type in options.
int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3;
// System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100);
this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420;
// Convert from a quality rating to a scaling factor.
int scale;
if (qlty < 50)
{
scale = 5000 / qlty;
}
else
{
scale = 200 - (qlty * 2);
}
// TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
// Initialize the quantization tables.
// TODO: This looks ugly, should we write chrominance table for luminance-only images?
// If not - this can code can be simplified
Block8x8F luminanceQuantTable = default;
Block8x8F chrominanceQuantTable = default;
InitQuantizationTable(0, scale, ref luminanceQuantTable);
if (componentCount > 1)
{
InitQuantizationTable(1, scale, ref chrominanceQuantTable);
}
this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
// Write the Start Of Image marker.
this.WriteApplicationHeader(metadata);
@ -176,10 +117,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
var scanEncoder = new HuffmanScanEncoder(stream);
if (this.colorType == JpegColorType.Luminance)
{
// luminance quantization table only
scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
}
else
{
// luminance and chrominance quantization tables
switch (this.subsample)
{
case JpegSubsample.Ratio444:
@ -690,31 +633,49 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Initializes quantization table.
/// Initializes quntization tables.
/// </summary>
/// <param name="i">The quantization index.</param>
/// <param name="scale">The scaling factor.</param>
/// <param name="quant">The quantization table.</param>
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
/// <remarks>
/// We take quality values in a hierarchical order:
/// 1. Check if encoder has set quality
/// 2. Check if metadata has special table for encoding
/// 3. Check if metadata has set quality
/// 4. Take default quality value - 75
/// </remarks>
/// <param name="componentCount">Color components count.</param>
/// <param name="metadata">Jpeg metadata instance.</param>
/// <param name="luminanceQuantTable">Output luminance quantization table.</param>
/// <param name="chrominanceQuantTable">Output chrominance quantization table.</param>
private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable)
{
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
ReadOnlySpan<byte> unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance;
int lumaQuality;
int chromaQuality;
if (this.quality.HasValue)
{
lumaQuality = this.quality.Value;
chromaQuality = this.quality.Value;
}
else
{
lumaQuality = metadata.LuminanceQuality;
chromaQuality = metadata.ChrominanceQuality;
}
for (int j = 0; j < Block8x8F.Size; j++)
// Luminance
lumaQuality = Numerics.Clamp(lumaQuality, 1, 100);
luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality);
// Chrominance
chrominanceQuantTable = default;
if (componentCount > 1)
{
int x = unscaledQuant[j];
x = ((x * scale) + 50) / 100;
if (x < 1)
{
x = 1;
}
chromaQuality = Numerics.Clamp(chromaQuality, 1, 100);
chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);
if (x > 255)
if (!this.subsample.HasValue)
{
x = 255;
this.subsample = chromaQuality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420;
}
quant[j] = x;
}
}
}

81
src/ImageSharp/Formats/Jpeg/JpegMetadata.cs

@ -1,6 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
@ -8,6 +11,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
public class JpegMetadata : IDeepCloneable
{
/// <summary>
/// Backing field for <see cref="LuminanceQuality"/>
/// </summary>
private int? luminanceQuality;
/// <summary>
/// Backing field for <see cref="ChrominanceQuality"/>
/// </summary>
private int? chrominanceQuality;
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetadata"/> class.
/// </summary>
@ -21,18 +34,80 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="other">The metadata to create an instance from.</param>
private JpegMetadata(JpegMetadata other)
{
this.Quality = other.Quality;
this.ColorType = other.ColorType;
this.luminanceQuality = other.luminanceQuality;
this.chrominanceQuality = other.chrominanceQuality;
}
/// <summary>
/// Gets or sets the encoded quality.
/// Gets or sets the jpeg luminance quality.
/// </summary>
/// <remarks>
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int LuminanceQuality
{
get => this.luminanceQuality ?? Quantization.DefaultQualityFactor;
set => this.luminanceQuality = value;
}
/// <summary>
/// Gets or sets the jpeg chrominance quality.
/// </summary>
public int Quality { get; set; } = 75;
/// <remarks>
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int ChrominanceQuality
{
get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor;
set => this.chrominanceQuality = value;
}
/// <summary>
/// Gets or sets the encoded quality.
/// </summary>
/// <remarks>
/// Note that jpeg image can have different quality for luminance and chrominance components.
/// This property returns maximum value of luma/chroma qualities.
/// </remarks>
public int Quality
{
get
{
// Jpeg always has a luminance table thus it must have a luminance quality derived from it
if (!this.luminanceQuality.HasValue)
{
return Quantization.DefaultQualityFactor;
}
int lumaQuality = this.luminanceQuality.Value;
// Jpeg might not have a chrominance table - return luminance quality (grayscale images)
if (!this.chrominanceQuality.HasValue)
{
return lumaQuality;
}
int chromaQuality = this.chrominanceQuality.Value;
// Theoretically, luma quality would always be greater or equal to chroma quality
// But we've already encountered images which can have higher quality of chroma components
return Math.Max(lumaQuality, chromaQuality);
}
set
{
this.LuminanceQuality = value;
this.ChrominanceQuality = value;
}
}
/// <summary>
/// Gets or sets the color type.
/// </summary>
public JpegColorType? ColorType { get; set; }
/// <inheritdoc/>

5
src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs

@ -36,7 +36,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTable() => throw new InvalidImageContentException("Bad Quantization Table index.");
public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTablePrecision(int precision) => throw new InvalidImageContentException($"Unknown Quantization Table precision {precision}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor.");

69
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -0,0 +1,69 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images.
/// </summary>
internal class BlackIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
private readonly Configuration configuration;
/// <summary>
/// Initializes a new instance of the <see cref="BlackIsZero16TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public BlackIsZero16TiffColor(Configuration configuration, bool isBigEndian)
{
this.configuration = configuration;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
}
}
else
{
int byteCount = pixelRow.Length * 2;
PixelOperations<TPixel>.Instance.FromL16Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);
offset += byteCount;
}
}
}
}
}

65
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs

@ -0,0 +1,65 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 24-bit grayscale images.
/// </summary>
internal class BlackIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="BlackIsZero24TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public BlackIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
}
}
}
}
}
}

61
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

@ -0,0 +1,61 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 32-bit grayscale images.
/// </summary>
internal class BlackIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="BlackIsZero32TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public BlackIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
}
}
}
}
}
}

24
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -14,25 +13,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal class BlackIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
public BlackIsZero8TiffColor(Configuration configuration) => this.configuration = configuration;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
byte intensity = data[offset++];
l8.PackedValue = intensity;
color.FromL8(l8);
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
int byteCount = pixelRow.Length;
PixelOperations<TPixel>.Instance.FromL8Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);
pixels[x, y] = color;
}
offset += byteCount;
}
}
}

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs

@ -34,13 +34,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = value / this.factor;
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color;
pixelRow[x] = color;
}
bitReader.NextRow();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs

@ -35,10 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
int index = bitReader.ReadBits(this.bitsPerSample0);
pixels[x, y] = this.palette[index];
pixelRow[x] = this.palette[index];
}
bitReader.NextRow();

53
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -16,43 +16,60 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
private readonly bool isBigEndian;
private readonly Configuration configuration;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb161616TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb161616TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
public Rgb161616TiffColor(Configuration configuration, bool isBigEndian)
{
this.configuration = configuration;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
var rgba = default(Rgba64);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = left; x < left + width; x++)
if (this.isBigEndian)
{
ulong r = this.ConvertToShort(data.Slice(offset, 2));
offset += 2;
ulong g = this.ConvertToShort(data.Slice(offset, 2));
offset += 2;
ulong b = this.ConvertToShort(data.Slice(offset, 2));
offset += 2;
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
color.FromRgba64(rgba);
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
}
}
else
{
int byteCount = pixelRow.Length * 6;
PixelOperations<TPixel>.Instance.FromRgb48Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);
pixelRow[x] = color;
offset += byteCount;
}
}
}
private ushort ConvertToShort(ReadOnlySpan<byte> buffer) => this.isBigEndian
? BinaryPrimitives.ReadUInt16BigEndian(buffer)
: BinaryPrimitives.ReadUInt16LittleEndian(buffer);
}
}

72
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 16 bit.
/// </summary>
internal class Rgb16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb16PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
}
}
}
}
}
}

82
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -0,0 +1,82 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 24 bits for each channel.
/// </summary>
internal class Rgb242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb242424TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
}
}
}
}
}
}

80
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -0,0 +1,80 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 24 bit.
/// </summary>
internal class Rgb24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb24PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
}
}
}
}
}
}

73
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs

@ -0,0 +1,73 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 32 bits for each channel.
/// </summary>
internal class Rgb323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb323232TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
}
}
}
}
}
}

71
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 32 bit.
/// </summary>
internal class Rgb32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb32PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgb32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
}
}
}
}
}
}

30
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -14,29 +13,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal class Rgb888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
public Rgb888TiffColor(Configuration configuration) => this.configuration = configuration;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
var rgba = default(Rgba32);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
for (int x = left; x < left + width; x++)
{
byte r = data[offset++];
byte g = data[offset++];
byte b = data[offset++];
rgba.PackedValue = (uint)(r | (g << 8) | (b << 16) | (0xff << 24));
color.FromRgba32(rgba);
pixelRow[x] = color;
}
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
int byteCount = pixelRow.Length * 3;
PixelOperations<TPixel>.Instance.FromRgb24Bytes(
this.configuration,
data.Slice(offset, byteCount),
pixelRow,
pixelRow.Length);
offset += byteCount;
}
}
}

10
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
@ -12,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
/// </summary>
internal class RgbPlanarTiffColor<TPixel>
internal class RgbPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
@ -47,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
@ -57,14 +58,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
pixelRow[x] = color;
}
rBitReader.NextRow();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs

@ -47,14 +47,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
pixelRow[x] = color;
}
bitReader.NextRow();

28
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// The base class for planar color decoders.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Decodes source raw pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffers to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public abstract void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height);
}
}

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

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal static class TiffColorDecoderFactory<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder)
public static TiffBaseColorDecoder<TPixel> Create(Configuration configuration, TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder)
{
switch (colorType)
{
@ -32,6 +32,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero8TiffColor<TPixel>();
case TiffColorType.WhiteIsZero16:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero16TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.WhiteIsZero24:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero24TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.WhiteIsZero32:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero32TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.BlackIsZero:
DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -50,7 +65,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.BlackIsZero8:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero8TiffColor<TPixel>();
return new BlackIsZero8TiffColor<TPixel>(configuration);
case TiffColorType.BlackIsZero16:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero16TiffColor<TPixel>(configuration, byteOrder == ByteOrder.BigEndian);
case TiffColorType.BlackIsZero24:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero24TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.BlackIsZero32:
DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero32TiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb:
DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -84,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 8,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb888TiffColor<TPixel>();
return new Rgb888TiffColor<TPixel>(configuration);
case TiffColorType.Rgb101010:
DebugGuard.IsTrue(
@ -124,7 +154,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 16,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb161616TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
return new Rgb161616TiffColor<TPixel>(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 24
&& bitsPerSample.Channel1 == 24
&& bitsPerSample.Channel0 == 24,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb242424TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 32
&& bitsPerSample.Channel1 == 32
&& bitsPerSample.Channel0 == 32,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.PaletteColor:
DebugGuard.NotNull(colorMap, "colorMap");
@ -135,14 +185,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
}
}
public static RgbPlanarTiffColor<TPixel> CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap)
public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder)
{
switch (colorType)
{
case TiffColorType.RgbPlanar:
case TiffColorType.Rgb888Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbPlanarTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb24PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb32PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
}

59
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs

@ -28,6 +28,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary>
BlackIsZero8,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 16-bit images.
/// </summary>
BlackIsZero16,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 24-bit images.
/// </summary>
BlackIsZero24,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 32-bit images.
/// </summary>
BlackIsZero32,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
/// </summary>
@ -48,6 +63,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary>
WhiteIsZero8,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 16-bit images.
/// </summary>
WhiteIsZero16,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 24-bit images.
/// </summary>
WhiteIsZero24,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 32-bit images.
/// </summary>
WhiteIsZero32,
/// <summary>
/// Palette-color.
/// </summary>
@ -94,8 +124,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Rgb161616,
/// <summary>
/// RGB Full Color. Planar configuration of data.
/// RGB color image with 24 bits for each channel.
/// </summary>
Rgb242424,
/// <summary>
/// RGB color image with 32 bits for each channel.
/// </summary>
Rgb323232,
/// <summary>
/// RGB Full Color. Planar configuration of data. 8 Bit per color channel.
/// </summary>
Rgb888Planar,
/// <summary>
/// RGB Full Color. Planar configuration of data. 16 Bit per color channel.
/// </summary>
Rgb161616Planar,
/// <summary>
/// RGB Full Color. Planar configuration of data. 24 Bit per color channel.
/// </summary>
Rgb242424Planar,
/// <summary>
/// RGB Full Color. Planar configuration of data. 32 Bit per color channel.
/// </summary>
RgbPlanar,
Rgb323232Planar,
}
}

61
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -0,0 +1,61 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 16-bit grayscale images.
/// </summary>
internal class WhiteIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="WhiteIsZero16TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public WhiteIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
}
}
}
}
}
}

65
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs

@ -0,0 +1,65 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 24-bit grayscale images.
/// </summary>
internal class WhiteIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="WhiteIsZero24TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public WhiteIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
}
}
}
}
}
}

60
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs

@ -0,0 +1,60 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit grayscale images.
/// </summary>
internal class WhiteIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
/// <summary>
/// Initializes a new instance of the <see cref="WhiteIsZero32TiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public WhiteIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
if (this.isBigEndian)
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
}
}
}
}
}
}

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -24,14 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
byte intensity = (byte)(255 - data[offset++]);
l8.PackedValue = intensity;
color.FromL8(l8);
pixels[x, y] = color;
pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color);
}
}
}

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs

@ -34,13 +34,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
Span<TPixel> pixelRow = pixels.GetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = 1.0f - (value / this.factor);
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color;
pixelRow[x] = color;
}
bitReader.NextRow();

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

@ -266,17 +266,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
for (int i = 0; i < stripsPerPlane; i++)
{
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
int stripIndex = i;
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
{
int stripIndex = (i * stripsPerPixel) + planeIndex;
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan());
stripIndex += stripsPerPlane;
}
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight);
@ -316,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.Predictor,
this.FaxCompressionOptions);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.Configuration, this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{

73
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -104,13 +104,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
ushort bitsPerChannel = options.BitsPerSample.Channel0;
if (bitsPerChannel > 16)
if (bitsPerChannel > 32)
{
TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
}
switch (bitsPerChannel)
{
case 32:
{
options.ColorType = TiffColorType.WhiteIsZero32;
break;
}
case 24:
{
options.ColorType = TiffColorType.WhiteIsZero24;
break;
}
case 16:
{
options.ColorType = TiffColorType.WhiteIsZero16;
break;
}
case 8:
{
options.ColorType = TiffColorType.WhiteIsZero8;
@ -147,13 +165,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
ushort bitsPerChannel = options.BitsPerSample.Channel0;
if (bitsPerChannel > 16)
if (bitsPerChannel > 32)
{
TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
}
switch (bitsPerChannel)
{
case 32:
{
options.ColorType = TiffColorType.BlackIsZero32;
break;
}
case 24:
{
options.ColorType = TiffColorType.BlackIsZero24;
break;
}
case 16:
{
options.ColorType = TiffColorType.BlackIsZero16;
break;
}
case 8:
{
options.ColorType = TiffColorType.BlackIsZero8;
@ -184,16 +220,30 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffPhotometricInterpretation.Rgb:
{
if (options.BitsPerSample.Channels != 3)
TiffBitsPerSample bitsPerSample = options.BitsPerSample;
if (bitsPerSample.Channels != 3)
{
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
if (!(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2))
{
TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported.");
}
if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
ushort bitsPerChannel = options.BitsPerSample.Channel0;
switch (bitsPerChannel)
{
case 32:
options.ColorType = TiffColorType.Rgb323232;
break;
case 24:
options.ColorType = TiffColorType.Rgb242424;
break;
case 16:
options.ColorType = TiffColorType.Rgb161616;
break;
@ -226,7 +276,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
options.ColorType = TiffColorType.RgbPlanar;
ushort bitsPerChannel = options.BitsPerSample.Channel0;
switch (bitsPerChannel)
{
case 32:
options.ColorType = TiffColorType.Rgb323232Planar;
break;
case 24:
options.ColorType = TiffColorType.Rgb242424Planar;
break;
case 16:
options.ColorType = TiffColorType.Rgb161616Planar;
break;
default:
options.ColorType = TiffColorType.Rgb888Planar;
break;
}
}
break;

61
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -74,6 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
private readonly List<(long, uint)> frameMarkers = new List<(long, uint)>();
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
@ -147,13 +149,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff
// Make sure, the Encoder options makes sense in combination with each other.
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
using (var writer = new TiffStreamWriter(stream))
using var writer = new TiffStreamWriter(stream);
long ifdMarker = this.WriteHeader(writer);
Image<TPixel> metadataImage = image;
foreach (ImageFrame<TPixel> frame in image.Frames)
{
long firstIfdMarker = this.WriteHeader(writer);
var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage);
// TODO: multiframing is not supported
this.WriteImage(writer, image, firstIfdMarker);
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
metadataImage = null;
}
long currentOffset = writer.BaseStream.Position;
foreach ((long, uint) marker in this.frameMarkers)
{
writer.WriteMarkerFast(marker.Item1, marker.Item2);
}
writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
}
/// <summary>
@ -174,41 +188,56 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Writes all data required to define an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="writer">The <see cref="BinaryWriter" /> to write data to.</param>
/// <param name="frame">The tiff frame.</param>
/// <param name="imageMetadata">The image metadata (resolution values for each frame).</param>
/// <param name="image">The image (common metadata for root frame).</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
private void WriteImage<TPixel>(TiffStreamWriter writer, Image<TPixel> image, long ifdOffset)
/// <returns>
/// The next IFD offset value.
/// </returns>
private long WriteFrame<TPixel>(
TiffStreamWriter writer,
ImageFrame<TPixel> frame,
ImageMetadata imageMetadata,
Image<TPixel> image,
long ifdOffset)
where TPixel : unmanaged, IPixel<TPixel>
{
var entriesCollector = new TiffEncoderEntriesCollector();
using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
this.CompressionType ?? TiffCompression.None,
writer.BaseStream,
this.memoryAllocator,
image.Width,
frame.Width,
(int)this.BitsPerPixel,
this.compressionLevel,
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None);
var entriesCollector = new TiffEncoderEntriesCollector();
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create(
this.PhotometricInterpretation,
image.Frames.RootFrame,
frame,
this.quantizer,
this.memoryAllocator,
this.configuration,
entriesCollector,
(int)this.BitsPerPixel);
int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow);
int rowsPerStrip = this.CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow);
colorWriter.Write(compressor, rowsPerStrip);
if (image != null)
{
entriesCollector.ProcessMetadata(image);
}
entriesCollector.ProcessFrameInfo(frame, imageMetadata);
entriesCollector.ProcessImageFormat(this);
entriesCollector.ProcessGeneral(image);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);
this.frameMarkers.Add((ifdOffset, (uint)writer.Position));
return this.WriteIfd(writer, entriesCollector.Entries);
}
/// <summary>
@ -272,7 +301,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
else
{
var raw = new byte[length];
byte[] raw = new byte[length];
int sz = ExifWriter.WriteValue(entry, raw, 0);
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written");
largeDataBlocks.Add(raw);

160
src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs

@ -6,7 +6,6 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
@ -16,9 +15,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public List<IExifValue> Entries { get; } = new List<IExifValue>();
public void ProcessGeneral<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
=> new GeneralProcessor(this).Process(image);
public void ProcessMetadata(Image image)
=> new MetadataProcessor(this).Process(image);
public void ProcessFrameInfo(ImageFrame frame, ImageMetadata imageMetadata)
=> new FrameInfoProcessor(this).Process(frame, imageMetadata);
public void ProcessImageFormat(TiffEncoderCore encoder)
=> new ImageFormatProcessor(this).Process(encoder);
@ -38,44 +39,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private void Add(IExifValue entry) => this.Entries.Add(entry);
private class GeneralProcessor
private abstract class BaseProcessor
{
private readonly TiffEncoderEntriesCollector collector;
public BaseProcessor(TiffEncoderEntriesCollector collector) => this.Collector = collector;
public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector;
protected TiffEncoderEntriesCollector Collector { get; }
}
public void Process<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
private class MetadataProcessor : BaseProcessor
{
public MetadataProcessor(TiffEncoderEntriesCollector collector)
: base(collector)
{
ImageFrame<TPixel> rootFrame = image.Frames.RootFrame;
ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile();
byte[] rootFrameXmpBytes = rootFrame.Metadata.XmpProfile;
var width = new ExifLong(ExifTagValue.ImageWidth)
{
Value = (uint)image.Width
};
var height = new ExifLong(ExifTagValue.ImageLength)
{
Value = (uint)image.Height
};
var software = new ExifString(ExifTagValue.Software)
{
Value = SoftwareValue
};
}
this.collector.AddOrReplace(width);
this.collector.AddOrReplace(height);
public void Process(Image image)
{
ImageFrame rootFrame = image.Frames.RootFrame;
ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile();
byte[] foorFrameXmpBytes = rootFrame.Metadata.XmpProfile;
this.ProcessResolution(image.Metadata, rootFrameExifProfile);
this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpBytes);
this.ProcessProfiles(image.Metadata, rootFrameExifProfile, foorFrameXmpBytes);
this.ProcessMetadata(rootFrameExifProfile);
if (!this.collector.Entries.Exists(t => t.Tag == ExifTag.Software))
if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software))
{
this.collector.Add(software);
this.Collector.Add(new ExifString(ExifTagValue.Software)
{
Value = SoftwareValue
});
}
}
@ -114,26 +106,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
}
private void ProcessResolution(ImageMetadata imageMetadata, ExifProfile exifProfile)
{
UnitConverter.SetResolutionValues(
exifProfile,
imageMetadata.ResolutionUnits,
imageMetadata.HorizontalResolution,
imageMetadata.VerticalResolution);
this.collector.Add(exifProfile.GetValue(ExifTag.ResolutionUnit).DeepClone());
IExifValue xResolution = exifProfile.GetValue(ExifTag.XResolution)?.DeepClone();
IExifValue yResolution = exifProfile.GetValue(ExifTag.YResolution)?.DeepClone();
if (xResolution != null && yResolution != null)
{
this.collector.Add(xResolution);
this.collector.Add(yResolution);
}
}
private void ProcessMetadata(ExifProfile exifProfile)
{
foreach (IExifValue entry in exifProfile.Values)
@ -170,9 +142,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag))
if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag))
{
this.collector.AddOrReplace(entry.DeepClone());
this.Collector.AddOrReplace(entry.DeepClone());
}
}
}
@ -183,12 +155,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
foreach (IExifValue entry in exifProfile.Values)
{
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null)
if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null)
{
ExifParts entryPart = ExifTags.GetPart(entry.Tag);
if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart))
{
this.collector.AddOrReplace(entry.DeepClone());
this.Collector.AddOrReplace(entry.DeepClone());
}
}
}
@ -206,7 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = imageMetadata.IptcProfile.Data
};
this.collector.Add(iptc);
this.Collector.Add(iptc);
}
else
{
@ -220,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = imageMetadata.IccProfile.ToByteArray()
};
this.collector.Add(icc);
this.Collector.Add(icc);
}
else
{
@ -234,7 +206,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = xmpProfile
};
this.collector.Add(xmp);
this.Collector.Add(xmp);
}
else
{
@ -243,11 +215,61 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
}
private class ImageFormatProcessor
private class FrameInfoProcessor : BaseProcessor
{
private readonly TiffEncoderEntriesCollector collector;
public FrameInfoProcessor(TiffEncoderEntriesCollector collector)
: base(collector)
{
}
public void Process(ImageFrame frame, ImageMetadata imageMetadata)
{
this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageWidth)
{
Value = (uint)frame.Width
});
this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageLength)
{
Value = (uint)frame.Height
});
this.ProcessResolution(imageMetadata);
}
private void ProcessResolution(ImageMetadata imageMetadata)
{
ExifResolutionValues resolution = UnitConverter.GetExifResolutionValues(
imageMetadata.ResolutionUnits,
imageMetadata.HorizontalResolution,
imageMetadata.VerticalResolution);
public ImageFormatProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector;
this.Collector.AddOrReplace(new ExifShort(ExifTagValue.ResolutionUnit)
{
Value = resolution.ResolutionUnit
});
if (resolution.VerticalResolution.HasValue && resolution.HorizontalResolution.HasValue)
{
this.Collector.AddOrReplace(new ExifRational(ExifTagValue.XResolution)
{
Value = new Rational(resolution.HorizontalResolution.Value)
});
this.Collector.AddOrReplace(new ExifRational(ExifTagValue.YResolution)
{
Value = new Rational(resolution.VerticalResolution.Value)
});
}
}
}
private class ImageFormatProcessor : BaseProcessor
{
public ImageFormatProcessor(TiffEncoderEntriesCollector collector)
: base(collector)
{
}
public void Process(TiffEncoderCore encoder)
{
@ -278,11 +300,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Value = (ushort)encoder.PhotometricInterpretation
};
this.collector.AddOrReplace(planarConfig);
this.collector.AddOrReplace(samplesPerPixel);
this.collector.AddOrReplace(bitPerSample);
this.collector.AddOrReplace(compression);
this.collector.AddOrReplace(photometricInterpretation);
this.Collector.AddOrReplace(planarConfig);
this.Collector.AddOrReplace(samplesPerPixel);
this.Collector.AddOrReplace(bitPerSample);
this.Collector.AddOrReplace(compression);
this.Collector.AddOrReplace(photometricInterpretation);
if (encoder.HorizontalPredictor == TiffPredictor.Horizontal)
{
@ -292,7 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
this.collector.AddOrReplace(predictor);
this.Collector.AddOrReplace(predictor);
}
}
}

102
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -0,0 +1,102 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
/// <summary>
/// Helper methods for TIFF decoding.
/// </summary>
internal static class TiffUtils
{
private const float Scale24Bit = 1.0f / 0xFFFFFF;
private const float Scale32Bit = 1.0f / 0xFFFFFFFF;
public static Vector4 Vector4Default { get; } = new Vector4(0.0f, 0.0f, 0.0f, 0.0f);
public static Rgba64 Rgba64Default { get; } = new Rgba64(0, 0, 0, 0);
public static L16 L16Default { get; } = new L16(0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToUShortBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToUShortLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL8<TPixel>(L8 l8, byte intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
l8.PackedValue = intensity;
color.FromL8(l8);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
color.FromRgba64(rgba);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f);
color.FromVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f);
color.FromVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
l16.PackedValue = intensity;
color.FromL16(l16);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f);
color.FromVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f);
color.FromVector4(colorVector);
return color;
}
}
}

10
src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs

@ -126,10 +126,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
/// <param name="value">The four-byte unsigned integer to write.</param>
public void WriteMarker(long offset, uint value)
{
long currentOffset = this.BaseStream.Position;
long back = this.BaseStream.Position;
this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value);
this.BaseStream.Seek(back, SeekOrigin.Begin);
}
public void WriteMarkerFast(long offset, uint value)
{
this.BaseStream.Seek(offset, SeekOrigin.Begin);
this.Write(value);
this.BaseStream.Seek(currentOffset, SeekOrigin.Begin);
}
/// <summary>

16
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs

@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)]
public ErrorDither(in DenseMatrix<float> matrix, int offset)
{
Guard.MustBeGreaterThan(offset, 0, nameof(offset));
this.matrix = matrix;
this.offset = offset;
}
@ -95,6 +97,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (this == default)
{
ThrowDefaultInstance();
}
int offsetY = bounds.Top;
int offsetX = bounds.Left;
float scale = quantizer.Options.DitherScale;
@ -122,6 +129,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (this == default)
{
ThrowDefaultInstance();
}
float scale = processor.DitherScale;
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
@ -210,5 +222,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(this.offset, this.matrix);
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowDefaultInstance()
=> throw new ImageProcessingException("Cannot use the default value type instance to dither.");
}
}

16
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -24,6 +24,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)]
public OrderedDither(uint length)
{
Guard.MustBeGreaterThan(length, 0, nameof(length));
DenseMatrix<uint> ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length);
// Create a new matrix to run against, that pre-thresholds the values.
@ -109,6 +111,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (this == default)
{
ThrowDefaultInstance();
}
int spread = CalculatePaletteSpread(destination.Palette.Length);
float scale = quantizer.Options.DitherScale;
@ -134,6 +141,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (this == default)
{
ThrowDefaultInstance();
}
int spread = CalculatePaletteSpread(processor.Palette.Length);
float scale = processor.DitherScale;
@ -201,5 +213,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode()
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY);
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowDefaultInstance()
=> throw new ImageProcessingException("Cannot use the default value type instance to dither.");
}
}

4
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs

@ -11,14 +11,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
public class OctreeQuantizer : IQuantizer
{
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class
/// using the default <see cref="QuantizerOptions"/>.
/// </summary>
public OctreeQuantizer()
: this(DefaultOptions)
: this(new QuantizerOptions())
{
}

3
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -11,7 +11,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
public class PaletteQuantizer : IQuantizer
{
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
private readonly ReadOnlyMemory<Color> colorPalette;
/// <summary>
@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
/// <param name="palette">The color palette.</param>
public PaletteQuantizer(ReadOnlyMemory<Color> palette)
: this(palette, DefaultOptions)
: this(palette, new QuantizerOptions())
{
}

6
src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
@ -10,13 +8,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
public class WebSafePaletteQuantizer : PaletteQuantizer
{
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary>
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary>
public WebSafePaletteQuantizer()
: this(DefaultOptions)
: this(new QuantizerOptions())
{
}

4
src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs

@ -9,13 +9,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
public class WernerPaletteQuantizer : PaletteQuantizer
{
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary>
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary>
public WernerPaletteQuantizer()
: this(DefaultOptions)
: this(new QuantizerOptions())
{
}

4
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs

@ -10,14 +10,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary>
public class WuQuantizer : IQuantizer
{
private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions();
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class
/// using the default <see cref="QuantizerOptions"/>.
/// </summary>
public WuQuantizer()
: this(DefaultOptions)
: this(new QuantizerOptions())
{
}

91
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -493,5 +493,96 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(data[i], dest[i]);
}
}
[Fact]
public void EqualsToScalar_AllOne()
{
static void RunTest()
{
// Fill matrix with valid value
Block8x8F block = default;
for (int i = 0; i < Block8x8F.Size; i++)
{
block[i] = 1;
}
bool isEqual = block.EqualsToScalar(1);
Assert.True(isEqual);
}
// 2 paths:
// 1. DisableFMA - call avx implementation
// 3. DisableAvx2 - call fallback code of float implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Theory]
[InlineData(10)]
public void EqualsToScalar_OneOffEachPosition(int equalsTo)
{
static void RunTest(string serializedEqualsTo)
{
int equalsTo = FeatureTestRunner.Deserialize<int>(serializedEqualsTo);
int offValue = 0;
// Fill matrix with valid value
Block8x8F block = default;
for (int i = 0; i < Block8x8F.Size; i++)
{
block[i] = equalsTo;
}
// Assert with invalid values at different positions
for (int i = 0; i < Block8x8F.Size; i++)
{
block[i] = offValue;
bool isEqual = block.EqualsToScalar(equalsTo);
Assert.False(isEqual, $"False equality:\n{block}");
// restore valid value for next iteration assertion
block[i] = equalsTo;
}
}
// 2 paths:
// 1. DisableFMA - call avx implementation
// 3. DisableAvx2 - call fallback code of float implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
equalsTo,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
[Theory]
[InlineData(39)]
public void EqualsToScalar_Valid(int equalsTo)
{
static void RunTest(string serializedEqualsTo)
{
int equalsTo = FeatureTestRunner.Deserialize<int>(serializedEqualsTo);
// Fill matrix with valid value
Block8x8F block = default;
for (int i = 0; i < Block8x8F.Size; i++)
{
block[i] = equalsTo;
}
// Assert
bool isEqual = block.EqualsToScalar(equalsTo);
Assert.True(isEqual);
}
// 2 paths:
// 1. DisableFMA - call avx implementation
// 3. DisableAvx2 - call fallback code of float implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
equalsTo,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
}
}
}

4
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -55,7 +55,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
{ TestImages.Jpeg.Baseline.Calliphora, 80 },
{ TestImages.Jpeg.Progressive.Fb, 75 },
{ TestImages.Jpeg.Issues.IncorrectQuality845, 99 }
{ TestImages.Jpeg.Issues.IncorrectQuality845, 98 },
{ TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, 89 },
{ TestImages.Jpeg.Progressive.Winter, 80 }
};
[Theory]

39
tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs

@ -21,5 +21,44 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.False(meta.Quality.Equals(clone.Quality));
Assert.False(meta.ColorType.Equals(clone.ColorType));
}
[Fact]
public void Quality_DefaultQuality()
{
var meta = new JpegMetadata();
Assert.Equal(meta.Quality, ImageSharp.Formats.Jpeg.Components.Quantization.DefaultQualityFactor);
}
[Fact]
public void Quality_LuminanceOnlyQuality()
{
int quality = 50;
var meta = new JpegMetadata { LuminanceQuality = quality };
Assert.Equal(meta.Quality, quality);
}
[Fact]
public void Quality_BothComponentsQuality()
{
int quality = 50;
var meta = new JpegMetadata { LuminanceQuality = quality, ChrominanceQuality = quality };
Assert.Equal(meta.Quality, quality);
}
[Fact]
public void Quality_ReturnsMaxQuality()
{
int qualityLuma = 50;
int qualityChroma = 30;
var meta = new JpegMetadata { LuminanceQuality = qualityLuma, ChrominanceQuality = qualityChroma };
Assert.Equal(meta.Quality, qualityLuma);
}
}
}

42
tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using Xunit;
using JpegQuantization = SixLabors.ImageSharp.Formats.Jpeg.Components.Quantization;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Trait("Format", "Jpg")]
public class QuantizationTests
{
[Fact]
public void QualityEstimationFromStandardEncoderTables_Luminance()
{
int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold;
int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold;
for (int quality = firstIndex; quality <= lastIndex; quality++)
{
Block8x8F table = JpegQuantization.ScaleLuminanceTable(quality);
int estimatedQuality = JpegQuantization.EstimateLuminanceQuality(ref table);
Assert.True(quality.Equals(estimatedQuality), $"Failed to estimate luminance quality for standard table at quality level {quality}");
}
}
[Fact]
public void QualityEstimationFromStandardEncoderTables_Chrominance()
{
int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold;
int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold;
for (int quality = firstIndex; quality <= lastIndex; quality++)
{
Block8x8F table = JpegQuantization.ScaleChrominanceTable(quality);
int estimatedQuality = JpegQuantization.EstimateChrominanceQuality(ref table);
Assert.True(quality.Equals(estimatedQuality), $"Failed to estimate chrominance quality for standard table at quality level {quality}");
}
}
}
}

1
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;

2
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs

@ -188,7 +188,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{
AssertDecode(expectedResult, pixels =>
{
new BlackIsZero8TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
new BlackIsZero8TiffColor<Rgba32>(Configuration.Default).Decode(inputData, pixels, left, top, width, height);
});
}
}

2
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs

@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{
AssertDecode(expectedResult, pixels =>
{
new Rgb888TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
new Rgb888TiffColor<Rgba32>(Configuration.Default).Decode(inputData, pixels, left, top, width, height);
});
}
}

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

@ -82,6 +82,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_Uncompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb888Planar6Strips, PixelTypes.Rgba32)]
[WithFile(FlowerRgb888Planar15Strips, PixelTypes.Rgba32)]
public void TiffDecoder_Planar<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)]
@ -105,49 +111,84 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(Flower2BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_2Bit<TPixel>(TestImageProvider<TPixel> provider)
public void TiffDecoder_CanDecode_2Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)]
[WithFile(Flower6BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_6Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower6BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_6Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower8BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_8Bit<TPixel>(TestImageProvider<TPixel> provider)
public void TiffDecoder_CanDecode_8Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower10BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_10Bit<TPixel>(TestImageProvider<TPixel> provider)
public void TiffDecoder_CanDecode_10Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)]
[WithFile(Flower12BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_12Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower12BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_12Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower14BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_14Bit<TPixel>(TestImageProvider<TPixel> provider)
public void TiffDecoder_CanDecode_14Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)]
[WithFile(Flower16BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
[WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)]
[WithFile(Flower16BitGrayMinIsWhiteBigEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_16Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower24BitGray, PixelTypes.Rgba32)]
[WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_30Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower32BitGray, PixelTypes.Rgba32)]
[WithFile(Flower32BitGrayLittleEndian, PixelTypes.Rgba32)]
[WithFile(Flower32BitGrayMinIsWhite, PixelTypes.Rgba32)]
[WithFile(Flower32BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_32Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_36Bit<TPixel>(TestImageProvider<TPixel> provider)
@ -161,11 +202,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616ContiguousLittleEndian, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616PlanarLittleEndian, PixelTypes.Rgba32)]
[WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)]
[WithFile(FlowerRgb242424Planar, PixelTypes.Rgba32)]
[WithFile(FlowerRgb242424PlanarLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_72Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb323232ContiguousLittleEndian, PixelTypes.Rgba32)]
[WithFile(FlowerRgb323232Planar, PixelTypes.Rgba32)]
[WithFile(FlowerRgb323232PlanarLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_96Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// Note: because the MagickReferenceDecoder fails to load the image, we only debug save them.
using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider);
}
[Theory]
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
[WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)]

114
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Format", "Tiff")]
public abstract class TiffEncoderBaseTester
{
protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
protected static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None;
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile;
TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata();
ImageFrame<Rgba32> rootFrame = output.Frames.RootFrame;
Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity;
Assert.True(output.Height > (int)rowsPerStrip);
Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1);
Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value;
Assert.NotNull(stripByteCounts);
Assert.True(stripByteCounts.Length > 1);
Assert.NotNull(outputMeta.BitsPerPixel);
foreach (Number sz in stripByteCounts)
{
Assert.True((uint)sz <= TiffConstants.DefaultStripSize);
}
// For uncompressed more accurate test.
if (compression == TiffCompression.None)
{
for (int i = 0; i < stripByteCounts.Length - 1; i++)
{
// The difference must be less than one row.
int stripBytes = (int)stripByteCounts[i];
int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width;
Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes);
}
}
// Compare with reference.
TestTiffEncoderCore(
provider,
inputMeta.BitsPerPixel,
photometricInterpretation,
inputCompression,
useExactComparer: useExactComparer,
compareTolerance: compareTolerance);
}
protected static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel? bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression = TiffCompression.None,
TiffPredictor predictor = TiffPredictor.None,
bool useExactComparer = true,
float compareTolerance = 0.001f,
IImageDecoder imageDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder
{
PhotometricInterpretation = photometricInterpretation,
BitsPerPixel = bitsPerPixel,
Compression = compression,
HorizontalPredictor = predictor
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance),
referenceDecoder: imageDecoder ?? ReferenceDecoder);
}
}
}

179
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs

@ -0,0 +1,179 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
using static SixLabors.ImageSharp.Tests.TestImages.Tiff;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Format", "Tiff")]
public class TiffEncoderMultiframeTests : TiffEncoderBaseTester
{
[Theory]
[WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb);
[Theory]
[WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)]
[WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_NotSupport<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb));
[Theory]
[WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_WithPreview<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb);
[Theory]
[WithFile(TestImages.Gif.Receipt, PixelTypes.Rgb24)]
[WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_Convert<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit48, TiffPhotometricInterpretation.Rgb);
[Theory]
[WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_RemoveFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
Assert.True(image.Frames.Count > 1);
image.Frames.RemoveFrame(0);
TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24;
var encoder = new TiffEncoder
{
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
BitsPerPixel = bitsPerPixel,
Compression = TiffCompression.Deflate
};
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
ImageComparer.Exact);
}
[Theory]
[WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_AddFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
Assert.Equal(1, image.Frames.Count);
using var image1 = new Image<Rgba32>(image.Width, image.Height, Color.Green.ToRgba32());
using var image2 = new Image<Rgba32>(image.Width, image.Height, Color.Yellow.ToRgba32());
image.Frames.AddFrame(image1.Frames.RootFrame);
image.Frames.AddFrame(image2.Frames.RootFrame);
TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24;
var encoder = new TiffEncoder
{
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
BitsPerPixel = bitsPerPixel,
Compression = TiffCompression.Deflate
};
using (var ms = new System.IO.MemoryStream())
{
image.Save(ms, encoder);
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
Assert.Equal(3, output.Frames.Count);
ImageFrame<Rgba32> frame1 = output.Frames[1];
ImageFrame<Rgba32> frame2 = output.Frames[2];
Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]);
Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]);
Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffPhotometricInterpretation.Rgb, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation);
Assert.Equal(TiffPhotometricInterpretation.Rgb, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation);
}
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
ImageComparer.Exact);
}
[Theory]
[WithBlankImages(100, 100, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeMultiframe_Create<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
using var image0 = new Image<Rgba32>(image.Width, image.Height, Color.Red.ToRgba32());
using var image1 = new Image<Rgba32>(image.Width, image.Height, Color.Green.ToRgba32());
using var image2 = new Image<Rgba32>(image.Width, image.Height, Color.Yellow.ToRgba32());
image.Frames.AddFrame(image0.Frames.RootFrame);
image.Frames.AddFrame(image1.Frames.RootFrame);
image.Frames.AddFrame(image2.Frames.RootFrame);
image.Frames.RemoveFrame(0);
TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit8;
var encoder = new TiffEncoder
{
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
BitsPerPixel = bitsPerPixel,
Compression = TiffCompression.Lzw
};
using (var ms = new System.IO.MemoryStream())
{
image.Save(ms, encoder);
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
Assert.Equal(3, output.Frames.Count);
ImageFrame<Rgba32> frame0 = output.Frames[0];
ImageFrame<Rgba32> frame1 = output.Frames[1];
ImageFrame<Rgba32> frame2 = output.Frames[2];
Assert.Equal(Color.Red.ToRgba32(), frame0[10, 10]);
Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]);
Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]);
Assert.Equal(TiffCompression.Lzw, frame0.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame0.Metadata.GetTiffMetadata().PhotometricInterpretation);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation);
}
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
ImageComparer.Exact);
}
}
}

100
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -2,14 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
@ -19,10 +14,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Collection("RunSerial")]
[Trait("Format", "Tiff")]
public class TiffEncoderTests
public class TiffEncoderTests : TiffEncoderBaseTester
{
private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
[Theory]
[InlineData(null, TiffBitsPerPixel.Bit24)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)]
@ -451,96 +444,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation };
image.DebugSave(provider, encoder);
}
private static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None;
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile;
TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata();
ImageFrame<Rgba32> rootFrame = output.Frames.RootFrame;
Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity;
Assert.True(output.Height > (int)rowsPerStrip);
Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1);
Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value;
Assert.NotNull(stripByteCounts);
Assert.True(stripByteCounts.Length > 1);
Assert.NotNull(outputMeta.BitsPerPixel);
foreach (Number sz in stripByteCounts)
{
Assert.True((uint)sz <= TiffConstants.DefaultStripSize);
}
// For uncompressed more accurate test.
if (compression == TiffCompression.None)
{
for (int i = 0; i < stripByteCounts.Length - 1; i++)
{
// The difference must be less than one row.
int stripBytes = (int)stripByteCounts[i];
int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width;
Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes);
}
}
// Compare with reference.
TestTiffEncoderCore(
provider,
inputMeta.BitsPerPixel,
photometricInterpretation,
inputCompression,
useExactComparer: useExactComparer,
compareTolerance: compareTolerance);
}
private static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel? bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression = TiffCompression.None,
TiffPredictor predictor = TiffPredictor.None,
bool useExactComparer = true,
float compareTolerance = 0.001f,
IImageDecoder imageDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder
{
PhotometricInterpretation = photometricInterpretation,
BitsPerPixel = bitsPerPixel,
Compression = compression,
HorizontalPredictor = predictor
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(
provider,
"tiff",
bitsPerPixel,
encoder,
useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance),
referenceDecoder: imageDecoder ?? ReferenceDecoder);
}
}
}

4
tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

@ -279,8 +279,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput);
PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile);
Assert.Equal(resolutionUnitInput, resolutionUnitEncoded);
Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution), encodedImageExifProfile.GetValue(ExifTag.XResolution));
Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution), encodedImageExifProfile.GetValue(ExifTag.YResolution));
Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble());
Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble());
Assert.Equal(xmpProfileInput, encodedImageXmpProfile);

20
tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -43,6 +43,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering
{ KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) }
};
public static readonly TheoryData<IDither> DefaultInstanceDitherers
= new TheoryData<IDither>
{
default(ErrorDither),
default(OrderedDither)
};
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f);
private static IDither DefaultDitherer => KnownDitherings.Bayer4x4;
@ -175,5 +182,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering
c => c.Dither(dither),
name);
}
[Theory]
[MemberData(nameof(DefaultInstanceDitherers))]
public void ShouldThrowForDefaultDitherInstance(IDither dither)
{
void Command()
{
using var image = new Image<Rgba32>(10, 10);
image.Mutate(x => x.Dither(dither));
}
Assert.Throws<ImageProcessingException>(Command);
}
}
}

23
tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs

@ -5,6 +5,7 @@ using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
@ -152,6 +153,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
new WuQuantizer(OrderedDitherOptions),
};
public static readonly TheoryData<IDither> DefaultInstanceDitherers
= new TheoryData<IDither>
{
default(ErrorDither),
default(OrderedDither)
};
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F);
[Theory]
@ -217,5 +225,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: false);
}
[Theory]
[MemberData(nameof(DefaultInstanceDitherers))]
public void ShouldThrowForDefaultDitherInstance(IDither dither)
{
void Command()
{
using var image = new Image<Rgba32>(10, 10);
var quantizer = new WebSafePaletteQuantizer();
quantizer.Options.Dither = dither;
image.Mutate(x => x.Quantize(quantizer));
}
Assert.Throws<ImageProcessingException>(Command);
}
}
}

24
tests/ImageSharp.Tests/TestImages.cs

@ -158,6 +158,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Fb = "Jpg/progressive/fb.jpg";
public const string Progress = "Jpg/progressive/progress.jpg";
public const string Festzug = "Jpg/progressive/Festzug.jpg";
public const string Winter = "Jpg/progressive/winter.jpg";
public static class Bad
{
@ -199,6 +200,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Iptc = "Jpg/baseline/iptc.jpg";
public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg";
public const string HistogramEqImage = "Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg";
public const string ForestBridgeDifferentComponentsQuality = "Jpg/baseline/forest_bridge.jpg";
public static readonly string[] All =
{
@ -752,8 +754,18 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbPalette = "Tiff/rgb_palette.tiff";
public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff";
public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff";
public const string FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.tiff";
public const string FlowerRgb323232ContiguousLittleEndian = "Tiff/flower-rgb-contig-32_lsb.tiff";
public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-32.tiff";
public const string FlowerRgb323232PlanarLittleEndian = "Tiff/flower-rgb-planar-32_lsb.tiff";
public const string FlowerRgb242424Planar = "Tiff/flower-rgb-planar-24.tiff";
public const string FlowerRgb242424PlanarLittleEndian = "Tiff/flower-rgb-planar-24_lsb.tiff";
public const string FlowerRgb242424Contiguous = "Tiff/flower-rgb-contig-24.tiff";
public const string FlowerRgb242424ContiguousLittleEndian = "Tiff/flower-rgb-contig-24_lsb.tiff";
public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff";
public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff";
public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff";
public const string FlowerRgb161616PlanarLittleEndian = "Tiff/flower-rgb-planar-16_lsb.tiff";
public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff";
public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff";
public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff";
@ -763,6 +775,8 @@ namespace SixLabors.ImageSharp.Tests
public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff";
public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff";
public const string FlowerRgb888Planar6Strips = "Tiff/flower-rgb-planar-08-6strips.tiff";
public const string FlowerRgb888Planar15Strips = "Tiff/flower-rgb-planar-08-15strips.tiff";
public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff";
public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff";
public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff";
@ -773,6 +787,16 @@ namespace SixLabors.ImageSharp.Tests
public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff";
public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff";
public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff";
public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff";
public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff";
public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff";
public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff";
public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff";
public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff";
public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff";
public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32.tiff";
public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff";
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";

3
tests/Images/Input/Jpg/baseline/forest_bridge.jpg

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

3
tests/Images/Input/Jpg/progressive/winter.jpg

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

3
tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-minisblack-24.tiff

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

3
tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-minisblack-32.tiff

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

3
tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-miniswhite-16.tiff

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

3
tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-miniswhite-32.tiff

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

3
tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-contig-24.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-contig-32.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-planar-08-15strips.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-planar-08-6strips.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-planar-24.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-planar-32.tiff

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

3
tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff

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