Browse Source

Merge remote-tracking branch 'origin/master' into af/improved-parallelization

# Conflicts:
#	src/ImageSharp/ImageFrame{TPixel}.cs
af/merge-core
Anton Firszov 8 years ago
parent
commit
18487030ae
  1. 9
      README.md
  2. 11
      src/ImageSharp/Common/Extensions/ComparableExtensions.cs
  3. 2
      src/ImageSharp/Configuration.cs
  4. 20
      src/ImageSharp/Formats/Bmp/BmpMetaData.cs
  5. 23
      src/ImageSharp/Formats/Gif/GifFrameMetaData.cs
  6. 23
      src/ImageSharp/Formats/Gif/GifMetaData.cs
  7. 18
      src/ImageSharp/Formats/Jpeg/JpegMetaData.cs
  8. 150
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  9. 19
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  10. 23
      src/ImageSharp/Formats/Png/PngMetaData.cs
  11. 48
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
  12. 31
      src/ImageSharp/IDeepCloneable.cs
  13. 18
      src/ImageSharp/ImageFrameCollection.cs
  14. 52
      src/ImageSharp/ImageFrame{TPixel}.cs
  15. 88
      src/ImageSharp/Image{TPixel}.cs
  16. 20
      src/ImageSharp/MetaData/ImageFrameMetaData.cs
  17. 38
      src/ImageSharp/MetaData/ImageMetaData.cs
  18. 27
      src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
  19. 69
      src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs
  20. 36
      src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
  21. 149
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  22. 4
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  23. 4
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  24. 4
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  25. 4
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  26. 2
      tests/ImageSharp.Tests/ConfigurationTests.cs
  27. 22
      tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs
  28. 32
      tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs
  29. 32
      tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs
  30. 22
      tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs
  31. 31
      tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs
  32. 2
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  33. 4
      tests/ImageSharp.Tests/Image/ImageTests.cs
  34. 8
      tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs
  35. 34
      tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
  36. 28
      tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
  37. 2
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  38. 2
      tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs

9
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<TPixel>(string path))
using (Image<Rgba32> image = Image.Load("foo.jpg"))
@ -83,6 +86,9 @@ using (Image<Rgba32> 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<TPixel>(Stream stream))
using (FileStream stream = File.OpenRead("foo.jpg"))
@ -99,6 +105,9 @@ using (Image<Rgba32> 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<Rgba32> image = new Image<Rgba32>(400, 400))
{

11
src/ImageSharp/Common/Extensions/ComparableExtensions.cs

@ -137,17 +137,6 @@ namespace SixLabors.ImageSharp
return value;
}
/// <summary>
/// Converts an <see cref="int"/> to a <see cref="byte"/> first restricting the value between the
/// minimum and maximum allowable ranges.
/// </summary>
/// <param name="value">The <see cref="int"/> this method extends.</param>
/// <returns>The <see cref="byte"/></returns>
public static byte ToByte(this int value)
{
return (byte)value.Clamp(0, 255);
}
/// <summary>
/// Converts an <see cref="float"/> to a <see cref="byte"/> first restricting the value between the
/// minimum and maximum allowable ranges.

2
src/ImageSharp/Configuration.cs

@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp
/// Creates a shallow copy of the <see cref="Configuration"/>
/// </summary>
/// <returns>A new configuration instance</returns>
public Configuration ShallowCopy()
public Configuration Clone()
{
return new Configuration
{

20
src/ImageSharp/Formats/Bmp/BmpMetaData.cs

@ -6,13 +6,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Provides Bmp specific metadata information for the image.
/// </summary>
public class BmpMetaData
public class BmpMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="BmpMetaData"/> class.
/// </summary>
public BmpMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BmpMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private BmpMetaData(BmpMetaData other) => this.BitsPerPixel = other.BitsPerPixel;
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new BmpMetaData(this);
// TODO: Colors used once we support encoding palette bmps.
}
}
}

23
src/ImageSharp/Formats/Gif/GifFrameMetaData.cs

