Browse Source

Merge branch 'main' into bp/animWebpBackground

pull/2547/head
Brian Popow 2 years ago
committed by GitHub
parent
commit
e0fcf8d2ae
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 2
      src/ImageSharp/Advanced/IConfigurationProvider.cs
  3. 12
      src/ImageSharp/Color/Color.cs
  4. 2
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  5. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  6. 2
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  7. 2
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  8. 4
      src/ImageSharp/Formats/ImageEncoder.cs
  9. 72
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  10. 8
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  11. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
  12. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs
  13. 2
      src/ImageSharp/Formats/Pbm/PbmEncoder.cs
  14. 2
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  15. 8
      src/ImageSharp/Formats/Png/PngDecoder.cs
  16. 103
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  17. 14
      src/ImageSharp/Formats/Png/PngEncoder.cs
  18. 48
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  19. 40
      src/ImageSharp/Formats/Png/PngMetadata.cs
  20. 175
      src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
  21. 4
      src/ImageSharp/Formats/Qoi/QoiEncoder.cs
  22. 5
      src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
  23. 6
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  24. 2
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  25. 3
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  26. 2
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  27. 2
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  28. 2
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  29. 13
      src/ImageSharp/Image.cs
  30. 12
      src/ImageSharp/ImageExtensions.cs
  31. 14
      src/ImageSharp/ImageFrame.cs
  32. 20
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  33. 12
      src/ImageSharp/ImageFrame{TPixel}.cs
  34. 4
      src/ImageSharp/ImageSharp.csproj
  35. 8
      src/ImageSharp/Image{TPixel}.cs
  36. 5
      src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs
  37. 3
      src/ImageSharp/Memory/Buffer2D{T}.cs
  38. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
  39. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs
  40. 13
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs
  41. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs
  42. 5
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs
  43. 7
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
  44. 4
      src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs
  45. 2
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs
  46. 12
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
  47. 12
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  48. 2
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  49. 40
      tests/ImageSharp.Tests/Color/ColorTests.cs
  50. 6
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  51. 37
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  52. 4
      tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs
  53. 8
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
  54. 2
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  55. 6
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  56. 13
      tests/ImageSharp.Tests/Image/ImageTests.cs
  57. 7
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
  58. 24
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs
  59. 2
      tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
  60. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  61. 2
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs
  62. 2
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs
  63. 6
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs
  64. 8
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs
  65. 4
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

40
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -27,11 +27,11 @@ public static class AdvancedImageExtensions
Guard.NotNull(filePath, nameof(filePath)); Guard.NotNull(filePath, nameof(filePath));
string ext = Path.GetExtension(filePath); string ext = Path.GetExtension(filePath);
if (!source.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format)) if (!source.Configuration.ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format))
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:"); sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:");
foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) foreach (IImageFormat fmt in source.Configuration.ImageFormats)
{ {
sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine);
} }
@ -39,13 +39,13 @@ public static class AdvancedImageExtensions
throw new UnknownImageFormatException(sb.ToString()); throw new UnknownImageFormatException(sb.ToString());
} }
IImageEncoder? encoder = source.GetConfiguration().ImageFormatsManager.GetEncoder(format); IImageEncoder? encoder = source.Configuration.ImageFormatsManager.GetEncoder(format);
if (encoder is null) if (encoder is null)
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.Configuration.ImageFormatsManager.ImageEncoders)
{ {
sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine);
} }
@ -76,30 +76,6 @@ public static class AdvancedImageExtensions
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default) public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default)
=> source.AcceptAsync(visitor, cancellationToken); => source.AcceptAsync(visitor, cancellationToken);
/// <summary>
/// Gets the configuration for the image.
/// </summary>
/// <param name="source">The source image.</param>
/// <returns>Returns the configuration.</returns>
public static Configuration GetConfiguration(this Image source)
=> GetConfiguration((IConfigurationProvider)source);
/// <summary>
/// Gets the configuration for the image frame.
/// </summary>
/// <param name="source">The source image.</param>
/// <returns>Returns the configuration.</returns>
public static Configuration GetConfiguration(this ImageFrame source)
=> GetConfiguration((IConfigurationProvider)source);
/// <summary>
/// Gets the configuration.
/// </summary>
/// <param name="source">The source image</param>
/// <returns>Returns the bounds of the image</returns>
private static Configuration GetConfiguration(IConfigurationProvider source)
=> source?.Configuration ?? Configuration.Default;
/// <summary> /// <summary>
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image /// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format. /// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
@ -167,12 +143,4 @@ public static class AdvancedImageExtensions
return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex); return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex);
} }
/// <summary>
/// Gets the <see cref="MemoryAllocator"/> assigned to 'source'.
/// </summary>
/// <param name="source">The source image.</param>
/// <returns>Returns the configuration.</returns>
internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
=> GetConfiguration(source).MemoryAllocator;
} }

