Browse Source

Begin migrating TiffMetadata

pull/2751/head
James Jackson-South 2 years ago
parent
commit
fdcd60200e
  1. 29
      src/ImageSharp/Formats/Bmp/BmpMetadata.cs
  2. 24
      src/ImageSharp/Formats/Gif/GifMetadata.cs
  3. 8
      src/ImageSharp/Formats/IFormatMetadata.cs
  4. 22
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  5. 20
      src/ImageSharp/Formats/Pbm/PbmMetadata.cs
  6. 22
      src/ImageSharp/Formats/Png/PngMetadata.cs
  7. 20
      src/ImageSharp/Formats/Qoi/QoiMetadata.cs
  8. 21
      src/ImageSharp/Formats/Tga/TgaMetadata.cs
  9. 45
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  10. 2
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  11. 8
      src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
  12. 78
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  13. 2
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  14. 239
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  15. 13
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  16. 18
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs
  17. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
  18. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
  19. 36
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  20. 17
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

29
src/ImageSharp/Formats/Bmp/BmpMetadata.cs

@ -50,15 +50,18 @@ public class BmpMetadata : IFormatMetadata<BmpMetadata>, IFormatFrameMetadata<Bm
<= 8 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel8 },
<= 16 => new BmpMetadata
{
BitsPerPixel = BmpBitsPerPixel.Pixel16, InfoHeaderType = BmpInfoHeaderType.WinVersion3
BitsPerPixel = BmpBitsPerPixel.Pixel16,
InfoHeaderType = BmpInfoHeaderType.WinVersion3
},
<= 24 => new BmpMetadata
{
BitsPerPixel = BmpBitsPerPixel.Pixel24, InfoHeaderType = BmpInfoHeaderType.WinVersion4
BitsPerPixel = BmpBitsPerPixel.Pixel24,
InfoHeaderType = BmpInfoHeaderType.WinVersion4
},
_ => new BmpMetadata
{
BitsPerPixel = BmpBitsPerPixel.Pixel32, InfoHeaderType = BmpInfoHeaderType.WinVersion5
BitsPerPixel = BmpBitsPerPixel.Pixel32,
InfoHeaderType = BmpInfoHeaderType.WinVersion5
}
};
}
@ -68,7 +71,7 @@ public class BmpMetadata : IFormatMetadata<BmpMetadata>, IFormatFrameMetadata<Bm
=> new();
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BitsPerPixel;
@ -122,17 +125,21 @@ public class BmpMetadata : IFormatMetadata<BmpMetadata>, IFormatFrameMetadata<Bm
break;
}
return new()
return new PixelTypeInfo(bpp)
{
PixelTypeInfo = new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
}
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/>
public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
=> new();

24
src/ImageSharp/Formats/Gif/GifMetadata.cs

@ -126,6 +126,20 @@ public class GifMetadata : IFormatMetadata<GifMetadata>
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = this.GlobalColorTable.HasValue
? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.GlobalColorTable.Value.Length), 1, 8)
: 8;
return new PixelTypeInfo(bpp)
{
ColorType = PixelColorType.Indexed,
ComponentInfo = PixelComponentInfo.Create(1, bpp, bpp),
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
{
@ -133,21 +147,13 @@ public class GifMetadata : IFormatMetadata<GifMetadata>
? this.GlobalColorTable.Value.Span[this.BackgroundColorIndex]
: Color.Transparent;
int bpp = this.GlobalColorTable.HasValue
? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.GlobalColorTable.Value.Length), 1, 8)
: 8;
return new()
{
AnimateRootFrame = true,
BackgroundColor = color,
ColorTable = this.GlobalColorTable,
ColorTableMode = this.ColorTableMode,
PixelTypeInfo = new PixelTypeInfo(bpp)
{
ColorType = PixelColorType.Indexed,
ComponentInfo = PixelComponentInfo.Create(1, bpp, bpp),
},
PixelTypeInfo = this.GetPixelTypeInfo(),
RepeatCount = this.RepeatCount,
};
}