@ -6,8 +6,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Provides Gif specific metadata information for the image frame.
/// </summary>
public class GifFrameMetaData
public class GifFrameMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="GifFrameMetaData"/> class.
/// </summary>
public GifFrameMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifFrameMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private GifFrameMetaData(GifFrameMetaData other)
{
this.ColorTableLength = other.ColorTableLength;
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
}
/// <summary>
/// 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.
/// </summary>
public GifDisposalMethod DisposalMethod { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new GifFrameMetaData(this);
}
}

23
src/ImageSharp/Formats/Gif/GifMetaData.cs

@ -6,8 +6,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Provides Gif specific metadata information for the image.
/// </summary>
public class GifMetaData
public class GifMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="GifMetaData"/> class.
/// </summary>
public GifMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private GifMetaData(GifMetaData other)
{
this.RepeatCount = other.RepeatCount;
this.ColorTableMode = other.ColorTableMode;
this.GlobalColorTableLength = other.GlobalColorTableLength;
}
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>
@ -25,5 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets or sets the length of the global color table if present.
/// </summary>
public int GlobalColorTableLength { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new GifMetaData(this);
}
}

18
src/ImageSharp/Formats/Jpeg/JpegMetaData.cs

@ -6,11 +6,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Provides Jpeg specific metadata information for the image.
/// </summary>
public class JpegMetaData
public class JpegMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetaData"/> class.
/// </summary>
public JpegMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private JpegMetaData(JpegMetaData other) => this.Quality = other.Quality;
/// <summary>
/// Gets or sets the encoded quality.
/// </summary>
public int Quality { get; set; } = 75;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new JpegMetaData(this);
}
}

150
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -187,6 +187,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private bool hasTrans;
/// <summary>
/// The next chunk of data to return
/// </summary>
private PngChunk? nextChunk;
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary>
@ -217,73 +222,73 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : struct, IPixel<TPixel>
{
var metaData = new ImageMetaData();
var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream;
this.currentStream.Skip(8);
Image<TPixel> 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));
}
/// <summary>
/// Reads the next data chunk.
/// </summary>
/// <returns>Count of bytes in the next data chunk, or 0 if there are no more data chunks left.</returns>
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;
}
/// <summary>
/// Reads a chunk from the stream.
/// </summary>
@ -1375,6 +1406,15 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </returns>
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)

19
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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
/// <param name="quantized">The quantized frame.</param>
private void WritePaletteChunk<TPixel>(Stream stream, in PngHeader header, QuantizedFrame<TPixel> quantized)
private void WritePaletteChunk<TPixel>(Stream stream, QuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// 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<byte> colorTableSpan = colorTable.GetSpan();
Span<byte> alphaTableSpan = alphaTable.GetSpan();
Span<byte> 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);
}
}
}

23
src/ImageSharp/Formats/Png/PngMetaData.cs

@ -6,8 +6,26 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Provides Png specific metadata information for the image.
/// </summary>
public class PngMetaData
public class PngMetaData : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="PngMetaData"/> class.
/// </summary>
public PngMetaData()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PngMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private PngMetaData(PngMetaData other)
{
this.BitDepth = other.BitDepth;
this.ColorType = other.ColorType;
this.Gamma = other.Gamma;
}
/// <summary>
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values.
@ -23,5 +41,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets or sets the gamma value for the image.
/// </summary>
public float Gamma { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PngMetaData(this);
}
}

48
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

@ -43,22 +43,24 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private bool isDisposed;
/// <summary>
/// Whether the crc value has been read.
/// The current data remaining to be read
/// </summary>
private bool crcRead;
private int currentDataRemaining;
/// <summary>
/// The current data remaining to be read
/// Delegate to get more data once we've exhausted the current data remaining
/// </summary>
private int currentDataRemaining;
private Func<int> getData;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary>
/// <param name="innerStream">The inner raw stream</param>
public ZlibInflateStream(Stream innerStream)
/// <param name="getData">A delegate to get more data from the inner stream</param>
public ZlibInflateStream(Stream innerStream, Func<int> getData)
{
this.innerStream = innerStream;
this.getData = getData;
}
/// <inheritdoc/>
@ -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;
}
/// <inheritdoc/>
@ -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;
}
}
}

