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+ On platforms supporting netstandard 1.3+
```csharp ```csharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
// Image.Load(string path) is a shortcut for our default type. // Image.Load(string path) is a shortcut for our default type.
// Other pixel formats use Image.Load<TPixel>(string path)) // Other pixel formats use Image.Load<TPixel>(string path))
using (Image<Rgba32> image = Image.Load("foo.jpg")) 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 On netstandard 1.1 - 1.2
```csharp ```csharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
// Image.Load(Stream stream) is a shortcut for our default type. // Image.Load(Stream stream) is a shortcut for our default type.
// Other pixel formats use Image.Load<TPixel>(Stream stream)) // Other pixel formats use Image.Load<TPixel>(Stream stream))
using (FileStream stream = File.OpenRead("foo.jpg")) 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: Setting individual pixel values can be performed as follows:
```csharp ```csharp
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
// Individual pixels // Individual pixels
using (Image<Rgba32> image = new Image<Rgba32>(400, 400)) 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; 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> /// <summary>
/// Converts an <see cref="float"/> to a <see cref="byte"/> first restricting the value between the /// Converts an <see cref="float"/> to a <see cref="byte"/> first restricting the value between the
/// minimum and maximum allowable ranges. /// 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"/> /// Creates a shallow copy of the <see cref="Configuration"/>
/// </summary> /// </summary>
/// <returns>A new configuration instance</returns> /// <returns>A new configuration instance</returns>
public Configuration ShallowCopy() public Configuration Clone()
{ {
return new Configuration return new Configuration
{ {

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

@ -6,13 +6,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary> /// <summary>
/// Provides Bmp specific metadata information for the image. /// Provides Bmp specific metadata information for the image.
/// </summary> /// </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> /// <summary>
/// Gets or sets the number of bits per pixel. /// Gets or sets the number of bits per pixel.
/// </summary> /// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new BmpMetaData(this);
// TODO: Colors used once we support encoding palette bmps. // 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> /// <summary>
/// Provides Gif specific metadata information for the image frame. /// Provides Gif specific metadata information for the image frame.
/// </summary> /// </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> /// <summary>
/// Gets or sets the length of the color table for paletted images. /// 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 /// 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. /// be treated after being displayed.
/// </summary> /// </summary>
public GifDisposalMethod DisposalMethod { get; set; } 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> /// <summary>
/// Provides Gif specific metadata information for the image. /// Provides Gif specific metadata information for the image.
/// </summary> /// </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> /// <summary>
/// Gets or sets the number of times any animation is repeated. /// Gets or sets the number of times any animation is repeated.
/// <remarks> /// <remarks>
@ -25,5 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets or sets the length of the global color table if present. /// Gets or sets the length of the global color table if present.
/// </summary> /// </summary>
public int GlobalColorTableLength { get; set; } 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> /// <summary>
/// Provides Jpeg specific metadata information for the image. /// Provides Jpeg specific metadata information for the image.
/// </summary> /// </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> /// <summary>
/// Gets or sets the encoded quality. /// Gets or sets the encoded quality.
/// </summary> /// </summary>
public int Quality { get; set; } = 75; 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> /// </summary>
private bool hasTrans; private bool hasTrans;
/// <summary>
/// The next chunk of data to return
/// </summary>
private PngChunk? nextChunk;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class. /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary> /// </summary>
@ -217,73 +222,73 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var metaData = new ImageMetaData(); var metaData = new ImageMetaData();
var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
Image<TPixel> image = null; Image<TPixel> image = null;
try 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);
case PngChunkType.Header: this.ValidateHeader();
this.ReadHeaderChunk(pngMetaData, chunk.Data.Array); break;
this.ValidateHeader(); case PngChunkType.Physical:
break; this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
case PngChunkType.Physical: break;
this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan()); case PngChunkType.Gamma:
break; this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
case PngChunkType.Gamma: break;
this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan()); case PngChunkType.Data:
break; if (image is null)
case PngChunkType.Data: {
if (image is null) this.InitializeImage(metaData, out image);
{ }
this.InitializeImage(metaData, out image);
}
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
deframeStream.AllocateNewBytes(chunk.Length); deframeStream.AllocateNewBytes(chunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame); this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame);
this.currentStream.Read(this.crcBuffer, 0, 4); }
break;
case PngChunkType.Palette: break;
byte[] pal = new byte[chunk.Length]; case PngChunkType.Palette:
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); byte[] pal = new byte[chunk.Length];
this.palette = pal; Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
break; this.palette = pal;
case PngChunkType.PaletteAlpha: break;
byte[] alpha = new byte[chunk.Length]; case PngChunkType.PaletteAlpha:
Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); byte[] alpha = new byte[chunk.Length];
this.paletteAlpha = alpha; Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
this.AssignTransparentMarkers(alpha); this.paletteAlpha = alpha;
break; this.AssignTransparentMarkers(alpha);
case PngChunkType.Text: break;
this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length); case PngChunkType.Text:
break; this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
case PngChunkType.Exif: break;
if (!this.ignoreMetadata) case PngChunkType.Exif:
{ if (!this.ignoreMetadata)
byte[] exifData = new byte[chunk.Length]; {
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); byte[] exifData = new byte[chunk.Length];
metaData.ExifProfile = new ExifProfile(exifData); Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
} metaData.ExifProfile = new ExifProfile(exifData);
}
break;
case PngChunkType.End: break;
this.isEndChunkReached = true; case PngChunkType.End:
break; this.isEndChunkReached = true;
} break;
}
finally
{
chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
} }
} }
finally
{
chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
}
} }
if (image is null) if (image is null)
@ -307,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public IImageInfo Identify(Stream stream) public IImageInfo Identify(Stream stream)
{ {
var metaData = new ImageMetaData(); var metaData = new ImageMetaData();
var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
try try
@ -1366,6 +1371,32 @@ namespace SixLabors.ImageSharp.Formats.Png
metadata.Properties.Add(new ImageProperty(name, value)); 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> /// <summary>
/// Reads a chunk from the stream. /// Reads a chunk from the stream.
/// </summary> /// </summary>
@ -1375,6 +1406,15 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </returns> /// </returns>
private bool TryReadChunk(out PngChunk chunk) private bool TryReadChunk(out PngChunk chunk)
{ {
if (this.nextChunk != null)
{
chunk = this.nextChunk.Value;
this.nextChunk = null;
return true;
}
int length = this.ReadChunkLength(); int length = this.ReadChunkLength();
if (length == -1) if (length == -1)

19
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Collect the indexed pixel data // Collect the indexed pixel data
if (quantized != null) if (quantized != null)
{ {
this.WritePaletteChunk(stream, header, quantized); this.WritePaletteChunk(stream, quantized);
} }
this.WritePhysicalChunk(stream, metaData); this.WritePhysicalChunk(stream, metaData);
@ -555,30 +555,27 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <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> /// <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> where TPixel : struct, IPixel<TPixel>
{ {
// Grab the palette and write it to the stream. // Grab the palette and write it to the stream.
TPixel[] palette = quantized.Palette; TPixel[] palette = quantized.Palette;
byte pixelCount = palette.Length.ToByte(); int paletteLength = Math.Min(palette.Length, 256);
int colorTableLength = paletteLength * 3;
// Get max colors for bit depth.
int colorTableLength = ImageMaths.GetColorCountForBitDepth(header.BitDepth) * 3;
Rgba32 rgba = default; Rgba32 rgba = default;
bool anyAlpha = false; bool anyAlpha = false;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) 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> colorTableSpan = colorTable.GetSpan();
Span<byte> alphaTableSpan = alphaTable.GetSpan(); Span<byte> alphaTableSpan = alphaTable.GetSpan();
Span<byte> quantizedSpan = quantized.GetPixelSpan(); 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; int offset = i * 3;
palette[i].ToRgba32(ref rgba); palette[i].ToRgba32(ref rgba);
@ -604,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Write the transparency data // Write the transparency data
if (anyAlpha) 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> /// <summary>
/// Provides Png specific metadata information for the image. /// Provides Png specific metadata information for the image.
/// </summary> /// </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> /// <summary>
/// Gets or sets the number of bits per sample or per palette index (not per pixel). /// 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. /// 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. /// Gets or sets the gamma value for the image.
/// </summary> /// </summary>
public float Gamma { get; set; } 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; private bool isDisposed;
/// <summary> /// <summary>
/// Whether the crc value has been read. /// The current data remaining to be read
/// </summary> /// </summary>
private bool crcRead; private int currentDataRemaining;
/// <summary> /// <summary>
/// The current data remaining to be read /// Delegate to get more data once we've exhausted the current data remaining
/// </summary> /// </summary>
private int currentDataRemaining; private Func<int> getData;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class. /// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary> /// </summary>
/// <param name="innerStream">The inner raw stream</param> /// <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.innerStream = innerStream;
this.getData = getData;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -112,12 +114,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
if (this.currentDataRemaining == 0) 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); int bytesToRead = Math.Min(count, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead; 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/> /// <inheritdoc/>
@ -153,14 +179,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
this.compressedStream.Dispose(); this.compressedStream.Dispose();
this.compressedStream = null; 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) public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source)
{ {
this.ValidateFrame(source); this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone(); ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Insert(index, clonedFrame); this.frames.Insert(index, clonedFrame);
return clonedFrame; return clonedFrame;
} }
@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source) public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source)
{ {
this.ValidateFrame(source); this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone(); ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Add(clonedFrame); this.frames.Add(clonedFrame);
return clonedFrame; return clonedFrame;
} }
@ -155,10 +155,7 @@ namespace SixLabors.ImageSharp
/// <returns> /// <returns>
/// <c>true</c> if the <seealso cref="ImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>. /// <c>true</c> if the <seealso cref="ImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
/// </returns> /// </returns>
public bool Contains(ImageFrame<TPixel> frame) public bool Contains(ImageFrame<TPixel> frame) => this.frames.Contains(frame);
{
return this.frames.Contains(frame);
}
/// <summary> /// <summary>
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>. /// 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); 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> /// <summary>
@ -208,7 +205,7 @@ namespace SixLabors.ImageSharp
{ {
ImageFrame<TPixel> frame = this[index]; ImageFrame<TPixel> frame = this[index];
ImageFrame<TPixel> clonedFrame = frame.Clone(); 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> /// <summary>
@ -217,10 +214,7 @@ namespace SixLabors.ImageSharp
/// <returns> /// <returns>
/// The new <see cref="ImageFrame{TPixel}" />. /// The new <see cref="ImageFrame{TPixel}" />.
/// </returns> /// </returns>
public ImageFrame<TPixel> CreateFrame() public ImageFrame<TPixel> CreateFrame() => this.CreateFrame(default);
{
return this.CreateFrame(default);
}
/// <summary> /// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection. /// 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> /// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer. /// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary> /// </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) internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource)
: this(configuration, width, height, memorySource, new ImageFrameMetaData()) : this(configuration, width, height, memorySource, new ImageFrameMetaData())
{ {
@ -104,12 +108,12 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer. /// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary> /// </summary>
internal ImageFrame( /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
Configuration configuration, /// <param name="width">The width of the image in pixels.</param>
int width, /// <param name="height">The height of the image in pixels.</param>
int height, /// <param name="memorySource">The memory source.</param>
MemorySource<TPixel> memorySource, /// <param name="metaData">The meta data.</param>
ImageFrameMetaData metaData) internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource, ImageFrameMetaData metaData)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(width, 0, nameof(width));
@ -136,7 +140,7 @@ namespace SixLabors.ImageSharp
this.MemoryAllocator = configuration.MemoryAllocator; this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height); this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height);
source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan());
this.MetaData = source.MetaData.Clone(); this.MetaData = source.MetaData.DeepClone();
} }
/// <summary> /// <summary>
@ -248,24 +252,46 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; 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> /// <summary>
/// Returns a copy of the image frame in the given pixel format. /// Returns a copy of the image frame in the given pixel format.
/// </summary> /// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam> /// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="ImageFrame{TPixel2}"/></returns> /// <returns>The <see cref="ImageFrame{TPixel2}"/></returns>
internal ImageFrame<TPixel2> CloneAs<TPixel2>() 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> where TPixel2 : struct, IPixel<TPixel2>
{ {
if (typeof(TPixel2) == typeof(TPixel)) 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>( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
this.Bounds(), this.Bounds(),
this.configuration, configuration,
(rows, tempRowBuffer) => (rows, tempRowBuffer) =>
{ {
for (int y = rows.Min; y < rows.Max; y++) 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/> /// <inheritdoc/>
void IDisposable.Dispose() => this.Dispose(); 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.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
@ -23,15 +22,12 @@ namespace SixLabors.ImageSharp
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly ImageFrameCollection<TPixel> frames;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.
/// </summary> /// </summary>
/// <param name="configuration"> /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="width">The width of the image in pixels.</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="height">The height of the image in pixels.</param>
public Image(Configuration configuration, int width, int height) 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 /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.
/// </summary> /// </summary>
/// <param name="configuration"> /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="width">The width of the image in pixels.</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="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</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 /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.
/// </summary> /// </summary>
/// <param name="configuration"> /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="width">The width of the image in pixels.</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="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param> /// <param name="metadata">The images metadata.</param>
@ -80,37 +72,41 @@ namespace SixLabors.ImageSharp
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8); this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData(); 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> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// wrapping an external <see cref="MemorySource{T}"/> /// wrapping an external <see cref="MemorySource{T}"/>
/// </summary> /// </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) internal Image(Configuration configuration, MemorySource<TPixel> memorySource, int width, int height, ImageMetaData metadata)
{ {
this.configuration = configuration; this.configuration = configuration;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8); this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata; this.MetaData = metadata;
this.frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource); this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.
/// </summary> /// </summary>
/// <param name="configuration"> /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="width">The width of the image in pixels.</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="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param> /// <param name="backgroundColor">The color to initialize the pixels with.</param>
/// <param name="metadata">The images metadata.</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.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8); this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData(); this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor); this.Frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
} }
/// <summary> /// <summary>
@ -126,7 +122,7 @@ namespace SixLabors.ImageSharp
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8); this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData(); this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, frames); this.Frames = new ImageFrameCollection<TPixel>(this, frames);
} }
/// <summary> /// <summary>
@ -138,10 +134,10 @@ namespace SixLabors.ImageSharp
public PixelTypeInfo PixelType { get; } public PixelTypeInfo PixelType { get; }
/// <inheritdoc/> /// <inheritdoc/>
public int Width => this.frames.RootFrame.Width; public int Width => this.Frames.RootFrame.Width;
/// <inheritdoc/> /// <inheritdoc/>
public int Height => this.frames.RootFrame.Height; public int Height => this.Frames.RootFrame.Height;
/// <inheritdoc/> /// <inheritdoc/>
public ImageMetaData MetaData { get; } public ImageMetaData MetaData { get; }
@ -149,12 +145,12 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Gets the frames. /// Gets the frames.
/// </summary> /// </summary>
public ImageFrameCollection<TPixel> Frames => this.frames; public ImageFrameCollection<TPixel> Frames { get; }
/// <summary> /// <summary>
/// Gets the root frame. /// Gets the root frame.
/// </summary> /// </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> /// <summary>
/// Gets or sets the pixel at the specified position. /// Gets or sets the pixel at the specified position.
@ -187,16 +183,17 @@ namespace SixLabors.ImageSharp
/// Clones the current image /// Clones the current image
/// </summary> /// </summary>
/// <returns>Returns a new image with all the same metadata as the original.</returns> /// <returns>Returns a new image with all the same metadata as the original.</returns>
public Image<TPixel> Clone() public Image<TPixel> Clone() => this.Clone(this.configuration);
{
IEnumerable<ImageFrame<TPixel>> clonedFrames = this.frames.Select(x => x.Clone());
return new Image<TPixel>(this.configuration, this.MetaData.Clone(), clonedFrames);
}
/// <inheritdoc/> /// <summary>
public override string ToString() /// 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> /// <summary>
@ -205,22 +202,27 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel2">The pixel format.</typeparam> /// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel2}"/></returns> /// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>() public Image<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2> where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.configuration);
{
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.frames.Select(x => x.CloneAs<TPixel2>());
var target = new Image<TPixel2>(this.configuration, this.MetaData.Clone(), clonedFrames);
return target;
}
/// <summary> /// <summary>
/// Releases managed resources. /// Returns a copy of the image in the given pixel format.
/// </summary> /// </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> /// <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. /// 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> /// </summary>
@ -229,9 +231,9 @@ namespace SixLabors.ImageSharp
{ {
Guard.NotNull(pixelSource, nameof(pixelSource)); 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -10,9 +9,9 @@ namespace SixLabors.ImageSharp.MetaData
/// <summary> /// <summary>
/// Encapsulates the metadata of an image frame. /// Encapsulates the metadata of an image frame.
/// </summary> /// </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> /// <summary>
/// Initializes a new instance of the <see cref="ImageFrameMetaData"/> class. /// Initializes a new instance of the <see cref="ImageFrameMetaData"/> class.
@ -32,17 +31,14 @@ namespace SixLabors.ImageSharp.MetaData
{ {
DebugGuard.NotNull(other, nameof(other)); 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> /// <inheritdoc/>
/// Clones this ImageFrameMetaData. public ImageFrameMetaData DeepClone() => new ImageFrameMetaData(this);
/// </summary>
/// <returns>The cloned instance.</returns>
public ImageFrameMetaData Clone() => new ImageFrameMetaData(this);
/// <summary> /// <summary>
/// Gets the metadata value associated with the specified key. /// Gets the metadata value associated with the specified key.
@ -55,9 +51,9 @@ namespace SixLabors.ImageSharp.MetaData
/// </returns> /// </returns>
public TFormatFrameMetaData GetFormatMetaData<TFormatMetaData, TFormatFrameMetaData>(IImageFormat<TFormatMetaData, TFormatFrameMetaData> key) public TFormatFrameMetaData GetFormatMetaData<TFormatMetaData, TFormatFrameMetaData>(IImageFormat<TFormatMetaData, TFormatFrameMetaData> key)
where TFormatMetaData : class 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; return (TFormatFrameMetaData)meta;
} }

38
src/ImageSharp/MetaData/ImageMetaData.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.MetaData
/// <summary> /// <summary>
/// Encapsulates the metadata of an image. /// Encapsulates the metadata of an image.
/// </summary> /// </summary>
public sealed class ImageMetaData public sealed class ImageMetaData : IDeepCloneable<ImageMetaData>
{ {
/// <summary> /// <summary>
/// The default horizontal resolution value (dots per inch) in x direction. /// The default horizontal resolution value (dots per inch) in x direction.
@ -26,7 +25,13 @@ namespace SixLabors.ImageSharp.MetaData
/// </summary> /// </summary>
public const double DefaultVerticalResolution = 96; 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 horizontalResolution;
private double verticalResolution; private double verticalResolution;
@ -37,6 +42,7 @@ namespace SixLabors.ImageSharp.MetaData
{ {
this.horizontalResolution = DefaultHorizontalResolution; this.horizontalResolution = DefaultHorizontalResolution;
this.verticalResolution = DefaultVerticalResolution; this.verticalResolution = DefaultVerticalResolution;
this.ResolutionUnits = DefaultPixelResolutionUnits;
} }
/// <summary> /// <summary>
@ -52,9 +58,9 @@ namespace SixLabors.ImageSharp.MetaData
this.VerticalResolution = other.VerticalResolution; this.VerticalResolution = other.VerticalResolution;
this.ResolutionUnits = other.ResolutionUnits; 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) foreach (ImageProperty property in other.Properties)
@ -62,13 +68,8 @@ namespace SixLabors.ImageSharp.MetaData
this.Properties.Add(property); this.Properties.Add(property);
} }
this.ExifProfile = other.ExifProfile != null this.ExifProfile = other.ExifProfile?.DeepClone();
? new ExifProfile(other.ExifProfile) this.IccProfile = other.IccProfile?.DeepClone();
: null;
this.IccProfile = other.IccProfile != null
? new IccProfile(other.IccProfile)
: null;
} }
/// <summary> /// <summary>
@ -114,7 +115,7 @@ namespace SixLabors.ImageSharp.MetaData
/// 02 : Pixels per centimeter /// 02 : Pixels per centimeter
/// 03 : Pixels per meter /// 03 : Pixels per meter
/// </summary> /// </summary>
public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch; public PixelResolutionUnit ResolutionUnits { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Exif profile. /// Gets or sets the Exif profile.
@ -140,9 +141,9 @@ namespace SixLabors.ImageSharp.MetaData
/// The <typeparamref name="TFormatMetaData"/>. /// The <typeparamref name="TFormatMetaData"/>.
/// </returns> /// </returns>
public TFormatMetaData GetFormatMetaData<TFormatMetaData>(IImageFormat<TFormatMetaData> key) 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; return (TFormatMetaData)meta;
} }
@ -152,11 +153,8 @@ namespace SixLabors.ImageSharp.MetaData
return newMeta; return newMeta;
} }
/// <summary> /// <inheritdoc/>
/// Clones this into a new instance public ImageMetaData DeepClone() => new ImageMetaData(this);
/// </summary>
/// <returns>The cloned metadata instance</returns>
public ImageMetaData Clone() => new ImageMetaData(this);
/// <summary> /// <summary>
/// Looks up a property with the provided name. /// 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> /// <summary>
/// Represents an EXIF profile providing access to the collection of values. /// Represents an EXIF profile providing access to the collection of values.
/// </summary> /// </summary>
public sealed class ExifProfile public sealed class ExifProfile : IDeepCloneable<ExifProfile>
{ {
/// <summary> /// <summary>
/// The byte array to read the EXIF profile from. /// The byte array to read the EXIF profile from.
/// </summary> /// </summary>
private byte[] data; private readonly byte[] data;
/// <summary> /// <summary>
/// The collection of EXIF values /// The collection of EXIF values
/// </summary> /// </summary>
private List<ExifValue> values; private List<ExifValue> values;
/// <summary>
/// The list of invalid EXIF tags
/// </summary>
private IReadOnlyList<ExifTag> invalidTags;
/// <summary> /// <summary>
/// The thumbnail offset position in the byte stream /// The thumbnail offset position in the byte stream
/// </summary> /// </summary>
@ -55,7 +50,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{ {
this.Parts = ExifParts.All; this.Parts = ExifParts.All;
this.data = data; this.data = data;
this.invalidTags = new List<ExifTag>(); this.InvalidTags = new List<ExifTag>();
} }
/// <summary> /// <summary>
@ -63,22 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// by making a copy from another EXIF profile. /// by making a copy from another EXIF profile.
/// </summary> /// </summary>
/// <param name="other">The other EXIF profile, where the clone should be made from.</param> /// <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> private ExifProfile(ExifProfile other)
public ExifProfile(ExifProfile other)
{ {
Guard.NotNull(other, nameof(other));
this.Parts = other.Parts; this.Parts = other.Parts;
this.thumbnailLength = other.thumbnailLength; this.thumbnailLength = other.thumbnailLength;
this.thumbnailOffset = other.thumbnailOffset; this.thumbnailOffset = other.thumbnailOffset;
this.invalidTags = new List<ExifTag>(other.invalidTags); this.InvalidTags = new List<ExifTag>(other.InvalidTags);
if (other.values != null) if (other.values != null)
{ {
this.values = new List<ExifValue>(other.Values.Count); this.values = new List<ExifValue>(other.Values.Count);
foreach (ExifValue value in other.Values) 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> /// <summary>
/// Gets the tags that where found but contained an invalid value. /// Gets the tags that where found but contained an invalid value.
/// </summary> /// </summary>
public IReadOnlyList<ExifTag> InvalidTags => this.invalidTags; public IReadOnlyList<ExifTag> InvalidTags { get; private set; }
/// <summary> /// <summary>
/// Gets the values of this EXIF profile. /// Gets the values of this EXIF profile.
@ -249,6 +241,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return writer.GetData(); return writer.GetData();
} }
/// <inheritdoc/>
public ExifProfile DeepClone() => new ExifProfile(this);
/// <summary> /// <summary>
/// Synchronizes the profiles with the specified meta data. /// Synchronizes the profiles with the specified meta data.
/// </summary> /// </summary>
@ -294,7 +289,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
this.values = reader.ReadValues(); this.values = reader.ReadValues();
this.invalidTags = new List<ExifTag>(reader.InvalidTags); this.InvalidTags = new List<ExifTag>(reader.InvalidTags);
this.thumbnailOffset = (int)reader.ThumbnailOffset; this.thumbnailOffset = (int)reader.ThumbnailOffset;
this.thumbnailLength = (int)reader.ThumbnailLength; this.thumbnailLength = (int)reader.ThumbnailLength;
} }

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

@ -11,15 +11,30 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary> /// <summary>
/// Represent the value of the EXIF profile. /// Represent the value of the EXIF profile.
/// </summary> /// </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> /// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class /// Initializes a new instance of the <see cref="ExifValue"/> class
/// by making a copy from another exif value. /// by making a copy from another exif value.
/// </summary> /// </summary>
/// <param name="other">The other exif value, where the clone should be made from.</param> /// <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> /// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
public ExifValue(ExifValue other) private ExifValue(ExifValue other)
{ {
Guard.NotNull(other, nameof(other)); Guard.NotNull(other, nameof(other));
@ -29,30 +44,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
if (!other.IsArray) if (!other.IsArray)
{ {
// All types are value types except for string which is immutable so safe to simply assign.
this.Value = other.Value; this.Value = other.Value;
} }
else else
{ {
// All array types are value types so Clone() is sufficient here.
var array = (Array)other.Value; var array = (Array)other.Value;
this.Value = array.Clone(); 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> /// <summary>
/// Gets the data type of the exif value. /// Gets the data type of the exif value.
/// </summary> /// </summary>
@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns> /// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false. /// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
public static bool operator ==(ExifValue left, ExifValue right) public static bool operator ==(ExifValue left, ExifValue right) => ReferenceEquals(left, right) || left.Equals(right);
{
return ReferenceEquals(left, right) || left.Equals(right);
}
/// <summary> /// <summary>
/// Compares two <see cref="ExifValue"/> objects for equality. /// Compares two <see cref="ExifValue"/> objects for equality.
@ -162,16 +161,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <returns> /// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false. /// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
public static bool operator !=(ExifValue left, ExifValue right) public static bool operator !=(ExifValue left, ExifValue right) => !(left == right);
{
return !(left == right);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj) => obj is ExifValue other && this.Equals(other);
{
return obj is ExifValue other && this.Equals(other);
}
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(ExifValue other) public bool Equals(ExifValue other)
@ -187,9 +180,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
} }
return return
this.Tag == other.Tag && this.Tag == other.Tag
this.DataType == other.DataType && && this.DataType == other.DataType
object.Equals(this.Value, other.Value); && object.Equals(this.Value, other.Value);
} }
/// <summary> /// <summary>
@ -205,10 +198,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode() => this.GetHashCode(this);
{
return this.GetHashCode(this);
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
@ -238,6 +228,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return sb.ToString(); return sb.ToString();
} }
/// <inheritdoc/>
public ExifValue DeepClone() => new ExifValue(this);
/// <summary> /// <summary>
/// Creates a new <see cref="ExifValue"/> /// Creates a new <see cref="ExifValue"/>
/// </summary> /// </summary>
@ -584,7 +577,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray) private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray)
{ {
Type type = value?.GetType(); Type type = value?.GetType();
if (type != null && type.IsArray) if (type?.IsArray == true)
{ {
type = type.GetElementType(); type = type.GetElementType();
} }

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

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <summary> /// <summary>
/// Represents an ICC profile /// Represents an ICC profile
/// </summary> /// </summary>
public sealed class IccProfile public sealed class IccProfile : IDeepCloneable<IccProfile>
{ {
/// <summary> /// <summary>
/// The byte array to read the ICC profile from /// 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. /// Initializes a new instance of the <see cref="IccProfile"/> class.
/// </summary> /// </summary>
/// <param name="data">The raw ICC profile data</param> /// <param name="data">The raw ICC profile data</param>
public IccProfile(byte[] data) public IccProfile(byte[] data) => this.data = 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();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class. /// 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); 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> /// <summary>
/// Gets or sets the profile header /// Gets or sets the profile header
/// </summary> /// </summary>
@ -100,6 +97,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
} }
} }
/// <inheritdoc/>
public IccProfile DeepClone() => new IccProfile(this);
#if !NETSTANDARD1_1 #if !NETSTANDARD1_1
/// <summary> /// <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> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: The WuFrameQuantizer<TPixel> code is rising several questions: // 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? // - 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! // (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! // - 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> /// <summary>
/// The index bits. /// The index bits.
/// </summary> /// </summary>
private const int IndexBits = 6; private const int IndexBits = 5;
/// <summary> /// <summary>
/// The index alpha bits. /// The index alpha bits. Keep separate for now to allow easy adjustment.
/// </summary> /// </summary>
private const int IndexAlphaBits = 3; private const int IndexAlphaBits = 5;
/// <summary> /// <summary>
/// The index count. /// The index count.
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
/// <summary> /// <summary>
/// The table length. /// The table length. Now 1185921.
/// </summary> /// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
@ -179,18 +179,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (this.palette is null) if (this.palette is null)
{ {
this.palette = new TPixel[this.colors]; 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++) for (int k = 0; k < this.colors; k++)
{ {
this.Mark(ref this.colorCube[k], (byte)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) if (MathF.Abs(weight) > Constants.Epsilon)
{ {
float r = Volume(ref this.colorCube[k], this.vmr.GetSpan()); float r = Volume(ref this.colorCube[k], vmrSpan);
float g = Volume(ref this.colorCube[k], this.vmg.GetSpan()); float g = Volume(ref this.colorCube[k], vmgSpan);
float b = Volume(ref this.colorCube[k], this.vmb.GetSpan()); float b = Volume(ref this.colorCube[k], vmbSpan);
float a = Volume(ref this.colorCube[k], this.vma.GetSpan()); float a = Volume(ref this.colorCube[k], vmaSpan);
ref TPixel color = ref this.palette[k]; ref TPixel color = ref this.palette[k];
color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F); color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F);
@ -201,57 +207,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return this.palette; 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/> /// <inheritdoc/>
protected override void FirstPass(ImageFrame<TPixel> source, int width, int height) protected override void FirstPass(ImageFrame<TPixel> source, int width, int height)
{ {
// Build up the 3-D color histogram this.Build3DHistogram(source, width, height);
// 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.Get3DMoments(source.MemoryAllocator); this.Get3DMoments(source.MemoryAllocator);
this.BuildCube(); 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> /// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary> /// </summary>
@ -631,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns> /// <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) 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()); Span<long> vwtSpan = this.vwt.GetSpan();
long baseG = Bottom(ref cube, direction, this.vmg.GetSpan()); Span<long> vmrSpan = this.vmr.GetSpan();
long baseB = Bottom(ref cube, direction, this.vmb.GetSpan()); Span<long> vmgSpan = this.vmg.GetSpan();
long baseA = Bottom(ref cube, direction, this.vma.GetSpan()); Span<long> vmbSpan = this.vmb.GetSpan();
long baseW = Bottom(ref cube, direction, this.vwt.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; float max = 0F;
cut = -1; cut = -1;
for (int i = first; i < last; i++) for (int i = first; i < last; i++)
{ {
float halfR = baseR + Top(ref cube, direction, i, this.vmr.GetSpan()); float halfR = baseR + Top(ref cube, direction, i, vmrSpan);
float halfG = baseG + Top(ref cube, direction, i, this.vmg.GetSpan()); float halfG = baseG + Top(ref cube, direction, i, vmgSpan);
float halfB = baseB + Top(ref cube, direction, i, this.vmb.GetSpan()); float halfB = baseB + Top(ref cube, direction, i, vmbSpan);
float halfA = baseA + Top(ref cube, direction, i, this.vma.GetSpan()); float halfA = baseA + Top(ref cube, direction, i, vmaSpan);
float halfW = baseW + Top(ref cube, direction, i, this.vwt.GetSpan()); float halfW = baseW + Top(ref cube, direction, i, vwtSpan);
if (MathF.Abs(halfW) < Constants.Epsilon) 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 // We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = 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 // 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/> /// <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) 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 // 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 // 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/> /// <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 // We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = 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 // 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/> /// <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) 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 // 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 // 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/> /// <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, // 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. // 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(); 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] [Fact]
public void WrapMemory_CreatedImageIsCorrect() public void WrapMemory_CreatedImageIsCorrect()
{ {
Configuration cfg = Configuration.Default.ShallowCopy(); Configuration cfg = Configuration.Default.Clone();
var metaData = new ImageMetaData(); var metaData = new ImageMetaData();
var array = new Rgba32[25]; var array = new Rgba32[25];

4
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Configuration_Width_Height() public void Configuration_Width_Height()
{ {
Configuration configuration = Configuration.Default.ShallowCopy(); Configuration configuration = Configuration.Default.Clone();
using (var image = new Image<Rgba32>(configuration, 11, 23)) using (var image = new Image<Rgba32>(configuration, 11, 23))
{ {
@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Configuration_Width_Height_BackroundColor() public void Configuration_Width_Height_BackroundColor()
{ {
Configuration configuration = Configuration.Default.ShallowCopy(); Configuration configuration = Configuration.Default.Clone();
Rgba32 color = Rgba32.Aquamarine; Rgba32 color = Rgba32.Aquamarine;
using (var image = new Image<Rgba32>(configuration, 11, 23, color)) 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(colorTableLength, cloneGifFrameMetaData.ColorTableLength);
Assert.Equal(disposalMethod, cloneGifFrameMetaData.DisposalMethod); 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // 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;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests
metaData.VerticalResolution = 2; metaData.VerticalResolution = 2;
metaData.Properties.Add(imageProperty); metaData.Properties.Add(imageProperty);
ImageMetaData clone = metaData.Clone(); ImageMetaData clone = metaData.DeepClone();
Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray());
Assert.Equal(4, clone.HorizontalResolution); Assert.Equal(4, clone.HorizontalResolution);
@ -37,19 +37,43 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(imageProperty, clone.Properties[0]); 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] [Fact]
public void HorizontalResolution() public void HorizontalResolution()
{ {
var metaData = new ImageMetaData(); var metaData = new ImageMetaData();
Assert.Equal(96, metaData.HorizontalResolution); Assert.Equal(96, metaData.HorizontalResolution);
metaData.HorizontalResolution=0; metaData.HorizontalResolution = 0;
Assert.Equal(96, metaData.HorizontalResolution); Assert.Equal(96, metaData.HorizontalResolution);
metaData.HorizontalResolution=-1; metaData.HorizontalResolution = -1;
Assert.Equal(96, metaData.HorizontalResolution); Assert.Equal(96, metaData.HorizontalResolution);
metaData.HorizontalResolution=1; metaData.HorizontalResolution = 1;
Assert.Equal(1, metaData.HorizontalResolution); Assert.Equal(1, metaData.HorizontalResolution);
} }

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

@ -67,16 +67,16 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void ConstructorCopy() public void ConstructorCopy()
{ {
Assert.Throws<ArgumentNullException>(() => { new ExifProfile((ExifProfile)null); }); Assert.Throws<NullReferenceException>(() => ((ExifProfile)null).DeepClone());
ExifProfile profile = GetExifProfile(); ExifProfile profile = GetExifProfile();
var clone = new ExifProfile(profile); ExifProfile clone = profile.DeepClone();
TestProfile(clone); TestProfile(clone);
profile.SetValue(ExifTag.ColorSpace, (ushort)2); profile.SetValue(ExifTag.ColorSpace, (ushort)2);
clone = new ExifProfile(profile); clone = profile.DeepClone();
TestProfile(clone); TestProfile(clone);
} }
@ -234,10 +234,12 @@ namespace SixLabors.ImageSharp.Tests
exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); exifProfile.SetValue(ExifTag.XResolution, new Rational(200));
exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); exifProfile.SetValue(ExifTag.YResolution, new Rational(300));
var metaData = new ImageMetaData(); var metaData = new ImageMetaData
metaData.ExifProfile = exifProfile; {
metaData.HorizontalResolution = 200; ExifProfile = exifProfile,
metaData.VerticalResolution = 300; HorizontalResolution = 200,
VerticalResolution = 300
};
metaData.HorizontalResolution = 100; metaData.HorizontalResolution = 100;
@ -355,11 +357,11 @@ namespace SixLabors.ImageSharp.Tests
// act // act
Image<Rgba32> reloadedImage = WriteAndRead(image, imageFormat); Image<Rgba32> reloadedImage = WriteAndRead(image, imageFormat);
// assert // assert
ExifProfile actual = reloadedImage.MetaData.ExifProfile; ExifProfile actual = reloadedImage.MetaData.ExifProfile;
Assert.NotNull(actual); Assert.NotNull(actual);
foreach(KeyValuePair<ExifTag, object> expectedProfileValue in TestProfileValues) foreach (KeyValuePair<ExifTag, object> expectedProfileValue in TestProfileValues)
{ {
ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key); ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key);
Assert.NotNull(actualProfileValue); Assert.NotNull(actualProfileValue);
@ -371,7 +373,7 @@ namespace SixLabors.ImageSharp.Tests
public void ProfileToByteArray() public void ProfileToByteArray()
{ {
// arrange // arrange
byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray();
byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker;
ExifProfile expectedProfile = CreateExifProfile(); ExifProfile expectedProfile = CreateExifProfile();
var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList();
@ -384,7 +386,7 @@ namespace SixLabors.ImageSharp.Tests
Assert.NotNull(actualBytes); Assert.NotNull(actualBytes);
Assert.NotEmpty(actualBytes); Assert.NotEmpty(actualBytes);
Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray());
foreach(ExifTag expectedProfileTag in expectedProfileTags) foreach (ExifTag expectedProfileTag in expectedProfileTags)
{ {
ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag);
ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag);
@ -396,7 +398,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
var profile = new ExifProfile(); var profile = new ExifProfile();
foreach(KeyValuePair<ExifTag, object> exifProfileValue in TestProfileValues) foreach (KeyValuePair<ExifTag, object> exifProfileValue in TestProfileValues)
{ {
profile.SetValue(exifProfileValue.Key, exifProfileValue.Value); profile.SetValue(exifProfileValue.Key, exifProfileValue.Value);
} }
@ -416,7 +418,7 @@ namespace SixLabors.ImageSharp.Tests
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image, TestImageWriteFormat imageFormat) private static Image<Rgba32> WriteAndRead(Image<Rgba32> image, TestImageWriteFormat imageFormat)
{ {
switch(imageFormat) switch (imageFormat)
{ {
case TestImageWriteFormat.Jpeg: case TestImageWriteFormat.Jpeg:
return WriteAndReadJpeg(image); return WriteAndReadJpeg(image);

2
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs

@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests
public virtual string SourceFileOrDescription => ""; public virtual string SourceFileOrDescription => "";
public Configuration Configuration { get; set; } = Configuration.Default.ShallowCopy(); public Configuration Configuration { get; set; } = Configuration.Default.Clone();
/// <summary> /// <summary>
/// Utility instance to provide informations about the test image & manage input/output /// 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; } private ITestOutputHelper Output { get; }
public const string SkipBenchmarks = public const string SkipBenchmarks =
#if false #if true
"Benchmark, enable manually!"; "Benchmark, enable manually!";
#else #else
null; null;

Loading…
Cancel
Save