From f1d9188253ff9f83fac9a1fd371309d3109169ea Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 7 Jun 2021 08:22:37 +0300 Subject: [PATCH 01/26] Implement encoding multi-frame tiff images 111 --- .../Common/Helpers/UnitConverter.cs | 28 ++- .../Formats/Tiff/TiffEncoderCore.cs | 66 ++++++-- .../Tiff/TiffEncoderEntriesCollector.cs | 160 ++++++++++-------- .../Formats/Tiff/Writers/TiffStreamWriter.cs | 2 - 4 files changed, 152 insertions(+), 104 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index efc0e0e152..03d50c025c 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -100,12 +100,11 @@ namespace SixLabors.ImageSharp.Common.Helpers /// /// Sets the exif profile resolution values. /// - /// The exif profile. /// The resolution unit. /// The horizontal resolution value. /// The vertical resolution value. [MethodImpl(InliningOptions.ShortMethod)] - public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical) + public static (ushort, double?, double?) AdjustToExif(PixelResolutionUnit unit, double horizontal, double vertical) { switch (unit) { @@ -114,30 +113,25 @@ namespace SixLabors.ImageSharp.Common.Helpers case PixelResolutionUnit.PixelsPerCentimeter: 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; + break; default: unit = PixelResolutionUnit.PixelsPerInch; 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 (exifUnit, null, null); } + + return (exifUnit, horizontal, vertical); } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 2273d759f0..9e7284aca8 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -74,6 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb; + private readonly List<(long, uint)> frameMarkers = new List<(long, uint)>(); + /// /// Initializes a new instance of the class. /// @@ -147,13 +149,30 @@ 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 metadataImage = image; + foreach (ImageFrame frame in image.Frames) { - long firstIfdMarker = this.WriteHeader(writer); + var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile.GetValue(ExifTag.SubfileType)?.Value ?? 0); + + if (subfileType != TiffNewSubfileType.FullImage) + { + continue; + } - // 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.WriteMarker(marker.Item1, marker.Item2); + } + + writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin); } /// @@ -174,41 +193,56 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Writes all data required to define an image. /// /// The pixel format. - /// The to write data to. - /// The to encode from. + /// The to write data to. + /// The tiff frame. + /// The image metadata (resolution values for each frame). + /// The image (common metadata for root frame). /// The marker to write this IFD offset. - private void WriteImage(TiffStreamWriter writer, Image image, long ifdOffset) + /// + /// The next IFD offset value. + /// + private long WriteFrame( + TiffStreamWriter writer, + ImageFrame frame, + ImageMetadata imageMetadata, + Image image, + long ifdOffset) where TPixel : unmanaged, IPixel { - 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 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); } /// @@ -272,7 +306,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); diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 43a0868496..b00dcc9673 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/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 Entries { get; } = new List(); - public void ProcessGeneral(Image image) - where TPixel : unmanaged, IPixel - => 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(Image image) - where TPixel : unmanaged, IPixel + private class MetadataProcessor : BaseProcessor + { + public MetadataProcessor(TiffEncoderEntriesCollector collector) + : base(collector) { - ImageFrame 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) + { + (ushort, double?, double?) exifValues = UnitConverter.AdjustToExif( + imageMetadata.ResolutionUnits, + imageMetadata.HorizontalResolution, + imageMetadata.VerticalResolution); - public ImageFormatProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; + this.Collector.Add(new ExifShort(ExifTagValue.ResolutionUnit) + { + Value = exifValues.Item1 + }); + + if (exifValues.Item2 != null && exifValues.Item3 != null) + { + this.Collector.Add(new ExifRational(ExifTagValue.XResolution) + { + Value = new Rational(exifValues.Item2.Value) + }); + + this.Collector.Add(new ExifRational(ExifTagValue.YResolution) + { + Value = new Rational(exifValues.Item3.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); } } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs index 05a1ca7a24..8c83f41ccf 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs @@ -126,10 +126,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers /// The four-byte unsigned integer to write. public void WriteMarker(long offset, uint value) { - long currentOffset = this.BaseStream.Position; this.BaseStream.Seek(offset, SeekOrigin.Begin); this.Write(value); - this.BaseStream.Seek(currentOffset, SeekOrigin.Begin); } /// From 4c1e7182bf29f75c200899caa9c44cc33ad885fa Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 26 Jun 2021 14:30:05 +0300 Subject: [PATCH 02/26] Add multi-frame tests --- .../Formats/Tiff/TiffEncoderBaseTester.cs | 114 ++++++++++++++++++ .../Tiff/TiffEncoderMultiframeTests.cs | 30 +++++ .../Formats/Tiff/TiffEncoderTests.cs | 100 +-------------- 3 files changed, 145 insertions(+), 99 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs create mode 100644 tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs new file mode 100644 index 0000000000..cdbecf124b --- /dev/null +++ b/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 + { + private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); + + protected static void TestStripLength( + TestImageProvider provider, + TiffPhotometricInterpretation photometricInterpretation, + TiffCompression compression, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; + using Image 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(memStream); + ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile; + TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + ImageFrame 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( + TestImageProvider 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 + { + using Image 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); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs new file mode 100644 index 0000000000..7f2acc2e1c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs @@ -0,0 +1,30 @@ +// 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 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(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); + + [Theory] + [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] + [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] + [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_NotSupport(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 09505692fc..95013088e2 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/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; @@ -18,10 +13,8 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [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)] @@ -450,96 +443,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; image.DebugSave(provider, encoder); } - - private static void TestStripLength( - TestImageProvider provider, - TiffPhotometricInterpretation photometricInterpretation, - TiffCompression compression, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, IPixel - { - // arrange - var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; - using Image 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(memStream); - ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile; - TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - ImageFrame 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( - TestImageProvider 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 - { - using Image 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); - } } } From c661fd4bd805fc3520c885f9e02aa34eb8196d83 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 3 Jul 2021 12:10:11 +0300 Subject: [PATCH 03/26] Add test --- .../Formats/Tiff/TiffEncoderCore.cs | 2 +- .../Formats/Tiff/TiffEncoderBaseTester.cs | 2 +- .../Tiff/TiffEncoderMultiframeTests.cs | 37 ++++++++++++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 9e7284aca8..3e785f612b 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff Image metadataImage = image; foreach (ImageFrame frame in image.Frames) { - var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile.GetValue(ExifTag.SubfileType)?.Value ?? 0); + var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage); if (subfileType != TiffNewSubfileType.FullImage) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs index cdbecf124b..71d3663692 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Trait("Format", "Tiff")] public abstract class TiffEncoderBaseTester { - private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); + protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); protected static void TestStripLength( TestImageProvider provider, diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs index 7f2acc2e1c..c9f670ea3c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; @@ -13,6 +15,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [Trait("Format", "Tiff")] + [Trait("Format", "Tiff.m")] public class TiffEncoderMultiframeTests : TiffEncoderBaseTester { [Theory] @@ -21,10 +24,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); [Theory] - [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] public void TiffEncoder_EncodeMultiframe_NotSupport(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); + + [Theory] + [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_WithoutPreview_ProblemTest(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); + + [Theory] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_Create(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using var image = provider.GetImage(); + using var image2 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); + + image.Frames.AddFrame(image2.Frames.RootFrame); + + TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; + var encoder = new TiffEncoder + { + PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, + BitsPerPixel = bitsPerPixel, + Compression = TiffCompression.Deflate + }; + + image.VerifyEncoder( + provider, + "tiff", + bitsPerPixel, + encoder, + ImageComparer.Exact); + } } } From de8ef67d6ab41d84122a2595d97d1ee92cc9a94f Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 3 Jul 2021 12:28:37 +0300 Subject: [PATCH 04/26] Build fix --- src/ImageSharp/Common/Helpers/UnitConverter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 03d50c025c..6bb9460e30 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -113,13 +113,13 @@ namespace SixLabors.ImageSharp.Common.Helpers case PixelResolutionUnit.PixelsPerCentimeter: break; case PixelResolutionUnit.PixelsPerMeter: - { + { unit = PixelResolutionUnit.PixelsPerCentimeter; horizontal = MeterToCm(horizontal); vertical = MeterToCm(vertical); - } + } - break; + break; default: unit = PixelResolutionUnit.PixelsPerInch; break; From 02990ac59ac7e341a98997e14e8148784d5c6391 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 3 Jul 2021 12:37:31 +0300 Subject: [PATCH 05/26] Cleanup --- .../Formats/Tiff/TiffEncoderMultiframeTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs index c9f670ea3c..4031607726 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs @@ -2,11 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -15,7 +13,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [Trait("Format", "Tiff")] - [Trait("Format", "Tiff.m")] public class TiffEncoderMultiframeTests : TiffEncoderBaseTester { [Theory] From 3fe4e7f96115e8b26115fba600d1ae27021e3850 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 3 Jul 2021 13:23:33 +0300 Subject: [PATCH 06/26] Test fixes --- src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs | 2 +- src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 3e785f612b..b87b927a92 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff long currentOffset = writer.BaseStream.Position; foreach ((long, uint) marker in this.frameMarkers) { - writer.WriteMarker(marker.Item1, marker.Item2); + writer.WriteMarkerFast(marker.Item1, marker.Item2); } writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs index 8c83f41ccf..138274d3f7 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs @@ -125,6 +125,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers /// The offset returned when placing the marker /// The four-byte unsigned integer to write. public void WriteMarker(long offset, uint value) + { + 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); From aeeb7db0bcdf8c5c203c40f64c7768521d2660ef Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 3 Jul 2021 13:31:32 +0300 Subject: [PATCH 07/26] Double tags fixing --- src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index b00dcc9673..1b042eec04 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -244,19 +244,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff imageMetadata.HorizontalResolution, imageMetadata.VerticalResolution); - this.Collector.Add(new ExifShort(ExifTagValue.ResolutionUnit) + this.Collector.AddOrReplace(new ExifShort(ExifTagValue.ResolutionUnit) { Value = exifValues.Item1 }); if (exifValues.Item2 != null && exifValues.Item3 != null) { - this.Collector.Add(new ExifRational(ExifTagValue.XResolution) + this.Collector.AddOrReplace(new ExifRational(ExifTagValue.XResolution) { Value = new Rational(exifValues.Item2.Value) }); - this.Collector.Add(new ExifRational(ExifTagValue.YResolution) + this.Collector.AddOrReplace(new ExifRational(ExifTagValue.YResolution) { Value = new Rational(exifValues.Item3.Value) }); From aaaf1242c6a985a49c9e85a57f2ec5a6e20f1891 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 3 Jul 2021 13:44:52 +0300 Subject: [PATCH 08/26] Resolution test fixes --- tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index c80d9fc165..f9535607c6 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -278,8 +278,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); From bfbde71d881228f96b7cd227422c95278015faef Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 18 Jul 2021 23:08:45 +0300 Subject: [PATCH 09/26] remove subfileType filtering --- src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index b87b927a92..d7c9848a44 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -157,11 +157,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff { var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage); - if (subfileType != TiffNewSubfileType.FullImage) - { - continue; - } - ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker); metadataImage = null; } From cad7340d5b47d0ecc897583b2d90525e49ac9fd7 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 18 Jul 2021 23:16:59 +0300 Subject: [PATCH 10/26] Add multi-frame tests --- .../Tiff/TiffEncoderMultiframeTests.cs | 131 +++++++++++++++++- 1 file changed, 125 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs index 4031607726..aeca38c5c1 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs @@ -28,17 +28,54 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_WithoutPreview_ProblemTest(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); + public void TiffEncoder_EncodeMultiframe_WithPreview(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); [Theory] - [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_Create(TestImageProvider provider) + [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgb24)] + [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_Convert(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit48, TiffPhotometricInterpretation.Rgb); + + [Theory] + [WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_RemoveFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image 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(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using var image = provider.GetImage(); - using var image2 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); + using Image image = provider.GetImage(); + Assert.Equal(1, image.Frames.Count); + using var image1 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); + + using var image2 = new Image(image.Width, image.Height, Color.Yellow.ToRgba32()); + + image.Frames.AddFrame(image1.Frames.RootFrame); image.Frames.AddFrame(image2.Frames.RootFrame); TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; @@ -49,6 +86,88 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Compression = TiffCompression.Deflate }; + using (var ms = new System.IO.MemoryStream()) + { + image.Save(ms, encoder); + + ms.Position = 0; + using var output = Image.Load(ms); + + Assert.Equal(3, output.Frames.Count); + + ImageFrame frame1 = output.Frames[1]; + ImageFrame 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(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + using var image0 = new Image(image.Width, image.Height, Color.Red.ToRgba32()); + + using var image1 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); + + using var image2 = new Image(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(ms); + + Assert.Equal(3, output.Frames.Count); + + ImageFrame frame0 = output.Frames[0]; + ImageFrame frame1 = output.Frames[1]; + ImageFrame 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", From a7d44a435c2424c4d67883fec124ece547c1f892 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 30 Jul 2021 16:27:53 +0200 Subject: [PATCH 11/26] Use same byte order as IFD directory to decode pixels for 16 bit per channel data, fixes #1716 --- .../Rgb161616TiffColor{TPixel}.cs | 58 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 4 +- .../Formats/Tiff/TiffDecoderCore.cs | 8 ++- .../Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Tiff/Issues/Issue1716.tiff | 3 + 6 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/Issues/Issue1716.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs new file mode 100644 index 0000000000..635be95f4a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with 16 bits for each channel. + /// + internal class Rgb161616TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb161616TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + var rgba = default(Rgba64); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.GetRowSpan(y); + + for (int x = left; x < left + width; x++) + { + 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; + + rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); + color.FromRgba64(rgba); + + pixelRow[x] = color; + } + } + } + + private ushort ConvertToShort(ReadOnlySpan buffer) => this.isBigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(buffer) + : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 36d2ab746e..8e711d3eb6 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation internal static class TiffColorDecoderFactory where TPixel : unmanaged, IPixel { - public static TiffBaseColorDecoder Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) + public static TiffBaseColorDecoder Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder) { switch (colorType) { @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 16, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); + return new Rgb161616TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 3d5bfc7374..484e182c59 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -36,6 +36,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// private BufferedReadStream inputStream; + /// + /// Indicates the byte order of the stream. + /// + private ByteOrder byteOrder; + /// /// Initializes a new instance of the class. /// @@ -109,6 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff var reader = new DirectoryReader(stream); IEnumerable directories = reader.Read(); + this.byteOrder = reader.ByteOrder; var frames = new List>(); foreach (ExifProfile ifd in directories) @@ -310,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.Predictor, this.FaxCompressionOptions); - TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap); + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index a007cd3a9c..ab53ca1561 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -164,6 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] + [WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index fac8cb4a32..c54c82d7da 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -582,6 +582,7 @@ 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 Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; diff --git a/tests/Images/Input/Tiff/Issues/Issue1716.tiff b/tests/Images/Input/Tiff/Issues/Issue1716.tiff new file mode 100644 index 0000000000..b7b1fe5565 --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue1716.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c734dd489c65fb77bd7a35cd663aa16ce986df2c2ab8c7ca43d8b65db9d47c03 +size 6666162 From 941df950e13971167f99b76dda9b9f0518e0e0f7 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 30 Jul 2021 19:43:03 +0300 Subject: [PATCH 12/26] ExifResolutionValues struct --- src/ImageSharp/Common/Helpers/UnitConverter.cs | 9 +++++---- .../Formats/Tiff/TiffEncoderEntriesCollector.cs | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 6bb9460e30..7ea64aa624 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -98,13 +98,14 @@ namespace SixLabors.ImageSharp.Common.Helpers } /// - /// Sets the exif profile resolution values. + /// Gets the exif profile resolution values. /// /// The resolution unit. /// The horizontal resolution value. /// The vertical resolution value. + /// [MethodImpl(InliningOptions.ShortMethod)] - public static (ushort, double?, double?) AdjustToExif(PixelResolutionUnit unit, double horizontal, double vertical) + public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical) { switch (unit) { @@ -128,10 +129,10 @@ namespace SixLabors.ImageSharp.Common.Helpers ushort exifUnit = (ushort)(unit + 1); if (unit == PixelResolutionUnit.AspectRatio) { - return (exifUnit, null, null); + return new ExifResolutionValues(exifUnit, null, null); } - return (exifUnit, horizontal, vertical); + return new ExifResolutionValues(exifUnit, horizontal, vertical); } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 1b042eec04..15694978fc 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -239,26 +239,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff private void ProcessResolution(ImageMetadata imageMetadata) { - (ushort, double?, double?) exifValues = UnitConverter.AdjustToExif( + ExifResolutionValues resolution = UnitConverter.GetExifResolutionValues( imageMetadata.ResolutionUnits, imageMetadata.HorizontalResolution, imageMetadata.VerticalResolution); this.Collector.AddOrReplace(new ExifShort(ExifTagValue.ResolutionUnit) { - Value = exifValues.Item1 + Value = resolution.ResolutionUnit }); - if (exifValues.Item2 != null && exifValues.Item3 != null) + if (resolution.VerticalResolution.HasValue && resolution.HorizontalResolution.HasValue) { this.Collector.AddOrReplace(new ExifRational(ExifTagValue.XResolution) { - Value = new Rational(exifValues.Item2.Value) + Value = new Rational(resolution.HorizontalResolution.Value) }); this.Collector.AddOrReplace(new ExifRational(ExifTagValue.YResolution) { - Value = new Rational(exifValues.Item3.Value) + Value = new Rational(resolution.VerticalResolution.Value) }); } } From c58a385103d5d08cfb693ba70e26d4132b612cca Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 30 Jul 2021 19:46:33 +0300 Subject: [PATCH 13/26] add missed file --- .../Common/Helpers/ExifResolutionValues.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/ImageSharp/Common/Helpers/ExifResolutionValues.cs diff --git a/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs b/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs new file mode 100644 index 0000000000..b6a628608b --- /dev/null +++ b/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; } + } +} From 5d888bef8f733bd67476d5c711dab26fa32777e1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Aug 2021 15:46:00 +0200 Subject: [PATCH 14/26] Tiff decoder now respects byte order for 16 bit gray images --- .../BlackIsZero16TiffColor{TPixel}.cs | 48 +++++++++++++++++++ .../Rgb161616TiffColor{TPixel}.cs | 11 ++--- .../TiffColorDecoderFactory{TPixel}.cs | 5 ++ .../TiffColorType.cs | 5 ++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 6 +++ .../Formats/Tiff/Utils/TiffUtils.cs | 18 +++++++ .../Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/flower-minisblack-16_lsb.tiff | 3 ++ 9 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs create mode 100644 tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs new file mode 100644 index 0000000000..13a570dbcb --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -0,0 +1,48 @@ +// 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 +{ + /// + /// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images. + /// + internal class BlackIsZero16TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public BlackIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + var l16 = default(L16); + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + ushort intensity = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); + offset += 2; + + l16.PackedValue = intensity; + color.FromL16(l16); + + pixels[x, y] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index 635be95f4a..b0106cc096 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -36,11 +37,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int x = left; x < left + width; x++) { - ulong r = this.ConvertToShort(data.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); offset += 2; - ulong g = this.ConvertToShort(data.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); offset += 2; - ulong b = this.ConvertToShort(data.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); offset += 2; rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); @@ -50,9 +51,5 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } } } - - private ushort ConvertToShort(ReadOnlySpan buffer) => this.isBigEndian - ? BinaryPrimitives.ReadUInt16BigEndian(buffer) - : BinaryPrimitives.ReadUInt16LittleEndian(buffer); } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 8e711d3eb6..ad9c478d23 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -52,6 +52,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZero8TiffColor(); + case TiffColorType.BlackIsZero16: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb: DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 517926c239..3c003cf92d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -28,6 +28,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// BlackIsZero8, + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 16-bit images. + /// + BlackIsZero16, + /// /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 288f01cd1c..0ac9fd7c9f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -154,6 +154,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff switch (bitsPerChannel) { + case 16: + { + options.ColorType = TiffColorType.BlackIsZero16; + break; + } + case 8: { options.ColorType = TiffColorType.BlackIsZero8; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs new file mode 100644 index 0000000000..5a31b231ec --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; + +namespace SixLabors.ImageSharp.Formats.Tiff.Utils +{ + /// + /// Helper methods for TIFF decoding. + /// + internal static class TiffUtils + { + public static ushort ConvertToShort(ReadOnlySpan buffer, bool isBigEndian) => isBigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(buffer) + : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index ab53ca1561..95d5437e24 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -140,6 +140,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] + [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower16BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c54c82d7da..d5aeba0bba 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -582,6 +582,7 @@ 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 Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; diff --git a/tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-16_lsb.tiff new file mode 100644 index 0000000000..62061bfaf6 --- /dev/null +++ b/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 From cc16677172175c5554df37807e4ca47e0afb2f53 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Aug 2021 16:13:45 +0200 Subject: [PATCH 15/26] Tiff decoder now respects byte order for 16 bit gray images with white is zero --- .../TiffColorDecoderFactory{TPixel}.cs | 5 ++ .../TiffColorType.cs | 5 ++ .../WhiteIsZero16TiffColor{TPixel}.cs | 48 +++++++++++++++++++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 6 +++ .../Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/flower-miniswhite-16_lsb.tiff | 3 ++ 7 files changed, 69 insertions(+) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index ad9c478d23..25b441ab0a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -32,6 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZero8TiffColor(); + case TiffColorType.WhiteIsZero16: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.BlackIsZero: DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 3c003cf92d..331065d273 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -53,6 +53,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// WhiteIsZero8, + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 16-bit images. + /// + WhiteIsZero16, + /// /// Palette-color. /// diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs new file mode 100644 index 0000000000..d84efef66a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -0,0 +1,48 @@ +// 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 +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation for 16-bit grayscale images. + /// + internal class WhiteIsZero16TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public WhiteIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + int offset = 0; + + var l16 = default(L16); + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian)); + offset += 2; + + l16.PackedValue = intensity; + color.FromL16(l16); + + pixels[x, y] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 0ac9fd7c9f..14c527a34c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -111,6 +111,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff switch (bitsPerChannel) { + case 16: + { + options.ColorType = TiffColorType.WhiteIsZero16; + break; + } + case 8: { options.ColorType = TiffColorType.WhiteIsZero8; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 95d5437e24..9346c4bfd6 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -142,6 +142,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower16BitGray, PixelTypes.Rgba32)] + [WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d5aeba0bba..212bb94e3c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -583,6 +583,7 @@ namespace SixLabors.ImageSharp.Tests 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 Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; diff --git a/tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-16_lsb.tiff new file mode 100644 index 0000000000..ec9ceb1848 --- /dev/null +++ b/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 From a1ee0d638d01b2d4c18ebfe233f354a63261ee5b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 2 Aug 2021 21:15:16 +0200 Subject: [PATCH 16/26] Decoding 16bit rgb planar now also respects byte order --- .../Rgb161616TiffColor{TPixel}.cs | 1 - .../Rgb16PlanarTiffColor{TPixel}.cs | 53 +++++++++++++++++++ .../RgbPlanarTiffColor{TPixel}.cs | 4 +- .../TiffBasePlanarColorDecoder{TPixel}.cs | 28 ++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 7 ++- .../Formats/Tiff/TiffDecoderCore.cs | 2 +- .../Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/flower-rgb-planar-16_lsb.tiff | 3 ++ 9 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index b0106cc096..89cf1cb48e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -2,7 +2,6 @@ // 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; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs new file mode 100644 index 0000000000..75b0b3a8b6 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with 'Planar' layout for all 16 bit. + /// + internal class Rgb16PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + + System.Span redData = data[0].GetSpan(); + System.Span greenData = data[1].GetSpan(); + System.Span blueData = data[2].GetSpan(); + + int offset = 0; + var rgba = default(Rgba64); + for (int y = top; y < top + height; y++) + { + for (int x = left; x < left + width; x++) + { + ulong r = TiffUtils.ConvertToShort(redData.Slice(offset, 2), this.isBigEndian); + ulong g = TiffUtils.ConvertToShort(greenData.Slice(offset, 2), this.isBigEndian); + ulong b = TiffUtils.ConvertToShort(blueData.Slice(offset, 2), this.isBigEndian); + + offset += 2; + + rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); + color.FromRgba64(rgba); + pixels[x, y] = color; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index 3400bd65d8..5df68ee59b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// /// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths). /// - internal class RgbPlanarTiffColor + internal class RgbPlanarTiffColor : TiffBasePlanarColorDecoder where TPixel : unmanaged, IPixel { private readonly float rFactor; @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// The y-coordinate of the top of the image block. /// The width of the image block. /// The height of the image block. - public void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) { var color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs new file mode 100644 index 0000000000..57d8588cee --- /dev/null +++ b/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 +{ + /// + /// The base class for planar color decoders. + /// + /// The pixel format. + internal abstract class TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + /// + /// Decodes source raw pixel data using the current photometric interpretation. + /// + /// The buffers to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public abstract void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height); + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 25b441ab0a..0c93998c48 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -145,12 +145,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } } - public static RgbPlanarTiffColor CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) + public static TiffBasePlanarColorDecoder CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder) { switch (colorType) { case TiffColorType.RgbPlanar: DebugGuard.IsTrue(colorMap == null, "colorMap"); + if (bitsPerSample.Channel0 == 16 && bitsPerSample.Channel1 == 16 && bitsPerSample.Channel2 == 16) + { + return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + } + return new RgbPlanarTiffColor(bitsPerSample); default: diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 484e182c59..72f2336a8c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -266,7 +266,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); - RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap); + TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); for (int i = 0; i < stripsPerPlane; i++) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 9346c4bfd6..105b871dae 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -166,6 +166,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616PlanarLittleEndian, PixelTypes.Rgba32)] [WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 212bb94e3c..e0ae8f63d2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -563,6 +563,7 @@ namespace SixLabors.ImageSharp.Tests public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.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"; diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-16_lsb.tiff new file mode 100644 index 0000000000..425ea42efd --- /dev/null +++ b/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 From 6b538f89671d67a6115d4c472281351851bc67af Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 14:05:14 +0200 Subject: [PATCH 17/26] Check for isBigEndian per row not per pixel --- .../BlackIsZero16TiffColor{TPixel}.cs | 22 ++++++++---- .../Rgb161616TiffColor{TPixel}.cs | 34 +++++++++++++------ .../Rgb16PlanarTiffColor{TPixel}.cs | 30 +++++++++++----- .../WhiteIsZero16TiffColor{TPixel}.cs | 22 ++++++++---- .../Formats/Tiff/Utils/TiffUtils.cs | 30 ++++++++++++++-- 5 files changed, 105 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index 13a570dbcb..79017d8ce8 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -32,15 +32,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l16 = default(L16); for (int y = top; y < top + height; y++) { - for (int x = left; x < left + width; x++) + if (this.isBigEndian) { - ushort intensity = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); - offset += 2; + for (int x = left; x < left + width; x++) + { + ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); + offset += 2; - l16.PackedValue = intensity; - color.FromL16(l16); + pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + } + } + else + { + for (int x = left; x < left + width; x++) + { + ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + offset += 2; - pixels[x, y] = color; + pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + } } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index 89cf1cb48e..d2cb65cd5e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -34,19 +34,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + if (this.isBigEndian) { - ulong r = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); - offset += 2; - ulong g = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); - offset += 2; - ulong b = TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian); - offset += 2; + for (int x = left; x < left + width; x++) + { + ulong r = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong g = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong b = TiffUtils.ConvertToShortBigEndian(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 + { + for (int x = left; x < left + width; x++) + { + ulong r = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + offset += 2; + ulong g = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + offset += 2; + ulong b = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + offset += 2; - pixelRow[x] = color; + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + } } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 75b0b3a8b6..43b985c907 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -35,17 +35,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var rgba = default(Rgba64); for (int y = top; y < top + height; y++) { - for (int x = left; x < left + width; x++) + if (this.isBigEndian) { - ulong r = TiffUtils.ConvertToShort(redData.Slice(offset, 2), this.isBigEndian); - ulong g = TiffUtils.ConvertToShort(greenData.Slice(offset, 2), this.isBigEndian); - ulong b = TiffUtils.ConvertToShort(blueData.Slice(offset, 2), this.isBigEndian); + for (int x = left; x < left + width; x++) + { + ulong r = TiffUtils.ConvertToShortBigEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToShortBigEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToShortBigEndian(blueData.Slice(offset, 2)); - offset += 2; + offset += 2; - rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); - color.FromRgba64(rgba); - pixels[x, y] = color; + pixels[x, y] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + } + } + else + { + for (int x = left; x < left + width; x++) + { + ulong r = TiffUtils.ConvertToShortLittleEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToShortLittleEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToShortLittleEndian(blueData.Slice(offset, 2)); + + offset += 2; + + pixels[x, y] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + } } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index d84efef66a..7a0aae2678 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -32,15 +32,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l16 = default(L16); for (int y = top; y < top + height; y++) { - for (int x = left; x < left + width; x++) + if (this.isBigEndian) { - ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShort(data.Slice(offset, 2), this.isBigEndian)); - offset += 2; + for (int x = left; x < left + width; x++) + { + ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2))); + offset += 2; - l16.PackedValue = intensity; - color.FromL16(l16); + pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + } + } + else + { + for (int x = left; x < left + width; x++) + { + ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2))); + offset += 2; - pixels[x, y] = color; + pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + } } } } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 5a31b231ec..4f769c0475 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -3,6 +3,8 @@ using System; using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.Utils { @@ -11,8 +13,30 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils /// internal static class TiffUtils { - public static ushort ConvertToShort(ReadOnlySpan buffer, bool isBigEndian) => isBigEndian - ? BinaryPrimitives.ReadUInt16BigEndian(buffer) - : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ConvertToShortBigEndian(ReadOnlySpan buffer) => + BinaryPrimitives.ReadUInt16BigEndian(buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ConvertToShortLittleEndian(ReadOnlySpan buffer) => + BinaryPrimitives.ReadUInt16LittleEndian(buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) + where TPixel : unmanaged, IPixel + { + l16.PackedValue = intensity; + color.FromL16(l16); + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) + where TPixel : unmanaged, IPixel + { + rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); + color.FromRgba64(rgba); + return color; + } } } From 0ae95f17d970f4ba663434d5f7be4a6cf7c862a6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 14:34:03 +0200 Subject: [PATCH 18/26] Use pixel row span to access pixels --- .../BlackIsZero16TiffColor{TPixel}.cs | 5 +++-- .../BlackIsZero8TiffColor{TPixel}.cs | 9 +++------ .../BlackIsZeroTiffColor{TPixel}.cs | 3 ++- .../PaletteTiffColor{TPixel}.cs | 3 ++- .../Rgb16PlanarTiffColor{TPixel}.cs | 5 +++-- .../RgbPlanarTiffColor{TPixel}.cs | 4 +++- .../PhotometricInterpretation/RgbTiffColor{TPixel}.cs | 3 ++- .../WhiteIsZero16TiffColor{TPixel}.cs | 5 +++-- .../WhiteIsZero8TiffColor{TPixel}.cs | 9 +++------ .../WhiteIsZeroTiffColor{TPixel}.cs | 3 ++- src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs | 9 +++++++++ 11 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index 79017d8ce8..735dad1e27 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -32,6 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l16 = default(L16); for (int y = top; y < top + height; y++) { + Span pixelRow = pixels.GetRowSpan(y); if (this.isBigEndian) { for (int x = left; x < left + width; x++) @@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); offset += 2; - pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); } } else @@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); offset += 2; - pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs index 096f0449bf..3a0f4771bc 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{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++) { + Span pixelRow = pixels.GetRowSpan(y); for (int x = left; x < left + width; x++) { byte intensity = data[offset++]; - - l8.PackedValue = intensity; - color.FromL8(l8); - - pixels[x, y] = color; + pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index a4e5e45dfb..79ee04e29b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/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++) { + Span pixelRow = pixels.GetRowSpan(y); for (int x = left; x < left + width; 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(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index 796227953e..c14ff2385f 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/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++) { + Span pixelRow = pixels.GetRowSpan(y); for (int x = left; x < left + width; x++) { int index = bitReader.ReadBits(this.bitsPerSample0); - pixels[x, y] = this.palette[index]; + pixelRow[x] = this.palette[index]; } bitReader.NextRow(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 43b985c907..3bad6c78c5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -35,6 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var rgba = default(Rgba64); for (int y = top; y < top + height; y++) { + System.Span pixelRow = pixels.GetRowSpan(y); if (this.isBigEndian) { for (int x = left; x < left + width; x++) @@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 2; - pixels[x, y] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); } } else @@ -58,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 2; - pixels[x, y] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); + pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index 5df68ee59b..11611907b0 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/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; @@ -57,6 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { + Span pixelRow = pixels.GetRowSpan(y); for (int x = left; x < left + width; x++) { float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; @@ -64,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation 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(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index 259bb8efa7..01574c7aac 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -47,6 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { + Span pixelRow = pixels.GetRowSpan(y); for (int x = left; x < left + width; x++) { float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation 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(); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index 7a0aae2678..8bda36899a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -32,6 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l16 = default(L16); for (int y = top; y < top + height; y++) { + Span pixelRow = pixels.GetRowSpan(y); if (this.isBigEndian) { for (int x = left; x < left + width; x++) @@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2))); offset += 2; - pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); } } else @@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2))); offset += 2; - pixels[x, y] = TiffUtils.ColorFromL16(l16, intensity, color); + pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs index 1b141f9f6a..eba6f6b729 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs +++ b/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++) { + Span pixelRow = pixels.GetRowSpan(y); for (int x = left; x < left + width; x++) { byte intensity = (byte)(255 - data[offset++]); - - l8.PackedValue = intensity; - color.FromL8(l8); - - pixels[x, y] = color; + pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index 04b6f98e50..5fb5fefb4c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/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++) { + Span pixelRow = pixels.GetRowSpan(y); for (int x = left; x < left + width; 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(); diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 4f769c0475..0a6e539b84 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -21,6 +21,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils public static ushort ConvertToShortLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromL8(L8 l8, byte intensity, TPixel color) + where TPixel : unmanaged, IPixel + { + l8.PackedValue = intensity; + color.FromL8(l8); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) where TPixel : unmanaged, IPixel From cbb5aafa77c5208a3e17ad17eb1f9d6278e4be3f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 3 Aug 2021 23:25:20 +1000 Subject: [PATCH 19/26] Do not use static default options --- .../Processing/Processors/Quantization/OctreeQuantizer.cs | 4 +--- .../Processing/Processors/Quantization/PaletteQuantizer.cs | 3 +-- .../Processors/Quantization/WebSafePaletteQuantizer.cs | 6 +----- .../Processors/Quantization/WernerPaletteQuantizer.cs | 4 +--- .../Processing/Processors/Quantization/WuQuantizer.cs | 4 +--- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 861697594a..c1b695f653 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -11,14 +11,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class OctreeQuantizer : IQuantizer { - private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); - /// /// Initializes a new instance of the class /// using the default . /// public OctreeQuantizer() - : this(DefaultOptions) + : this(new QuantizerOptions()) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 4f73f4ac81..5da674cc9c 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -11,7 +11,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class PaletteQuantizer : IQuantizer { - private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); private readonly ReadOnlyMemory colorPalette; /// @@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The color palette. public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, DefaultOptions) + : this(palette, new QuantizerOptions()) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index 5dda17dc60..e717152f95 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/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 { /// @@ -10,13 +8,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WebSafePaletteQuantizer : PaletteQuantizer { - private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); - /// /// Initializes a new instance of the class. /// public WebSafePaletteQuantizer() - : this(DefaultOptions) + : this(new QuantizerOptions()) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index 6675263df6..8a96f8ecce 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -9,13 +9,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WernerPaletteQuantizer : PaletteQuantizer { - private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); - /// /// Initializes a new instance of the class. /// public WernerPaletteQuantizer() - : this(DefaultOptions) + : this(new QuantizerOptions()) { } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 95adb7e5d6..337948bef5 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -10,14 +10,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WuQuantizer : IQuantizer { - private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); - /// /// Initializes a new instance of the class /// using the default . /// public WuQuantizer() - : this(DefaultOptions) + : this(new QuantizerOptions()) { } From 522a879a1826a1fb47aafae6eaa61ccd7ab5562f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 15:55:49 +0200 Subject: [PATCH 20/26] Avoid using defaults, because of issue with netcore2.1 in Release mode --- .../BlackIsZero16TiffColor{TPixel}.cs | 6 ++++-- .../Rgb161616TiffColor{TPixel}.cs | 5 ++++- .../Rgb16PlanarTiffColor{TPixel}.cs | 6 +++++- .../WhiteIsZero16TiffColor{TPixel}.cs | 6 ++++-- src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs | 7 +++++++ 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index 735dad1e27..e80b1d99d1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -25,11 +25,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// public override void Decode(ReadOnlySpan data, Buffer2D 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; - - var l16 = default(L16); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.GetRowSpan(y); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index d2cb65cd5e..2e10916c04 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -25,11 +25,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// public override void Decode(ReadOnlySpan data, Buffer2D 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 pixelRow = pixels.GetRowSpan(y); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 3bad6c78c5..6adaab8bd5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Buffers; +using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -25,14 +26,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// public override void Decode(IMemoryOwner[] data, Buffer2D 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); System.Span redData = data[0].GetSpan(); System.Span greenData = data[1].GetSpan(); System.Span blueData = data[2].GetSpan(); int offset = 0; - var rgba = default(Rgba64); for (int y = top; y < top + height; y++) { System.Span pixelRow = pixels.GetRowSpan(y); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index 8bda36899a..3c7bfc99cc 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -25,11 +25,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// public override void Decode(ReadOnlySpan data, Buffer2D 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; - - var l16 = default(L16); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.GetRowSpan(y); diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 0a6e539b84..a0ba40f4e0 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; @@ -13,6 +14,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils /// internal static class TiffUtils { + 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 ConvertToShortBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer); From 547a90780fa4c4d20784d4a368ff8f6e7f5de374 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 4 Aug 2021 00:19:03 +1000 Subject: [PATCH 21/26] Handle default instances. #1583 --- .../Processors/Dithering/ErrorDither.cs | 9 ++++++++ .../Processors/Dithering/OrderedDither.cs | 9 ++++++++ .../Processors/Quantization/QuantizerTests.cs | 23 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 1a107c2cfd..35347933d9 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -95,6 +95,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TFrameQuantizer : struct, IQuantizer where TPixel : unmanaged, IPixel { + if (this == default) + { + ThrowDefaultInstance(); + } + int offsetY = bounds.Top; int offsetX = bounds.Left; float scale = quantizer.Options.DitherScale; @@ -210,5 +215,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// 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."); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index c317ddf02a..b8eb0e390d 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -109,6 +109,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TFrameQuantizer : struct, IQuantizer where TPixel : unmanaged, IPixel { + if (this == default) + { + ThrowDefaultInstance(); + } + int spread = CalculatePaletteSpread(destination.Palette.Length); float scale = quantizer.Options.DitherScale; @@ -201,5 +206,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."); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs index af1d7f3f38..41d860246b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/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 DefaultInstanceDitherers + = new TheoryData + { + 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(10, 10); + var quantizer = new WebSafePaletteQuantizer(); + quantizer.Options.Dither = dither; + image.Mutate(x => x.Quantize(quantizer)); + } + + Assert.Throws(Command); + } } } From 8238c8fd6fd3414abae22adfcccda8537b45bbe5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 4 Aug 2021 00:25:22 +1000 Subject: [PATCH 22/26] Add additional checks --- src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs | 2 ++ src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 35347933d9..09ce4ebd91 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering [MethodImpl(InliningOptions.ShortMethod)] public ErrorDither(in DenseMatrix matrix, int offset) { + Guard.MustBeGreaterThan(offset, 0, nameof(offset)); + this.matrix = matrix; this.offset = offset; } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index b8eb0e390d..2b5c7fc6b4 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/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 ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); // Create a new matrix to run against, that pre-thresholds the values. From f1834c7816d90597c8681e777954a5fc7816ac2c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 16:40:33 +0200 Subject: [PATCH 23/26] Avoid bounds checks --- .../BlackIsZero16TiffColor{TPixel}.cs | 6 +++--- .../BlackIsZero8TiffColor{TPixel}.cs | 4 ++-- .../BlackIsZeroTiffColor{TPixel}.cs | 4 ++-- .../PaletteTiffColor{TPixel}.cs | 4 ++-- .../Rgb161616TiffColor{TPixel}.cs | 4 ++-- .../Rgb16PlanarTiffColor{TPixel}.cs | 14 +++++++------- .../Rgb888TiffColor{TPixel}.cs | 4 ++-- .../RgbPlanarTiffColor{TPixel}.cs | 4 ++-- .../RgbTiffColor{TPixel}.cs | 4 ++-- .../WhiteIsZero16TiffColor{TPixel}.cs | 6 +++--- .../WhiteIsZero8TiffColor{TPixel}.cs | 4 ++-- .../WhiteIsZeroTiffColor{TPixel}.cs | 4 ++-- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index e80b1d99d1..a31ff7a9fd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -34,10 +34,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); if (this.isBigEndian) { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); offset += 2; @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } else { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); offset += 2; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs index 3a0f4771bc..ea2608f6fd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs @@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l8 = default(L8); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { byte intensity = data[offset++]; pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index 79ee04e29b..9956db5230 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -34,8 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + Span 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; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index c14ff2385f..b392fe1a36 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -35,8 +35,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { int index = bitReader.ReadBits(this.bitsPerSample0); pixelRow[x] = this.palette[index]; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index 2e10916c04..00dc9fd43e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -35,11 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); if (this.isBigEndian) { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ulong r = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); offset += 2; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 6adaab8bd5..c24fada54c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -1,8 +1,8 @@ // 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; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -32,17 +32,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); - System.Span redData = data[0].GetSpan(); - System.Span greenData = data[1].GetSpan(); - System.Span blueData = data[2].GetSpan(); + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); int offset = 0; for (int y = top; y < top + height; y++) { - System.Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); if (this.isBigEndian) { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ulong r = TiffUtils.ConvertToShortBigEndian(redData.Slice(offset, 2)); ulong g = TiffUtils.ConvertToShortBigEndian(greenData.Slice(offset, 2)); @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } else { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ulong r = TiffUtils.ConvertToShortLittleEndian(redData.Slice(offset, 2)); ulong g = TiffUtils.ConvertToShortLittleEndian(greenData.Slice(offset, 2)); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs index e45863a57f..536dece8e3 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs @@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var rgba = default(Rgba32); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { byte r = data[offset++]; byte g = data[offset++]; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index 11611907b0..b442c4ae47 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -58,8 +58,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + Span 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; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index 01574c7aac..1377598cc9 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -47,8 +47,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + Span 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; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index 3c7bfc99cc..a6b5151d70 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -34,10 +34,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); if (this.isBigEndian) { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2))); offset += 2; @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } else { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2))); offset += 2; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs index eba6f6b729..6a6c2af225 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs @@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l8 = default(L8); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { byte intensity = (byte)(255 - data[offset++]); pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index 5fb5fefb4c..9129559647 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -34,8 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); - for (int x = left; x < left + width; x++) + Span 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); From 9e12a72a55487dd877613208abf01c91c3069b48 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 16:57:03 +0200 Subject: [PATCH 24/26] Fix loop bounds for little endian, add little endian test file --- .../PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs | 2 +- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index 00dc9fd43e..4b34d5a0db 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } else { - for (int x = left; x < left + width; x++) + for (int x = 0; x < pixelRow.Length; x++) { ulong r = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); offset += 2; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 105b871dae..7ee6e70e63 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -165,6 +165,7 @@ 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)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e0ae8f63d2..e7641a7cab 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -562,6 +562,7 @@ namespace SixLabors.ImageSharp.Tests public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.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"; diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16_lsb.tiff new file mode 100644 index 0000000000..967d8bbf38 --- /dev/null +++ b/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 From f868b7a2aa2a7a2cf2f9836fd8c84e5dbfe08544 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 17:21:08 +0200 Subject: [PATCH 25/26] Add min is white 16 bit gray big endian test file --- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Tiff/flower-miniswhite-16.tiff | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 tests/Images/Input/Tiff/flower-miniswhite-16.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 7ee6e70e63..3bf1c25f3f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -143,6 +143,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower16BitGray, PixelTypes.Rgba32)] [WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] + [WithFile(Flower16BitGrayMinIsWhiteBigEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e7641a7cab..9c24184366 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -586,6 +586,7 @@ namespace SixLabors.ImageSharp.Tests 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 Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; diff --git a/tests/Images/Input/Tiff/flower-miniswhite-16.tiff b/tests/Images/Input/Tiff/flower-miniswhite-16.tiff new file mode 100644 index 0000000000..83266873c5 --- /dev/null +++ b/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 From 8e8123c598dc7ff8d09877643ba2d37bf6a68081 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 4 Aug 2021 10:06:16 +1000 Subject: [PATCH 26/26] Add sanitation for dithering methods. --- .../Processors/Dithering/ErrorDither.cs | 5 +++++ .../Processors/Dithering/OrderedDither.cs | 5 +++++ .../Processors/Dithering/DitherTests.cs | 20 +++++++++++++++++++ .../Processors/Quantization/QuantizerTests.cs | 8 ++++---- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 09ce4ebd91..0fe2d4b2c3 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -129,6 +129,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : unmanaged, IPixel { + if (this == default) + { + ThrowDefaultInstance(); + } + float scale = processor.DitherScale; for (int y = bounds.Top; y < bounds.Bottom; y++) { diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 2b5c7fc6b4..2f5a5cf85e 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -141,6 +141,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor where TPixel : unmanaged, IPixel { + if (this == default) + { + ThrowDefaultInstance(); + } + int spread = CalculatePaletteSpread(processor.Palette.Length); float scale = processor.DitherScale; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 37443a5b40..0465cae940 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/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 DefaultInstanceDitherers + = new TheoryData + { + 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(10, 10); + image.Mutate(x => x.Dither(dither)); + } + + Assert.Throws(Command); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs index 41d860246b..c99e101380 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -155,10 +155,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization public static readonly TheoryData DefaultInstanceDitherers = new TheoryData - { - default(ErrorDither), - default(OrderedDither) - }; + { + default(ErrorDither), + default(OrderedDither) + }; private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F);