31
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
{
/// <summary>
/// A generic interface for a deeply cloneable type.
/// </summary>
/// <typeparam name="T">The type of object to clone.</typeparam>
public interface IDeepCloneable<out T>
where T : class
{
/// <summary>
/// Creates a new <typeparamref name="T"/> that is a deep copy of the current instance.
/// </summary>
/// <returns>The <typeparamref name="T"/>.</returns>
T DeepClone();
}
/// <summary>
/// An interface for objects that can be cloned. This creates a deep copy of the object.
/// </summary>
public interface IDeepCloneable
{
/// <summary>
/// Creates a new object that is a deep copy of the current instance.
/// </summary>
/// <returns>The <see cref="IDeepCloneable"/>.</returns>
IDeepCloneable DeepClone();
}
}

18
src/ImageSharp/ImageFrameCollection.cs

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp
public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source)
{
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone();
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Insert(index, clonedFrame);
return clonedFrame;
}
@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source)
{
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone();
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Add(clonedFrame);
return clonedFrame;
}
@ -155,10 +155,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// <c>true</c> if the <seealso cref="ImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
/// </returns>
public bool Contains(ImageFrame<TPixel> frame)
{
return this.frames.Contains(frame);
}
public bool Contains(ImageFrame<TPixel> frame) => this.frames.Contains(frame);
/// <summary>
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>.
@ -195,7 +192,7 @@ namespace SixLabors.ImageSharp
this.frames.Remove(frame);
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { frame });
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { frame });
}
/// <summary>
@ -208,7 +205,7 @@ namespace SixLabors.ImageSharp
{
ImageFrame<TPixel> frame = this[index];
ImageFrame<TPixel> clonedFrame = frame.Clone();
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { clonedFrame });
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { clonedFrame });
}
/// <summary>
@ -217,10 +214,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The new <see cref="ImageFrame{TPixel}" />.
/// </returns>
public ImageFrame<TPixel> CreateFrame()
{
return this.CreateFrame(default);
}
public ImageFrame<TPixel> CreateFrame() => this.CreateFrame(default);
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.

52
src/ImageSharp/ImageFrame{TPixel}.cs

@ -96,6 +96,10 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource)
: this(configuration, width, height, memorySource, new ImageFrameMetaData())
{
@ -104,12 +108,12 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary>
internal ImageFrame(
Configuration configuration,
int width,
int height,
MemorySource<TPixel> memorySource,
ImageFrameMetaData metaData)
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="metaData">The meta data.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> 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<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height);
source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan());
this.MetaData = source.MetaData.Clone();
this.MetaData = source.MetaData.DeepClone();
}
/// <summary>
@ -248,24 +252,46 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <summary>
/// Clones the current instance.
/// </summary>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone() => this.Clone(this.configuration);
/// <summary>
/// Clones the current instance.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone(Configuration configuration) => new ImageFrame<TPixel>(configuration, this);
/// <summary>
/// Returns a copy of the image frame in the given pixel format.
/// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="ImageFrame{TPixel2}"/></returns>
internal ImageFrame<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.configuration);
/// <summary>
/// Returns a copy of the image frame in the given pixel format.
/// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="ImageFrame{TPixel2}"/></returns>
internal ImageFrame<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>
{
if (typeof(TPixel2) == typeof(TPixel))
{
return this.Clone() as ImageFrame<TPixel2>;
return this.Clone(configuration) as ImageFrame<TPixel2>;
}
var target = new ImageFrame<TPixel2>(this.configuration, this.Width, this.Height, this.MetaData.Clone());
var target = new ImageFrame<TPixel2>(configuration, this.Width, this.Height, this.MetaData.DeepClone());
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
this.Bounds(),
this.configuration,
configuration,
(rows, tempRowBuffer) =>
{
for (int y = rows.Min; y < rows.Max; y++)
@ -303,12 +329,6 @@ namespace SixLabors.ImageSharp
});
}
/// <summary>
/// Clones the current instance.
/// </summary>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone() => new ImageFrame<TPixel>(this.configuration, this);
/// <inheritdoc/>
void IDisposable.Dispose() => this.Dispose();
}

