From f1d9188253ff9f83fac9a1fd371309d3109169ea Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 7 Jun 2021 08:22:37 +0300 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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 941df950e13971167f99b76dda9b9f0518e0e0f7 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 30 Jul 2021 19:43:03 +0300 Subject: [PATCH 11/12] 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 12/12] 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; } + } +}