diff --git a/README.md b/README.md index a420c07c8e..66dc0dcf43 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,9 @@ Our API is designed to be simple to consume. Here's an example of the code requi On platforms supporting netstandard 1.3+ ```csharp +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + // Image.Load(string path) is a shortcut for our default type. // Other pixel formats use Image.Load(string path)) using (Image image = Image.Load("foo.jpg")) @@ -83,6 +86,9 @@ using (Image image = Image.Load("foo.jpg")) On netstandard 1.1 - 1.2 ```csharp +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + // Image.Load(Stream stream) is a shortcut for our default type. // Other pixel formats use Image.Load(Stream stream)) using (FileStream stream = File.OpenRead("foo.jpg")) @@ -99,6 +105,9 @@ using (Image image = Image.Load(stream)) Setting individual pixel values can be performed as follows: ```csharp +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + // Individual pixels using (Image image = new Image(400, 400)) { diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs index d6dade7703..1b0f8ad095 100644 --- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs @@ -137,17 +137,6 @@ namespace SixLabors.ImageSharp return value; } - /// - /// Converts an to a first restricting the value between the - /// minimum and maximum allowable ranges. - /// - /// The this method extends. - /// The - public static byte ToByte(this int value) - { - return (byte)value.Clamp(0, 255); - } - /// /// Converts an to a first restricting the value between the /// minimum and maximum allowable ranges. diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index c54d02fa30..576f7bf3d0 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp /// Creates a shallow copy of the /// /// A new configuration instance - public Configuration ShallowCopy() + public Configuration Clone() { return new Configuration { diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs index 3d678c13e1..8b33e30fa6 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs @@ -6,13 +6,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Provides Bmp specific metadata information for the image. /// - public class BmpMetaData + public class BmpMetaData : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public BmpMetaData() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private BmpMetaData(BmpMetaData other) => this.BitsPerPixel = other.BitsPerPixel; + /// /// Gets or sets the number of bits per pixel. /// public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + /// + public IDeepCloneable DeepClone() => new BmpMetaData(this); + // TODO: Colors used once we support encoding palette bmps. } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs index cc04d48314..0042c6a108 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs @@ -6,8 +6,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Provides Gif specific metadata information for the image frame. /// - public class GifFrameMetaData + public class GifFrameMetaData : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public GifFrameMetaData() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private GifFrameMetaData(GifFrameMetaData other) + { + this.ColorTableLength = other.ColorTableLength; + this.FrameDelay = other.FrameDelay; + this.DisposalMethod = other.DisposalMethod; + } + /// /// Gets or sets the length of the color table for paletted images. /// If not 0, then this field indicates the maximum number of colors to use when quantizing the @@ -29,5 +47,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// be treated after being displayed. /// public GifDisposalMethod DisposalMethod { get; set; } + + /// + public IDeepCloneable DeepClone() => new GifFrameMetaData(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs index f58f5dff3e..bb7fb50518 100644 --- a/src/ImageSharp/Formats/Gif/GifMetaData.cs +++ b/src/ImageSharp/Formats/Gif/GifMetaData.cs @@ -6,8 +6,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Provides Gif specific metadata information for the image. /// - public class GifMetaData + public class GifMetaData : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public GifMetaData() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private GifMetaData(GifMetaData other) + { + this.RepeatCount = other.RepeatCount; + this.ColorTableMode = other.ColorTableMode; + this.GlobalColorTableLength = other.GlobalColorTableLength; + } + /// /// Gets or sets the number of times any animation is repeated. /// @@ -25,5 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Gets or sets the length of the global color table if present. /// public int GlobalColorTableLength { get; set; } + + /// + public IDeepCloneable DeepClone() => new GifMetaData(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs index bd7232e602..fcad29e5d0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs @@ -6,11 +6,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Provides Jpeg specific metadata information for the image. /// - public class JpegMetaData + public class JpegMetaData : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public JpegMetaData() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private JpegMetaData(JpegMetaData other) => this.Quality = other.Quality; + /// /// Gets or sets the encoded quality. /// public int Quality { get; set; } = 75; + + /// + public IDeepCloneable DeepClone() => new JpegMetaData(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 7837c2da5c..ef2f226556 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -187,6 +187,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private bool hasTrans; + /// + /// The next chunk of data to return + /// + private PngChunk? nextChunk; + /// /// Initializes a new instance of the class. /// @@ -217,73 +222,73 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : struct, IPixel { var metaData = new ImageMetaData(); - var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); + PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); this.currentStream = stream; this.currentStream.Skip(8); Image image = null; try { - using (var deframeStream = new ZlibInflateStream(this.currentStream)) + while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) { - while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) + try { - try + switch (chunk.Type) { - switch (chunk.Type) - { - case PngChunkType.Header: - this.ReadHeaderChunk(pngMetaData, chunk.Data.Array); - this.ValidateHeader(); - break; - case PngChunkType.Physical: - this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan()); - break; - case PngChunkType.Gamma: - this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan()); - break; - case PngChunkType.Data: - if (image is null) - { - this.InitializeImage(metaData, out image); - } + case PngChunkType.Header: + this.ReadHeaderChunk(pngMetaData, chunk.Data.Array); + this.ValidateHeader(); + break; + case PngChunkType.Physical: + this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan()); + break; + case PngChunkType.Gamma: + this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan()); + break; + case PngChunkType.Data: + if (image is null) + { + this.InitializeImage(metaData, out image); + } + using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) + { deframeStream.AllocateNewBytes(chunk.Length); this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame); - this.currentStream.Read(this.crcBuffer, 0, 4); - break; - case PngChunkType.Palette: - byte[] pal = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); - this.palette = pal; - break; - case PngChunkType.PaletteAlpha: - byte[] alpha = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); - this.paletteAlpha = alpha; - this.AssignTransparentMarkers(alpha); - break; - case PngChunkType.Text: - this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length); - break; - case PngChunkType.Exif: - if (!this.ignoreMetadata) - { - byte[] exifData = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); - metaData.ExifProfile = new ExifProfile(exifData); - } - - break; - case PngChunkType.End: - this.isEndChunkReached = true; - break; - } - } - finally - { - chunk.Data?.Dispose(); // Data is rented in ReadChunkData() + } + + break; + case PngChunkType.Palette: + byte[] pal = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); + this.palette = pal; + break; + case PngChunkType.PaletteAlpha: + byte[] alpha = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); + this.paletteAlpha = alpha; + this.AssignTransparentMarkers(alpha); + break; + case PngChunkType.Text: + this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length); + break; + case PngChunkType.Exif: + if (!this.ignoreMetadata) + { + byte[] exifData = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + metaData.ExifProfile = new ExifProfile(exifData); + } + + break; + case PngChunkType.End: + this.isEndChunkReached = true; + break; } } + finally + { + chunk.Data?.Dispose(); // Data is rented in ReadChunkData() + } } if (image is null) @@ -307,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.Png public IImageInfo Identify(Stream stream) { var metaData = new ImageMetaData(); - var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); + PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); this.currentStream = stream; this.currentStream.Skip(8); try @@ -1366,6 +1371,32 @@ namespace SixLabors.ImageSharp.Formats.Png metadata.Properties.Add(new ImageProperty(name, value)); } + /// + /// Reads the next data chunk. + /// + /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. + private int ReadNextDataChunk() + { + if (this.nextChunk != null) + { + return 0; + } + + this.currentStream.Read(this.crcBuffer, 0, 4); + + if (this.TryReadChunk(out PngChunk chunk)) + { + if (chunk.Type == PngChunkType.Data) + { + return chunk.Length; + } + + this.nextChunk = chunk; + } + + return 0; + } + /// /// Reads a chunk from the stream. /// @@ -1375,6 +1406,15 @@ namespace SixLabors.ImageSharp.Formats.Png /// private bool TryReadChunk(out PngChunk chunk) { + if (this.nextChunk != null) + { + chunk = this.nextChunk.Value; + + this.nextChunk = null; + + return true; + } + int length = this.ReadChunkLength(); if (length == -1) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 0b47c1c63e..e4d2fc510d 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Formats.Png // Collect the indexed pixel data if (quantized != null) { - this.WritePaletteChunk(stream, header, quantized); + this.WritePaletteChunk(stream, quantized); } this.WritePhysicalChunk(stream, metaData); @@ -555,30 +555,27 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The pixel format. /// The containing image data. - /// The . /// The quantized frame. - private void WritePaletteChunk(Stream stream, in PngHeader header, QuantizedFrame quantized) + private void WritePaletteChunk(Stream stream, QuantizedFrame quantized) where TPixel : struct, IPixel { // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; - byte pixelCount = palette.Length.ToByte(); - - // Get max colors for bit depth. - int colorTableLength = ImageMaths.GetColorCountForBitDepth(header.BitDepth) * 3; + int paletteLength = Math.Min(palette.Length, 256); + int colorTableLength = paletteLength * 3; Rgba32 rgba = default; bool anyAlpha = false; using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(pixelCount)) + using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) { Span colorTableSpan = colorTable.GetSpan(); Span alphaTableSpan = alphaTable.GetSpan(); Span quantizedSpan = quantized.GetPixelSpan(); - for (byte i = 0; i < pixelCount; i++) + for (int i = 0; i < paletteLength; i++) { - if (quantizedSpan.IndexOf(i) > -1) + if (quantizedSpan.IndexOf((byte)i) > -1) { int offset = i * 3; palette[i].ToRgba32(ref rgba); @@ -604,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png // Write the transparency data if (anyAlpha) { - this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount); + this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, paletteLength); } } } diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs index 1eb3cdad6a..9c76765146 100644 --- a/src/ImageSharp/Formats/Png/PngMetaData.cs +++ b/src/ImageSharp/Formats/Png/PngMetaData.cs @@ -6,8 +6,26 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Provides Png specific metadata information for the image. /// - public class PngMetaData + public class PngMetaData : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public PngMetaData() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private PngMetaData(PngMetaData other) + { + this.BitDepth = other.BitDepth; + this.ColorType = other.ColorType; + this.Gamma = other.Gamma; + } + /// /// Gets or sets the number of bits per sample or per palette index (not per pixel). /// Not all values are allowed for all values. @@ -23,5 +41,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// Gets or sets the gamma value for the image. /// public float Gamma { get; set; } + + /// + public IDeepCloneable DeepClone() => new PngMetaData(this); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 55432d60b1..a92220a595 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -43,22 +43,24 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private bool isDisposed; /// - /// Whether the crc value has been read. + /// The current data remaining to be read /// - private bool crcRead; + private int currentDataRemaining; /// - /// The current data remaining to be read + /// Delegate to get more data once we've exhausted the current data remaining /// - private int currentDataRemaining; + private Func getData; /// /// Initializes a new instance of the class. /// /// The inner raw stream - public ZlibInflateStream(Stream innerStream) + /// A delegate to get more data from the inner stream + public ZlibInflateStream(Stream innerStream, Func getData) { this.innerStream = innerStream; + this.getData = getData; } /// @@ -112,12 +114,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { if (this.currentDataRemaining == 0) { - return 0; + // last buffer was read in its entirety, let's make sure we don't actually have more + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining == 0) + { + return 0; + } } int bytesToRead = Math.Min(count, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; - return this.innerStream.Read(buffer, offset, bytesToRead); + int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + + // keep reading data until we've reached the end of the stream or filled the buffer + while (this.currentDataRemaining == 0 && bytesRead < count) + { + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining == 0) + { + return bytesRead; + } + + offset += bytesRead; + bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + bytesRead += this.innerStream.Read(buffer, offset, bytesToRead); + } + + return bytesRead; } /// @@ -153,14 +179,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.compressedStream.Dispose(); this.compressedStream = null; - - if (!this.crcRead) - { - // Consume the trailing 4 bytes - this.innerStream.Read(ChecksumBuffer, 0, 4); - this.currentDataRemaining -= 4; - this.crcRead = true; - } } } diff --git a/src/ImageSharp/IDeepCloneable.cs b/src/ImageSharp/IDeepCloneable.cs new file mode 100644 index 0000000000..f80247a5d0 --- /dev/null +++ b/src/ImageSharp/IDeepCloneable.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp +{ + /// + /// A generic interface for a deeply cloneable type. + /// + /// The type of object to clone. + public interface IDeepCloneable + where T : class + { + /// + /// Creates a new that is a deep copy of the current instance. + /// + /// The . + T DeepClone(); + } + + /// + /// An interface for objects that can be cloned. This creates a deep copy of the object. + /// + public interface IDeepCloneable + { + /// + /// Creates a new object that is a deep copy of the current instance. + /// + /// The . + IDeepCloneable DeepClone(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 59571ce92e..bbe05ce435 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp public ImageFrame InsertFrame(int index, ImageFrame source) { this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); this.frames.Insert(index, clonedFrame); return clonedFrame; } @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp public ImageFrame AddFrame(ImageFrame source) { this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); this.frames.Add(clonedFrame); return clonedFrame; } @@ -155,10 +155,7 @@ namespace SixLabors.ImageSharp /// /// true if the contains the specified frame; otherwise, false. /// - public bool Contains(ImageFrame frame) - { - return this.frames.Contains(frame); - } + public bool Contains(ImageFrame frame) => this.frames.Contains(frame); /// /// Moves an from to . @@ -195,7 +192,7 @@ namespace SixLabors.ImageSharp this.frames.Remove(frame); - return new Image(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { frame }); + return new Image(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { frame }); } /// @@ -208,7 +205,7 @@ namespace SixLabors.ImageSharp { ImageFrame frame = this[index]; ImageFrame clonedFrame = frame.Clone(); - return new Image(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { clonedFrame }); + return new Image(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { clonedFrame }); } /// @@ -217,10 +214,7 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame() - { - return this.CreateFrame(default); - } + public ImageFrame CreateFrame() => this.CreateFrame(default); /// /// Creates a new and appends it to the end of the collection. diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 511e8ad687..92470e2543 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -96,6 +96,10 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class wrapping an existing buffer. /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The memory source. internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource) : this(configuration, width, height, memorySource, new ImageFrameMetaData()) { @@ -104,12 +108,12 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class wrapping an existing buffer. /// - internal ImageFrame( - Configuration configuration, - int width, - int height, - MemorySource memorySource, - ImageFrameMetaData metaData) + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The memory source. + /// The meta data. + internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetaData metaData) { Guard.NotNull(configuration, nameof(configuration)); Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -136,7 +140,7 @@ namespace SixLabors.ImageSharp this.MemoryAllocator = configuration.MemoryAllocator; this.PixelBuffer = this.MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); - this.MetaData = source.MetaData.Clone(); + this.MetaData = source.MetaData.DeepClone(); } /// @@ -248,24 +252,46 @@ namespace SixLabors.ImageSharp /// public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; + /// + /// Clones the current instance. + /// + /// The + internal ImageFrame Clone() => this.Clone(this.configuration); + + /// + /// Clones the current instance. + /// + /// The configuration providing initialization code which allows extending the library. + /// The + internal ImageFrame Clone(Configuration configuration) => new ImageFrame(configuration, this); + /// /// Returns a copy of the image frame in the given pixel format. /// /// The pixel format. /// The internal ImageFrame CloneAs() + where TPixel2 : struct, IPixel => this.CloneAs(this.configuration); + + /// + /// Returns a copy of the image frame in the given pixel format. + /// + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The + internal ImageFrame CloneAs(Configuration configuration) where TPixel2 : struct, IPixel { if (typeof(TPixel2) == typeof(TPixel)) { - return this.Clone() as ImageFrame; + return this.Clone(configuration) as ImageFrame; } - var target = new ImageFrame(this.configuration, this.Width, this.Height, this.MetaData.Clone()); + var target = new ImageFrame(configuration, this.Width, this.Height, this.MetaData.DeepClone()); ParallelHelper.IterateRowsWithTempBuffer( this.Bounds(), - this.configuration, + configuration, (rows, tempRowBuffer) => { for (int y = rows.Min; y < rows.Max; y++) @@ -303,12 +329,6 @@ namespace SixLabors.ImageSharp }); } - /// - /// Clones the current instance. - /// - /// The - internal ImageFrame Clone() => new ImageFrame(this.configuration, this); - /// void IDisposable.Dispose() => this.Dispose(); } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 8bc5a40bdc..9d4c1ef0b3 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -11,7 +11,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp { @@ -23,15 +22,12 @@ namespace SixLabors.ImageSharp where TPixel : struct, IPixel { private readonly Configuration configuration; - private readonly ImageFrameCollection frames; /// /// Initializes a new instance of the class /// with the height and the width of the image. /// - /// - /// The configuration providing initialization code which allows extending the library. - /// + /// The configuration providing initialization code which allows extending the library. /// The width of the image in pixels. /// The height of the image in pixels. public Image(Configuration configuration, int width, int height) @@ -43,9 +39,7 @@ namespace SixLabors.ImageSharp /// Initializes a new instance of the class /// with the height and the width of the image. /// - /// - /// The configuration providing initialization code which allows extending the library. - /// + /// The configuration providing initialization code which allows extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The color to initialize the pixels with. @@ -69,9 +63,7 @@ namespace SixLabors.ImageSharp /// Initializes a new instance of the class /// with the height and the width of the image. /// - /// - /// The configuration providing initialization code which allows extending the library. - /// + /// The configuration providing initialization code which allows extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The images metadata. @@ -80,37 +72,41 @@ namespace SixLabors.ImageSharp this.configuration = configuration ?? Configuration.Default; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); + this.Frames = new ImageFrameCollection(this, width, height, default(TPixel)); } /// /// Initializes a new instance of the class /// wrapping an external /// + /// The configuration providing initialization code which allows extending the library. + /// The memory source. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. internal Image(Configuration configuration, MemorySource memorySource, int width, int height, ImageMetaData metadata) { this.configuration = configuration; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata; - this.frames = new ImageFrameCollection(this, width, height, memorySource); + this.Frames = new ImageFrameCollection(this, width, height, memorySource); } /// /// Initializes a new instance of the class /// with the height and the width of the image. /// - /// - /// The configuration providing initialization code which allows extending the library. - /// + /// The configuration providing initialization code which allows extending the library. /// The width of the image in pixels. /// The height of the image in pixels. /// The color to initialize the pixels with. /// The images metadata. - internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetaData metadata) { + internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetaData metadata) + { this.configuration = configuration ?? Configuration.Default; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.frames = new ImageFrameCollection(this, width, height, backgroundColor); + this.Frames = new ImageFrameCollection(this, width, height, backgroundColor); } /// @@ -126,7 +122,7 @@ namespace SixLabors.ImageSharp this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.frames = new ImageFrameCollection(this, frames); + this.Frames = new ImageFrameCollection(this, frames); } /// @@ -138,10 +134,10 @@ namespace SixLabors.ImageSharp public PixelTypeInfo PixelType { get; } /// - public int Width => this.frames.RootFrame.Width; + public int Width => this.Frames.RootFrame.Width; /// - public int Height => this.frames.RootFrame.Height; + public int Height => this.Frames.RootFrame.Height; /// public ImageMetaData MetaData { get; } @@ -149,12 +145,12 @@ namespace SixLabors.ImageSharp /// /// Gets the frames. /// - public ImageFrameCollection Frames => this.frames; + public ImageFrameCollection Frames { get; } /// /// Gets the root frame. /// - private IPixelSource PixelSource => this.frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image)); + private IPixelSource PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image)); /// /// Gets or sets the pixel at the specified position. @@ -187,16 +183,17 @@ namespace SixLabors.ImageSharp /// Clones the current image /// /// Returns a new image with all the same metadata as the original. - public Image Clone() - { - IEnumerable> clonedFrames = this.frames.Select(x => x.Clone()); - return new Image(this.configuration, this.MetaData.Clone(), clonedFrames); - } + public Image Clone() => this.Clone(this.configuration); - /// - public override string ToString() + /// + /// Clones the current image with the given configueation. + /// + /// The configuration providing initialization code which allows extending the library. + /// Returns a new with all the same pixel data as the original. + public Image Clone(Configuration configuration) { - return $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; + IEnumerable> clonedFrames = this.Frames.Select(x => x.Clone(configuration)); + return new Image(configuration, this.MetaData.DeepClone(), clonedFrames); } /// @@ -205,22 +202,27 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The public Image CloneAs() - where TPixel2 : struct, IPixel - { - IEnumerable> clonedFrames = this.frames.Select(x => x.CloneAs()); - var target = new Image(this.configuration, this.MetaData.Clone(), clonedFrames); - - return target; - } + where TPixel2 : struct, IPixel => this.CloneAs(this.configuration); /// - /// Releases managed resources. + /// Returns a copy of the image in the given pixel format. /// - public void Dispose() + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The + public Image CloneAs(Configuration configuration) + where TPixel2 : struct, IPixel { - this.frames.Dispose(); + IEnumerable> clonedFrames = this.Frames.Select(x => x.CloneAs(configuration)); + return new Image(configuration, this.MetaData.DeepClone(), clonedFrames); } + /// + public void Dispose() => this.Frames.Dispose(); + + /// + public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; + /// /// 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. /// @@ -229,9 +231,9 @@ namespace SixLabors.ImageSharp { Guard.NotNull(pixelSource, nameof(pixelSource)); - for (int i = 0; i < this.frames.Count; i++) + for (int i = 0; i < this.Frames.Count; i++) { - this.frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.frames[i]); + this.Frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.Frames[i]); } } } diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs index 4b819e2013..f1f884be68 100644 --- a/src/ImageSharp/MetaData/ImageFrameMetaData.cs +++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; @@ -10,9 +9,9 @@ namespace SixLabors.ImageSharp.MetaData /// /// Encapsulates the metadata of an image frame. /// - public sealed class ImageFrameMetaData + public sealed class ImageFrameMetaData : IDeepCloneable { - private readonly Dictionary formatMetaData = new Dictionary(); + private readonly Dictionary formatMetaData = new Dictionary(); /// /// Initializes a new instance of the class. @@ -32,17 +31,14 @@ namespace SixLabors.ImageSharp.MetaData { DebugGuard.NotNull(other, nameof(other)); - foreach (KeyValuePair meta in other.formatMetaData) + foreach (KeyValuePair meta in other.formatMetaData) { - this.formatMetaData.Add(meta.Key, meta.Value); + this.formatMetaData.Add(meta.Key, meta.Value.DeepClone()); } } - /// - /// Clones this ImageFrameMetaData. - /// - /// The cloned instance. - public ImageFrameMetaData Clone() => new ImageFrameMetaData(this); + /// + public ImageFrameMetaData DeepClone() => new ImageFrameMetaData(this); /// /// Gets the metadata value associated with the specified key. @@ -55,9 +51,9 @@ namespace SixLabors.ImageSharp.MetaData /// public TFormatFrameMetaData GetFormatMetaData(IImageFormat key) where TFormatMetaData : class - where TFormatFrameMetaData : class + where TFormatFrameMetaData : class, IDeepCloneable { - if (this.formatMetaData.TryGetValue(key, out object meta)) + if (this.formatMetaData.TryGetValue(key, out IDeepCloneable meta)) { return (TFormatFrameMetaData)meta; } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 7e74157e70..73549d98aa 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.MetaData.Profiles.Exif; @@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.MetaData /// /// Encapsulates the metadata of an image. /// - public sealed class ImageMetaData + public sealed class ImageMetaData : IDeepCloneable { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -26,7 +25,13 @@ namespace SixLabors.ImageSharp.MetaData /// public const double DefaultVerticalResolution = 96; - private readonly Dictionary formatMetaData = new Dictionary(); + /// + /// The default pixel resolution units. + /// The default value is . + /// + public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; + + private readonly Dictionary formatMetaData = new Dictionary(); private double horizontalResolution; private double verticalResolution; @@ -37,6 +42,7 @@ namespace SixLabors.ImageSharp.MetaData { this.horizontalResolution = DefaultHorizontalResolution; this.verticalResolution = DefaultVerticalResolution; + this.ResolutionUnits = DefaultPixelResolutionUnits; } /// @@ -52,9 +58,9 @@ namespace SixLabors.ImageSharp.MetaData this.VerticalResolution = other.VerticalResolution; this.ResolutionUnits = other.ResolutionUnits; - foreach (KeyValuePair meta in other.formatMetaData) + foreach (KeyValuePair meta in other.formatMetaData) { - this.formatMetaData.Add(meta.Key, meta.Value); + this.formatMetaData.Add(meta.Key, meta.Value.DeepClone()); } foreach (ImageProperty property in other.Properties) @@ -62,13 +68,8 @@ namespace SixLabors.ImageSharp.MetaData this.Properties.Add(property); } - this.ExifProfile = other.ExifProfile != null - ? new ExifProfile(other.ExifProfile) - : null; - - this.IccProfile = other.IccProfile != null - ? new IccProfile(other.IccProfile) - : null; + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); } /// @@ -114,7 +115,7 @@ namespace SixLabors.ImageSharp.MetaData /// 02 : Pixels per centimeter /// 03 : Pixels per meter /// - public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch; + public PixelResolutionUnit ResolutionUnits { get; set; } /// /// Gets or sets the Exif profile. @@ -140,9 +141,9 @@ namespace SixLabors.ImageSharp.MetaData /// The . /// public TFormatMetaData GetFormatMetaData(IImageFormat key) - where TFormatMetaData : class + where TFormatMetaData : class, IDeepCloneable { - if (this.formatMetaData.TryGetValue(key, out object meta)) + if (this.formatMetaData.TryGetValue(key, out IDeepCloneable meta)) { return (TFormatMetaData)meta; } @@ -152,11 +153,8 @@ namespace SixLabors.ImageSharp.MetaData return newMeta; } - /// - /// Clones this into a new instance - /// - /// The cloned metadata instance - public ImageMetaData Clone() => new ImageMetaData(this); + /// + public ImageMetaData DeepClone() => new ImageMetaData(this); /// /// Looks up a property with the provided name. diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 1dd8857217..b48b146f11 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -12,23 +12,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Represents an EXIF profile providing access to the collection of values. /// - public sealed class ExifProfile + public sealed class ExifProfile : IDeepCloneable { /// /// The byte array to read the EXIF profile from. /// - private byte[] data; + private readonly byte[] data; /// /// The collection of EXIF values /// private List values; - /// - /// The list of invalid EXIF tags - /// - private IReadOnlyList invalidTags; - /// /// The thumbnail offset position in the byte stream /// @@ -55,7 +50,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { this.Parts = ExifParts.All; this.data = data; - this.invalidTags = new List(); + this.InvalidTags = new List(); } /// @@ -63,22 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// by making a copy from another EXIF profile. /// /// The other EXIF profile, where the clone should be made from. - /// is null. - public ExifProfile(ExifProfile other) + private ExifProfile(ExifProfile other) { - Guard.NotNull(other, nameof(other)); - this.Parts = other.Parts; this.thumbnailLength = other.thumbnailLength; this.thumbnailOffset = other.thumbnailOffset; - this.invalidTags = new List(other.invalidTags); + this.InvalidTags = new List(other.InvalidTags); if (other.values != null) { this.values = new List(other.Values.Count); foreach (ExifValue value in other.Values) { - this.values.Add(new ExifValue(value)); + this.values.Add(value.DeepClone()); } } @@ -97,7 +89,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Gets the tags that where found but contained an invalid value. /// - public IReadOnlyList InvalidTags => this.invalidTags; + public IReadOnlyList InvalidTags { get; private set; } /// /// Gets the values of this EXIF profile. @@ -249,6 +241,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return writer.GetData(); } + /// + public ExifProfile DeepClone() => new ExifProfile(this); + /// /// Synchronizes the profiles with the specified meta data. /// @@ -294,7 +289,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif this.values = reader.ReadValues(); - this.invalidTags = new List(reader.InvalidTags); + this.InvalidTags = new List(reader.InvalidTags); this.thumbnailOffset = (int)reader.ThumbnailOffset; this.thumbnailLength = (int)reader.ThumbnailLength; } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs index e6da9b7d1e..ccacfd0bf9 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs @@ -11,15 +11,30 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Represent the value of the EXIF profile. /// - public sealed class ExifValue : IEquatable + public sealed class ExifValue : IEquatable, IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + /// The tag. + /// The data type. + /// The value. + /// Whether the value is an array. + internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) + { + this.Tag = tag; + this.DataType = dataType; + this.IsArray = isArray && dataType != ExifDataType.Ascii; + this.Value = value; + } + /// /// Initializes a new instance of the class /// by making a copy from another exif value. /// /// The other exif value, where the clone should be made from. - /// is null. - public ExifValue(ExifValue other) + /// is null. + private ExifValue(ExifValue other) { Guard.NotNull(other, nameof(other)); @@ -29,30 +44,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif if (!other.IsArray) { + // All types are value types except for string which is immutable so safe to simply assign. this.Value = other.Value; } else { + // All array types are value types so Clone() is sufficient here. var array = (Array)other.Value; this.Value = array.Clone(); } } - /// - /// Initializes a new instance of the class. - /// - /// The tag. - /// The data type. - /// The value. - /// Whether the value is an array. - internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) - { - this.Tag = tag; - this.DataType = dataType; - this.IsArray = isArray && dataType != ExifDataType.Ascii; - this.Value = value; - } - /// /// Gets the data type of the exif value. /// @@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// True if the parameter is equal to the parameter; otherwise, false. /// - public static bool operator ==(ExifValue left, ExifValue right) - { - return ReferenceEquals(left, right) || left.Equals(right); - } + public static bool operator ==(ExifValue left, ExifValue right) => ReferenceEquals(left, right) || left.Equals(right); /// /// Compares two objects for equality. @@ -162,16 +161,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// True if the parameter is not equal to the parameter; otherwise, false. /// - public static bool operator !=(ExifValue left, ExifValue right) - { - return !(left == right); - } + public static bool operator !=(ExifValue left, ExifValue right) => !(left == right); /// - public override bool Equals(object obj) - { - return obj is ExifValue other && this.Equals(other); - } + public override bool Equals(object obj) => obj is ExifValue other && this.Equals(other); /// public bool Equals(ExifValue other) @@ -187,9 +180,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } return - this.Tag == other.Tag && - this.DataType == other.DataType && - object.Equals(this.Value, other.Value); + this.Tag == other.Tag + && this.DataType == other.DataType + && object.Equals(this.Value, other.Value); } /// @@ -205,10 +198,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } + public override int GetHashCode() => this.GetHashCode(this); /// public override string ToString() @@ -238,6 +228,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return sb.ToString(); } + /// + public ExifValue DeepClone() => new ExifValue(this); + /// /// Creates a new /// @@ -584,7 +577,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray) { Type type = value?.GetType(); - if (type != null && type.IsArray) + if (type?.IsArray == true) { type = type.GetElementType(); } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index dac56c608e..44990b7ecc 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Represents an ICC profile /// - public sealed class IccProfile + public sealed class IccProfile : IDeepCloneable { /// /// The byte array to read the ICC profile from @@ -42,23 +42,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// Initializes a new instance of the class. /// /// The raw ICC profile data - public IccProfile(byte[] data) - { - this.data = data; - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another ICC profile. - /// - /// The other ICC profile, where the clone should be made from. - /// is null.> - public IccProfile(IccProfile other) - { - Guard.NotNull(other, nameof(other)); - - this.data = other.ToByteArray(); - } + public IccProfile(byte[] data) => this.data = data; /// /// Initializes a new instance of the class. @@ -74,6 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc this.entries = new List(entries); } + /// + /// Initializes a new instance of the class + /// by making a copy from another ICC profile. + /// + /// The other ICC profile, where the clone should be made from. + /// is null.> + private IccProfile(IccProfile other) + { + Guard.NotNull(other, nameof(other)); + + this.data = other.ToByteArray(); + } + /// /// Gets or sets the profile header /// @@ -100,6 +97,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } } + /// + public IccProfile DeepClone() => new IccProfile(this); + #if !NETSTANDARD1_1 /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index d71221b9d6..13bc057da8 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : struct, IPixel { // TODO: The WuFrameQuantizer code is rising several questions: - // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) + // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) JS. I'm afraid so. // - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? // (T, R, G, B, A, M2) could be grouped together! // - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them! @@ -46,12 +46,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The index bits. /// - private const int IndexBits = 6; + private const int IndexBits = 5; /// - /// The index alpha bits. + /// The index alpha bits. Keep separate for now to allow easy adjustment. /// - private const int IndexAlphaBits = 3; + private const int IndexAlphaBits = 5; /// /// The index count. @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; /// - /// The table length. + /// The table length. Now 1185921. /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; @@ -179,18 +179,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (this.palette is null) { this.palette = new TPixel[this.colors]; + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + for (int k = 0; k < this.colors; k++) { this.Mark(ref this.colorCube[k], (byte)k); - float weight = Volume(ref this.colorCube[k], this.vwt.GetSpan()); + float weight = Volume(ref this.colorCube[k], vwtSpan); if (MathF.Abs(weight) > Constants.Epsilon) { - float r = Volume(ref this.colorCube[k], this.vmr.GetSpan()); - float g = Volume(ref this.colorCube[k], this.vmg.GetSpan()); - float b = Volume(ref this.colorCube[k], this.vmb.GetSpan()); - float a = Volume(ref this.colorCube[k], this.vma.GetSpan()); + float r = Volume(ref this.colorCube[k], vmrSpan); + float g = Volume(ref this.colorCube[k], vmgSpan); + float b = Volume(ref this.colorCube[k], vmbSpan); + float a = Volume(ref this.colorCube[k], vmaSpan); ref TPixel color = ref this.palette[k]; color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F); @@ -201,57 +207,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return this.palette; } - /// - /// Quantizes the pixel - /// - /// The rgba used to quantize the pixel input - private void QuantizePixel(ref Rgba32 rgba) - { - // Add the color to a 3-D color histogram. - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); - - int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.GetSpan(); - - vwtSpan[index]++; - vmrSpan[index] += rgba.R; - vmgSpan[index] += rgba.G; - vmbSpan[index] += rgba.B; - vmaSpan[index] += rgba.A; - - var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); - m2Span[index] += Vector4.Dot(vector, vector); - } - /// protected override void FirstPass(ImageFrame source, int width, int height) { - // Build up the 3-D color histogram - // Loop through each row - for (int y = 0; y < height; y++) - { - Span row = source.GetPixelRowSpan(y); - ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); - - // And loop through each column - Rgba32 rgba = default; - for (int x = 0; x < width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); - pixel.ToRgba32(ref rgba); - this.QuantizePixel(ref rgba); - } - } - + this.Build3DHistogram(source, width, height); this.Get3DMoments(source.MemoryAllocator); this.BuildCube(); } @@ -466,6 +425,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The source data. + /// The width in pixels of the image. + /// The height in pixels of the image. + private void Build3DHistogram(ImageFrame source, int width, int height) + { + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + Span m2Span = this.m2.GetSpan(); + + // Build up the 3-D color histogram + // Loop through each row + for (int y = 0; y < height; y++) + { + Span row = source.GetPixelRowSpan(y); + ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); + + // And loop through each column + Rgba32 rgba = default; + for (int x = 0; x < width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); + pixel.ToRgba32(ref rgba); + + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); + + int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + + vwtSpan[index]++; + vmrSpan[index] += rgba.R; + vmgSpan[index] += rgba.G; + vmbSpan[index] += rgba.B; + vmaSpan[index] += rgba.A; + + var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); + m2Span[index] += Vector4.Dot(vector, vector); + } + } + } + /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// @@ -631,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) { - long baseR = Bottom(ref cube, direction, this.vmr.GetSpan()); - long baseG = Bottom(ref cube, direction, this.vmg.GetSpan()); - long baseB = Bottom(ref cube, direction, this.vmb.GetSpan()); - long baseA = Bottom(ref cube, direction, this.vma.GetSpan()); - long baseW = Bottom(ref cube, direction, this.vwt.GetSpan()); + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + + long baseR = Bottom(ref cube, direction, vmrSpan); + long baseG = Bottom(ref cube, direction, vmgSpan); + long baseB = Bottom(ref cube, direction, vmbSpan); + long baseA = Bottom(ref cube, direction, vmaSpan); + long baseW = Bottom(ref cube, direction, vwtSpan); float max = 0F; cut = -1; for (int i = first; i < last; i++) { - float halfR = baseR + Top(ref cube, direction, i, this.vmr.GetSpan()); - float halfG = baseG + Top(ref cube, direction, i, this.vmg.GetSpan()); - float halfB = baseB + Top(ref cube, direction, i, this.vmb.GetSpan()); - float halfA = baseA + Top(ref cube, direction, i, this.vma.GetSpan()); - float halfW = baseW + Top(ref cube, direction, i, this.vwt.GetSpan()); + float halfR = baseR + Top(ref cube, direction, i, vmrSpan); + float halfG = baseG + Top(ref cube, direction, i, vmgSpan); + float halfB = baseB + Top(ref cube, direction, i, vmbSpan); + float halfA = baseA + Top(ref cube, direction, i, vmaSpan); + float halfW = baseW + Top(ref cube, direction, i, vwtSpan); if (MathF.Abs(halfW) < Constants.Epsilon) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index b72b81a209..3469161e6d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -52,10 +52,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // We will always be creating the clone even for mutate because we may need to resize the canvas IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index bb26216ece..8e6a826fd3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -36,10 +36,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.MetaData.Clone())); + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 4c789ef026..b03dec032f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -52,10 +52,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // We will always be creating the clone even for mutate because we may need to resize the canvas IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index aa4c3ada28..76abc64996 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -216,10 +216,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.Width, this.Height, x.MetaData.Clone())); + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.Width, this.Height, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 6a8479b2b2..963d674466 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests { // the shallow copy of configuration should behave exactly like the default configuration, // so by using the copy, we test both the default and the copy. - this.DefaultConfiguration = Configuration.CreateDefaultInstance().ShallowCopy(); + this.DefaultConfiguration = Configuration.CreateDefaultInstance().Clone(); this.ConfigurationEmpty = new Configuration(); } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs new file mode 100644 index 0000000000..991768a11a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Bmp +{ + public class BmpMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new BmpMetaData() { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; + var clone = (BmpMetaData)meta.DeepClone(); + + clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; + + Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs new file mode 100644 index 0000000000..a39fc47b40 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifFrameMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new GifFrameMetaData() + { + FrameDelay = 1, + DisposalMethod = GifDisposalMethod.RestoreToBackground, + ColorTableLength = 2 + }; + + var clone = (GifFrameMetaData)meta.DeepClone(); + + clone.FrameDelay = 2; + clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; + clone.ColorTableLength = 1; + + Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); + Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); + Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs new file mode 100644 index 0000000000..29db32b4ab --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new GifMetaData() + { + RepeatCount = 1, + ColorTableMode = GifColorTableMode.Global, + GlobalColorTableLength = 2 + }; + + var clone = (GifMetaData)meta.DeepClone(); + + clone.RepeatCount = 2; + clone.ColorTableMode = GifColorTableMode.Local; + clone.GlobalColorTableLength = 1; + + Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); + Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); + Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs new file mode 100644 index 0000000000..431de4be31 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class JpegMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new JpegMetaData() { Quality = 50 }; + var clone = (JpegMetaData)meta.DeepClone(); + + clone.Quality = 99; + + Assert.False(meta.Quality.Equals(clone.Quality)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs new file mode 100644 index 0000000000..a21bb9acbe --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Png; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class PngMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new PngMetaData() + { + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.GrayscaleWithAlpha, + Gamma = 2 + }; + var clone = (PngMetaData)meta.DeepClone(); + + clone.BitDepth = PngBitDepth.Bit2; + clone.ColorType = PngColorType.Palette; + clone.Gamma = 1; + + Assert.False(meta.BitDepth.Equals(clone.BitDepth)); + Assert.False(meta.ColorType.Equals(clone.ColorType)); + Assert.False(meta.Gamma.Equals(clone.Gamma)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 69572425c9..16d999ad25 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WrapMemory_CreatedImageIsCorrect() { - Configuration cfg = Configuration.Default.ShallowCopy(); + Configuration cfg = Configuration.Default.Clone(); var metaData = new ImageMetaData(); var array = new Rgba32[25]; diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index ed142ed974..f3c04d5e14 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Width_Height() { - Configuration configuration = Configuration.Default.ShallowCopy(); + Configuration configuration = Configuration.Default.Clone(); using (var image = new Image(configuration, 11, 23)) { @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Width_Height_BackroundColor() { - Configuration configuration = Configuration.Default.ShallowCopy(); + Configuration configuration = Configuration.Default.Clone(); Rgba32 color = Rgba32.Aquamarine; using (var image = new Image(configuration, 11, 23, color)) diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs index 0a0ca1efa4..8c49039603 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs @@ -32,5 +32,13 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(colorTableLength, cloneGifFrameMetaData.ColorTableLength); Assert.Equal(disposalMethod, cloneGifFrameMetaData.DisposalMethod); } + + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageFrameMetaData(); + ImageFrameMetaData clone = metaData.DeepClone(); + Assert.False(metaData.GetFormatMetaData(GifFormat.Instance).Equals(clone.GetFormatMetaData(GifFormat.Instance))); + } } } diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index d681c90ba2..b9619cb3f8 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests metaData.VerticalResolution = 2; metaData.Properties.Add(imageProperty); - ImageMetaData clone = metaData.Clone(); + ImageMetaData clone = metaData.DeepClone(); Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); Assert.Equal(4, clone.HorizontalResolution); @@ -37,19 +37,43 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(imageProperty, clone.Properties[0]); } + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageMetaData(); + + var exifProfile = new ExifProfile(); + var imageProperty = new ImageProperty("name", "value"); + + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; + metaData.Properties.Add(imageProperty); + + ImageMetaData clone = metaData.DeepClone(); + clone.HorizontalResolution = 2; + clone.VerticalResolution = 4; + + Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); + Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution)); + Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution)); + Assert.False(metaData.Properties.Equals(clone.Properties)); + Assert.False(metaData.GetFormatMetaData(GifFormat.Instance).Equals(clone.GetFormatMetaData(GifFormat.Instance))); + } + [Fact] public void HorizontalResolution() { var metaData = new ImageMetaData(); Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution=0; + metaData.HorizontalResolution = 0; Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution=-1; + metaData.HorizontalResolution = -1; Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution=1; + metaData.HorizontalResolution = 1; Assert.Equal(1, metaData.HorizontalResolution); } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 3deb382ea6..c10ffb6c8e 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -67,16 +67,16 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ConstructorCopy() { - Assert.Throws(() => { new ExifProfile((ExifProfile)null); }); + Assert.Throws(() => ((ExifProfile)null).DeepClone()); ExifProfile profile = GetExifProfile(); - var clone = new ExifProfile(profile); + ExifProfile clone = profile.DeepClone(); TestProfile(clone); profile.SetValue(ExifTag.ColorSpace, (ushort)2); - clone = new ExifProfile(profile); + clone = profile.DeepClone(); TestProfile(clone); } @@ -234,10 +234,12 @@ namespace SixLabors.ImageSharp.Tests exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - var metaData = new ImageMetaData(); - metaData.ExifProfile = exifProfile; - metaData.HorizontalResolution = 200; - metaData.VerticalResolution = 300; + var metaData = new ImageMetaData + { + ExifProfile = exifProfile, + HorizontalResolution = 200, + VerticalResolution = 300 + }; metaData.HorizontalResolution = 100; @@ -355,11 +357,11 @@ namespace SixLabors.ImageSharp.Tests // act Image reloadedImage = WriteAndRead(image, imageFormat); - + // assert ExifProfile actual = reloadedImage.MetaData.ExifProfile; Assert.NotNull(actual); - foreach(KeyValuePair expectedProfileValue in TestProfileValues) + foreach (KeyValuePair expectedProfileValue in TestProfileValues) { ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key); Assert.NotNull(actualProfileValue); @@ -371,7 +373,7 @@ namespace SixLabors.ImageSharp.Tests public void ProfileToByteArray() { // arrange - byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); + byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; ExifProfile expectedProfile = CreateExifProfile(); var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); @@ -384,7 +386,7 @@ namespace SixLabors.ImageSharp.Tests Assert.NotNull(actualBytes); Assert.NotEmpty(actualBytes); Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); - foreach(ExifTag expectedProfileTag in expectedProfileTags) + foreach (ExifTag expectedProfileTag in expectedProfileTags) { ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); @@ -396,7 +398,7 @@ namespace SixLabors.ImageSharp.Tests { var profile = new ExifProfile(); - foreach(KeyValuePair exifProfileValue in TestProfileValues) + foreach (KeyValuePair exifProfileValue in TestProfileValues) { profile.SetValue(exifProfileValue.Key, exifProfileValue.Value); } @@ -416,7 +418,7 @@ namespace SixLabors.ImageSharp.Tests private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) { - switch(imageFormat) + switch (imageFormat) { case TestImageWriteFormat.Jpeg: return WriteAndReadJpeg(image); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index ab0cc42f93..30ac0856c7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests public virtual string SourceFileOrDescription => ""; - public Configuration Configuration { get; set; } = Configuration.Default.ShallowCopy(); + public Configuration Configuration { get; set; } = Configuration.Default.Clone(); /// /// Utility instance to provide informations about the test image & manage input/output diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index 724c2e4144..033f0866a3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests private ITestOutputHelper Output { get; } public const string SkipBenchmarks = -#if false +#if true "Benchmark, enable manually!"; #else null;