8
src/ImageSharp/Formats/IFormatMetadata.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
@ -8,6 +10,12 @@ namespace SixLabors.ImageSharp.Formats;
/// </summary>
public interface IFormatMetadata : IDeepCloneable
{
/// <summary>
/// Converts the metadata to a <see cref="PixelTypeInfo"/> instance.
/// </summary>
/// <returns>The pixel type info.</returns>
PixelTypeInfo GetPixelTypeInfo();
/// <summary>
/// Converts the metadata to a <see cref="FormatConnectingMetadata"/> instance.
/// </summary>

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

@ -139,7 +139,7 @@ public class JpegMetadata : IFormatMetadata<JpegMetadata>
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
@ -173,18 +173,22 @@ public class JpegMetadata : IFormatMetadata<JpegMetadata>
break;
}
return new FormatConnectingMetadata
return new PixelTypeInfo(bpp)
{
PixelTypeInfo = new PixelTypeInfo(bpp)
{
AlphaRepresentation = PixelAlphaRepresentation.None,
ColorType = colorType,
ComponentInfo = info,
},
Quality = this.Quality,
AlphaRepresentation = PixelAlphaRepresentation.None,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo(),
Quality = this.Quality,
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

20
src/ImageSharp/Formats/Pbm/PbmMetadata.cs

@ -85,7 +85,7 @@ public class PbmMetadata : IFormatMetadata<PbmMetadata>
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
@ -114,17 +114,21 @@ public class PbmMetadata : IFormatMetadata<PbmMetadata>
break;
}
return new FormatConnectingMetadata
return new PixelTypeInfo(bpp)
{
PixelTypeInfo = new PixelTypeInfo(bpp)
{
AlphaRepresentation = PixelAlphaRepresentation.None,
ColorType = colorType,
ComponentInfo = info,
},
AlphaRepresentation = PixelAlphaRepresentation.None,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo(),
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

22
src/ImageSharp/Formats/Png/PngMetadata.cs

@ -191,7 +191,7 @@ public class PngMetadata : IFormatMetadata<PngMetadata>
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
@ -262,19 +262,23 @@ public class PngMetadata : IFormatMetadata<PngMetadata>
break;
}
return new()
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
ColorTable = this.ColorTable,
ColorTableMode = FrameColorTableMode.Global,
PixelTypeInfo = new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ColorType = colorType,
ComponentInfo = info,
},
PixelTypeInfo = this.GetPixelTypeInfo(),
RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue),
};
}
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

20
src/ImageSharp/Formats/Qoi/QoiMetadata.cs

@ -51,7 +51,7 @@ public class QoiMetadata : IFormatMetadata<QoiMetadata>
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
@ -73,17 +73,21 @@ public class QoiMetadata : IFormatMetadata<QoiMetadata>
break;
}
return new()
return new PixelTypeInfo(bpp)
{
PixelTypeInfo = new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ColorType = colorType,
ComponentInfo = info,
}
AlphaRepresentation = alpha,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

21
src/ImageSharp/Formats/Tga/TgaMetadata.cs

@ -49,10 +49,9 @@ public class TgaMetadata : IFormatMetadata<TgaMetadata>
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BitsPerPixel;
PixelComponentInfo info;
PixelColorType color;
PixelAlphaRepresentation alpha;
@ -80,17 +79,21 @@ public class TgaMetadata : IFormatMetadata<TgaMetadata>
break;
}
return new()
return new PixelTypeInfo(bpp)
{
PixelTypeInfo = new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
}
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();

45
src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs

@ -39,9 +39,9 @@ internal static class TiffConstants
public const ushort BigTiffHeaderMagicNumber = 43;
/// <summary>
/// The big tiff bytesize of offsets value.
/// The big tiff byte size of offsets value.
/// </summary>
public const ushort BigTiffBytesize = 8;
public const ushort BigTiffByteSize = 8;
/// <summary>
/// RowsPerStrip default value, which is effectively infinity.
@ -58,38 +58,63 @@ internal static class TiffConstants
/// </summary>
public const int DefaultStripSize = 8 * 1024;
/// <summary>
/// The default predictor is None.
/// </summary>
public const TiffPredictor DefaultPredictor = TiffPredictor.None;
/// <summary>
/// The default bits per pixel is Bit24.
/// </summary>
public const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24;
/// <summary>
/// The default bits per sample for color images with 8 bits for each color channel.
/// </summary>
public static readonly TiffBitsPerSample DefaultBitsPerSample = BitsPerSampleRgb8Bit;
/// <summary>
/// The default compression is None.
/// </summary>
public const TiffCompression DefaultCompression = TiffCompression.None;
/// <summary>
/// The default photometric interpretation is Rgb.
/// </summary>
public const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
/// <summary>
/// The bits per sample for 1 bit bicolor images.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample1Bit = new(1, 0, 0);
/// <summary>
/// The bits per sample for images with a 4 color palette.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample4Bit = new(4, 0, 0);
/// <summary>
/// The bits per sample for 8 bit images.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample8Bit = new(8, 0, 0);
/// <summary>
/// The bits per sample for 16-bit grayscale images.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample16Bit = new TiffBitsPerSample(16, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample16Bit = new(16, 0, 0);
/// <summary>
/// The bits per sample for color images with 8 bits for each color channel.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8);
public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new(8, 8, 8);
/// <summary>
/// The list of mimetypes that equate to a tiff.
/// The list of mime types that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" };
public static readonly IEnumerable<string> MimeTypes = ["image/tiff", "image/tiff-fx"];
/// <summary>
/// The list of file extensions that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" };
public static readonly IEnumerable<string> FileExtensions = ["tiff", "tif"];
}