2
src/ImageSharp/Advanced/IConfigurationProvider.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Advanced;
/// <summary> /// <summary>
/// Defines the contract for objects that can provide access to configuration. /// Defines the contract for objects that can provide access to configuration.
/// </summary> /// </summary>
internal interface IConfigurationProvider public interface IConfigurationProvider
{ {
/// <summary> /// <summary>
/// Gets the configuration which allows altering default behaviour or extending the library. /// Gets the configuration which allows altering default behaviour or extending the library.

12
src/ImageSharp/Color/Color.cs

@ -251,7 +251,17 @@ public readonly partial struct Color : IEquatable<Color>
/// </summary> /// </summary>
/// <returns>A hexadecimal string representation of the value.</returns> /// <returns>A hexadecimal string representation of the value.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public string ToHex() => this.data.ToRgba32().ToHex(); public string ToHex()
{
if (this.boxedHighPrecisionPixel is not null)
{
Rgba32 rgba = default;
this.boxedHighPrecisionPixel.ToRgba32(ref rgba);
return rgba.ToHex();
}
return this.data.ToRgba32().ToHex();
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => this.ToHex(); public override string ToString() => this.ToHex();

2
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -32,7 +32,7 @@ public sealed class BmpEncoder : QuantizingImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
BmpEncoderCore encoder = new(this, image.GetMemoryAllocator()); BmpEncoderCore encoder = new(this, image.Configuration.MemoryAllocator);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

2
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -119,7 +119,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
Configuration configuration = image.GetConfiguration(); Configuration configuration = image.Configuration;
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); BmpMetadata bmpMetadata = metadata.GetBmpMetadata();
this.bitsPerPixel ??= bmpMetadata.BitsPerPixel; this.bitsPerPixel ??= bmpMetadata.BitsPerPixel;

2
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -18,7 +18,7 @@ public sealed class GifEncoder : QuantizingImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
GifEncoderCore encoder = new(image.GetConfiguration(), this); GifEncoderCore encoder = new(image.Configuration, this);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

2
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -189,7 +189,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
// This frame is reused to store de-duplicated pixel buffers. // This frame is reused to store de-duplicated pixel buffers.
// This is more expensive memory-wise than de-duplicating indexed buffer but allows us to deduplicate // This is more expensive memory-wise than de-duplicating indexed buffer but allows us to deduplicate
// frames using both local and global palettes. // frames using both local and global palettes.
using ImageFrame<TPixel> encodingFrame = new(previousFrame.GetConfiguration(), previousFrame.Size()); using ImageFrame<TPixel> encodingFrame = new(previousFrame.Configuration, previousFrame.Size());
for (int i = 1; i < image.Frames.Count; i++) for (int i = 1; i < image.Frames.Count; i++)
{ {

4
src/ImageSharp/Formats/ImageEncoder.cs

@ -42,7 +42,7 @@ public abstract class ImageEncoder : IImageEncoder
private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Configuration configuration = image.GetConfiguration(); Configuration configuration = image.Configuration;
if (stream.CanSeek) if (stream.CanSeek)
{ {
this.Encode(image, stream, cancellationToken); this.Encode(image, stream, cancellationToken);
@ -59,7 +59,7 @@ public abstract class ImageEncoder : IImageEncoder
private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Configuration configuration = image.GetConfiguration(); Configuration configuration = image.Configuration;
if (stream.CanSeek) if (stream.CanSeek)
{ {
await DoEncodeAsync(stream).ConfigureAwait(false); await DoEncodeAsync(stream).ConfigureAwait(false);

72
src/ImageSharp/Formats/ImageExtensions.Save.cs

@ -59,7 +59,7 @@ public static partial class ImageExtensions
public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Bmp format. /// Saves the image to the given stream with the Bmp format.
@ -73,7 +73,7 @@ public static partial class ImageExtensions
public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -106,7 +106,7 @@ public static partial class ImageExtensions
public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Bmp format. /// Saves the image to the given stream with the Bmp format.
@ -120,7 +120,7 @@ public static partial class ImageExtensions
public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(BmpFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -161,7 +161,7 @@ public static partial class ImageExtensions
public static void SaveAsGif(this Image source, string path, GifEncoder encoder) => public static void SaveAsGif(this Image source, string path, GifEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Gif format. /// Saves the image to the given stream with the Gif format.
@ -175,7 +175,7 @@ public static partial class ImageExtensions
public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -208,7 +208,7 @@ public static partial class ImageExtensions
public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Gif format. /// Saves the image to the given stream with the Gif format.
@ -222,7 +222,7 @@ public static partial class ImageExtensions
public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(GifFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -263,7 +263,7 @@ public static partial class ImageExtensions
public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) => public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Jpeg format. /// Saves the image to the given stream with the Jpeg format.
@ -277,7 +277,7 @@ public static partial class ImageExtensions
public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -310,7 +310,7 @@ public static partial class ImageExtensions
public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Jpeg format. /// Saves the image to the given stream with the Jpeg format.
@ -324,7 +324,7 @@ public static partial class ImageExtensions
public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(JpegFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -365,7 +365,7 @@ public static partial class ImageExtensions
public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) => public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Pbm format. /// Saves the image to the given stream with the Pbm format.
@ -379,7 +379,7 @@ public static partial class ImageExtensions
public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -412,7 +412,7 @@ public static partial class ImageExtensions
public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder) public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Pbm format. /// Saves the image to the given stream with the Pbm format.
@ -426,7 +426,7 @@ public static partial class ImageExtensions
public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PbmFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -467,7 +467,7 @@ public static partial class ImageExtensions
public static void SaveAsPng(this Image source, string path, PngEncoder encoder) => public static void SaveAsPng(this Image source, string path, PngEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Png format. /// Saves the image to the given stream with the Png format.
@ -481,7 +481,7 @@ public static partial class ImageExtensions
public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -514,7 +514,7 @@ public static partial class ImageExtensions
public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Png format. /// Saves the image to the given stream with the Png format.
@ -528,7 +528,7 @@ public static partial class ImageExtensions
public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(PngFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -569,7 +569,7 @@ public static partial class ImageExtensions
public static void SaveAsQoi(this Image source, string path, QoiEncoder encoder) => public static void SaveAsQoi(this Image source, string path, QoiEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Qoi format. /// Saves the image to the given stream with the Qoi format.
@ -583,7 +583,7 @@ public static partial class ImageExtensions
public static Task SaveAsQoiAsync(this Image source, string path, QoiEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsQoiAsync(this Image source, string path, QoiEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -616,7 +616,7 @@ public static partial class ImageExtensions
public static void SaveAsQoi(this Image source, Stream stream, QoiEncoder encoder) public static void SaveAsQoi(this Image source, Stream stream, QoiEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Qoi format. /// Saves the image to the given stream with the Qoi format.
@ -630,7 +630,7 @@ public static partial class ImageExtensions
public static Task SaveAsQoiAsync(this Image source, Stream stream, QoiEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsQoiAsync(this Image source, Stream stream, QoiEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(QoiFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(QoiFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -671,7 +671,7 @@ public static partial class ImageExtensions
public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) => public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tga format. /// Saves the image to the given stream with the Tga format.
@ -685,7 +685,7 @@ public static partial class ImageExtensions
public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -718,7 +718,7 @@ public static partial class ImageExtensions
public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tga format. /// Saves the image to the given stream with the Tga format.
@ -732,7 +732,7 @@ public static partial class ImageExtensions
public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TgaFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -773,7 +773,7 @@ public static partial class ImageExtensions
public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) => public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tiff format. /// Saves the image to the given stream with the Tiff format.
@ -787,7 +787,7 @@ public static partial class ImageExtensions
public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -820,7 +820,7 @@ public static partial class ImageExtensions
public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tiff format. /// Saves the image to the given stream with the Tiff format.
@ -834,7 +834,7 @@ public static partial class ImageExtensions
public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(TiffFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -875,7 +875,7 @@ public static partial class ImageExtensions
public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) => public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Webp format. /// Saves the image to the given stream with the Webp format.
@ -889,7 +889,7 @@ public static partial class ImageExtensions
public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -922,7 +922,7 @@ public static partial class ImageExtensions
public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder) public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Webp format. /// Saves the image to the given stream with the Webp format.
@ -936,7 +936,7 @@ public static partial class ImageExtensions
public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(WebpFormat.Instance),
cancellationToken); cancellationToken);
} }

8
src/ImageSharp/Formats/ImageExtensions.Save.tt

@ -78,7 +78,7 @@ public static partial class ImageExtensions
public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) => public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the <#= fmt #> format. /// Saves the image to the given stream with the <#= fmt #> format.
@ -92,7 +92,7 @@ public static partial class ImageExtensions
public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -125,7 +125,7 @@ public static partial class ImageExtensions
public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder) public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance)); encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the <#= fmt #> format. /// Saves the image to the given stream with the <#= fmt #> format.
@ -139,7 +139,7 @@ public static partial class ImageExtensions
public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance), encoder ?? source.Configuration.ImageFormatsManager.GetEncoder(<#= fmt #>Format.Instance),
cancellationToken); cancellationToken);
<# <#

2
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs

@ -20,7 +20,7 @@ internal sealed class JpegFrame : IDisposable
this.PixelWidth = image.Width; this.PixelWidth = image.Width;
this.PixelHeight = image.Height; this.PixelHeight = image.Height;
MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; MemoryAllocator allocator = image.Configuration.MemoryAllocator;
JpegComponentConfig[] componentConfigs = frameConfig.Components; JpegComponentConfig[] componentConfigs = frameConfig.Components;
this.Components = new Component[componentConfigs.Length]; this.Components = new Component[componentConfigs.Length];

2
src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs

@ -32,7 +32,7 @@ internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
public SpectralConverter(JpegFrame frame, Image<TPixel> image, Block8x8F[] dequantTables) public SpectralConverter(JpegFrame frame, Image<TPixel> image, Block8x8F[] dequantTables)
{ {
MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; MemoryAllocator allocator = image.Configuration.MemoryAllocator;
// iteration data // iteration data
int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width);

2
src/ImageSharp/Formats/Pbm/PbmEncoder.cs

@ -49,7 +49,7 @@ public sealed class PbmEncoder : ImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
PbmEncoderCore encoder = new(image.GetConfiguration(), this); PbmEncoderCore encoder = new(image.Configuration, this);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

2
src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs

@ -78,7 +78,7 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals
private void SanitizeAndSetEncoderOptions<TPixel>(Image<TPixel> image) private void SanitizeAndSetEncoderOptions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.configuration = image.GetConfiguration(); this.configuration = image.Configuration;
PbmMetadata metadata = image.Metadata.GetPbmMetadata(); PbmMetadata metadata = image.Metadata.GetPbmMetadata();
this.encoding = this.encoder.Encoding ?? metadata.Encoding; this.encoding = this.encoder.Encoding ?? metadata.Encoding;
this.colorType = this.encoder.ColorType ?? metadata.ColorType; this.colorType = this.encoder.ColorType ?? metadata.ColorType;

8
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -61,24 +61,24 @@ public sealed class PngDecoder : ImageDecoder
case PngColorType.Grayscale: case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16) if (bits == PngBitDepth.Bit16)
{ {
return !meta.HasTransparency return !meta.TransparentColor.HasValue
? this.Decode<L16>(options, stream, cancellationToken) ? this.Decode<L16>(options, stream, cancellationToken)
: this.Decode<La32>(options, stream, cancellationToken); : this.Decode<La32>(options, stream, cancellationToken);
} }
return !meta.HasTransparency return !meta.TransparentColor.HasValue
? this.Decode<L8>(options, stream, cancellationToken) ? this.Decode<L8>(options, stream, cancellationToken)
: this.Decode<La16>(options, stream, cancellationToken); : this.Decode<La16>(options, stream, cancellationToken);
case PngColorType.Rgb: case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16) if (bits == PngBitDepth.Bit16)
{ {
return !meta.HasTransparency return !meta.TransparentColor.HasValue
? this.Decode<Rgb48>(options, stream, cancellationToken) ? this.Decode<Rgb48>(options, stream, cancellationToken)
: this.Decode<Rgba64>(options, stream, cancellationToken); : this.Decode<Rgba64>(options, stream, cancellationToken);
} }
return !meta.HasTransparency return !meta.TransparentColor.HasValue
? this.Decode<Rgb24>(options, stream, cancellationToken) ? this.Decode<Rgb24>(options, stream, cancellationToken)
: this.Decode<Rgba32>(options, stream, cancellationToken); : this.Decode<Rgba32>(options, stream, cancellationToken);

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

@ -172,21 +172,20 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
if (image is null) if (image is null)
{ {
this.InitializeImage(metadata, out image); this.InitializeImage(metadata, out image);
// Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
} }
this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata, cancellationToken); this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata, cancellationToken);
break; break;
case PngChunkType.Palette: case PngChunkType.Palette:
byte[] pal = new byte[chunk.Length]; this.palette = chunk.Data.GetSpan().ToArray();
chunk.Data.GetSpan().CopyTo(pal);
this.palette = pal;
break; break;
case PngChunkType.Transparency: case PngChunkType.Transparency:
byte[] alpha = new byte[chunk.Length]; this.paletteAlpha = chunk.Data.GetSpan().ToArray();
chunk.Data.GetSpan().CopyTo(alpha); this.AssignTransparentMarkers(this.paletteAlpha, pngMetadata);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
break; break;
case PngChunkType.Text: case PngChunkType.Text:
this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan());
@ -292,12 +291,15 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.SkipChunkDataAndCrc(chunk); this.SkipChunkDataAndCrc(chunk);
break; break;
case PngChunkType.Palette:
this.palette = chunk.Data.GetSpan().ToArray();
break;
case PngChunkType.Transparency: case PngChunkType.Transparency:
byte[] alpha = new byte[chunk.Length]; this.paletteAlpha = chunk.Data.GetSpan().ToArray();
chunk.Data.GetSpan().CopyTo(alpha); this.AssignTransparentMarkers(this.paletteAlpha, pngMetadata);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
// Spec says tRNS must be after PLTE so safe to exit.
if (this.colorMetadataOnly) if (this.colorMetadataOnly)
{ {
goto EOF; goto EOF;
@ -370,6 +372,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngThrowHelper.ThrowNoHeader(); PngThrowHelper.ThrowNoHeader();
} }
// Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata); return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata);
} }
finally finally
@ -766,9 +771,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
pngMetadata.HasTransparency, pngMetadata.TransparentColor);
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
break; break;
@ -787,8 +790,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
this.palette, pngMetadata.ColorTable);
this.paletteAlpha);
break; break;
@ -800,9 +802,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
rowSpan, rowSpan,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
pngMetadata.HasTransparency, pngMetadata.TransparentColor);
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
break; break;
@ -860,9 +860,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
rowSpan, rowSpan,
(uint)pixelOffset, (uint)pixelOffset,
(uint)increment, (uint)increment,
pngMetadata.HasTransparency, pngMetadata.TransparentColor);
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
break; break;
@ -885,8 +883,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
rowSpan, rowSpan,
(uint)pixelOffset, (uint)pixelOffset,
(uint)increment, (uint)increment,
this.palette, pngMetadata.ColorTable);
this.paletteAlpha);
break; break;
@ -899,9 +896,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
(uint)increment, (uint)increment,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
pngMetadata.HasTransparency, pngMetadata.TransparentColor);
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
break; break;
@ -924,10 +919,44 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
} }
} }
/// <summary>
/// Decodes and assigns the color palette to the metadata
/// </summary>
/// <param name="palette">The palette buffer.</param>
/// <param name="alpha">The alpha palette buffer.</param>
/// <param name="pngMetadata">The png metadata.</param>
private static void AssignColorPalette(ReadOnlySpan<byte> palette, ReadOnlySpan<byte> alpha, PngMetadata pngMetadata)
{
if (palette.Length == 0)
{
return;
}
Color[] colorTable = new Color[palette.Length / Unsafe.SizeOf<Rgb24>()];
ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(palette);
for (int i = 0; i < colorTable.Length; i++)
{
colorTable[i] = new Color(rgbTable[i]);
}
if (alpha.Length > 0)
{
// The alpha chunk may contain as many transparency entries as there are palette entries
// (more than that would not make any sense) or as few as one.
for (int i = 0; i < alpha.Length; i++)
{
ref Color color = ref colorTable[i];
color = color.WithAlpha(alpha[i] / 255F);
}
}
pngMetadata.ColorTable = colorTable;
}
/// <summary> /// <summary>
/// Decodes and assigns marker colors that identify transparent pixels in non indexed images. /// Decodes and assigns marker colors that identify transparent pixels in non indexed images.
/// </summary> /// </summary>
/// <param name="alpha">The alpha tRNS array.</param> /// <param name="alpha">The alpha tRNS buffer.</param>
/// <param name="pngMetadata">The png metadata.</param> /// <param name="pngMetadata">The png metadata.</param>
private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha, PngMetadata pngMetadata) private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha, PngMetadata pngMetadata)
{ {
@ -941,16 +970,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2));
ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2));
pngMetadata.TransparentRgb48 = new Rgb48(rc, gc, bc); pngMetadata.TransparentColor = new(new Rgb48(rc, gc, bc));
pngMetadata.HasTransparency = true;
return; return;
} }
byte r = ReadByteLittleEndian(alpha, 0); byte r = ReadByteLittleEndian(alpha, 0);
byte g = ReadByteLittleEndian(alpha, 2); byte g = ReadByteLittleEndian(alpha, 2);
byte b = ReadByteLittleEndian(alpha, 4); byte b = ReadByteLittleEndian(alpha, 4);
pngMetadata.TransparentRgb24 = new Rgb24(r, g, b); pngMetadata.TransparentColor = new(new Rgb24(r, g, b));
pngMetadata.HasTransparency = true;
} }
} }
else if (this.pngColorType == PngColorType.Grayscale) else if (this.pngColorType == PngColorType.Grayscale)
@ -959,20 +986,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{ {
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
{ {
pngMetadata.TransparentL16 = new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2])); pngMetadata.TransparentColor = Color.FromPixel(new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2])));
} }
else else
{ {
pngMetadata.TransparentL8 = new L8(ReadByteLittleEndian(alpha, 0)); pngMetadata.TransparentColor = Color.FromPixel(new L8(ReadByteLittleEndian(alpha, 0)));
} }
pngMetadata.HasTransparency = true;
} }
} }
else if (this.pngColorType == PngColorType.Palette && alpha.Length > 0)
{
pngMetadata.HasTransparency = true;
}
} }
/// <summary> /// <summary>
@ -1461,7 +1482,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
// If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks.
// We can skip all other chunk data in the stream for better performance. // We can skip all other chunk data in the stream for better performance.
if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency) if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency && type != PngChunkType.Palette)
{ {
chunk = new PngChunk(length, type); chunk = new PngChunk(length, type);

14
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -2,7 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable #nullable disable
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Png; namespace SixLabors.ImageSharp.Formats.Png;
@ -11,6 +11,16 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// </summary> /// </summary>
public class PngEncoder : QuantizingImageEncoder public class PngEncoder : QuantizingImageEncoder
{ {
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoder"/> class.
/// </summary>
public PngEncoder()
// Hack. TODO: Investigate means to fix/optimize the Wu quantizer.
// The Wu quantizer does not handle the default sampling strategy well for some larger images.
// It's expensive and the results are not better than the extensive strategy.
=> this.PixelSamplingStrategy = new ExtensivePixelSamplingStrategy();
/// <summary> /// <summary>
/// Gets the number of bits per sample or per palette index (not per pixel). /// Gets 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.
@ -68,7 +78,7 @@ public class PngEncoder : QuantizingImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
using PngEncoderCore encoder = new(image.GetMemoryAllocator(), image.GetConfiguration(), this); using PngEncoderCore encoder = new(image.Configuration, this);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

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

@ -6,7 +6,6 @@ using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Chunks;
@ -116,13 +115,12 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore" /> class. /// Initializes a new instance of the <see cref="PngEncoderCore" /> class.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator" /> to use for buffer allocations.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="encoder">The encoder with options.</param> /// <param name="encoder">The encoder with options.</param>
public PngEncoderCore(MemoryAllocator memoryAllocator, Configuration configuration, PngEncoder encoder) public PngEncoderCore(Configuration configuration, PngEncoder encoder)
{ {
this.memoryAllocator = memoryAllocator;
this.configuration = configuration; this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.encoder = encoder; this.encoder = encoder;
} }
@ -875,7 +873,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
// 4-byte unsigned integer of gamma * 100,000. // 4-byte unsigned integer of gamma * 100,000.
uint gammaValue = (uint)(this.gamma * 100_000F); uint gammaValue = (uint)(this.gamma * 100_000F);
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.Span.Slice(0, 4), gammaValue); BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.Span[..4], gammaValue);
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer.Span, 0, 4); this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer.Span, 0, 4);
} }
@ -889,7 +887,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// <param name="pngMetadata">The image metadata.</param> /// <param name="pngMetadata">The image metadata.</param>
private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata) private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
{ {
if (!pngMetadata.HasTransparency) if (pngMetadata.TransparentColor is null)
{ {
return; return;
} }
@ -897,19 +895,19 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
Span<byte> alpha = this.chunkDataBuffer.Span; Span<byte> alpha = this.chunkDataBuffer.Span;
if (pngMetadata.ColorType == PngColorType.Rgb) if (pngMetadata.ColorType == PngColorType.Rgb)
{ {
if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit) if (this.use16Bit)
{ {
Rgb48 rgb = pngMetadata.TransparentRgb48.Value; Rgb48 rgb = pngMetadata.TransparentColor.Value.ToPixel<Rgb48>();
BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R); BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G); BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B); BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6); this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6);
} }
else if (pngMetadata.TransparentRgb24.HasValue) else
{ {
alpha.Clear(); alpha.Clear();
Rgb24 rgb = pngMetadata.TransparentRgb24.Value; Rgb24 rgb = pngMetadata.TransparentColor.Value.ToRgb24();
alpha[1] = rgb.R; alpha[1] = rgb.R;
alpha[3] = rgb.G; alpha[3] = rgb.G;
alpha[5] = rgb.B; alpha[5] = rgb.B;
@ -918,15 +916,17 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
} }
else if (pngMetadata.ColorType == PngColorType.Grayscale) else if (pngMetadata.ColorType == PngColorType.Grayscale)
{ {
if (pngMetadata.TransparentL16.HasValue && this.use16Bit) if (this.use16Bit)
{ {
BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue); L16 l16 = pngMetadata.TransparentColor.Value.ToPixel<L16>();
BinaryPrimitives.WriteUInt16LittleEndian(alpha, l16.PackedValue);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2); this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
} }
else if (pngMetadata.TransparentL8.HasValue) else
{ {
L8 l8 = pngMetadata.TransparentColor.Value.ToPixel<L8>();
alpha.Clear(); alpha.Clear();
alpha[1] = pngMetadata.TransparentL8.Value.PackedValue; alpha[1] = l8.PackedValue;
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2); this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
} }
} }
@ -1175,7 +1175,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
stream.Write(buffer); stream.Write(buffer);
uint crc = Crc32.Calculate(buffer.Slice(4)); // Write the type buffer uint crc = Crc32.Calculate(buffer[4..]); // Write the type buffer
if (data.Length > 0 && length > 0) if (data.Length > 0 && length > 0)
{ {
@ -1290,11 +1290,23 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
} }
// Use the metadata to determine what quantization depth to use if no quantizer has been set. // Use the metadata to determine what quantization depth to use if no quantizer has been set.
IQuantizer quantizer = encoder.Quantizer IQuantizer quantizer = encoder.Quantizer;
?? new WuQuantizer(new QuantizerOptions { MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) }); if (quantizer is null)
{
PngMetadata metadata = image.Metadata.GetPngMetadata();
if (metadata.ColorTable is not null)
{
// Use the provided palette in total. The caller is responsible for setting values.
quantizer = new PaletteQuantizer(metadata.ColorTable.Value);
}
else
{
quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) });
}
}
// Create quantized frame returning the palette and set the bit depth. // Create quantized frame returning the palette and set the bit depth.
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(image.GetConfiguration()); using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(image.Configuration);
frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, image); frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, image);
return frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds); return frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);