88
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<TPixel>
{
private readonly Configuration configuration;
private readonly ImageFrameCollection<TPixel> frames;
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
public Image(Configuration configuration, int width, int height)
@ -43,9 +39,7 @@ namespace SixLabors.ImageSharp
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
@ -69,9 +63,7 @@ namespace SixLabors.ImageSharp
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
@ -80,37 +72,41 @@ namespace SixLabors.ImageSharp
this.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// wrapping an external <see cref="MemorySource{T}"/>
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(Configuration configuration, MemorySource<TPixel> memorySource, int width, int height, ImageMetaData metadata)
{
this.configuration = configuration;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata;
this.frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
/// <param name="metadata">The images metadata.</param>
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<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
}
/// <summary>
@ -126,7 +122,7 @@ namespace SixLabors.ImageSharp
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, frames);
this.Frames = new ImageFrameCollection<TPixel>(this, frames);
}
/// <summary>
@ -138,10 +134,10 @@ namespace SixLabors.ImageSharp
public PixelTypeInfo PixelType { get; }
/// <inheritdoc/>
public int Width => this.frames.RootFrame.Width;
public int Width => this.Frames.RootFrame.Width;
/// <inheritdoc/>
public int Height => this.frames.RootFrame.Height;
public int Height => this.Frames.RootFrame.Height;
/// <inheritdoc/>
public ImageMetaData MetaData { get; }
@ -149,12 +145,12 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets the frames.
/// </summary>
public ImageFrameCollection<TPixel> Frames => this.frames;
public ImageFrameCollection<TPixel> Frames { get; }
/// <summary>
/// Gets the root frame.
/// </summary>
private IPixelSource<TPixel> PixelSource => this.frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image<TPixel>));
private IPixelSource<TPixel> PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image<TPixel>));
/// <summary>
/// Gets or sets the pixel at the specified position.
@ -187,16 +183,17 @@ namespace SixLabors.ImageSharp
/// Clones the current image
/// </summary>
/// <returns>Returns a new image with all the same metadata as the original.</returns>
public Image<TPixel> Clone()
{
IEnumerable<ImageFrame<TPixel>> clonedFrames = this.frames.Select(x => x.Clone());
return new Image<TPixel>(this.configuration, this.MetaData.Clone(), clonedFrames);
}
public Image<TPixel> Clone() => this.Clone(this.configuration);
/// <inheritdoc/>
public override string ToString()
/// <summary>
/// Clones the current image with the given configueation.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>Returns a new <see cref="Image{TPixel}"/> with all the same pixel data as the original.</returns>
public Image<TPixel> Clone(Configuration configuration)
{
return $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
IEnumerable<ImageFrame<TPixel>> clonedFrames = this.Frames.Select(x => x.Clone(configuration));
return new Image<TPixel>(configuration, this.MetaData.DeepClone(), clonedFrames);
}
/// <summary>
@ -205,22 +202,27 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2>
{
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.frames.Select(x => x.CloneAs<TPixel2>());
var target = new Image<TPixel2>(this.configuration, this.MetaData.Clone(), clonedFrames);
return target;
}
where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.configuration);
/// <summary>
/// Releases managed resources.
/// Returns a copy of the image in the given pixel format.
/// </summary>
public void Dispose()
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>
{
this.frames.Dispose();
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.Frames.Select(x => x.CloneAs<TPixel2>(configuration));
return new Image<TPixel2>(configuration, this.MetaData.DeepClone(), clonedFrames);
}
/// <inheritdoc/>
public void Dispose() => this.Frames.Dispose();
/// <inheritdoc/>
public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <summary>
/// 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.
/// </summary>
@ -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]);
}
}
}