2
src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

@ -64,7 +64,7 @@ internal class HeaderReader : BaseExifReader
ushort bytesize = this.ReadUInt16();
ushort reserve = this.ReadUInt16();
if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0)
if (bytesize == TiffConstants.BigTiffByteSize && reserve == 0)
{
this.FirstIfdOffset = this.ReadUInt64();
return;

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

@ -147,20 +147,20 @@ public readonly struct TiffBitsPerSample : IEquatable<TiffBitsPerSample>
{
if (this.Channel1 == 0)
{
return new[] { this.Channel0 };
return [this.Channel0];
}
if (this.Channel2 == 0)
{
return new[] { this.Channel0, this.Channel1 };
return [this.Channel0, this.Channel1];
}
if (this.Channel3 == 0)
{
return new[] { this.Channel0, this.Channel1, this.Channel2 };
return [this.Channel0, this.Channel1, this.Channel2];
}
return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 };
return [this.Channel0, this.Channel1, this.Channel2, this.Channel3];
}
/// <summary>

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

@ -63,7 +63,7 @@ internal static class TiffDecoderOptionsParser
}
TiffSampleFormat? sampleFormat = null;
if (exifProfile.TryGetValue(ExifTag.SampleFormat, out var formatValue))
if (exifProfile.TryGetValue(ExifTag.SampleFormat, out IExifValue<ushort[]> formatValue))
{
TiffSampleFormat[] sampleFormats = formatValue.Value.Select(a => (TiffSampleFormat)a).ToArray();
sampleFormat = sampleFormats[0];
@ -106,11 +106,11 @@ internal static class TiffDecoderOptionsParser
options.PlanarConfiguration = DefaultPlanarConfiguration;
}
options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None;
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
options.Predictor = frameMetadata.Predictor;
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation;
options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
options.BitsPerPixel = (int)frameMetadata.BitsPerPixel;
options.BitsPerSample = frameMetadata.BitsPerSample;
if (exifProfile.TryGetValue(ExifTag.ReferenceBlackWhite, out IExifValue<Rational[]> blackWhiteValue))
{
@ -142,9 +142,7 @@ internal static class TiffDecoderOptionsParser
options.ParseCompression(frameMetadata.Compression, exifProfile);
options.ParseColorType(exifProfile);
bool isTiled = VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration);
return isTiled;
return VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration);
}
/// <summary>
@ -194,13 +192,6 @@ internal static class TiffDecoderOptionsParser
}
}
// For BiColor compressed images, the BitsPerPixel value will be set explicitly to 1, so we don't throw in those cases.
// See: https://github.com/SixLabors/ImageSharp/issues/2587
if (frameMetadata.BitsPerPixel == null && !IsBiColorCompression(frameMetadata.Compression))
{
TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!");
}
return isTiled;
}
@ -224,7 +215,6 @@ internal static class TiffDecoderOptionsParser
switch (bitsPerChannel)
{
case 32:
{
if (options.SampleFormat == TiffSampleFormat.Float)
{
options.ColorType = TiffColorType.WhiteIsZero32Float;
@ -233,43 +223,30 @@ internal static class TiffDecoderOptionsParser
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;
break;
}
case 4:
{
options.ColorType = TiffColorType.WhiteIsZero4;
break;
}
case 1:
{
options.ColorType = TiffColorType.WhiteIsZero1;
break;
}
default:
{
options.ColorType = TiffColorType.WhiteIsZero;
break;
}
}
break;
@ -291,7 +268,6 @@ internal static class TiffDecoderOptionsParser
switch (bitsPerChannel)
{
case 32:
{
if (options.SampleFormat == TiffSampleFormat.Float)
{
options.ColorType = TiffColorType.BlackIsZero32Float;
@ -300,43 +276,30 @@ internal static class TiffDecoderOptionsParser
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;
break;
}
case 4:
{
options.ColorType = TiffColorType.BlackIsZero4;
break;
}
case 1:
{
options.ColorType = TiffColorType.BlackIsZero1;
break;
}
default:
{
options.ColorType = TiffColorType.BlackIsZero;
break;
}
}
break;
@ -535,29 +498,21 @@ internal static class TiffDecoderOptionsParser
switch (compression ?? TiffCompression.None)
{
case TiffCompression.None:
{
options.CompressionType = TiffDecoderCompressionType.None;
break;
}
case TiffCompression.PackBits:
{
options.CompressionType = TiffDecoderCompressionType.PackBits;
break;
}
case TiffCompression.Deflate:
case TiffCompression.OldDeflate:
{
options.CompressionType = TiffDecoderCompressionType.Deflate;
break;
}
case TiffCompression.Lzw:
{
options.CompressionType = TiffDecoderCompressionType.Lzw;
break;
}
case TiffCompression.CcittGroup3Fax:
{
@ -599,16 +554,13 @@ internal static class TiffDecoderOptionsParser
}
case TiffCompression.Ccitt1D:
{
options.CompressionType = TiffDecoderCompressionType.HuffmanRle;
options.BitsPerSample = new TiffBitsPerSample(1, 0, 0);
options.BitsPerPixel = 1;
break;
}
case TiffCompression.OldJpeg:
{
if (!options.OldJpegCompressionStartOfImageMarker.HasValue)
{
TiffThrowHelper.ThrowNotSupported("Missing SOI marker offset for tiff with old jpeg compression");
@ -629,10 +581,8 @@ internal static class TiffDecoderOptionsParser
}
break;
}
case TiffCompression.Jpeg:
{
options.CompressionType = TiffDecoderCompressionType.Jpeg;
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null)
@ -643,30 +593,14 @@ internal static class TiffDecoderOptionsParser
}
break;
}
case TiffCompression.Webp:
{
options.CompressionType = TiffDecoderCompressionType.Webp;
break;
}
default:
{
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported");
break;
}
}
}
private static bool IsBiColorCompression(TiffCompression? compression)
{
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or
TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
}