40
src/ImageSharp/Formats/Png/PngMetadata.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png; namespace SixLabors.ImageSharp.Formats.Png;
/// <summary> /// <summary>
@ -27,11 +25,12 @@ public class PngMetadata : IDeepCloneable
this.ColorType = other.ColorType; this.ColorType = other.ColorType;
this.Gamma = other.Gamma; this.Gamma = other.Gamma;
this.InterlaceMethod = other.InterlaceMethod; this.InterlaceMethod = other.InterlaceMethod;
this.HasTransparency = other.HasTransparency; this.TransparentColor = other.TransparentColor;
this.TransparentL8 = other.TransparentL8;
this.TransparentL16 = other.TransparentL16; if (other.ColorTable?.Length > 0)
this.TransparentRgb24 = other.TransparentRgb24; {
this.TransparentRgb48 = other.TransparentRgb48; this.ColorTable = other.ColorTable.Value.ToArray();
}
for (int i = 0; i < other.TextData.Count; i++) for (int i = 0; i < other.TextData.Count; i++)
{ {
@ -61,33 +60,14 @@ public class PngMetadata : IDeepCloneable
public float Gamma { get; set; } public float Gamma { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Rgb24 transparent color. /// Gets or sets the color table, if any.
/// This represents any color in an 8 bit Rgb24 encoded png that should be transparent.
/// </summary>
public Rgb24? TransparentRgb24 { get; set; }
/// <summary>
/// Gets or sets the Rgb48 transparent color.
/// This represents any color in a 16 bit Rgb24 encoded png that should be transparent.
/// </summary>
public Rgb48? TransparentRgb48 { get; set; }
/// <summary>
/// Gets or sets the 8 bit grayscale transparent color.
/// This represents any color in an 8 bit grayscale encoded png that should be transparent.
/// </summary>
public L8? TransparentL8 { get; set; }
/// <summary>
/// Gets or sets the 16 bit grayscale transparent color.
/// This represents any color in a 16 bit grayscale encoded png that should be transparent.
/// </summary> /// </summary>
public L16? TransparentL16 { get; set; } public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the image contains a transparency chunk and markers were decoded. /// Gets or sets the transparent color used with non palette based images, if a transparency chunk and markers were decoded.
/// </summary> /// </summary>
public bool HasTransparency { get; set; } public Color? TransparentColor { get; set; }
/// <summary> /// <summary>
/// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks. /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks.

175
src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

@ -18,9 +18,7 @@ internal static class PngScanlineProcessor
in PngHeader header, in PngHeader header,
ReadOnlySpan<byte> scanlineSpan, ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
bool hasTrans, Color? transparentColor)
L16 luminance16Trans,
L8 luminanceTrans)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
TPixel pixel = default; TPixel pixel = default;
@ -28,7 +26,7 @@ internal static class PngScanlineProcessor
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1); int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1);
if (!hasTrans) if (transparentColor is null)
{ {
if (header.BitDepth == 16) if (header.BitDepth == 16)
{ {
@ -55,13 +53,14 @@ internal static class PngScanlineProcessor
if (header.BitDepth == 16) if (header.BitDepth == 16)
{ {
L16 transparent = transparentColor.Value.ToPixel<L16>();
La32 source = default; La32 source = default;
int o = 0; int o = 0;
for (nuint x = 0; x < (uint)header.Width; x++, o += 2) for (nuint x = 0; x < (uint)header.Width; x++, o += 2)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
source.L = luminance; source.L = luminance;
source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; source.A = luminance.Equals(transparent.PackedValue) ? ushort.MinValue : ushort.MaxValue;
pixel.FromLa32(source); pixel.FromLa32(source);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -69,13 +68,13 @@ internal static class PngScanlineProcessor
} }
else else
{ {
byte transparent = (byte)(transparentColor.Value.ToPixel<L8>().PackedValue * scaleFactor);
La16 source = default; La16 source = default;
byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor);
for (nuint x = 0; x < (uint)header.Width; x++) for (nuint x = 0; x < (uint)header.Width; x++)
{ {
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor);
source.L = luminance; source.L = luminance;
source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; source.A = luminance.Equals(transparent) ? byte.MinValue : byte.MaxValue;
pixel.FromLa16(source); pixel.FromLa16(source);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -89,9 +88,7 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
uint pixelOffset, uint pixelOffset,
uint increment, uint increment,
bool hasTrans, Color? transparentColor)
L16 luminance16Trans,
L8 luminanceTrans)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
TPixel pixel = default; TPixel pixel = default;
@ -99,7 +96,7 @@ internal static class PngScanlineProcessor
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1); int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1);
if (!hasTrans) if (transparentColor is null)
{ {
if (header.BitDepth == 16) if (header.BitDepth == 16)
{ {
@ -126,13 +123,14 @@ internal static class PngScanlineProcessor
if (header.BitDepth == 16) if (header.BitDepth == 16)
{ {
L16 transparent = transparentColor.Value.ToPixel<L16>();
La32 source = default; La32 source = default;
int o = 0; int o = 0;
for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += 2) for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += 2)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
source.L = luminance; source.L = luminance;
source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; source.A = luminance.Equals(transparent.PackedValue) ? ushort.MinValue : ushort.MaxValue;
pixel.FromLa32(source); pixel.FromLa32(source);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -140,13 +138,13 @@ internal static class PngScanlineProcessor
} }
else else
{ {
byte transparent = (byte)(transparentColor.Value.ToPixel<L8>().PackedValue * scaleFactor);
La16 source = default; La16 source = default;
byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor);
for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++) for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++)
{ {
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor);
source.L = luminance; source.L = luminance;
source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; source.A = luminance.Equals(transparent) ? byte.MinValue : byte.MaxValue;
pixel.FromLa16(source); pixel.FromLa16(source);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -241,11 +239,10 @@ internal static class PngScanlineProcessor
in PngHeader header, in PngHeader header,
ReadOnlySpan<byte> scanlineSpan, ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
ReadOnlySpan<byte> palette, ReadOnlyMemory<Color>? palette)
byte[] paletteAlpha)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (palette.IsEmpty) if (palette is null)
{ {
PngThrowHelper.ThrowMissingPalette(); PngThrowHelper.ThrowMissingPalette();
} }
@ -253,36 +250,13 @@ internal static class PngScanlineProcessor
TPixel pixel = default; TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
ReadOnlySpan<Rgb24> palettePixels = MemoryMarshal.Cast<byte, Rgb24>(palette); ref Color paletteBase = ref MemoryMarshal.GetReference(palette.Value.Span);
ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels);
if (paletteAlpha?.Length > 0) for (nuint x = 0; x < (uint)header.Width; x++)
{ {
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha uint index = Unsafe.Add(ref scanlineSpanRef, x);
// channel and we should try to read it. pixel.FromRgba32(Unsafe.Add(ref paletteBase, index).ToRgba32());
Rgba32 rgba = default; Unsafe.Add(ref rowSpanRef, x) = pixel;
ref byte paletteAlphaRef = ref MemoryMarshal.GetArrayDataReference(paletteAlpha);
for (nuint x = 0; x < (uint)header.Width; x++)
{
uint index = Unsafe.Add(ref scanlineSpanRef, x);
rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue;
pixel.FromRgba32(rgba);
Unsafe.Add(ref rowSpanRef, x) = pixel;
}
}
else
{
for (nuint x = 0; x < (uint)header.Width; x++)
{
int index = Unsafe.Add(ref scanlineSpanRef, x);
Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index);
pixel.FromRgb24(rgb);
Unsafe.Add(ref rowSpanRef, x) = pixel;
}
} }
} }
@ -292,42 +266,24 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
uint pixelOffset, uint pixelOffset,
uint increment, uint increment,
ReadOnlySpan<byte> palette, ReadOnlyMemory<Color>? palette)
byte[] paletteAlpha)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (palette is null)
{
PngThrowHelper.ThrowMissingPalette();
}
TPixel pixel = default; TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
ReadOnlySpan<Rgb24> palettePixels = MemoryMarshal.Cast<byte, Rgb24>(palette); ref Color paletteBase = ref MemoryMarshal.GetReference(palette.Value.Span);
ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels);
if (paletteAlpha?.Length > 0) for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++)
{ {
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha uint index = Unsafe.Add(ref scanlineSpanRef, o);
// channel and we should try to read it. pixel.FromRgba32(Unsafe.Add(ref paletteBase, index).ToRgba32());
Rgba32 rgba = default; Unsafe.Add(ref rowSpanRef, x) = pixel;
ref byte paletteAlphaRef = ref MemoryMarshal.GetArrayDataReference(paletteAlpha);
for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++)
{
uint index = Unsafe.Add(ref scanlineSpanRef, o);
rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue;
rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
pixel.FromRgba32(rgba);
Unsafe.Add(ref rowSpanRef, x) = pixel;
}
}
else
{
for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++)
{
int index = Unsafe.Add(ref scanlineSpanRef, o);
Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index);
pixel.FromRgb24(rgb);
Unsafe.Add(ref rowSpanRef, x) = pixel;
}
} }
} }
@ -338,15 +294,13 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan, Span<TPixel> rowSpan,
int bytesPerPixel, int bytesPerPixel,
int bytesPerSample, int bytesPerSample,
bool hasTrans, Color? transparentColor)
Rgb48 rgb48Trans,
Rgb24 rgb24Trans)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
TPixel pixel = default; TPixel pixel = default;
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
if (!hasTrans) if (transparentColor is null)
{ {
if (header.BitDepth == 16) if (header.BitDepth == 16)
{ {
@ -372,6 +326,8 @@ internal static class PngScanlineProcessor
if (header.BitDepth == 16) if (header.BitDepth == 16)
{ {
Rgb48 transparent = transparentColor.Value.ToPixel<Rgb48>();
Rgb48 rgb48 = default; Rgb48 rgb48 = default;
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
int o = 0; int o = 0;
@ -382,7 +338,7 @@ internal static class PngScanlineProcessor
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
rgba64.Rgb = rgb48; rgba64.Rgb = rgb48;
rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; rgba64.A = rgb48.Equals(transparent) ? ushort.MinValue : ushort.MaxValue;
pixel.FromRgba64(rgba64); pixel.FromRgba64(rgba64);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -390,6 +346,8 @@ internal static class PngScanlineProcessor
} }
else else
{ {
Rgb24 transparent = transparentColor.Value.ToPixel<Rgb24>();
Rgba32 rgba32 = default; Rgba32 rgba32 = default;
ReadOnlySpan<Rgb24> rgb24Span = MemoryMarshal.Cast<byte, Rgb24>(scanlineSpan); ReadOnlySpan<Rgb24> rgb24Span = MemoryMarshal.Cast<byte, Rgb24>(scanlineSpan);
ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span);
@ -397,7 +355,7 @@ internal static class PngScanlineProcessor
{ {
ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x); ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x);
rgba32.Rgb = rgb24; rgba32.Rgb = rgb24;
rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue; rgba32.A = rgb24.Equals(transparent) ? byte.MinValue : byte.MaxValue;
pixel.FromRgba32(rgba32); pixel.FromRgba32(rgba32);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
@ -413,21 +371,19 @@ internal static class PngScanlineProcessor
uint increment, uint increment,
int bytesPerPixel, int bytesPerPixel,
int bytesPerSample, int bytesPerSample,
bool hasTrans, Color? transparentColor)
Rgb48 rgb48Trans,
Rgb24 rgb24Trans)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
TPixel pixel = default; TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
bool hasTransparency = transparentColor is not null;
if (header.BitDepth == 16) if (transparentColor is null)
{ {
if (hasTrans) if (header.BitDepth == 16)
{ {
Rgb48 rgb48 = default; Rgb48 rgb48 = default;
Rgba64 rgba64 = default;
int o = 0; int o = 0;
for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel)
{ {
@ -435,24 +391,21 @@ internal static class PngScanlineProcessor
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
rgba64.Rgb = rgb48; pixel.FromRgb48(rgb48);
rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
pixel.FromRgba64(rgba64);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
} }
} }
else else
{ {
Rgb48 rgb48 = default; Rgb24 rgb = default;
int o = 0; int o = 0;
for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel)
{ {
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); rgb.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); rgb.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); rgb.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
pixel.FromRgb48(rgb48); pixel.FromRgb24(rgb);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
} }
} }
@ -460,32 +413,40 @@ internal static class PngScanlineProcessor
return; return;
} }
if (hasTrans) if (header.BitDepth == 16)
{ {
Rgba32 rgba = default; Rgb48 transparent = transparentColor.Value.ToPixel<Rgb48>();
Rgb48 rgb48 = default;
Rgba64 rgba64 = default;
int o = 0; int o = 0;
for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel)
{ {
rgba.R = Unsafe.Add(ref scanlineSpanRef, (uint)o); rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
rgba.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
rgba.A = rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue;
pixel.FromRgba32(rgba); rgba64.Rgb = rgb48;
rgba64.A = rgb48.Equals(transparent) ? ushort.MinValue : ushort.MaxValue;
pixel.FromRgba64(rgba64);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
} }
} }
else else
{ {
Rgb24 rgb = default; Rgb24 transparent = transparentColor.Value.ToPixel<Rgb24>();
Rgba32 rgba = default;
int o = 0; int o = 0;
for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel)
{ {
rgb.R = Unsafe.Add(ref scanlineSpanRef, (uint)o); rgba.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
rgb.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample)); rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
rgb.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample))); rgba.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
rgba.A = transparent.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue;
pixel.FromRgb24(rgb); pixel.FromRgba32(rgba);
Unsafe.Add(ref rowSpanRef, x) = pixel; Unsafe.Add(ref rowSpanRef, x) = pixel;
} }
} }

