diff --git a/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs b/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs deleted file mode 100644 index 8f8e187403..0000000000 --- a/src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -internal class AnimatedImageFrameMetadata -{ - /// - /// Gets or sets the frame color table. - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - /// Gets or sets the frame color table mode. - /// - public FrameColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets the duration of the frame. - /// - public TimeSpan Duration { get; set; } - - /// - /// Gets or sets the frame alpha blending mode. - /// - public FrameBlendMode BlendMode { get; set; } - - /// - /// Gets or sets the frame disposal mode. - /// - public FrameDisposalMode DisposalMode { get; set; } -} diff --git a/src/ImageSharp/Formats/AnimatedImageMetadata.cs b/src/ImageSharp/Formats/AnimatedImageMetadata.cs deleted file mode 100644 index ac3ca29f4f..0000000000 --- a/src/ImageSharp/Formats/AnimatedImageMetadata.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats; - -internal class AnimatedImageMetadata -{ - /// - /// Gets or sets the shared color table. - /// - public ReadOnlyMemory? ColorTable { get; set; } - - /// - /// Gets or sets the shared color table mode. - /// - public FrameColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets the default background color of the canvas when animating. - /// This color may be used to fill the unused space on the canvas around the frames, - /// as well as the transparent pixels of the first frame. - /// The background color is also used when the disposal mode is . - /// - public Color BackgroundColor { get; set; } - - /// - /// Gets or sets the number of times any animation is repeated. - /// - /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. - /// - /// - public ushort RepeatCount { get; set; } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs index 68e99bdc5f..d0c60421c4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs @@ -154,4 +154,10 @@ public class BmpMetadata : IFormatMetadata /// public BmpMetadata DeepClone() => new(this); + + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } } diff --git a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs index a8a51878e0..6fc8905279 100644 --- a/src/ImageSharp/Formats/Cur/CurDecoderCore.cs +++ b/src/ImageSharp/Formats/Cur/CurDecoderCore.cs @@ -35,10 +35,6 @@ internal sealed class CurDecoderCore : IconDecoderCore curMetadata.Compression = compression; curMetadata.BmpBitsPerPixel = bitsPerPixel; curMetadata.ColorTable = colorTable; - curMetadata.EncodingWidth = curFrameMetadata.EncodingWidth; - curMetadata.EncodingHeight = curFrameMetadata.EncodingHeight; - curMetadata.HotspotX = curFrameMetadata.HotspotX; - curMetadata.HotspotY = curFrameMetadata.HotspotY; } } } diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs index 06cf426dc4..4e9a432b16 100644 --- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs @@ -132,6 +132,16 @@ public class CurFrameMetadata : IFormatFrameMetadata EncodingHeight = this.EncodingHeight }; + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + float ratioX = destination.Width / (float)source.Width; + float ratioY = destination.Height / (float)source.Height; + this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); + this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); @@ -222,4 +232,14 @@ public class CurFrameMetadata : IFormatFrameMetadata ColorType = color }; } + + private static byte Scale(byte? value, int destination, float ratio) + { + if (value is null) + { + return (byte)Math.Clamp(destination, 0, 255); + } + + return Math.Min((byte)MathF.Ceiling(value.Value * ratio), (byte)Math.Clamp(destination, 0, 255)); + } } diff --git a/src/ImageSharp/Formats/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs index 6e97a8584a..19de7f434d 100644 --- a/src/ImageSharp/Formats/Cur/CurMetadata.cs +++ b/src/ImageSharp/Formats/Cur/CurMetadata.cs @@ -22,10 +22,6 @@ public class CurMetadata : IFormatMetadata private CurMetadata(CurMetadata other) { this.Compression = other.Compression; - this.HotspotX = other.HotspotX; - this.HotspotY = other.HotspotY; - this.EncodingWidth = other.EncodingWidth; - this.EncodingHeight = other.EncodingHeight; this.BmpBitsPerPixel = other.BmpBitsPerPixel; if (other.ColorTable?.Length > 0) @@ -39,28 +35,6 @@ public class CurMetadata : IFormatMetadata /// public IconFrameCompression Compression { get; set; } - /// - /// Gets or sets the horizontal coordinates of the hotspot in number of pixels from the left. Derived from the root frame. - /// - public ushort HotspotX { get; set; } - - /// - /// Gets or sets the vertical coordinates of the hotspot in number of pixels from the top. Derived from the root frame. - /// - public ushort HotspotY { get; set; } - - /// - /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingHeight { get; set; } - /// /// Gets or sets the number of bits per pixel.
/// Used when is @@ -175,6 +149,12 @@ public class CurMetadata : IFormatMetadata ColorTable = this.ColorTable }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index f81329e973..5fe892c656 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -126,40 +126,15 @@ public class GifFrameMetadata : IFormatFrameMetadata }; } + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); /// public GifFrameMetadata DeepClone() => new(this); - - internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata) - { - // TODO: v4 How do I link the parent metadata to the frame metadata to get the global color table? - int index = -1; - const float background = 1f; - if (metadata.ColorTable.HasValue) - { - ReadOnlySpan colorTable = metadata.ColorTable.Value.Span; - for (int i = 0; i < colorTable.Length; i++) - { - Vector4 vector = colorTable[i].ToScaledVector4(); - if (vector.W < background) - { - index = i; - } - } - } - - bool hasTransparency = index >= 0; - - return new() - { - LocalColorTable = metadata.ColorTable, - ColorTableMode = metadata.ColorTableMode, - FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10), - DisposalMode = metadata.DisposalMode, - HasTransparency = hasTransparency, - TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue, - }; - } } diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index 565038b55a..517609af45 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -130,6 +130,12 @@ public class GifMetadata : IFormatMetadata }; } + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/IFormatFrameMetadata.cs b/src/ImageSharp/Formats/IFormatFrameMetadata.cs index 4eef93ad34..20f27d050c 100644 --- a/src/ImageSharp/Formats/IFormatFrameMetadata.cs +++ b/src/ImageSharp/Formats/IFormatFrameMetadata.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats; /// @@ -13,6 +15,15 @@ public interface IFormatFrameMetadata : IDeepCloneable /// /// The . FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata(); + + /// + /// This method is called after a process has been applied to the image frame. + /// + /// The type of pixel format. + /// The source image frame. + /// The destination image frame. + void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel; } /// diff --git a/src/ImageSharp/Formats/IFormatMetadata.cs b/src/ImageSharp/Formats/IFormatMetadata.cs index 8d695306e4..a351431c94 100644 --- a/src/ImageSharp/Formats/IFormatMetadata.cs +++ b/src/ImageSharp/Formats/IFormatMetadata.cs @@ -21,6 +21,14 @@ public interface IFormatMetadata : IDeepCloneable /// /// The . FormatConnectingMetadata ToFormatConnectingMetadata(); + + /// + /// This method is called after a process has been applied to the image. + /// + /// The type of pixel format. + /// The destination image . + void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel; } /// diff --git a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs index 8b59974eb3..b8a1dded15 100644 --- a/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs +++ b/src/ImageSharp/Formats/Ico/IcoDecoderCore.cs @@ -35,8 +35,6 @@ internal sealed class IcoDecoderCore : IconDecoderCore curMetadata.Compression = compression; curMetadata.BmpBitsPerPixel = bitsPerPixel; curMetadata.ColorTable = colorTable; - curMetadata.EncodingWidth = icoFrameMetadata.EncodingWidth; - curMetadata.EncodingHeight = icoFrameMetadata.EncodingHeight; } } } diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs index c244e38981..a2d1c01391 100644 --- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs @@ -125,6 +125,16 @@ public class IcoFrameMetadata : IFormatFrameMetadata EncodingHeight = this.EncodingHeight }; + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + float ratioX = destination.Width / (float)source.Width; + float ratioY = destination.Height / (float)source.Height; + this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); + this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); @@ -217,4 +227,14 @@ public class IcoFrameMetadata : IFormatFrameMetadata ColorType = color }; } + + private static byte Scale(byte? value, int destination, float ratio) + { + if (value is null) + { + return (byte)Math.Clamp(destination, 0, 255); + } + + return Math.Min((byte)MathF.Ceiling(value.Value * ratio), (byte)Math.Clamp(destination, 0, 255)); + } } diff --git a/src/ImageSharp/Formats/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs index 7e31468ecc..a6c2704b31 100644 --- a/src/ImageSharp/Formats/Ico/IcoMetadata.cs +++ b/src/ImageSharp/Formats/Ico/IcoMetadata.cs @@ -22,8 +22,6 @@ public class IcoMetadata : IFormatMetadata private IcoMetadata(IcoMetadata other) { this.Compression = other.Compression; - this.EncodingWidth = other.EncodingWidth; - this.EncodingHeight = other.EncodingHeight; this.BmpBitsPerPixel = other.BmpBitsPerPixel; if (other.ColorTable?.Length > 0) @@ -37,18 +35,6 @@ public class IcoMetadata : IFormatMetadata /// public IconFrameCompression Compression { get; set; } - /// - /// Gets or sets the encoding width.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingWidth { get; set; } - - /// - /// Gets or sets the encoding height.
- /// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame. - ///
- public byte EncodingHeight { get; set; } - /// /// Gets or sets the number of bits per pixel.
/// Used when is @@ -163,6 +149,12 @@ public class IcoMetadata : IFormatMetadata ColorTable = this.ColorTable }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index f2f34ec496..fe4855dc77 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -199,6 +199,12 @@ public class JpegMetadata : IFormatMetadata Quality = this.Quality, }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs index fec4beda7c..d852f3c8eb 100644 --- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -129,6 +129,12 @@ public class PbmMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo(), }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs index c142a1c8e0..b8086cd6d1 100644 --- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Png.Chunks; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png; @@ -84,6 +85,12 @@ public class PngFrameMetadata : IFormatFrameMetadata }; } + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index a7b3672ef5..00cba088cb 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -247,6 +247,12 @@ public class PngMetadata : IFormatMetadata RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue), }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs index e2062014d7..e463d511d2 100644 --- a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs +++ b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs @@ -88,6 +88,12 @@ public class QoiMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo() }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs index 58b5119523..8d40f86464 100644 --- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs +++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs @@ -94,6 +94,12 @@ public class TgaMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo() }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 0ba9755b15..d699a7b631 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -211,17 +211,21 @@ internal class TiffDecoderCore : ImageDecoderCore IList directories = reader.Read(); List framesMetadata = []; - foreach (ExifProfile dir in directories) + int width = 0; + int height = 0; + + for (int i = 0; i < directories.Count; i++) { - framesMetadata.Add(this.CreateFrameMetadata(dir)); - } + (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffMetadata) meta + = this.CreateFrameMetadata(directories[i]); - ExifProfile rootFrameExifProfile = directories[0]; + framesMetadata.Add(meta.FrameMetadata); - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); + width = Math.Max(width, meta.TiffMetadata.EncodingWidth); + height = Math.Max(height, meta.TiffMetadata.EncodingHeight); + } - int width = GetImageWidth(rootFrameExifProfile); - int height = GetImageHeight(rootFrameExifProfile); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); return new ImageInfo(new(width, height), metadata, framesMetadata); } @@ -237,11 +241,11 @@ internal class TiffDecoderCore : ImageDecoderCore private ImageFrame DecodeFrame(ExifProfile tags, Size? size, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - ImageFrameMetadata imageFrameMetaData = this.CreateFrameMetadata(tags); - bool isTiled = this.VerifyAndParse(tags, imageFrameMetaData.GetTiffMetadata()); + (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffFrameMetadata) metadata = this.CreateFrameMetadata(tags); + bool isTiled = this.VerifyAndParse(tags, metadata.TiffFrameMetadata); - int width = GetImageWidth(tags); - int height = GetImageHeight(tags); + int width = metadata.TiffFrameMetadata.EncodingWidth; + int height = metadata.TiffFrameMetadata.EncodingHeight; // If size has a value and the width/height off the tiff is smaller we much capture the delta. if (size.HasValue) @@ -256,7 +260,7 @@ internal class TiffDecoderCore : ImageDecoderCore size = new Size(width, height); } - ImageFrame frame = new(this.configuration, size.Value.Width, size.Value.Height, imageFrameMetaData); + ImageFrame frame = new(this.configuration, size.Value.Width, size.Value.Height, metadata.FrameMetadata); if (isTiled) { @@ -270,7 +274,7 @@ internal class TiffDecoderCore : ImageDecoderCore return frame; } - private ImageFrameMetadata CreateFrameMetadata(ExifProfile tags) + private (ImageFrameMetadata FrameMetadata, TiffFrameMetadata TiffMetadata) CreateFrameMetadata(ExifProfile tags) { ImageFrameMetadata imageFrameMetaData = new(); if (!this.skipMetadata) @@ -278,9 +282,10 @@ internal class TiffDecoderCore : ImageDecoderCore imageFrameMetaData.ExifProfile = tags; } - TiffFrameMetadata.Parse(imageFrameMetaData.GetTiffMetadata(), tags); + TiffFrameMetadata tiffMetadata = TiffFrameMetadata.Parse(tags); + imageFrameMetaData.SetFormatMetadata(TiffFormat.Instance, tiffMetadata); - return imageFrameMetaData; + return (imageFrameMetaData, tiffMetadata); } /// @@ -825,38 +830,6 @@ internal class TiffDecoderCore : ImageDecoderCore return bytesPerRow * height; } - /// - /// Gets the width of the image frame. - /// - /// The image frame exif profile. - /// The image width. - private static int GetImageWidth(ExifProfile exifProfile) - { - if (!exifProfile.TryGetValue(ExifTag.ImageWidth, out IExifValue width)) - { - TiffThrowHelper.ThrowInvalidImageContentException("The TIFF image frame is missing the ImageWidth"); - } - - DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); - - return (int)width.Value; - } - - /// - /// Gets the height of the image frame. - /// - /// The image frame exif profile. - /// The image height. - private static int GetImageHeight(ExifProfile exifProfile) - { - if (!exifProfile.TryGetValue(ExifTag.ImageLength, out IExifValue height)) - { - TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); - } - - return (int)height.Value; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int RoundUpToMultipleOfEight(int value) => (int)(((uint)value + 7) / 8); } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 5f91fd7393..b560067f3f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -189,11 +189,22 @@ internal sealed class TiffEncoderCore long ifdOffset) where TPixel : unmanaged, IPixel { + // Get the width and height of the frame. + // This can differ from the frame bounds in-memory if the image represents only + // a subregion. + TiffFrameMetadata frameMetaData = frame.Metadata.GetTiffMetadata(); + int width = frameMetaData.EncodingWidth > 0 ? frameMetaData.EncodingWidth : frame.Width; + int height = frameMetaData.EncodingHeight > 0 ? frameMetaData.EncodingHeight : frame.Height; + + width = Math.Min(width, frame.Width); + height = Math.Min(height, frame.Height); + Size encodingSize = new(width, height); + using TiffBaseCompressor compressor = TiffCompressorFactory.Create( compression, writer.BaseStream, this.memoryAllocator, - frame.Width, + width, (int)bitsPerPixel, this.compressionLevel, this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); @@ -202,6 +213,7 @@ internal sealed class TiffEncoderCore using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( this.PhotometricInterpretation, frame, + encodingSize, this.quantizer, this.pixelSamplingStrategy, this.memoryAllocator, @@ -209,7 +221,7 @@ internal sealed class TiffEncoderCore entriesCollector, (int)bitsPerPixel); - int rowsPerStrip = CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType); + int rowsPerStrip = CalcRowsPerStrip(height, colorWriter.BytesPerRow, this.CompressionType); colorWriter.Write(compressor, rowsPerStrip); @@ -222,7 +234,7 @@ internal sealed class TiffEncoderCore // Write the metadata for the frame entriesCollector.ProcessMetadata(frame, this.skipMetadata); - entriesCollector.ProcessFrameInfo(frame, imageMetadata); + entriesCollector.ProcessFrameInfo(frame, encodingSize, imageMetadata); entriesCollector.ProcessImageFormat(this); if (writer.Position % 2 != 0) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index c8e28111ec..803b77fb0a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -24,8 +24,8 @@ internal class TiffEncoderEntriesCollector public void ProcessMetadata(ImageFrame frame, bool skipMetadata) => new MetadataProcessor(this).Process(frame, skipMetadata); - public void ProcessFrameInfo(ImageFrame frame, ImageMetadata imageMetadata) - => new FrameInfoProcessor(this).Process(frame, imageMetadata); + public void ProcessFrameInfo(ImageFrame frame, Size encodingSize, ImageMetadata imageMetadata) + => new FrameInfoProcessor(this).Process(frame, encodingSize, imageMetadata); public void ProcessImageFormat(TiffEncoderCore encoder) => new ImageFormatProcessor(this).Process(encoder); @@ -267,16 +267,16 @@ internal class TiffEncoderEntriesCollector { } - public void Process(ImageFrame frame, ImageMetadata imageMetadata) + public void Process(ImageFrame frame, Size encodingSize, ImageMetadata imageMetadata) { this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageWidth) { - Value = (uint)frame.Width + Value = (uint)encodingSize.Width }); this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageLength) { - Value = (uint)frame.Height + Value = (uint)encodingSize.Height }); this.ProcessResolution(imageMetadata); diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index bb5da37411..189fee8b0c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff; @@ -29,6 +30,8 @@ public class TiffFrameMetadata : IFormatFrameMetadata this.PhotometricInterpretation = other.PhotometricInterpretation; this.Predictor = other.Predictor; this.InkSet = other.InkSet; + this.EncodingWidth = other.EncodingWidth; + this.EncodingHeight = other.EncodingHeight; } /// @@ -61,13 +64,59 @@ public class TiffFrameMetadata : IFormatFrameMetadata /// public TiffInkSet? InkSet { get; set; } + /// + /// Gets or sets the encoding width. + /// + public int EncodingWidth { get; set; } + + /// + /// Gets or sets the encoding height. + /// + public int EncodingHeight { get; set; } + /// public static TiffFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata) - => new(); + { + TiffFrameMetadata frameMetadata = new(); + if (metadata.EncodingWidth.HasValue && metadata.EncodingHeight.HasValue) + { + frameMetadata.EncodingWidth = metadata.EncodingWidth.Value; + frameMetadata.EncodingHeight = metadata.EncodingHeight.Value; + } + + return frameMetadata; + } /// public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata() - => new(); + => new() + { + EncodingWidth = this.EncodingWidth, + EncodingHeight = this.EncodingHeight + }; + + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + float ratioX = destination.Width / (float)source.Width; + float ratioY = destination.Height / (float)source.Height; + this.EncodingWidth = Scale(this.EncodingWidth, destination.Width, ratioX); + this.EncodingHeight = Scale(this.EncodingHeight, destination.Height, ratioY); + + // Overwrite the EXIF dimensional metadata with the encoding dimensions of the image. + destination.Metadata.ExifProfile?.SyncDimensions(this.EncodingWidth, this.EncodingHeight); + } + + private static int Scale(int value, int destination, float ratio) + { + if (value <= 0) + { + return destination; + } + + return Math.Min((int)MathF.Ceiling(value * ratio), destination); + } /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); @@ -93,43 +142,75 @@ public class TiffFrameMetadata : IFormatFrameMetadata /// /// The tiff frame meta data. /// The Exif profile containing tiff frame directory tags. - internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) + private static void Parse(TiffFrameMetadata meta, ExifProfile profile) { - if (profile != null) + meta.EncodingWidth = GetImageWidth(profile); + meta.EncodingHeight = GetImageHeight(profile); + + if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue? bitsPerSampleValue) + && TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample)) + { + meta.BitsPerSample = bitsPerSample; + } + + meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel(); + + if (profile.TryGetValue(ExifTag.Compression, out IExifValue? compressionValue)) { - if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue? bitsPerSampleValue) - && TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample)) - { - meta.BitsPerSample = bitsPerSample; - } - - meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel(); - - if (profile.TryGetValue(ExifTag.Compression, out IExifValue? compressionValue)) - { - meta.Compression = (TiffCompression)compressionValue.Value; - } - - if (profile.TryGetValue(ExifTag.PhotometricInterpretation, out IExifValue? photometricInterpretationValue)) - { - meta.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretationValue.Value; - } - - if (profile.TryGetValue(ExifTag.Predictor, out IExifValue? predictorValue)) - { - meta.Predictor = (TiffPredictor)predictorValue.Value; - } - - if (profile.TryGetValue(ExifTag.InkSet, out IExifValue? inkSetValue)) - { - meta.InkSet = (TiffInkSet)inkSetValue.Value; - } - - // TODO: Why do we remove this? Encoding should overwrite. - profile.RemoveValue(ExifTag.BitsPerSample); - profile.RemoveValue(ExifTag.Compression); - profile.RemoveValue(ExifTag.PhotometricInterpretation); - profile.RemoveValue(ExifTag.Predictor); + meta.Compression = (TiffCompression)compressionValue.Value; } + + if (profile.TryGetValue(ExifTag.PhotometricInterpretation, out IExifValue? photometricInterpretationValue)) + { + meta.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretationValue.Value; + } + + if (profile.TryGetValue(ExifTag.Predictor, out IExifValue? predictorValue)) + { + meta.Predictor = (TiffPredictor)predictorValue.Value; + } + + if (profile.TryGetValue(ExifTag.InkSet, out IExifValue? inkSetValue)) + { + meta.InkSet = (TiffInkSet)inkSetValue.Value; + } + + // Remove values, we've explicitly captured them and they could change on encode. + profile.RemoveValue(ExifTag.BitsPerSample); + profile.RemoveValue(ExifTag.Compression); + profile.RemoveValue(ExifTag.PhotometricInterpretation); + profile.RemoveValue(ExifTag.Predictor); + } + + /// + /// Gets the width of the image frame. + /// + /// The image frame exif profile. + /// The image width. + private static int GetImageWidth(ExifProfile exifProfile) + { + if (!exifProfile.TryGetValue(ExifTag.ImageWidth, out IExifValue? width)) + { + TiffThrowHelper.ThrowInvalidImageContentException("The TIFF image frame is missing the ImageWidth"); + } + + DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); + + return (int)width.Value; + } + + /// + /// Gets the height of the image frame. + /// + /// The image frame exif profile. + /// The image height. + private static int GetImageHeight(ExifProfile exifProfile) + { + if (!exifProfile.TryGetValue(ExifTag.ImageLength, out IExifValue? height)) + { + TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); + } + + return (int)height.Value; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index cc70941d51..e965fcb4f6 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -180,6 +180,12 @@ public class TiffMetadata : IFormatMetadata PixelTypeInfo = this.GetPixelTypeInfo() }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs index c4a7492553..9fd730f416 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs @@ -13,8 +13,15 @@ internal abstract class TiffBaseColorWriter : IDisposable { private bool isDisposed; - protected TiffBaseColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + protected TiffBaseColorWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) { + this.Width = encodingSize.Width; + this.Height = encodingSize.Height; this.Image = image; this.MemoryAllocator = memoryAllocator; this.Configuration = configuration; @@ -26,10 +33,20 @@ internal abstract class TiffBaseColorWriter : IDisposable ///
public abstract int BitsPerPixel { get; } + /// + /// Gets the width of the portion of the image to be encoded. + /// + public int Width { get; } + + /// + /// Gets the height of the portion of the image to be encoded. + /// + public int Height { get; } + /// /// Gets the bytes per row. /// - public int BytesPerRow => (int)(((uint)(this.Image.Width * this.BitsPerPixel) + 7) / 8); + public int BytesPerRow => (int)(((uint)(this.Width * this.BitsPerPixel) + 7) / 8); protected ImageFrame Image { get; } @@ -42,18 +59,18 @@ internal abstract class TiffBaseColorWriter : IDisposable public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) { DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer"); - int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; + int stripsCount = (this.Height + rowsPerStrip - 1) / rowsPerStrip; uint[] stripOffsets = new uint[stripsCount]; uint[] stripByteCounts = new uint[stripsCount]; int stripIndex = 0; compressor.Initialize(rowsPerStrip); - for (int y = 0; y < this.Image.Height; y += rowsPerStrip) + for (int y = 0; y < this.Height; y += rowsPerStrip) { long offset = compressor.Output.Position; - int height = Math.Min(rowsPerStrip, this.Image.Height - y); + int height = Math.Min(rowsPerStrip, this.Height - y); this.EncodeStrip(y, height, compressor); long endOffset = compressor.Output.Position; diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index a6f4c31060..647ff8a1a3 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -21,11 +21,16 @@ internal sealed class TiffBiColorWriter : TiffBaseColorWriter private IMemoryOwner bitStrip; - public TiffBiColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffBiColorWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { // Convert image to black and white. - this.imageBlackWhite = new Image(configuration, new ImageMetadata(), new[] { image.Clone() }); + this.imageBlackWhite = new Image(configuration, new ImageMetadata(), [image.Clone()]); this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg)); } @@ -35,9 +40,9 @@ internal sealed class TiffBiColorWriter : TiffBaseColorWriter /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - int width = this.Image.Width; + int width = this.Width; - if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D || compressor.Method == TiffCompression.CcittGroup4Fax) + if (compressor.Method is TiffCompression.CcittGroup3Fax or TiffCompression.Ccitt1D or TiffCompression.CcittGroup4Fax) { // Special case for T4BitCompressor. int stripPixels = width * height; @@ -77,9 +82,9 @@ internal sealed class TiffBiColorWriter : TiffBaseColorWriter int bitIndex = 0; int byteIndex = 0; Span outputRow = rows[(outputRowIdx * this.BytesPerRow)..]; - Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row); + Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row)[..width]; PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); - for (int x = 0; x < this.Image.Width; x++) + for (int x = 0; x < this.Width; x++) { int shift = 7 - bitIndex; if (pixelAsGraySpan[x] == 255) diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs index 96c8aeb324..31a1b0e414 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs @@ -13,6 +13,7 @@ internal static class TiffColorWriterFactory public static TiffBaseColorWriter Create( TiffPhotometricInterpretation? photometricInterpretation, ImageFrame image, + Size encodingSize, IQuantizer quantizer, IPixelSamplingStrategy pixelSamplingStrategy, MemoryAllocator memoryAllocator, @@ -20,22 +21,15 @@ internal static class TiffColorWriterFactory TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) where TPixel : unmanaged, IPixel - { - switch (photometricInterpretation) + => photometricInterpretation switch { - case TiffPhotometricInterpretation.PaletteColor: - return new TiffPaletteWriter(image, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel); - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - return bitsPerPixel switch - { - 1 => new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector), - 16 => new TiffGrayL16Writer(image, memoryAllocator, configuration, entriesCollector), - _ => new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector) - }; - - default: - return new TiffRgbWriter(image, memoryAllocator, configuration, entriesCollector); - } - } + TiffPhotometricInterpretation.PaletteColor => new TiffPaletteWriter(image, encodingSize, quantizer, pixelSamplingStrategy, memoryAllocator, configuration, entriesCollector, bitsPerPixel), + TiffPhotometricInterpretation.BlackIsZero or TiffPhotometricInterpretation.WhiteIsZero => bitsPerPixel switch + { + 1 => new TiffBiColorWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector), + 16 => new TiffGrayL16Writer(image, encodingSize, memoryAllocator, configuration, entriesCollector), + _ => new TiffGrayWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector) + }, + _ => new TiffRgbWriter(image, encodingSize, memoryAllocator, configuration, entriesCollector), + }; } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs index 007857148a..67dde493c5 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -12,35 +12,36 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; /// /// The base class for composite color types: 8-bit gray, 24-bit RGB (4-bit gray, 16-bit (565/555) RGB, 32-bit RGB, CMYK, YCbCr). /// +/// The tpe of pixel format. internal abstract class TiffCompositeColorWriter : TiffBaseColorWriter where TPixel : unmanaged, IPixel { private IMemoryOwner rowBuffer; - protected TiffCompositeColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + protected TiffCompositeColorWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - if (this.rowBuffer == null) - { - this.rowBuffer = this.MemoryAllocator.Allocate(this.BytesPerRow * height); - } - - this.rowBuffer.Clear(); + (this.rowBuffer ??= this.MemoryAllocator.Allocate(this.BytesPerRow * height)).Clear(); Span outputRowSpan = this.rowBuffer.GetSpan()[..(this.BytesPerRow * height)]; - int width = this.Image.Width; + int width = this.Width; using IMemoryOwner stripPixelBuffer = this.MemoryAllocator.Allocate(height * width); Span stripPixels = stripPixelBuffer.GetSpan(); int lastRow = y + height; int stripPixelsRowIdx = 0; for (int row = y; row < lastRow; row++) { - Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row); + Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row)[..width]; stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); stripPixelsRowIdx++; } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs index 3e0e074e95..857f551f41 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayL16Writer{TPixel}.cs @@ -9,8 +9,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; internal sealed class TiffGrayL16Writer : TiffCompositeColorWriter where TPixel : unmanaged, IPixel { - public TiffGrayL16Writer(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffGrayL16Writer( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } @@ -18,5 +23,6 @@ internal sealed class TiffGrayL16Writer : TiffCompositeColorWriter 16; /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToL16Bytes(this.Configuration, pixels, buffer, pixels.Length); + protected override void EncodePixels(Span pixels, Span buffer) + => PixelOperations.Instance.ToL16Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs index b2a476b9aa..4a037f0d33 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs @@ -9,8 +9,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; internal sealed class TiffGrayWriter : TiffCompositeColorWriter where TPixel : unmanaged, IPixel { - public TiffGrayWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffGrayWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } @@ -18,5 +23,6 @@ internal sealed class TiffGrayWriter : TiffCompositeColorWriter public override int BitsPerPixel => 8; /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); + protected override void EncodePixels(Span pixels, Span buffer) + => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index d9a0960d9b..da66373631 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -23,13 +23,14 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter public TiffPaletteWriter( ImageFrame frame, + Size encodingSize, IQuantizer quantizer, IPixelSamplingStrategy pixelSamplingStrategy, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) - : base(frame, memoryAllocator, configuration, entriesCollector) + : base(frame, encodingSize, memoryAllocator, configuration, entriesCollector) { DebugGuard.NotNull(quantizer, nameof(quantizer)); DebugGuard.NotNull(quantizer, nameof(pixelSamplingStrategy)); @@ -49,7 +50,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter }); frameQuantizer.BuildPalette(pixelSamplingStrategy, frame); - this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + this.quantizedFrame = frameQuantizer.QuantizeFrame(frame, new Rectangle(Point.Empty, encodingSize)); this.AddColorMapTag(); } @@ -60,7 +61,7 @@ internal sealed class TiffPaletteWriter : TiffBaseColorWriter /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - int width = this.Image.Width; + int width = this.quantizedFrame.Width; if (this.BitsPerPixel == 4) { diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs index 3494b6ceae..93c46a92e4 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs @@ -9,8 +9,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers; internal sealed class TiffRgbWriter : TiffCompositeColorWriter where TPixel : unmanaged, IPixel { - public TiffRgbWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + public TiffRgbWriter( + ImageFrame image, + Size encodingSize, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + : base(image, encodingSize, memoryAllocator, configuration, entriesCollector) { } @@ -18,5 +23,6 @@ internal sealed class TiffRgbWriter : TiffCompositeColorWriter public override int BitsPerPixel => 24; /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); + protected override void EncodePixels(Span pixels, Span buffer) + => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs index 45e182d223..3865f9837f 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats.Webp; /// @@ -61,6 +63,12 @@ public class WebpFrameMetadata : IFormatFrameMetadata BlendMode = this.BlendMethod, }; + /// + public void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index 33ebbbf6dc..db57bd8f27 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -145,6 +145,12 @@ public class WebpMetadata : IFormatMetadata BackgroundColor = this.BackgroundColor }; + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } + /// IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 9a5bd4cf06..07b40a41a1 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -72,7 +72,7 @@ public abstract partial class Image : IDisposable, IConfigurationProvider /// /// Gets any metadata associated with the image. /// - public ImageMetadata Metadata { get; } + public ImageMetadata Metadata { get; private set; } /// /// Gets the size of the image in px units. @@ -185,6 +185,12 @@ public abstract partial class Image : IDisposable, IConfigurationProvider /// The . protected void UpdateSize(Size size) => this.Size = size; + /// + /// Updates the metadata of the image after mutation. + /// + /// The . + protected void UpdateMetadata(ImageMetadata metadata) => this.Metadata = metadata; + /// /// Disposes the object and frees resources for the Garbage Collector. /// diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index 3c5338fac3..fdde5019e1 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -42,7 +42,7 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable /// /// Gets the metadata of the frame. /// - public ImageFrameMetadata Metadata { get; } + public ImageFrameMetadata Metadata { get; private set; } /// public Configuration Configuration { get; } @@ -75,8 +75,14 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable where TDestinationPixel : unmanaged, IPixel; /// - /// Updates the size of the image frame. + /// Updates the size of the image frame after mutation. /// - /// The size. + /// The . protected void UpdateSize(Size size) => this.Size = size; + + /// + /// Updates the metadata of the image frame after mutation. + /// + /// The . + protected void UpdateMetadata(ImageFrameMetadata metadata) => this.Metadata = metadata; } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 3877a2920f..2287f65cd8 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -331,17 +331,29 @@ public sealed class ImageFrame : ImageFrame, IPixelSource } /// - /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// Switches the buffers used by the image and the pixel source meaning that the Image will "own" the buffer + /// from the pixelSource and the pixel source will now own the Image buffer. /// - /// The pixel source. - internal void SwapOrCopyPixelsBufferFrom(ImageFrame pixelSource) + /// The pixel source. + internal void SwapOrCopyPixelsBufferFrom(ImageFrame source) { - Guard.NotNull(pixelSource, nameof(pixelSource)); + Guard.NotNull(source, nameof(source)); - Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); + Buffer2D.SwapOrCopyContent(this.PixelBuffer, source.PixelBuffer); this.UpdateSize(this.PixelBuffer.Size()); } + /// + /// Copies the metadata from the source image. + /// + /// The metadata source. + internal void CopyMetadataFrom(ImageFrame source) + { + Guard.NotNull(source, nameof(source)); + + this.UpdateMetadata(source.Metadata); + } + /// protected override void Dispose(bool disposing) { diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 1a50229ca9..02403923d2 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -395,22 +395,42 @@ public sealed class Image : Image } /// - /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// Switches the buffers used by the image and the pixel source meaning that the Image will + /// "own" the buffer from the pixelSource and the pixel source will now own the Image buffer. /// - /// The pixel source. - internal void SwapOrCopyPixelsBuffersFrom(Image pixelSource) + /// The pixel source. + internal void SwapOrCopyPixelsBuffersFrom(Image source) { - Guard.NotNull(pixelSource, nameof(pixelSource)); + Guard.NotNull(source, nameof(source)); this.EnsureNotDisposed(); - ImageFrameCollection sourceFrames = pixelSource.Frames; + ImageFrameCollection sourceFrames = source.Frames; for (int i = 0; i < this.frames.Count; i++) { this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]); } - this.UpdateSize(pixelSource.Size); + this.UpdateSize(source.Size); + } + + /// + /// Copies the metadata from the source image. + /// + /// The metadata source. + internal void CopyMetadataFrom(Image source) + { + Guard.NotNull(source, nameof(source)); + + this.EnsureNotDisposed(); + + ImageFrameCollection sourceFrames = source.Frames; + for (int i = 0; i < this.frames.Count; i++) + { + this.frames[i].CopyMetadataFrom(sourceFrames[i]); + } + + this.UpdateMetadata(source.Metadata); } private static Size ValidateFramesAndGetSize(IEnumerable> frames) diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 9c0de1edbe..b24aa140fc 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Metadata; @@ -110,16 +111,23 @@ public sealed class ImageFrameMetadata : IDeepCloneable && this.formatMetadata.TryGetValue(this.DecodedImageFormat, out IFormatFrameMetadata? decodedMetadata)) { TFormatFrameMetadata derivedMeta = TFormatFrameMetadata.FromFormatConnectingFrameMetadata(decodedMetadata.ToFormatConnectingFrameMetadata()); - this.formatMetadata[key] = derivedMeta; + this.SetFormatMetadata(key, derivedMeta); return derivedMeta; } TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata(); - this.formatMetadata[key] = newMeta; + this.SetFormatMetadata(key, newMeta); return newMeta; } - internal void SetFormatMetadata(IImageFormat key, TFormatFrameMetadata value) + /// + /// Sets the metadata value associated with the specified key. + /// + /// The type of format metadata. + /// The type of format frame metadata. + /// The key of the value to set. + /// The value to set. + public void SetFormatMetadata(IImageFormat key, TFormatFrameMetadata value) where TFormatMetadata : class where TFormatFrameMetadata : class, IFormatFrameMetadata => this.formatMetadata[key] = value; @@ -143,4 +151,23 @@ public sealed class ImageFrameMetadata : IDeepCloneable /// Synchronizes the profiles with the current metadata. /// internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this); + + /// + /// This method is called after a process has been applied to the image frame. + /// + /// The type of pixel format. + /// The source image frame. + /// The destination image frame. + internal void AfterFrameApply(ImageFrame source, ImageFrame destination) + where TPixel : unmanaged, IPixel + { + // Always updated using the full frame dimensions. + // Individual format frame metadata will update with sub region dimensions if appropriate. + this.ExifProfile?.SyncDimensions(destination.Width, destination.Height); + + foreach (KeyValuePair meta in this.formatMetadata) + { + meta.Value.AfterFrameApply(source, destination); + } + } } diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 37557ba1dc..1961dbf192 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -230,6 +230,22 @@ public sealed class ImageMetadata : IDeepCloneable /// internal void SynchronizeProfiles() => this.ExifProfile?.Sync(this); + /// + /// This method is called after a process has been applied to the image. + /// + /// The type of pixel format. + /// The destination image. + internal void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + this.ExifProfile?.SyncDimensions(destination.Width, destination.Height); + + foreach (KeyValuePair meta in this.formatMetadata) + { + meta.Value.AfterImageApply(destination); + } + } + internal PixelTypeInfo GetDecodedPixelTypeInfo() { // None found. Check if we have a decoded format to convert from. diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index 41d3c293b6..e91a69444d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.PixelFormats; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -298,6 +299,19 @@ public sealed class ExifProfile : IDeepCloneable this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); } + internal void SyncDimensions(int width, int height) + { + if (this.TryGetValue(ExifTag.PixelXDimension, out _)) + { + this.SetValue(ExifTag.PixelXDimension, width); + } + + if (this.TryGetValue(ExifTag.PixelYDimension, out _)) + { + this.SetValue(ExifTag.PixelYDimension, height); + } + } + /// /// Synchronizes the profiles with the specified metadata. /// diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index aa000a10e7..abe32e3882 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -48,7 +48,6 @@ public abstract class CloningImageProcessor : ICloningImageProcessor clone = this.CreateTarget(); this.CheckFrameCount(this.Source, clone); - Configuration configuration = this.Configuration; this.BeforeImageApply(clone); for (int i = 0; i < this.Source.Frames.Count; i++) @@ -77,9 +76,10 @@ public abstract class CloningImageProcessor : ICloningImageProcessor)this).CloneAndExecute(); - // We now need to move the pixel data/size data from the clone to the source. + // We now need to move the pixel data/size data and any metadata from the clone to the source. this.CheckFrameCount(this.Source, clone); this.Source.SwapOrCopyPixelsBuffersFrom(clone); + this.Source.CopyMetadataFrom(clone); } finally { @@ -157,7 +157,7 @@ public abstract class CloningImageProcessor : ICloningImageProcessor[source.Frames.Count]; + ImageFrame[] destinationFrames = new ImageFrame[source.Frames.Count]; for (int i = 0; i < destinationFrames.Length; i++) { destinationFrames[i] = new ImageFrame( diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs deleted file mode 100644 index 0bb4920f0f..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms; - -/// -/// Contains helper methods for working with transforms. -/// -internal static class TransformProcessorHelpers -{ - /// - /// Updates the dimensional metadata of a transformed image - /// - /// The pixel format. - /// The image to update - public static void UpdateDimensionalMetadata(Image image) - where TPixel : unmanaged, IPixel - { - ExifProfile? profile = image.Metadata.ExifProfile; - if (profile is null) - { - return; - } - - // Only set the value if it already exists. - if (profile.TryGetValue(ExifTag.PixelXDimension, out _)) - { - profile.SetValue(ExifTag.PixelXDimension, image.Width); - } - - if (profile.TryGetValue(ExifTag.PixelYDimension, out _)) - { - profile.SetValue(ExifTag.PixelYDimension, image.Height); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs similarity index 80% rename from src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs index 0c2c29391b..bdfac00366 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs @@ -23,10 +23,17 @@ internal abstract class TransformProcessor : CloningImageProcessor + protected override void AfterFrameApply(ImageFrame source, ImageFrame destination) + { + base.AfterFrameApply(source, destination); + destination.Metadata.AfterFrameApply(source, destination); + } + /// protected override void AfterImageApply(Image destination) { - TransformProcessorHelpers.UpdateDimensionalMetadata(destination); base.AfterImageApply(destination); + destination.Metadata.AfterImageApply(destination); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 1972101164..0d59625ca7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff; @@ -292,6 +293,82 @@ public class TiffEncoderTests : TiffEncoderBaseTester Assert.Equal(expectedCompression, frameMetaData.Compression); } + [Theory] + [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] + public void TiffEncoder_EncodesMultiFrameMipMap(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + Assert.Equal(7, image.Frames.Count); + + using MemoryStream memStream = new(); + image.SaveAsTiff(memStream); + + memStream.Position = 0; + using Image output = Image.Load(memStream); + + Assert.Equal(image.Size, output.Size); + Assert.Equal(image.Frames.Count, output.Frames.Count); + + for (int i = 0; i < image.Frames.Count; i++) + { + TiffFrameMetadata inputMetadata = image.Frames[i].Metadata.GetTiffMetadata(); + TiffFrameMetadata outputMetadata = output.Frames[i].Metadata.GetTiffMetadata(); + + Assert.Equal(inputMetadata.EncodingWidth, outputMetadata.EncodingWidth); + Assert.Equal(inputMetadata.EncodingHeight, outputMetadata.EncodingHeight); + } + } + + [Theory] + [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] + public void TiffEncoder_EncodesMultiFrameMipMap_WithScaling(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + Assert.Equal(7, image.Frames.Count); + + Size size = image.Size; + + List encodedDimensions = []; + foreach (ImageFrame frame in image.Frames) + { + TiffFrameMetadata metadata = frame.Metadata.GetTiffMetadata(); + encodedDimensions.Add(new Size(metadata.EncodingWidth, metadata.EncodingHeight)); + } + + const int scale = 2; + image.Mutate(x => x.Resize(image.Width / scale, image.Height / scale)); + + using MemoryStream memStream = new(); + image.SaveAsTiff(memStream); + + memStream.Position = 0; + using Image output = Image.Load(memStream); + + Assert.Equal(image.Size, output.Size); + Assert.Equal(image.Frames.Count, output.Frames.Count); + + // The encoded dimensions should automatically be scaled down by the + // horizontal and vertical scaling factors. + float ratioX = output.Width / (float)size.Width; + float ratioY = output.Height / (float)size.Height; + + for (int i = 0; i < image.Frames.Count; i++) + { + TiffFrameMetadata inputMetadata = image.Frames[i].Metadata.GetTiffMetadata(); + TiffFrameMetadata outputMetadata = output.Frames[i].Metadata.GetTiffMetadata(); + + int expectedWidth = (int)MathF.Ceiling(encodedDimensions[i].Width * ratioX); + int expectedHeight = (int)MathF.Ceiling(encodedDimensions[i].Height * ratioY); + + Assert.Equal(expectedWidth, inputMetadata.EncodingWidth); + Assert.Equal(expectedHeight, inputMetadata.EncodingHeight); + Assert.Equal(inputMetadata.EncodingWidth, outputMetadata.EncodingWidth); + Assert.Equal(inputMetadata.EncodingHeight, outputMetadata.EncodingHeight); + } + } + // This makes sure, that when decoding a planar tiff, the planar configuration is not carried over to the encoded image. [Theory] [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs deleted file mode 100644 index 21b92a01e8..0000000000 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.ImageSharp.Tests.TestUtilities; - -namespace SixLabors.ImageSharp.Tests.Processing.Transforms; - -[Trait("Category", "Processors")] -public class TransformsHelpersTest -{ - [Fact] - public void HelperCanChangeExifDataType() - { - int xy = 1; - - using (var img = new Image(xy, xy)) - { - var profile = new ExifProfile(); - img.Metadata.ExifProfile = profile; - profile.SetValue(ExifTag.PixelXDimension, xy + ushort.MaxValue); - profile.SetValue(ExifTag.PixelYDimension, xy + ushort.MaxValue); - - Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); - Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); - - TransformProcessorHelpers.UpdateDimensionalMetadata(img); - - Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); - Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType); - } - } -}