2
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -47,7 +47,7 @@ public class TiffEncoder : QuantizingImageEncoder
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
TiffEncoderCore encode = new(this, image.Configuration.MemoryAllocator);
TiffEncoderCore encode = new(this, image.Configuration);
encode.Encode(image, stream, cancellationToken);
}
}

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

@ -1,8 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Advanced;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
@ -50,41 +49,22 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// </summary>
private readonly DeflateCompressionLevel compressionLevel;
/// <summary>
/// The default predictor is None.
/// </summary>
private const TiffPredictor DefaultPredictor = TiffPredictor.None;
/// <summary>
/// The default bits per pixel is Bit24.
/// </summary>
private const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24;
/// <summary>
/// The default compression is None.
/// </summary>
private const TiffCompression DefaultCompression = TiffCompression.None;
/// <summary>
/// The default photometric interpretation is Rgb.
/// </summary>
private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
/// <summary>
/// Whether to skip metadata during encoding.
/// </summary>
private readonly bool skipMetadata;
private readonly List<(long, uint)> frameMarkers = new();
private readonly List<(long, uint)> frameMarkers = [];
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
public TiffEncoderCore(TiffEncoder options, MemoryAllocator memoryAllocator)
/// <param name="configuration">The global configuration.</param>
public TiffEncoderCore(TiffEncoder options, Configuration configuration)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.PhotometricInterpretation = options.PhotometricInterpretation;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.pixelSamplingStrategy = options.PixelSamplingStrategy;
@ -135,35 +115,29 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
// Determine the correct values to encode with.
// EncoderOptions > Metadata > Default.
TiffBitsPerPixel? bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel;
TiffBitsPerPixel bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel;
TiffPhotometricInterpretation? photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation;
TiffPhotometricInterpretation photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation;
TiffPredictor predictor =
this.HorizontalPredictor
?? rootFrameTiffMetaData.Predictor
?? DefaultPredictor;
TiffPredictor predictor = this.HorizontalPredictor ?? rootFrameTiffMetaData.Predictor;
TiffCompression compression =
this.CompressionType
?? rootFrameTiffMetaData.Compression
?? DefaultCompression;
TiffCompression compression = this.CompressionType ?? rootFrameTiffMetaData.Compression;
// Make sure, the Encoder options makes sense in combination with each other.
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
// Make sure the Encoder options makes sense in combination with each other.
this.SanitizeAndSetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor);
using TiffStreamWriter writer = new(stream);
Span<byte> buffer = stackalloc byte[4];
long ifdMarker = WriteHeader(writer, buffer);
Image<TPixel> metadataImage = image;
Image<TPixel>? metadataImage = image;
foreach (ImageFrame<TPixel> frame in image.Frames)
{
cancellationToken.ThrowIfCancellationRequested();
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker);
metadataImage = null;
}
@ -199,6 +173,8 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// <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="bitsPerPixel">The bits per pixel.</param>
/// <param name="compression">The compression type.</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
/// <returns>
/// The next IFD offset value.
@ -207,16 +183,18 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
TiffStreamWriter writer,
ImageFrame<TPixel> frame,
ImageMetadata imageMetadata,
Image<TPixel> image,
Image<TPixel>? image,
TiffBitsPerPixel bitsPerPixel,
TiffCompression compression,
long ifdOffset)
where TPixel : unmanaged, IPixel<TPixel>
{
using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
this.CompressionType ?? TiffCompression.None,
compression,
writer.BaseStream,
this.memoryAllocator,
frame.Width,
(int)this.BitsPerPixel,
(int)bitsPerPixel,
this.compressionLevel,
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None);
@ -229,7 +207,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
this.memoryAllocator,
this.configuration,
entriesCollector,
(int)this.BitsPerPixel);
(int)bitsPerPixel);
int rowsPerStrip = CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType);
@ -307,7 +285,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
}
uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12));
List<byte[]> largeDataBlocks = new();
List<byte[]> largeDataBlocks = [];
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
@ -354,135 +332,80 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
return nextIfdMarker;
}
[MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
private void SanitizeAndSetEncoderOptions(
TiffBitsPerPixel? bitsPerPixel,
int inputBitsPerPixel,
TiffPhotometricInterpretation? photometricInterpretation,
TiffBitsPerPixel bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
TiffPredictor predictor)
{
// BitsPerPixel should be the primary source of truth for the encoder options.
if (bitsPerPixel.HasValue)
{
switch (bitsPerPixel)
{
case TiffBitsPerPixel.Bit1:
if (IsOneBitCompression(compression))
{
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
break;
}
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit4:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit8:
this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break;
case TiffBitsPerPixel.Bit16:
// Assume desire to encode as L16 grayscale
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break;
case TiffBitsPerPixel.Bit6:
case TiffBitsPerPixel.Bit10:
case TiffBitsPerPixel.Bit12:
case TiffBitsPerPixel.Bit14:
case TiffBitsPerPixel.Bit30:
case TiffBitsPerPixel.Bit36:
case TiffBitsPerPixel.Bit42:
case TiffBitsPerPixel.Bit48:
// Encoding not yet supported bits per pixel will default to 24 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit64:
// Encoding not yet supported bits per pixel will default to 32 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
break;
default:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
break;
}
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
this.CompressionType = DefaultCompression;
}
return;
}
// If no photometric interpretation was chosen, the input image bit per pixel should be preserved.
if (!photometricInterpretation.HasValue)
switch (bitsPerPixel)
{
if (IsOneBitCompression(this.CompressionType))
{
// We need to make sure bits per pixel is set to Bit1 now. WhiteIsZero is set because its the default for bilevel compressed data.
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
return;
}
// At the moment only 8, 16 and 32 bits per pixel can be preserved by the tiff encoder.
if (inputBitsPerPixel == 8)
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
return;
}
if (inputBitsPerPixel == 16)
{
// Assume desire to encode as L16 grayscale
this.SetEncoderOptions(TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
return;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor);
return;
}
switch (photometricInterpretation)
{
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (IsOneBitCompression(this.CompressionType))
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None);
return;
}
if (inputBitsPerPixel == 16)
case TiffBitsPerPixel.Bit1:
if (IsOneBitCompression(compression))
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit16, photometricInterpretation, compression, predictor);
return;
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
break;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
case TiffPhotometricInterpretation.PaletteColor:
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
case TiffPhotometricInterpretation.Rgb:
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType))
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit4:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit8:
// Allow any combination of the below for 8 bit images.
if (photometricInterpretation is TiffPhotometricInterpretation.BlackIsZero
or TiffPhotometricInterpretation.WhiteIsZero
or TiffPhotometricInterpretation.PaletteColor)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
compression = DefaultCompression;
this.SetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor);
break;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor);
return;
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor);
break;
case TiffBitsPerPixel.Bit16:
// Assume desire to encode as L16 grayscale
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break;
case TiffBitsPerPixel.Bit6:
case TiffBitsPerPixel.Bit10:
case TiffBitsPerPixel.Bit12:
case TiffBitsPerPixel.Bit14:
case TiffBitsPerPixel.Bit30:
case TiffBitsPerPixel.Bit36:
case TiffBitsPerPixel.Bit42:
case TiffBitsPerPixel.Bit48:
// Encoding not yet supported bits per pixel will default to 24 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit64:
// Encoding not yet supported bits per pixel will default to 32 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
break;
default:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
break;
}
this.SetEncoderOptions(DefaultBitsPerPixel, DefaultPhotometricInterpretation, compression, predictor);
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
this.CompressionType = TiffCompression.None;
}
}
private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor)
[MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
private void SetEncoderOptions(
TiffBitsPerPixel bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
TiffPredictor predictor)
{
this.BitsPerPixel = bitsPerPixel;
this.PhotometricInterpretation = photometricInterpretation;

13
src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs

@ -34,27 +34,27 @@ public class TiffFrameMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the bits per pixel.
/// </summary>
public TiffBitsPerPixel? BitsPerPixel { get; set; }
public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel;
/// <summary>
/// Gets or sets number of bits per component.
/// </summary>
public TiffBitsPerSample? BitsPerSample { get; set; }
public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample;
/// <summary>
/// Gets or sets the compression scheme used on the image data.
/// </summary>
public TiffCompression? Compression { get; set; }
public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression;
/// <summary>
/// Gets or sets the color space of the image data.
/// </summary>
public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; }
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation;
/// <summary>
/// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
/// </summary>
public TiffPredictor? Predictor { get; set; }
public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor;
/// <summary>
/// Gets or sets the set of inks used in a separated (<see cref="TiffPhotometricInterpretation.Separated"/>) image.
@ -89,7 +89,7 @@ public class TiffFrameMetadata : IDeepCloneable
meta.BitsPerSample = bitsPerSample;
}
meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel();
meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel();
if (profile.TryGetValue(ExifTag.Compression, out IExifValue<ushort>? compressionValue))
{
@ -111,6 +111,7 @@ public class TiffFrameMetadata : IDeepCloneable
meta.InkSet = (TiffInkSet)inkSetValue.Value;
}
// TODO: Why do we remove this? Encoding should overwrite.
profile.RemoveValue(ExifTag.BitsPerSample);
profile.RemoveValue(ExifTag.Compression);
profile.RemoveValue(ExifTag.PhotometricInterpretation);