4
src/ImageSharp/Formats/Qoi/QoiEncoder.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
namespace SixLabors.ImageSharp.Formats.Qoi; namespace SixLabors.ImageSharp.Formats.Qoi;
/// <summary> /// <summary>
@ -27,7 +25,7 @@ public class QoiEncoder : ImageEncoder
/// <inheritdoc /> /// <inheritdoc />
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
QoiEncoderCore encoder = new(this, image.GetMemoryAllocator(), image.GetConfiguration()); QoiEncoderCore encoder = new(this, image.Configuration);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

5
src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs

@ -33,13 +33,12 @@ internal class QoiEncoderCore : IImageEncoderInternals
/// Initializes a new instance of the <see cref="QoiEncoderCore"/> class. /// Initializes a new instance of the <see cref="QoiEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="encoder">The encoder with options.</param> /// <param name="encoder">The encoder with options.</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator" /> to use for buffer allocations.</param>
/// <param name="configuration">The configuration of the Encoder.</param> /// <param name="configuration">The configuration of the Encoder.</param>
public QoiEncoderCore(QoiEncoder encoder, MemoryAllocator memoryAllocator, Configuration configuration) public QoiEncoderCore(QoiEncoder encoder, Configuration configuration)
{ {
this.encoder = encoder; this.encoder = encoder;
this.memoryAllocator = memoryAllocator;
this.configuration = configuration; this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
} }
/// <inheritdoc /> /// <inheritdoc />

6
src/ImageSharp/Formats/Tga/TgaEncoder.cs

@ -1,12 +1,10 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
namespace SixLabors.ImageSharp.Formats.Tga; namespace SixLabors.ImageSharp.Formats.Tga;
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as a targa truevision image. /// Image encoder for writing an image to a stream as a Targa true-vision image.
/// </summary> /// </summary>
public sealed class TgaEncoder : ImageEncoder public sealed class TgaEncoder : ImageEncoder
{ {
@ -23,7 +21,7 @@ public sealed class TgaEncoder : ImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
TgaEncoderCore encoder = new(this, image.GetMemoryAllocator()); TgaEncoderCore encoder = new(this, image.Configuration.MemoryAllocator);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

2
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -112,7 +112,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
} }
else else
{ {
this.WriteImage(image.GetConfiguration(), stream, image.Frames.RootFrame); this.WriteImage(image.Configuration, stream, image.Frames.RootFrame);
} }
stream.Flush(); stream.Flush();

3
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -48,7 +47,7 @@ public class TiffEncoder : QuantizingImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
TiffEncoderCore encode = new(this, image.GetMemoryAllocator()); TiffEncoderCore encode = new(this, image.Configuration.MemoryAllocator);
encode.Encode(image, stream, cancellationToken); encode.Encode(image, stream, cancellationToken);
} }
} }

2
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -128,7 +128,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration(); this.configuration = image.Configuration;
ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata;
TiffFrameMetadata rootFrameTiffMetaData = rootFrameMetaData.GetTiffMetadata(); TiffFrameMetadata rootFrameTiffMetaData = rootFrameMetaData.GetTiffMetadata();

2
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -502,7 +502,7 @@ internal class Vp8LEncoder : IDisposable
doNotCache = true; doNotCache = true;
// Go brute force on all transforms. // Go brute force on all transforms.
foreach (EntropyIx entropyIx in Enum.GetValues(typeof(EntropyIx)).Cast<EntropyIx>()) foreach (EntropyIx entropyIx in Enum.GetValues<EntropyIx>())
{ {
// We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette. // We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette.
if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette) if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette)

2
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -82,7 +82,7 @@ public sealed class WebpEncoder : ImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
WebpEncoderCore encoder = new(this, image.GetConfiguration()); WebpEncoderCore encoder = new(this, image.Configuration);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

13
src/ImageSharp/Image.cs