20
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
/// <summary>
/// Encapsulates the metadata of an image frame.
/// </summary>
public sealed class ImageFrameMetaData
public sealed class ImageFrameMetaData : IDeepCloneable<ImageFrameMetaData>
{
private readonly Dictionary<IImageFormat, object> formatMetaData = new Dictionary<IImageFormat, object>();
private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetaData = new Dictionary<IImageFormat, IDeepCloneable>();
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrameMetaData"/> class.
@ -32,17 +31,14 @@ namespace SixLabors.ImageSharp.MetaData
{
DebugGuard.NotNull(other, nameof(other));
foreach (KeyValuePair<IImageFormat, object> meta in other.formatMetaData)
foreach (KeyValuePair<IImageFormat, IDeepCloneable> meta in other.formatMetaData)
{
this.formatMetaData.Add(meta.Key, meta.Value);
this.formatMetaData.Add(meta.Key, meta.Value.DeepClone());
}
}
/// <summary>
/// Clones this ImageFrameMetaData.
/// </summary>
/// <returns>The cloned instance.</returns>
public ImageFrameMetaData Clone() => new ImageFrameMetaData(this);
/// <inheritdoc/>
public ImageFrameMetaData DeepClone() => new ImageFrameMetaData(this);
/// <summary>
/// Gets the metadata value associated with the specified key.
@ -55,9 +51,9 @@ namespace SixLabors.ImageSharp.MetaData
/// </returns>
public TFormatFrameMetaData GetFormatMetaData<TFormatMetaData, TFormatFrameMetaData>(IImageFormat<TFormatMetaData, TFormatFrameMetaData> 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;
}

38
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
/// <summary>
/// Encapsulates the metadata of an image.
/// </summary>
public sealed class ImageMetaData
public sealed class ImageMetaData : IDeepCloneable<ImageMetaData>
{
/// <summary>
/// The default horizontal resolution value (dots per inch) in x direction.
@ -26,7 +25,13 @@ namespace SixLabors.ImageSharp.MetaData
/// </summary>
public const double DefaultVerticalResolution = 96;
private readonly Dictionary<IImageFormat, object> formatMetaData = new Dictionary<IImageFormat, object>();
/// <summary>
/// The default pixel resolution units.
/// <remarks>The default value is <see cref="PixelResolutionUnit.PixelsPerInch"/>.</remarks>
/// </summary>
public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch;
private readonly Dictionary<IImageFormat, IDeepCloneable> formatMetaData = new Dictionary<IImageFormat, IDeepCloneable>();
private double horizontalResolution;
private double verticalResolution;
@ -37,6 +42,7 @@ namespace SixLabors.ImageSharp.MetaData
{
this.horizontalResolution = DefaultHorizontalResolution;
this.verticalResolution = DefaultVerticalResolution;
this.ResolutionUnits = DefaultPixelResolutionUnits;
}
/// <summary>
@ -52,9 +58,9 @@ namespace SixLabors.ImageSharp.MetaData
this.VerticalResolution = other.VerticalResolution;
this.ResolutionUnits = other.ResolutionUnits;
foreach (KeyValuePair<IImageFormat, object> meta in other.formatMetaData)
foreach (KeyValuePair<IImageFormat, IDeepCloneable> 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();
}
/// <summary>
@ -114,7 +115,7 @@ namespace SixLabors.ImageSharp.MetaData
/// 02 : Pixels per centimeter
/// 03 : Pixels per meter
/// </summary>
public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch;
public PixelResolutionUnit ResolutionUnits { get; set; }
/// <summary>
/// Gets or sets the Exif profile.
@ -140,9 +141,9 @@ namespace SixLabors.ImageSharp.MetaData
/// The <typeparamref name="TFormatMetaData"/>.
/// </returns>
public TFormatMetaData GetFormatMetaData<TFormatMetaData>(IImageFormat<TFormatMetaData> 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;
}
/// <summary>
/// Clones this into a new instance
/// </summary>
/// <returns>The cloned metadata instance</returns>
public ImageMetaData Clone() => new ImageMetaData(this);
/// <inheritdoc/>
public ImageMetaData DeepClone() => new ImageMetaData(this);
/// <summary>
/// Looks up a property with the provided name.

27
src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs

@ -12,23 +12,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// Represents an EXIF profile providing access to the collection of values.
/// </summary>
public sealed class ExifProfile
public sealed class ExifProfile : IDeepCloneable<ExifProfile>
{
/// <summary>
/// The byte array to read the EXIF profile from.
/// </summary>
private byte[] data;
private readonly byte[] data;
/// <summary>
/// The collection of EXIF values
/// </summary>
private List<ExifValue> values;
/// <summary>
/// The list of invalid EXIF tags
/// </summary>
private IReadOnlyList<ExifTag> invalidTags;
/// <summary>
/// The thumbnail offset position in the byte stream
/// </summary>
@ -55,7 +50,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
this.Parts = ExifParts.All;
this.data = data;
this.invalidTags = new List<ExifTag>();
this.InvalidTags = new List<ExifTag>();
}
/// <summary>
@ -63,22 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// by making a copy from another EXIF profile.
/// </summary>
/// <param name="other">The other EXIF profile, where the clone should be made from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="other"/> is null.</exception>
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<ExifTag>(other.invalidTags);
this.InvalidTags = new List<ExifTag>(other.InvalidTags);
if (other.values != null)
{
this.values = new List<ExifValue>(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
/// <summary>
/// Gets the tags that where found but contained an invalid value.
/// </summary>
public IReadOnlyList<ExifTag> InvalidTags => this.invalidTags;
public IReadOnlyList<ExifTag> InvalidTags { get; private set; }
/// <summary>
/// Gets the values of this EXIF profile.
@ -249,6 +241,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return writer.GetData();
}
/// <inheritdoc/>
public ExifProfile DeepClone() => new ExifProfile(this);
/// <summary>
/// Synchronizes the profiles with the specified meta data.
/// </summary>
@ -294,7 +289,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
this.values = reader.ReadValues();
this.invalidTags = new List<ExifTag>(reader.InvalidTags);
this.InvalidTags = new List<ExifTag>(reader.InvalidTags);
this.thumbnailOffset = (int)reader.ThumbnailOffset;
this.thumbnailLength = (int)reader.ThumbnailLength;
}

69
src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs

@ -11,15 +11,30 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// Represent the value of the EXIF profile.
/// </summary>
public sealed class ExifValue : IEquatable<ExifValue>
public sealed class ExifValue : IEquatable<ExifValue>, IDeepCloneable<ExifValue>
{
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="dataType">The data type.</param>
/// <param name="value">The value.</param>
/// <param name="isArray">Whether the value is an array.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class
/// by making a copy from another exif value.
/// </summary>
/// <param name="other">The other exif value, where the clone should be made from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public ExifValue(ExifValue other)
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
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();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class.
/// </summary>
/// <param name="tag">The tag.</param>
/// <param name="dataType">The data type.</param>
/// <param name="value">The value.</param>
/// <param name="isArray">Whether the value is an array.</param>
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;
}
/// <summary>
/// Gets the data type of the exif value.
/// </summary>
@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
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);
/// <summary>
/// Compares two <see cref="ExifValue"/> objects for equality.
@ -162,16 +161,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(ExifValue left, ExifValue right)
{
return !(left == right);
}
public static bool operator !=(ExifValue left, ExifValue right) => !(left == right);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
}
/// <summary>
@ -205,10 +198,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -238,6 +228,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return sb.ToString();
}
/// <inheritdoc/>
public ExifValue DeepClone() => new ExifValue(this);
/// <summary>
/// Creates a new <see cref="ExifValue"/>
/// </summary>
@ -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();
}