18
src/ImageSharp/Formats/Tiff/TiffMetadata.cs

@ -1,12 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff;
/// <summary>
/// Provides Tiff specific metadata information for the image.
/// </summary>
public class TiffMetadata : IDeepCloneable
public class TiffMetadata : IFormatMetadata<TiffMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffMetadata"/> class.
@ -36,5 +38,17 @@ public class TiffMetadata : IDeepCloneable
public TiffFormatType FormatType { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffMetadata(this);
public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException();
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException();
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException();
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public TiffMetadata DeepClone() => new(this);
}

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

@ -30,7 +30,7 @@ public abstract class TiffEncoderBaseTester
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None;
TiffCompression inputCompression = inputMeta.Compression;
// act
input.Save(memStream, tiffEncoder);

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

@ -15,7 +15,7 @@ public class TiffEncoderHeaderTests
public void WriteHeader_WritesValidHeader()
{
using MemoryStream stream = new();
TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
TiffEncoderCore encoder = new(Encoder, Configuration.Default);
using (TiffStreamWriter writer = new(stream))
{
@ -29,7 +29,7 @@ public class TiffEncoderHeaderTests
public void WriteHeader_ReturnsFirstIfdMarker()
{
using MemoryStream stream = new();
TiffEncoderCore encoder = new(Encoder, Configuration.Default.MemoryAllocator);
TiffEncoderCore encoder = new(Encoder, Configuration.Default);
using TiffStreamWriter writer = new(stream);
long firstIfdMarker = TiffEncoderCore.WriteHeader(writer, stackalloc byte[4]);

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

@ -31,7 +31,7 @@ public class TiffEncoderTests : TiffEncoderBaseTester
public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel)
{
// arrange
TiffEncoder tiffEncoder = new() { PhotometricInterpretation = photometricInterpretation };
TiffEncoder tiffEncoder = new() { BitsPerPixel = expectedBitsPerPixel, PhotometricInterpretation = photometricInterpretation };
using Image input = expectedBitsPerPixel is TiffBitsPerPixel.Bit16
? new Image<L16>(10, 10)
: new Image<Rgb24>(10, 10);
@ -57,8 +57,7 @@ public class TiffEncoderTests : TiffEncoderBaseTester
public void EncoderOptions_SetBitPerPixel_Works(TiffBitsPerPixel bitsPerPixel)
{
// arrange
TiffEncoder tiffEncoder = new()
{ BitsPerPixel = bitsPerPixel };
TiffEncoder tiffEncoder = new() { BitsPerPixel = bitsPerPixel };
using Image input = new Image<Rgb24>(10, 10);
using MemoryStream memStream = new();
@ -156,7 +155,11 @@ public class TiffEncoderTests : TiffEncoderBaseTester
{
// arrange
TiffEncoder tiffEncoder = new()
{ PhotometricInterpretation = photometricInterpretation, Compression = compression };
{
BitsPerPixel = expectedBitsPerPixel,
PhotometricInterpretation = photometricInterpretation,
Compression = compression
};
using Image input = expectedBitsPerPixel is TiffBitsPerPixel.Bit16
? new Image<L16>(10, 10)
: new Image<Rgb24>(10, 10);
@ -199,25 +202,6 @@ public class TiffEncoderTests : TiffEncoderBaseTester
Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel);
}
[Fact]
public void TiffEncoder_PreservesBitsPerPixel_WhenInputIsL8()
{
// arrange
TiffEncoder tiffEncoder = new();
using Image input = new Image<L8>(10, 10);
using MemoryStream memStream = new();
const TiffBitsPerPixel expectedBitsPerPixel = TiffBitsPerPixel.Bit8;
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel);
}
[Theory]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)]
[WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)]
@ -241,11 +225,11 @@ public class TiffEncoderTests : TiffEncoderBaseTester
}
[Theory]
[WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, null)]
[WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffPredictor.None)]
[WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)]
[WithFile(RgbDeflate, PixelTypes.Rgba32, null)]
[WithFile(RgbDeflate, PixelTypes.Rgba32, TiffPredictor.None)]
[WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)]
public void TiffEncoder_PreservesPredictor<TPixel>(TestImageProvider<TPixel> provider, TiffPredictor? expectedPredictor)
public void TiffEncoder_PreservesPredictor<TPixel>(TestImageProvider<TPixel> provider, TiffPredictor expectedPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange

17
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -697,23 +697,6 @@ public static class TestImageExtensions
return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf<TPixel>());
}
internal static Image<Rgba32> ToGrayscaleImage(this Buffer2D<float> buffer, float scale)
{
Image<Rgba32> image = new(buffer.Width, buffer.Height);
Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> pixelMem));
Span<Rgba32> pixels = pixelMem.Span;
Span<float> bufferSpan = buffer.DangerousGetSingleSpan();
for (int i = 0; i < bufferSpan.Length; i++)
{
float value = bufferSpan[i] * scale;
pixels[i] = Rgba32.FromVector4(new Vector4(value, value, value, 1f));
}
return image;
}
private class MakeOpaqueProcessor : IImageProcessor
{
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)

Loading…
Cancel
Save