@ -17,7 +17,6 @@ namespace SixLabors.ImageSharp;
public abstract partial class Image : IDisposable, IConfigurationProvider public abstract partial class Image : IDisposable, IConfigurationProvider
{ {
private bool isDisposed; private bool isDisposed;
private readonly Configuration configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image"/> class. /// Initializes a new instance of the <see cref="Image"/> class.
@ -26,12 +25,12 @@ public abstract partial class Image : IDisposable, IConfigurationProvider
/// <param name="pixelType">The pixel type information.</param> /// <param name="pixelType">The pixel type information.</param>
/// <param name="metadata">The image metadata.</param> /// <param name="metadata">The image metadata.</param>
/// <param name="size">The size in px units.</param> /// <param name="size">The size in px units.</param>
protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata? metadata, Size size) protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size)
{ {
this.configuration = configuration; this.Configuration = configuration;
this.PixelType = pixelType; this.PixelType = pixelType;
this.Size = size; this.Size = size;
this.Metadata = metadata ?? new ImageMetadata(); this.Metadata = metadata;
} }
/// <summary> /// <summary>
@ -45,7 +44,7 @@ public abstract partial class Image : IDisposable, IConfigurationProvider
internal Image( internal Image(
Configuration configuration, Configuration configuration,
PixelTypeInfo pixelType, PixelTypeInfo pixelType,
ImageMetadata? metadata, ImageMetadata metadata,
int width, int width,
int height) int height)
: this(configuration, pixelType, metadata, new Size(width, height)) : this(configuration, pixelType, metadata, new Size(width, height))
@ -53,7 +52,7 @@ public abstract partial class Image : IDisposable, IConfigurationProvider
} }
/// <inheritdoc/> /// <inheritdoc/>
Configuration IConfigurationProvider.Configuration => this.configuration; public Configuration Configuration { get; }
/// <summary> /// <summary>
/// Gets information about the image pixels. /// Gets information about the image pixels.
@ -147,7 +146,7 @@ public abstract partial class Image : IDisposable, IConfigurationProvider
/// <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 : unmanaged, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.GetConfiguration()); where TPixel2 : unmanaged, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.Configuration);
/// <summary> /// <summary>
/// Returns a copy of the image in the given pixel format. /// Returns a copy of the image in the given pixel format.

12
src/ImageSharp/ImageExtensions.cs

@ -47,7 +47,7 @@ public static partial class ImageExtensions
{ {
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
using Stream fs = source.GetConfiguration().FileSystem.Create(path); using Stream fs = source.Configuration.FileSystem.Create(path);
source.Save(fs, encoder); source.Save(fs, encoder);
} }
@ -70,7 +70,7 @@ public static partial class ImageExtensions
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
await using Stream fs = source.GetConfiguration().FileSystem.CreateAsynchronous(path); await using Stream fs = source.Configuration.FileSystem.CreateAsynchronous(path);
await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false); await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false);
} }
@ -94,14 +94,14 @@ public static partial class ImageExtensions
throw new NotSupportedException("Cannot write to the stream."); throw new NotSupportedException("Cannot write to the stream.");
} }
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.GetEncoder(format); IImageEncoder encoder = source.Configuration.ImageFormatsManager.GetEncoder(format);
if (encoder is null) if (encoder is null)
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.Configuration.ImageFormatsManager.ImageEncoders)
{ {
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
} }
@ -138,14 +138,14 @@ public static partial class ImageExtensions
throw new NotSupportedException("Cannot write to the stream."); throw new NotSupportedException("Cannot write to the stream.");
} }
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.GetEncoder(format); IImageEncoder encoder = source.Configuration.ImageFormatsManager.GetEncoder(format);
if (encoder is null) if (encoder is null)
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.Configuration.ImageFormatsManager.ImageEncoders)
{ {
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
} }

14
src/ImageSharp/ImageFrame.cs

@ -15,8 +15,6 @@ namespace SixLabors.ImageSharp;
/// </summary> /// </summary>
public abstract partial class ImageFrame : IConfigurationProvider, IDisposable public abstract partial class ImageFrame : IConfigurationProvider, IDisposable
{ {
private readonly Configuration configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageFrame"/> class. /// Initializes a new instance of the <see cref="ImageFrame"/> class.
/// </summary> /// </summary>
@ -26,10 +24,7 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable
/// <param name="metadata">The <see cref="ImageFrameMetadata"/>.</param> /// <param name="metadata">The <see cref="ImageFrameMetadata"/>.</param>
protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata)
{ {
Guard.NotNull(configuration, nameof(configuration)); this.Configuration = configuration;
Guard.NotNull(metadata, nameof(metadata));
this.configuration = configuration ?? Configuration.Default;
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
this.Metadata = metadata; this.Metadata = metadata;
@ -51,19 +46,19 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable
public ImageFrameMetadata Metadata { get; } public ImageFrameMetadata Metadata { get; }
/// <inheritdoc/> /// <inheritdoc/>
Configuration IConfigurationProvider.Configuration => this.configuration; public Configuration Configuration { get; }
/// <summary> /// <summary>
/// Gets the size of the frame. /// Gets the size of the frame.
/// </summary> /// </summary>
/// <returns>The <see cref="Size"/></returns> /// <returns>The <see cref="Size"/></returns>
public Size Size() => new Size(this.Width, this.Height); public Size Size() => new(this.Width, this.Height);
/// <summary> /// <summary>
/// Gets the bounds of the frame. /// Gets the bounds of the frame.
/// </summary> /// </summary>
/// <returns>The <see cref="Rectangle"/></returns> /// <returns>The <see cref="Rectangle"/></returns>
public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); public Rectangle Bounds() => new(0, 0, this.Width, this.Height);
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
@ -84,6 +79,7 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable
/// <summary> /// <summary>
/// Updates the size of the image frame. /// Updates the size of the image frame.
/// </summary> /// </summary>
/// <param name="size">The size.</param>
internal void UpdateSize(Size size) internal void UpdateSize(Size size)
{ {
this.Width = size.Width; this.Width = size.Width;

20
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -24,7 +24,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
// Frames are already cloned within the caller // Frames are already cloned within the caller
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, backgroundColor)); this.frames.Add(new ImageFrame<TPixel>(parent.Configuration, width, height, backgroundColor));
} }
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemoryGroup<TPixel> memorySource) internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemoryGroup<TPixel> memorySource)
@ -32,7 +32,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
// Frames are already cloned within the caller // Frames are already cloned within the caller
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, memorySource)); this.frames.Add(new ImageFrame<TPixel>(parent.Configuration, width, height, memorySource));
} }
internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames) internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames)
@ -138,7 +138,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
this.EnsureNotDisposed(); this.EnsureNotDisposed();
this.ValidateFrame(source); this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration()); ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.Configuration);
this.frames.Insert(index, clonedFrame); this.frames.Insert(index, clonedFrame);
return clonedFrame; return clonedFrame;
} }
@ -153,7 +153,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
this.EnsureNotDisposed(); this.EnsureNotDisposed();
this.ValidateFrame(source); this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration()); ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.Configuration);
this.frames.Add(clonedFrame); this.frames.Add(clonedFrame);
return clonedFrame; return clonedFrame;
} }
@ -169,7 +169,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
this.EnsureNotDisposed(); this.EnsureNotDisposed();
ImageFrame<TPixel> frame = ImageFrame.LoadPixelData( ImageFrame<TPixel> frame = ImageFrame.LoadPixelData(
this.parent.GetConfiguration(), this.parent.Configuration,
source, source,
this.RootFrame.Width, this.RootFrame.Width,
this.RootFrame.Height); this.RootFrame.Height);
@ -270,7 +270,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
this.frames.Remove(frame); this.frames.Remove(frame);
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { frame }); return new Image<TPixel>(this.parent.Configuration, this.parent.Metadata.DeepClone(), new[] { frame });
} }
/// <summary> /// <summary>
@ -285,7 +285,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
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.DeepClone(), new[] { clonedFrame }); return new Image<TPixel>(this.parent.Configuration, this.parent.Metadata.DeepClone(), new[] { clonedFrame });
} }
/// <summary> /// <summary>
@ -299,7 +299,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
this.EnsureNotDisposed(); this.EnsureNotDisposed();
ImageFrame<TPixel> frame = new( ImageFrame<TPixel> frame = new(
this.parent.GetConfiguration(), this.parent.Configuration,
this.RootFrame.Width, this.RootFrame.Width,
this.RootFrame.Height); this.RootFrame.Height);
this.frames.Add(frame); this.frames.Add(frame);
@ -365,7 +365,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
public ImageFrame<TPixel> CreateFrame(TPixel backgroundColor) public ImageFrame<TPixel> CreateFrame(TPixel backgroundColor)
{ {
ImageFrame<TPixel> frame = new( ImageFrame<TPixel> frame = new(
this.parent.GetConfiguration(), this.parent.Configuration,
this.RootFrame.Width, this.RootFrame.Width,
this.RootFrame.Height, this.RootFrame.Height,
backgroundColor); backgroundColor);
@ -414,7 +414,7 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
private ImageFrame<TPixel> CopyNonCompatibleFrame(ImageFrame source) private ImageFrame<TPixel> CopyNonCompatibleFrame(ImageFrame source)
{ {
ImageFrame<TPixel> result = new( ImageFrame<TPixel> result = new(
this.parent.GetConfiguration(), this.parent.Configuration,
source.Size(), source.Size(),
source.Metadata.DeepClone()); source.Metadata.DeepClone());
source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup);

12
src/ImageSharp/ImageFrame{TPixel}.cs

@ -66,7 +66,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.MustBeGreaterThan(height, 0, nameof(height));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>( this.PixelBuffer = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(
width, width,
height, height,
configuration.PreferContiguousImageBuffers, configuration.PreferContiguousImageBuffers,
@ -99,7 +99,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.MustBeGreaterThan(height, 0, nameof(height));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>( this.PixelBuffer = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(
width, width,
height, height,
configuration.PreferContiguousImageBuffers); configuration.PreferContiguousImageBuffers);
@ -146,7 +146,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(source, nameof(source)); Guard.NotNull(source, nameof(source));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>( this.PixelBuffer = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(
source.PixelBuffer.Width, source.PixelBuffer.Width,
source.PixelBuffer.Height, source.PixelBuffer.Height,
configuration.PreferContiguousImageBuffers); configuration.PreferContiguousImageBuffers);
@ -371,7 +371,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
} }
this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d)
=> PixelOperations<TPixel>.Instance.To(this.GetConfiguration(), s, d)); => PixelOperations<TPixel>.Instance.To(this.Configuration, s, d));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -381,7 +381,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
/// Clones the current instance. /// Clones the current instance.
/// </summary> /// </summary>
/// <returns>The <see cref="ImageFrame{TPixel}"/></returns> /// <returns>The <see cref="ImageFrame{TPixel}"/></returns>
internal ImageFrame<TPixel> Clone() => this.Clone(this.GetConfiguration()); internal ImageFrame<TPixel> Clone() => this.Clone(this.Configuration);
/// <summary> /// <summary>
/// Clones the current instance. /// Clones the current instance.
@ -396,7 +396,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
/// <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 : unmanaged, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.GetConfiguration()); where TPixel2 : unmanaged, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.Configuration);
/// <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.

4
src/ImageSharp/ImageSharp.csproj

@ -22,8 +22,8 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!--Bump to V3 prior to tagged release.--> <!--Bump to v3.1 prior to tagged release.-->
<MinVerMinimumMajorMinor>3.0</MinVerMinimumMajorMinor> <MinVerMinimumMajorMinor>3.1</MinVerMinimumMajorMinor>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>

8
src/ImageSharp/Image{TPixel}.cs

@ -78,12 +78,12 @@ public sealed class Image<TPixel> : Image
/// <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>
internal Image(Configuration configuration, int width, int height, ImageMetadata? metadata) internal Image(Configuration configuration, int width, int height, ImageMetadata? metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height) : base(configuration, PixelTypeInfo.Create<TPixel>(), metadata ?? new(), width, height)
=> 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="Buffer2D{TPixel}"/> pixel bufferx. /// wrapping an external <see cref="Buffer2D{TPixel}"/> pixel buffer.
/// </summary> /// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param> /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="pixelBuffer">Pixel buffer.</param> /// <param name="pixelBuffer">Pixel buffer.</param>
@ -129,7 +129,7 @@ public sealed class Image<TPixel> : Image
int height, int height,
TPixel backgroundColor, TPixel backgroundColor,
ImageMetadata? metadata) ImageMetadata? metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height) : base(configuration, PixelTypeInfo.Create<TPixel>(), metadata ?? new(), width, height)
=> this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor); => this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
/// <summary> /// <summary>
@ -328,7 +328,7 @@ public sealed class Image<TPixel> : Image
/// 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() => this.Clone(this.GetConfiguration()); public Image<TPixel> Clone() => this.Clone(this.Configuration);
/// <summary> /// <summary>
/// Clones the current image with the given configuration. /// Clones the current image with the given configuration.