36
src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <summary>
/// Represents an ICC profile
/// </summary>
public sealed class IccProfile
public sealed class IccProfile : IDeepCloneable<IccProfile>
{
/// <summary>
/// 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 <see cref="IccProfile"/> class.
/// </summary>
/// <param name="data">The raw ICC profile data</param>
public IccProfile(byte[] data)
{
this.data = data;
}
/// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class
/// by making a copy from another ICC profile.
/// </summary>
/// <param name="other">The other ICC profile, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
public IccProfile(IccProfile other)
{
Guard.NotNull(other, nameof(other));
this.data = other.ToByteArray();
}
public IccProfile(byte[] data) => this.data = data;
/// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class.
@ -74,6 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
this.entries = new List<IccTagDataEntry>(entries);
}
/// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class
/// by making a copy from another ICC profile.
/// </summary>
/// <param name="other">The other ICC profile, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
private IccProfile(IccProfile other)
{
Guard.NotNull(other, nameof(other));
this.data = other.ToByteArray();
}
/// <summary>
/// Gets or sets the profile header
/// </summary>
@ -100,6 +97,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
}
}
/// <inheritdoc/>
public IccProfile DeepClone() => new IccProfile(this);
#if !NETSTANDARD1_1
/// <summary>

149
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel>
{
// TODO: The WuFrameQuantizer<TPixel> 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
/// <summary>
/// The index bits.
/// </summary>
private const int IndexBits = 6;
private const int IndexBits = 5;
/// <summary>
/// The index alpha bits.
/// The index alpha bits. Keep separate for now to allow easy adjustment.
/// </summary>
private const int IndexAlphaBits = 3;
private const int IndexAlphaBits = 5;
/// <summary>
/// The index count.
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
/// <summary>
/// The table length.
/// The table length. Now 1185921.
/// </summary>
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<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> 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;
}
/// <summary>
/// Quantizes the pixel
/// </summary>
/// <param name="rgba">The rgba used to quantize the pixel input</param>
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<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<float> 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);
}
/// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, int width, int height)
{
// Build up the 3-D color histogram
// Loop through each row
for (int y = 0; y < height; y++)
{
Span<TPixel> 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
}
}
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, int width, int height)
{
Span<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> vmaSpan = this.vma.GetSpan();
Span<float> m2Span = this.m2.GetSpan();
// Build up the 3-D color histogram
// Loop through each row
for (int y = 0; y < height; y++)
{
Span<TPixel> 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);
}
}
}
/// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary>
@ -631,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns>
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<long> vwtSpan = this.vwt.GetSpan();
Span<long> vmrSpan = this.vmr.GetSpan();
Span<long> vmgSpan = this.vmg.GetSpan();
Span<long> vmbSpan = this.vmb.GetSpan();
Span<long> 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)
{

4
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<ImageFrame<TPixel>> frames =
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions, x.MetaData.Clone()));
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions, x.MetaData.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.DeepClone(), frames);
}
/// <inheritdoc/>