5
src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

@ -117,6 +117,11 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato
AllocationOptions options = AllocationOptions.None) AllocationOptions options = AllocationOptions.None)
{ {
long totalLengthInBytes = totalLength * Unsafe.SizeOf<T>(); long totalLengthInBytes = totalLength * Unsafe.SizeOf<T>();
if (totalLengthInBytes < 0)
{
throw new InvalidMemoryOperationException("Attempted to allocate a MemoryGroup of a size that is not representable.");
}
if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes) if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes)
{ {
var buffer = new SharedArrayPoolBuffer<T>((int)totalLength); var buffer = new SharedArrayPoolBuffer<T>((int)totalLength);

3
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -9,9 +9,6 @@ namespace SixLabors.ImageSharp.Memory;
/// Represents a buffer of value type objects /// Represents a buffer of value type objects
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements. /// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary> /// </summary>
/// <remarks>
/// Before RC1, this class might be target of API changes, use it on your own risk!
/// </remarks>
/// <typeparam name="T">The value type.</typeparam> /// <typeparam name="T">The value type.</typeparam>
public sealed class Buffer2D<T> : IDisposable public sealed class Buffer2D<T> : IDisposable
where T : struct where T : struct

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs

@ -56,11 +56,6 @@ public abstract partial class ExifTag
/// </summary> /// </summary>
public static ExifTag<uint[]> IntergraphRegisters { get; } = new ExifTag<uint[]>(ExifTagValue.IntergraphRegisters); public static ExifTag<uint[]> IntergraphRegisters { get; } = new ExifTag<uint[]>(ExifTagValue.IntergraphRegisters);
/// <summary>
/// Gets the TimeZoneOffset exif tag.
/// </summary>
public static ExifTag<uint[]> TimeZoneOffset { get; } = new ExifTag<uint[]>(ExifTagValue.TimeZoneOffset);
/// <summary> /// <summary>
/// Gets the offset to child IFDs exif tag. /// Gets the offset to child IFDs exif tag.
/// </summary> /// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs

@ -165,4 +165,9 @@ public abstract partial class ExifTag
/// Gets the GPSDestDistance exif tag. /// Gets the GPSDestDistance exif tag.
/// </summary> /// </summary>
public static ExifTag<Rational> GPSDestDistance { get; } = new ExifTag<Rational>(ExifTagValue.GPSDestDistance); public static ExifTag<Rational> GPSDestDistance { get; } = new ExifTag<Rational>(ExifTagValue.GPSDestDistance);
/// <summary>
/// Gets the GPSHPositioningError exif tag.
/// </summary>
public static ExifTag<Rational> GPSHPositioningError { get; } = new ExifTag<Rational>(ExifTagValue.GPSHPositioningError);
} }

13
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedShortArray.cs

@ -0,0 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
/// <content/>
public abstract partial class ExifTag
{
/// <summary>
/// Gets the TimeZoneOffset exif tag.
/// </summary>
public static ExifTag<short[]> TimeZoneOffset { get; } = new ExifTag<short[]>(ExifTagValue.TimeZoneOffset);
}

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs

@ -1691,6 +1691,11 @@ internal enum ExifTagValue
/// </summary> /// </summary>
GPSDifferential = 0x001E, GPSDifferential = 0x001E,
/// <summary>
/// GPSHPositioningError
/// </summary>
GPSHPositioningError = 0x001F,
/// <summary> /// <summary>
/// Used in the Oce scanning process. /// Used in the Oce scanning process.
/// Identifies the scanticket used in the scanning process. /// Identifies the scanticket used in the scanning process.

5
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs

@ -5,6 +5,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
internal sealed class ExifSignedShortArray : ExifArrayValue<short> internal sealed class ExifSignedShortArray : ExifArrayValue<short>
{ {
public ExifSignedShortArray(ExifTag<short[]> tag)
: base(tag)
{
}
public ExifSignedShortArray(ExifTagValue tag) public ExifSignedShortArray(ExifTagValue tag)
: base(tag) : base(tag)
{ {

7
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs

@ -144,8 +144,6 @@ internal static partial class ExifValues
return new ExifLongArray(ExifTag.StripRowCounts); return new ExifLongArray(ExifTag.StripRowCounts);
case ExifTagValue.IntergraphRegisters: case ExifTagValue.IntergraphRegisters:
return new ExifLongArray(ExifTag.IntergraphRegisters); return new ExifLongArray(ExifTag.IntergraphRegisters);
case ExifTagValue.TimeZoneOffset:
return new ExifLongArray(ExifTag.TimeZoneOffset);
case ExifTagValue.SubIFDs: case ExifTagValue.SubIFDs:
return new ExifLongArray(ExifTag.SubIFDs); return new ExifLongArray(ExifTag.SubIFDs);
@ -243,6 +241,8 @@ internal static partial class ExifValues
return new ExifRational(ExifTag.GPSDestBearing); return new ExifRational(ExifTag.GPSDestBearing);
case ExifTagValue.GPSDestDistance: case ExifTagValue.GPSDestDistance:
return new ExifRational(ExifTag.GPSDestDistance); return new ExifRational(ExifTag.GPSDestDistance);
case ExifTagValue.GPSHPositioningError:
return new ExifRational(ExifTag.GPSHPositioningError);
case ExifTagValue.WhitePoint: case ExifTagValue.WhitePoint:
return new ExifRationalArray(ExifTag.WhitePoint); return new ExifRationalArray(ExifTag.WhitePoint);
@ -417,6 +417,9 @@ internal static partial class ExifValues
case ExifTagValue.Decode: case ExifTagValue.Decode:
return new ExifSignedRationalArray(ExifTag.Decode); return new ExifSignedRationalArray(ExifTag.Decode);
case ExifTagValue.TimeZoneOffset:
return new ExifSignedShortArray(ExifTag.TimeZoneOffset);
case ExifTagValue.ImageDescription: case ExifTagValue.ImageDescription:
return new ExifString(ExifTag.ImageDescription); return new ExifString(ExifTag.ImageDescription);
case ExifTagValue.Make: case ExifTagValue.Make:

4
src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs

@ -88,9 +88,9 @@ internal sealed class IccReader
foreach (IccTagTableEntry tag in tagTable) foreach (IccTagTableEntry tag in tagTable)
{ {
IccTagDataEntry entry; IccTagDataEntry entry;
if (store.ContainsKey(tag.Offset)) if (store.TryGetValue(tag.Offset, out IccTagDataEntry? value))
{ {
entry = store[tag.Offset]; entry = value;
} }
else else
{ {

2
src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs

@ -54,7 +54,7 @@ public static partial class ProcessingExtensions
public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this ImageFrame<TPixel> source, Rectangle bounds) public static Buffer2D<ulong> CalculateIntegralImage<TPixel>(this ImageFrame<TPixel> source, Rectangle bounds)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Configuration configuration = source.GetConfiguration(); Configuration configuration = source.Configuration;
var interest = Rectangle.Intersect(bounds, source.Bounds()); var interest = Rectangle.Intersect(bounds, source.Bounds());
int startY = interest.Y; int startY = interest.Y;

12
src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs

@ -22,7 +22,7 @@ public static partial class ProcessingExtensions
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception> /// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception> /// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate(this Image source, Action<IImageProcessingContext> operation) public static void Mutate(this Image source, Action<IImageProcessingContext> operation)
=> Mutate(source, source.GetConfiguration(), operation); => Mutate(source, source.Configuration, operation);
/// <summary> /// <summary>
/// Mutates the source image by applying the image operation to it. /// Mutates the source image by applying the image operation to it.
@ -57,7 +57,7 @@ public static partial class ProcessingExtensions
/// <exception cref="ImageProcessingException">The processing operation failed.</exception> /// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation) public static void Mutate<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Mutate(source, source.GetConfiguration(), operation); => Mutate(source, source.Configuration, operation);
/// <summary> /// <summary>
/// Mutates the source image by applying the image operation to it. /// Mutates the source image by applying the image operation to it.
@ -97,7 +97,7 @@ public static partial class ProcessingExtensions
/// <exception cref="ImageProcessingException">The processing operation failed.</exception> /// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations) public static void Mutate<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Mutate(source, source.GetConfiguration(), operations); => Mutate(source, source.Configuration, operations);
/// <summary> /// <summary>
/// Mutates the source image by applying the operations to it. /// Mutates the source image by applying the operations to it.
@ -135,7 +135,7 @@ public static partial class ProcessingExtensions
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception> /// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception> /// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static Image Clone(this Image source, Action<IImageProcessingContext> operation) public static Image Clone(this Image source, Action<IImageProcessingContext> operation)
=> Clone(source, source.GetConfiguration(), operation); => Clone(source, source.Configuration, operation);
/// <summary> /// <summary>
/// Creates a deep clone of the current image. The clone is then mutated by the given operation. /// Creates a deep clone of the current image. The clone is then mutated by the given operation.
@ -174,7 +174,7 @@ public static partial class ProcessingExtensions
/// <returns>The new <see cref="Image{TPixel}"/>.</returns> /// <returns>The new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation) public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Clone(source, source.GetConfiguration(), operation); => Clone(source, source.Configuration, operation);
/// <summary> /// <summary>
/// Creates a deep clone of the current image. The clone is then mutated by the given operation. /// Creates a deep clone of the current image. The clone is then mutated by the given operation.
@ -217,7 +217,7 @@ public static partial class ProcessingExtensions
/// <returns>The new <see cref="Image{TPixel}"/></returns> /// <returns>The new <see cref="Image{TPixel}"/></returns>
public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations) public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Clone(source, source.GetConfiguration(), operations); => Clone(source, source.Configuration, operations);
/// <summary> /// <summary>
/// Creates a deep clone of the current image. The clone is then mutated by the given operations. /// Creates a deep clone of the current image. The clone is then mutated by the given operations.

12
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -111,7 +111,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
public QuantizerOptions Options { get; } public QuantizerOptions Options { get; }
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlyMemory<TPixel> Palette public readonly ReadOnlyMemory<TPixel> Palette
{ {
get get
{ {
@ -362,7 +362,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
/// </summary> /// </summary>
/// <param name="source">The source data.</param> /// <param name="source">The source data.</param>
/// <param name="bounds">The bounds within the source image to quantize.</param> /// <param name="bounds">The bounds within the source image to quantize.</param>
private void Build3DHistogram(Buffer2D<TPixel> source, Rectangle bounds) private readonly void Build3DHistogram(Buffer2D<TPixel> source, Rectangle bounds)
{ {
Span<Moment> momentSpan = this.momentsOwner.GetSpan(); Span<Moment> momentSpan = this.momentsOwner.GetSpan();
@ -393,7 +393,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
/// 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>
/// <param name="allocator">The memory allocator used for allocating buffers.</param> /// <param name="allocator">The memory allocator used for allocating buffers.</param>
private void Get3DMoments(MemoryAllocator allocator) private readonly void Get3DMoments(MemoryAllocator allocator)
{ {
using IMemoryOwner<Moment> volume = allocator.Allocate<Moment>(IndexCount * IndexAlphaCount); using IMemoryOwner<Moment> volume = allocator.Allocate<Moment>(IndexCount * IndexAlphaCount);
using IMemoryOwner<Moment> area = allocator.Allocate<Moment>(IndexAlphaCount); using IMemoryOwner<Moment> area = allocator.Allocate<Moment>(IndexAlphaCount);
@ -462,7 +462,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
/// </summary> /// </summary>
/// <param name="cube">The cube.</param> /// <param name="cube">The cube.</param>
/// <returns>The <see cref="float"/>.</returns> /// <returns>The <see cref="float"/>.</returns>
private double Variance(ref Box cube) private readonly double Variance(ref Box cube)
{ {
ReadOnlySpan<Moment> momentSpan = this.momentsOwner.GetSpan(); ReadOnlySpan<Moment> momentSpan = this.momentsOwner.GetSpan();
@ -503,7 +503,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
/// <param name="cut">The cutting point.</param> /// <param name="cut">The cutting point.</param>
/// <param name="whole">The whole moment.</param> /// <param name="whole">The whole moment.</param>
/// <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, Moment whole) private readonly float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole)
{ {
ReadOnlySpan<Moment> momentSpan = this.momentsOwner.GetSpan(); ReadOnlySpan<Moment> momentSpan = this.momentsOwner.GetSpan();
Moment bottom = Bottom(ref cube, direction, momentSpan); Moment bottom = Bottom(ref cube, direction, momentSpan);
@ -634,7 +634,7 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
/// </summary> /// </summary>
/// <param name="cube">The cube.</param> /// <param name="cube">The cube.</param>
/// <param name="label">A label.</param> /// <param name="label">A label.</param>
private void Mark(ref Box cube, byte label) private readonly void Mark(ref Box cube, byte label)
{ {
Span<byte> tagSpan = this.tagsOwner.GetSpan(); Span<byte> tagSpan = this.tagsOwner.GetSpan();

2
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs

@ -38,7 +38,7 @@ internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel>
// All frames have be the same size so we only need to calculate the correct dimensions for the first frame // All frames have be the same size so we only need to calculate the correct dimensions for the first frame
using (Image<TPixel> temp = new(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() })) using (Image<TPixel> temp = new(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() }))
{ {
Configuration configuration = this.Source.GetConfiguration(); Configuration configuration = this.Source.Configuration;
// Detect the edges. // Detect the edges.
new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle); new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle);

40
tests/ImageSharp.Tests/Color/ColorTests.cs

@ -18,25 +18,42 @@ public partial class ColorTests
Assert.Equal(expected, (Rgba32)c2); Assert.Equal(expected, (Rgba32)c2);
} }
[Fact] [Theory]
public void Equality_WhenTrue() [InlineData(false)]
[InlineData(true)]
public void Equality_WhenTrue(bool highPrecision)
{ {
Color c1 = new Rgba64(100, 2000, 3000, 40000); Color c1 = new Rgba64(100, 2000, 3000, 40000);
Color c2 = new Rgba64(100, 2000, 3000, 40000); Color c2 = new Rgba64(100, 2000, 3000, 40000);
if (highPrecision)
{
c1 = Color.FromPixel(c1.ToPixel<RgbaVector>());
c2 = Color.FromPixel(c2.ToPixel<RgbaVector>());
}
Assert.True(c1.Equals(c2)); Assert.True(c1.Equals(c2));
Assert.True(c1 == c2); Assert.True(c1 == c2);
Assert.False(c1 != c2); Assert.False(c1 != c2);
Assert.True(c1.GetHashCode() == c2.GetHashCode()); Assert.True(c1.GetHashCode() == c2.GetHashCode());
} }
[Fact] [Theory]
public void Equality_WhenFalse() [InlineData(false)]
[InlineData(true)]
public void Equality_WhenFalse(bool highPrecision)
{ {
Color c1 = new Rgba64(100, 2000, 3000, 40000); Color c1 = new Rgba64(100, 2000, 3000, 40000);
Color c2 = new Rgba64(101, 2000, 3000, 40000); Color c2 = new Rgba64(101, 2000, 3000, 40000);
Color c3 = new Rgba64(100, 2000, 3000, 40001); Color c3 = new Rgba64(100, 2000, 3000, 40001);
if (highPrecision)
{
c1 = Color.FromPixel(c1.ToPixel<RgbaVector>());
c2 = Color.FromPixel(c2.ToPixel<RgbaVector>());
c3 = Color.FromPixel(c3.ToPixel<RgbaVector>());
}
Assert.False(c1.Equals(c2)); Assert.False(c1.Equals(c2));
Assert.False(c2.Equals(c3)); Assert.False(c2.Equals(c3));
Assert.False(c3.Equals(c1)); Assert.False(c3.Equals(c1));
@ -47,13 +64,20 @@ public partial class ColorTests
Assert.False(c1.Equals(null)); Assert.False(c1.Equals(null));
} }
[Fact] [Theory]
public void ToHex() [InlineData(false)]
[InlineData(true)]
public void ToHex(bool highPrecision)
{ {
string expected = "ABCD1234"; string expected = "ABCD1234";
var color = Color.ParseHex(expected); Color color = Color.ParseHex(expected);
string actual = color.ToHex();
if (highPrecision)
{
color = Color.FromPixel(color.ToPixel<RgbaVector>());
}
string actual = color.ToHex();
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }

6
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -539,7 +539,8 @@ public partial class PngDecoderTests
{ {
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance); using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
PngMetadata metadata = image.Metadata.GetPngMetadata(); PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency); Assert.NotNull(metadata.ColorTable);
Assert.Contains(metadata.ColorTable.Value.ToArray(), x => x.ToRgba32().A < 255);
} }
// https://github.com/SixLabors/ImageSharp/issues/2209 // https://github.com/SixLabors/ImageSharp/issues/2209
@ -551,7 +552,8 @@ public partial class PngDecoderTests
using MemoryStream stream = new(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
PngMetadata metadata = imageInfo.Metadata.GetPngMetadata(); PngMetadata metadata = imageInfo.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency); Assert.NotNull(metadata.ColorTable);
Assert.Contains(metadata.ColorTable.Value.ToArray(), x => x.ToRgba32().A < 255);
} }
// https://github.com/SixLabors/ImageSharp/issues/410 // https://github.com/SixLabors/ImageSharp/issues/410

37
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -449,44 +449,17 @@ public partial class PngEncoderTests
TestFile testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image(); using Image<Rgba32> input = testFile.CreateRgba32Image();
PngMetadata inMeta = input.Metadata.GetPngMetadata(); PngMetadata inMeta = input.Metadata.GetPngMetadata();
Assert.True(inMeta.HasTransparency); Assert.True(inMeta.TransparentColor.HasValue);
using MemoryStream memStream = new(); using MemoryStream memStream = new();
input.Save(memStream, PngEncoder); input.Save(memStream, PngEncoder);
memStream.Position = 0; memStream.Position = 0;
using Image<Rgba32> output = Image.Load<Rgba32>(memStream); using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
PngMetadata outMeta = output.Metadata.GetPngMetadata(); PngMetadata outMeta = output.Metadata.GetPngMetadata();
Assert.True(outMeta.HasTransparency); Assert.True(outMeta.TransparentColor.HasValue);
Assert.Equal(inMeta.TransparentColor, outMeta.TransparentColor);
switch (pngColorType) Assert.Equal(pngBitDepth, outMeta.BitDepth);
{ Assert.Equal(pngColorType, outMeta.ColorType);
case PngColorType.Grayscale:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
Assert.True(outMeta.TransparentL16.HasValue);
Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16);
}
else
{
Assert.True(outMeta.TransparentL8.HasValue);
Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8);
}
break;
case PngColorType.Rgb:
if (pngBitDepth.Equals(PngBitDepth.Bit16))
{
Assert.True(outMeta.TransparentRgb48.HasValue);
Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48);
}
else
{
Assert.True(outMeta.TransparentRgb24.HasValue);
Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24);
}
break;
}
} }
[Theory] [Theory]

4
tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs

@ -42,7 +42,7 @@ public class YuvConversionTests
{ {
// arrange // arrange
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();
Configuration config = image.GetConfiguration(); Configuration config = image.Configuration;
MemoryAllocator memoryAllocator = config.MemoryAllocator; MemoryAllocator memoryAllocator = config.MemoryAllocator;
int pixels = image.Width * image.Height; int pixels = image.Width * image.Height;
int uvWidth = (image.Width + 1) >> 1; int uvWidth = (image.Width + 1) >> 1;
@ -158,7 +158,7 @@ public class YuvConversionTests
{ {
// arrange // arrange
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();
Configuration config = image.GetConfiguration(); Configuration config = image.Configuration;
MemoryAllocator memoryAllocator = config.MemoryAllocator; MemoryAllocator memoryAllocator = config.MemoryAllocator;
int pixels = image.Width * image.Height; int pixels = image.Width * image.Height;
int uvWidth = (image.Width + 1) >> 1; int uvWidth = (image.Width + 1) >> 1;

8
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs

@ -20,7 +20,7 @@ public abstract partial class ImageFrameCollectionTests
public void AddFrame_OfDifferentPixelType() public void AddFrame_OfDifferentPixelType()
{ {
using (Image<Bgra32> sourceImage = new( using (Image<Bgra32> sourceImage = new(
this.Image.GetConfiguration(), this.Image.Configuration,
this.Image.Width, this.Image.Width,
this.Image.Height, this.Image.Height,
Color.Blue)) Color.Blue))
@ -41,7 +41,7 @@ public abstract partial class ImageFrameCollectionTests
public void InsertFrame_OfDifferentPixelType() public void InsertFrame_OfDifferentPixelType()
{ {
using (Image<Bgra32> sourceImage = new( using (Image<Bgra32> sourceImage = new(
this.Image.GetConfiguration(), this.Image.Configuration,
this.Image.Width, this.Image.Width,
this.Image.Height, this.Image.Height,
Color.Blue)) Color.Blue))
@ -278,8 +278,8 @@ public abstract partial class ImageFrameCollectionTests
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image source = provider.GetImage(); using Image source = provider.GetImage();
using Image<TPixel> dest = new(source.GetConfiguration(), source.Width, source.Height); using Image<TPixel> dest = new(source.Configuration, source.Width, source.Height);
// Giphy.gif has 5 frames // Giphy.gif has 5 frames
ImportFrameAs<Bgra32>(source.Frames, dest.Frames, 0); ImportFrameAs<Bgra32>(source.Frames, dest.Frames, 0);
ImportFrameAs<Argb32>(source.Frames, dest.Frames, 1); ImportFrameAs<Argb32>(source.Frames, dest.Frames, 1);

2
tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

@ -70,7 +70,7 @@ public partial class ImageTests
{ {
using Image<Rgba32> image = new(5, 5); using Image<Rgba32> image = new(5, 5);
string ext = Path.GetExtension(filename); string ext = Path.GetExtension(filename);
image.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat format); image.Configuration.ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat format);
Assert.Equal(mimeType, format!.DefaultMimeType); Assert.Equal(mimeType, format!.DefaultMimeType);
using MemoryStream stream = new(); using MemoryStream stream = new();

6
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -136,7 +136,7 @@ public partial class ImageTests
ref Rgba32 pixel0 = ref imageMem.Span[0]; ref Rgba32 pixel0 = ref imageMem.Span[0];
Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); Assert.True(Unsafe.AreSame(ref array[0], ref pixel0));
Assert.Equal(cfg, image.GetConfiguration()); Assert.Equal(cfg, image.Configuration);
Assert.Equal(metaData, image.Metadata); Assert.Equal(metaData, image.Metadata);
} }
} }
@ -239,7 +239,7 @@ public partial class ImageTests
ref Rgba32 pixel0 = ref imageMem.Span[0]; ref Rgba32 pixel0 = ref imageMem.Span[0];
Assert.True(Unsafe.AreSame(ref Unsafe.As<byte, Rgba32>(ref array[0]), ref pixel0)); Assert.True(Unsafe.AreSame(ref Unsafe.As<byte, Rgba32>(ref array[0]), ref pixel0));
Assert.Equal(cfg, image.GetConfiguration()); Assert.Equal(cfg, image.Configuration);
Assert.Equal(metaData, image.Metadata); Assert.Equal(metaData, image.Metadata);
} }
} }
@ -336,7 +336,7 @@ public partial class ImageTests
ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1]; ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1];
Assert.True(Unsafe.AreSame(ref array[array.Length - 1], ref pixel_1)); Assert.True(Unsafe.AreSame(ref array[array.Length - 1], ref pixel_1));
Assert.Equal(cfg, image.GetConfiguration()); Assert.Equal(cfg, image.Configuration);
Assert.Equal(metaData, image.Metadata); Assert.Equal(metaData, image.Metadata);
} }
} }

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

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.Memory;
@ -31,10 +32,14 @@ public partial class ImageTests
Assert.Equal(11 * 23, imageMem.Length); Assert.Equal(11 * 23, imageMem.Length);
image.ComparePixelBufferTo(default(Rgba32)); image.ComparePixelBufferTo(default(Rgba32));
Assert.Equal(Configuration.Default, image.GetConfiguration()); Assert.Equal(Configuration.Default, image.Configuration);
} }
} }
[Fact]
public void Width_Height_SizeNotRepresentable_ThrowsInvalidImageOperationException()
=> Assert.Throws<InvalidMemoryOperationException>(() => new Image<Rgba32>(int.MaxValue, int.MaxValue));
[Fact] [Fact]
public void Configuration_Width_Height() public void Configuration_Width_Height()
{ {
@ -48,7 +53,7 @@ public partial class ImageTests
Assert.Equal(11 * 23, imageMem.Length); Assert.Equal(11 * 23, imageMem.Length);
image.ComparePixelBufferTo(default(Rgba32)); image.ComparePixelBufferTo(default(Rgba32));
Assert.Equal(configuration, image.GetConfiguration()); Assert.Equal(configuration, image.Configuration);
} }
} }
@ -66,7 +71,7 @@ public partial class ImageTests
Assert.Equal(11 * 23, imageMem.Length); Assert.Equal(11 * 23, imageMem.Length);
image.ComparePixelBufferTo(color); image.ComparePixelBufferTo(color);
Assert.Equal(configuration, image.GetConfiguration()); Assert.Equal(configuration, image.Configuration);
} }
} }
@ -83,7 +88,7 @@ public partial class ImageTests
{ {
Assert.Equal(21, image.Width); Assert.Equal(21, image.Width);
Assert.Equal(22, image.Height); Assert.Equal(22, image.Height);
Assert.Same(configuration, image.GetConfiguration()); Assert.Same(configuration, image.Configuration);
Assert.Same(metadata, image.Metadata); Assert.Same(metadata, image.Metadata);
Assert.Equal(dirtyValue, image[5, 5].PackedValue); Assert.Equal(dirtyValue, image[5, 5].PackedValue);

7
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs

@ -107,6 +107,13 @@ public class UniformUnmanagedPoolMemoryAllocatorTests
} }
} }
[Fact]
public void AllocateGroup_SizeInBytesOverLongMaxValue_ThrowsInvalidMemoryOperationException()
{
var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(null);
Assert.Throws<InvalidMemoryOperationException>(() => allocator.AllocateGroup<S4>(int.MaxValue * (long)int.MaxValue, int.MaxValue));
}
[Fact] [Fact]
public unsafe void Allocate_MemoryIsPinnableMultipleTimes() public unsafe void Allocate_MemoryIsPinnableMultipleTimes()
{ {

24
tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs

@ -70,8 +70,7 @@ public class ExifValuesTests
{ ExifTag.JPEGDCTables }, { ExifTag.JPEGDCTables },
{ ExifTag.JPEGACTables }, { ExifTag.JPEGACTables },
{ ExifTag.StripRowCounts }, { ExifTag.StripRowCounts },
{ ExifTag.IntergraphRegisters }, { ExifTag.IntergraphRegisters }
{ ExifTag.TimeZoneOffset }
}; };
public static TheoryData<ExifTag> NumberTags => new TheoryData<ExifTag> public static TheoryData<ExifTag> NumberTags => new TheoryData<ExifTag>
@ -129,6 +128,7 @@ public class ExifValuesTests
{ ExifTag.GPSImgDirection }, { ExifTag.GPSImgDirection },
{ ExifTag.GPSDestBearing }, { ExifTag.GPSDestBearing },
{ ExifTag.GPSDestDistance }, { ExifTag.GPSDestDistance },
{ ExifTag.GPSHPositioningError },
}; };
public static TheoryData<ExifTag> RationalArrayTags => new TheoryData<ExifTag> public static TheoryData<ExifTag> RationalArrayTags => new TheoryData<ExifTag>
@ -235,6 +235,11 @@ public class ExifValuesTests
{ ExifTag.Decode } { ExifTag.Decode }
}; };
public static TheoryData<ExifTag> SignedShortArrayTags => new TheoryData<ExifTag>
{
{ ExifTag.TimeZoneOffset }
};
public static TheoryData<ExifTag> StringTags => new TheoryData<ExifTag> public static TheoryData<ExifTag> StringTags => new TheoryData<ExifTag>
{ {
{ ExifTag.ImageDescription }, { ExifTag.ImageDescription },
@ -559,6 +564,21 @@ public class ExifValuesTests
Assert.Equal(expected, typed.Value); Assert.Equal(expected, typed.Value);
} }
[Theory]
[MemberData(nameof(SignedShortArrayTags))]
public void ExifSignedShortArrayTests(ExifTag tag)
{
short[] expected = new short[] { 21, 42 };
ExifValue value = ExifValues.Create(tag);
Assert.False(value.TrySetValue(expected.ToString()));
Assert.True(value.TrySetValue(expected));
var typed = (ExifSignedShortArray)value;
Assert.Equal(expected, typed.Value);
}
[Theory] [Theory]
[MemberData(nameof(StringTags))] [MemberData(nameof(StringTags))]
public void ExifStringTests(ExifTag tag) public void ExifStringTests(ExifTag tag)