4
src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs

@ -36,10 +36,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.MetaData.Clone()));
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.MetaData.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.DeepClone(), frames);
}
/// <inheritdoc/>

4
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<ImageFrame<TPixel>> frames =
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.Clone()));
source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.DeepClone(), frames);
}
/// <inheritdoc/>

4
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -216,10 +216,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.Width, this.Height, x.MetaData.Clone()));
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.Width, this.Height, x.MetaData.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
return new Image<TPixel>(source.GetConfiguration(), source.MetaData.DeepClone(), frames);
}
/// <inheritdoc/>

2
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();
}

22
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));
}
}
}

32
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));
}
}
}

32
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));
}
}
}

22
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));
}
}
}

31
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));
}
}
}

2
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];

4
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<Rgba32>(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<Rgba32>(configuration, 11, 23, color))

8
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)));
}
}
}

34
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);
}

28
tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs

@ -67,16 +67,16 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConstructorCopy()
{
Assert.Throws<ArgumentNullException>(() => { new ExifProfile((ExifProfile)null); });
Assert.Throws<NullReferenceException>(() => ((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<Rgba32> reloadedImage = WriteAndRead(image, imageFormat);
// assert
ExifProfile actual = reloadedImage.MetaData.ExifProfile;
Assert.NotNull(actual);
foreach(KeyValuePair<ExifTag, object> expectedProfileValue in TestProfileValues)
foreach (KeyValuePair<ExifTag, object> 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<ExifTag, object> exifProfileValue in TestProfileValues)
foreach (KeyValuePair<ExifTag, object> exifProfileValue in TestProfileValues)
{
profile.SetValue(exifProfileValue.Key, exifProfileValue.Value);
}
@ -416,7 +418,7 @@ namespace SixLabors.ImageSharp.Tests
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image, TestImageWriteFormat imageFormat)
{
switch(imageFormat)
switch (imageFormat)
{
case TestImageWriteFormat.Jpeg:
return WriteAndReadJpeg(image);

2
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();
/// <summary>
/// Utility instance to provide informations about the test image & manage input/output

2
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;

Loading…
Cancel
Save