2
tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs

@ -22,7 +22,7 @@ public abstract class BaseImageOperationsExtensionTest : IDisposable
this.options = new GraphicsOptions { Antialias = false }; this.options = new GraphicsOptions { Antialias = false };
this.source = new Image<Rgba32>(91 + 324, 123 + 56); this.source = new Image<Rgba32>(91 + 324, 123 + 56);
this.rect = new Rectangle(91, 123, 324, 56); // make this random? this.rect = new Rectangle(91, 123, 324, 56); // make this random?
this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(this.source.GetConfiguration(), this.source, false); this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(this.source.Configuration, this.source, false);
this.internalOperations.SetGraphicsOptions(this.options); this.internalOperations.SetGraphicsOptions(this.options);
this.operations = this.internalOperations; this.operations = this.internalOperations;
} }

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -65,7 +65,7 @@ public class BokehBlurTest
// Make sure the kernel components are the same // Make sure the kernel components are the same
using Image<Rgb24> image = new(1, 1); using Image<Rgb24> image = new(1, 1);
Configuration configuration = image.GetConfiguration(); Configuration configuration = image.Configuration;
BokehBlurProcessor definition = new(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); BokehBlurProcessor definition = new(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma);
using BokehBlurProcessor<Rgb24> processor = (BokehBlurProcessor<Rgb24>)definition.CreatePixelSpecificProcessor(configuration, image, image.Bounds); using BokehBlurProcessor<Rgb24> processor = (BokehBlurProcessor<Rgb24>)definition.CreatePixelSpecificProcessor(configuration, image, image.Bounds);

2
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs

@ -28,7 +28,7 @@ public class ExactImageComparer : ImageComparer
var bBuffer = new Rgba64[width]; var bBuffer = new Rgba64[width];
var differences = new List<PixelDifference>(); var differences = new List<PixelDifference>();
Configuration configuration = expected.GetConfiguration(); Configuration configuration = expected.Configuration;
Buffer2D<TPixelA> expectedBuffer = expected.PixelBuffer; Buffer2D<TPixelA> expectedBuffer = expected.PixelBuffer;
Buffer2D<TPixelB> actualBuffer = actual.PixelBuffer; Buffer2D<TPixelB> actualBuffer = actual.PixelBuffer;

2
tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs

@ -72,7 +72,7 @@ public class TolerantImageComparer : ImageComparer
float totalDifference = 0F; float totalDifference = 0F;
var differences = new List<PixelDifference>(); var differences = new List<PixelDifference>();
Configuration configuration = expected.GetConfiguration(); Configuration configuration = expected.Configuration;
Buffer2D<TPixelA> expectedBuffer = expected.PixelBuffer; Buffer2D<TPixelA> expectedBuffer = expected.PixelBuffer;
Buffer2D<TPixelB> actualBuffer = actual.PixelBuffer; Buffer2D<TPixelB> actualBuffer = actual.PixelBuffer;

6
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
@ -15,10 +14,7 @@ public sealed class ImageSharpPngEncoderWithDefaultConfiguration : PngEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
Configuration configuration = Configuration.Default; using PngEncoderCore encoder = new(Configuration.Default, this);
MemoryAllocator allocator = configuration.MemoryAllocator;
using PngEncoderCore encoder = new(allocator, configuration, this);
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

8
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs

@ -45,7 +45,7 @@ public static class SystemDrawingBridge
long sourceRowByteCount = data.Stride; long sourceRowByteCount = data.Stride;
long destRowByteCount = w * sizeof(Bgra32); long destRowByteCount = w * sizeof(Bgra32);
Configuration configuration = image.GetConfiguration(); Configuration configuration = image.Configuration;
image.ProcessPixelRows(accessor => image.ProcessPixelRows(accessor =>
{ {
using IMemoryOwner<Bgra32> workBuffer = Configuration.Default.MemoryAllocator.Allocate<Bgra32>(w); using IMemoryOwner<Bgra32> workBuffer = Configuration.Default.MemoryAllocator.Allocate<Bgra32>(w);
@ -104,7 +104,7 @@ public static class SystemDrawingBridge
long sourceRowByteCount = data.Stride; long sourceRowByteCount = data.Stride;
long destRowByteCount = w * sizeof(Bgr24); long destRowByteCount = w * sizeof(Bgr24);
Configuration configuration = image.GetConfiguration(); Configuration configuration = image.Configuration;
Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer; Buffer2D<TPixel> imageBuffer = image.Frames.RootFrame.PixelBuffer;
using (IMemoryOwner<Bgr24> workBuffer = Configuration.Default.MemoryAllocator.Allocate<Bgr24>(w)) using (IMemoryOwner<Bgr24> workBuffer = Configuration.Default.MemoryAllocator.Allocate<Bgr24>(w))
@ -134,7 +134,7 @@ public static class SystemDrawingBridge
internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap<TPixel>(Image<TPixel> image) internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Configuration configuration = image.GetConfiguration(); Configuration configuration = image.Configuration;
int w = image.Width; int w = image.Width;
int h = image.Height; int h = image.Height;
@ -148,7 +148,7 @@ public static class SystemDrawingBridge
long sourceRowByteCount = w * sizeof(Bgra32); long sourceRowByteCount = w * sizeof(Bgra32);
image.ProcessPixelRows(accessor => image.ProcessPixelRows(accessor =>
{ {
using IMemoryOwner<Bgra32> workBuffer = image.GetConfiguration().MemoryAllocator.Allocate<Bgra32>(w); using IMemoryOwner<Bgra32> workBuffer = image.Configuration.MemoryAllocator.Allocate<Bgra32>(w);
fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) fixed (Bgra32* sourcePtr = &workBuffer.GetReference())
{ {
for (int y = 0; y < h; y++) for (int y = 0; y < h; y++)

4
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -342,8 +342,8 @@ public class TestImageProviderTests
using Image<TPixel> image2 = provider.GetImage(); using Image<TPixel> image2 = provider.GetImage();
using Image<TPixel> image3 = provider.GetImage(); using Image<TPixel> image3 = provider.GetImage();
Assert.Same(customConfiguration, image2.GetConfiguration()); Assert.Same(customConfiguration, image2.Configuration);
Assert.Same(customConfiguration, image3.GetConfiguration()); Assert.Same(customConfiguration, image3.Configuration);
} }
} }

Loading…
Cancel
Save