Browse Source

Merge pull request #2751 from SixLabors/js/format-conversion

Add API for metadata conversion between formats.
pull/2786/head
James Jackson-South 2 years ago
committed by GitHub
parent
commit
8da27c94fa
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 39
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 62
      src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs
  3. 14
      src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
  4. 19
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  5. 1
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  6. 26
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  7. 113
      src/ImageSharp/Formats/Bmp/BmpMetadata.cs
  8. 20
      src/ImageSharp/Formats/Bmp/MetadataExtensions.cs
  9. 18
      src/ImageSharp/Formats/Cur/CurDecoderCore.cs
  10. 133
      src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
  11. 173
      src/ImageSharp/Formats/Cur/CurMetadata.cs
  12. 45
      src/ImageSharp/Formats/Cur/MetadataExtensions.cs
  13. 20
      src/ImageSharp/Formats/EncodingType.cs
  14. 54
      src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs
  15. 70
      src/ImageSharp/Formats/FormatConnectingMetadata.cs
  16. 23
      src/ImageSharp/Formats/FrameBlendMode.cs
  17. 20
      src/ImageSharp/Formats/FrameColorTableMode.cs
  18. 38
      src/ImageSharp/Formats/FrameDisposalMode.cs
  19. 20
      src/ImageSharp/Formats/Gif/GifColorTableMode.cs
  20. 32
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  21. 37
      src/ImageSharp/Formats/Gif/GifDisposalMethod.cs
  22. 2
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  23. 75
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  24. 76
      src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
  25. 58
      src/ImageSharp/Formats/Gif/GifMetadata.cs
  26. 105
      src/ImageSharp/Formats/Gif/MetadataExtensions.cs
  27. 6
      src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
  28. 33
      src/ImageSharp/Formats/IFormatFrameMetadata.cs
  29. 41
      src/ImageSharp/Formats/IFormatMetadata.cs
  30. 50
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  31. 22
      src/ImageSharp/Formats/IImageEncoderInternals.cs
  32. 6
      src/ImageSharp/Formats/IImageFormat.cs
  33. 16
      src/ImageSharp/Formats/Ico/IcoDecoderCore.cs
  34. 133
      src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
  35. 159
      src/ImageSharp/Formats/Ico/IcoMetadata.cs
  36. 45
      src/ImageSharp/Formats/Ico/MetadataExtensions.cs
  37. 46
      src/ImageSharp/Formats/Icon/IconDecoderCore.cs
  38. 6
      src/ImageSharp/Formats/Icon/IconEncoderCore.cs
  39. 26
      src/ImageSharp/Formats/ImageDecoder.cs
  40. 127
      src/ImageSharp/Formats/ImageDecoderCore.cs
  41. 81
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  42. 7
      src/ImageSharp/Formats/ImageEncoder.cs
  43. 4
      src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs
  44. 6
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  45. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
  46. 2
      src/ImageSharp/Formats/Jpeg/JpegColorType.cs
  47. 42
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  48. 2
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  49. 18
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs
  50. 12
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  51. 138
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  52. 21
      src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs
  53. 25
      src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs
  54. 20
      src/ImageSharp/Formats/Pbm/MetadataExtensions.cs
  55. 26
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  56. 2
      src/ImageSharp/Formats/Pbm/PbmEncoder.cs
  57. 3
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  58. 96
      src/ImageSharp/Formats/Pbm/PbmMetadata.cs
  59. 20
      src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
  60. 84
      src/ImageSharp/Formats/Png/MetadataExtensions.cs
  61. 22
      src/ImageSharp/Formats/Png/PngBlendMethod.cs
  62. 6
      src/ImageSharp/Formats/Png/PngDecoder.cs
  63. 87
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  64. 25
      src/ImageSharp/Formats/Png/PngDisposalMethod.cs
  65. 149
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  66. 56
      src/ImageSharp/Formats/Png/PngFrameMetadata.cs
  67. 165
      src/ImageSharp/Formats/Png/PngMetadata.cs
  68. 20
      src/ImageSharp/Formats/Qoi/MetadataExtensions.cs
  69. 23
      src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
  70. 10
      src/ImageSharp/Formats/Qoi/QoiEncoderCore.cs
  71. 60
      src/ImageSharp/Formats/Qoi/QoiMetadata.cs
  72. 20
      src/ImageSharp/Formats/Tga/MetadataExtensions.cs
  73. 8
      src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
  74. 20
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  75. 110
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  76. 71
      src/ImageSharp/Formats/Tga/TgaMetadata.cs
  77. 2
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs
  78. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
  79. 45
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  80. 2
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  81. 27
      src/ImageSharp/Formats/Tiff/MetadataExtensions.cs
  82. 8
      src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
  83. 17
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  84. 16
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  85. 78
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  86. 2
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  87. 245
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  88. 32
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  89. 152
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs
  90. 20
      src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
  91. 4
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  92. 8
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  93. 75
      src/ImageSharp/Formats/Webp/MetadataExtensions.cs
  94. 4
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  95. 4
      src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs
  96. 22
      src/ImageSharp/Formats/Webp/WebpBlendMethod.cs
  97. 33
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  98. 25
      src/ImageSharp/Formats/Webp/WebpColorType.cs
  99. 50
      src/ImageSharp/Formats/Webp/WebpCommonUtils.cs
  100. 37
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

39
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -10,10 +10,13 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -129,6 +132,7 @@ internal static class AotCompilerTools
AotCompileImageDecoderInternals<TPixel>();
AotCompileImageEncoders<TPixel>();
AotCompileImageDecoders<TPixel>();
AotCompileSpectralConverter<TPixel>();
AotCompileImageProcessors<TPixel>();
AotCompileGenericImageProcessors<TPixel>();
AotCompileResamplers<TPixel>();
@ -195,39 +199,41 @@ internal static class AotCompilerTools
=> default(DefaultImageOperationsProviderFactory).CreateImageProcessingContext<TPixel>(default, default, default);
/// <summary>
/// This method pre-seeds the all <see cref="IImageEncoderInternals"/> in the AoT compiler.
/// This method pre-seeds the all core encoders in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileImageEncoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(WebpEncoderCore).Encode<TPixel>(default, default, default);
default(BmpEncoderCore).Encode<TPixel>(default, default, default);
default(GifEncoderCore).Encode<TPixel>(default, default, default);
default(JpegEncoderCore).Encode<TPixel>(default, default, default);
default(PbmEncoderCore).Encode<TPixel>(default, default, default);
default(PngEncoderCore).Encode<TPixel>(default, default, default);
default(QoiEncoderCore).Encode<TPixel>(default, default, default);
default(TgaEncoderCore).Encode<TPixel>(default, default, default);
default(TiffEncoderCore).Encode<TPixel>(default, default, default);
default(WebpEncoderCore).Encode<TPixel>(default, default, default);
}
/// <summary>
/// This method pre-seeds the all <see cref="IImageDecoderInternals"/> in the AoT compiler.
/// This method pre-seeds the all <see cref="ImageDecoderCore"/> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileImageDecoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(WebpDecoderCore).Decode<TPixel>(default, default);
default(BmpDecoderCore).Decode<TPixel>(default, default);
default(GifDecoderCore).Decode<TPixel>(default, default);
default(JpegDecoderCore).Decode<TPixel>(default, default);
default(PbmDecoderCore).Decode<TPixel>(default, default);
default(PngDecoderCore).Decode<TPixel>(default, default);
default(TgaDecoderCore).Decode<TPixel>(default, default);
default(TiffDecoderCore).Decode<TPixel>(default, default);
default(BmpDecoderCore).Decode<TPixel>(default, default, default);
default(GifDecoderCore).Decode<TPixel>(default, default, default);
default(JpegDecoderCore).Decode<TPixel>(default, default, default);
default(PbmDecoderCore).Decode<TPixel>(default, default, default);
default(PngDecoderCore).Decode<TPixel>(default, default, default);
default(QoiDecoderCore).Decode<TPixel>(default, default, default);
default(TgaDecoderCore).Decode<TPixel>(default, default, default);
default(TiffDecoderCore).Decode<TPixel>(default, default, default);
default(WebpDecoderCore).Decode<TPixel>(default, default, default);
}
/// <summary>
@ -266,6 +272,17 @@ internal static class AotCompilerTools
AotCompileImageDecoder<TPixel, TiffDecoder>();
}
[Preserve]
private static void AotCompileSpectralConverter<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(SpectralConverter<TPixel>).GetPixelBuffer(default);
default(GrayJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
default(RgbJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
default(TiffJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
default(TiffOldJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
}
/// <summary>
/// This method pre-seeds the <see cref="IImageEncoder"/> in the AoT compiler.
/// </summary>

62
src/ImageSharp/Formats/AnimatedImageFrameMetadata.cs

@ -30,65 +30,3 @@ internal class AnimatedImageFrameMetadata
/// </summary>
public FrameDisposalMode DisposalMode { get; set; }
}
#pragma warning disable SA1201 // Elements should appear in the correct order
internal enum FrameBlendMode
#pragma warning restore SA1201 // Elements should appear in the correct order
{
/// <summary>
/// Do not blend. Render the current frame on the canvas by overwriting the rectangle covered by the current frame.
/// </summary>
Source = 0,
/// <summary>
/// Blend the current frame with the previous frame in the animation sequence within the rectangle covered
/// by the current frame.
/// If the current has any transparent areas, the corresponding areas of the previous frame will be visible
/// through these transparent regions.
/// </summary>
Over = 1
}
internal enum FrameDisposalMode
{
/// <summary>
/// No disposal specified.
/// The decoder is not required to take any action.
/// </summary>
Unspecified = 0,
/// <summary>
/// Do not dispose. The current frame is not disposed of, or in other words, not cleared or altered when moving to
/// the next frame. This means that the next frame is drawn over the current frame, and if the next frame contains
/// transparency, the previous frame will be visible through these transparent areas.
/// </summary>
DoNotDispose = 1,
/// <summary>
/// Restore to background color. When transitioning to the next frame, the area occupied by the current frame is
/// filled with the background color specified in the image metadata.
/// This effectively erases the current frame by replacing it with the background color before the next frame is displayed.
/// </summary>
RestoreToBackground = 2,
/// <summary>
/// Restore to previous. This method restores the area affected by the current frame to what it was before the
/// current frame was displayed. It essentially "undoes" the current frame, reverting to the state of the image
/// before the frame was displayed, then the next frame is drawn. This is useful for animations where only a small
/// part of the image changes from frame to frame.
/// </summary>
RestoreToPrevious = 3
}
internal enum FrameColorTableMode
{
/// <summary>
/// The frame uses the shared color table specified by the image metadata.
/// </summary>
Global,
/// <summary>
/// The frame uses a color table specified by the frame metadata.
/// </summary>
Local
}

14
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

@ -11,35 +11,35 @@ public enum BmpBitsPerPixel : short
/// <summary>
/// 1 bit per pixel.
/// </summary>
Pixel1 = 1,
Bit1 = 1,
/// <summary>
/// 2 bits per pixel.
/// </summary>
Pixel2 = 2,
Bit2 = 2,
/// <summary>
/// 4 bits per pixel.
/// </summary>
Pixel4 = 4,
Bit4 = 4,
/// <summary>
/// 8 bits per pixel. Each pixel consists of 1 byte.
/// </summary>
Pixel8 = 8,
Bit8 = 8,
/// <summary>
/// 16 bits per pixel. Each pixel consists of 2 bytes.
/// </summary>
Pixel16 = 16,
Bit16 = 16,
/// <summary>
/// 24 bits per pixel. Each pixel consists of 3 bytes.
/// </summary>
Pixel24 = 24,
Bit24 = 24,
/// <summary>
/// 32 bits per pixel. Each pixel consists of 4 bytes.
/// </summary>
Pixel32 = 32
Bit32 = 32
}

19
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp;
/// <remarks>
/// A useful decoding source example can be found at <see href="https://dxr.mozilla.org/mozilla-central/source/image/decoders/nsBMPDecoder.cpp"/>
/// </remarks>
internal sealed class BmpDecoderCore : IImageDecoderInternals
internal sealed class BmpDecoderCore : ImageDecoderCore
{
/// <summary>
/// The default mask for the red part of the color for 16 bit rgb bitmaps.
@ -114,8 +114,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="options">The options.</param>
public BmpDecoderCore(BmpDecoderOptions options)
: base(options.GeneralOptions)
{
this.Options = options.GeneralOptions;
this.rleSkippedPixelHandling = options.RleSkippedPixelHandling;
this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
@ -125,14 +125,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public DecoderOptions Options { get; }
/// <inheritdoc />
public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
Image<TPixel>? image = null;
try
@ -224,10 +217,10 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), new(this.infoHeader.Width, this.infoHeader.Height), this.metadata);
return new ImageInfo(new(this.infoHeader.Width, this.infoHeader.Height), this.metadata);
}
/// <summary>
@ -1463,6 +1456,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType;
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
this.Dimensions = new(this.infoHeader.Width, this.infoHeader.Height);
}
/// <summary>

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

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.Bmp;

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

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp;
/// <summary>
/// Image encoder for writing an image to a stream as a Windows bitmap.
/// </summary>
internal sealed class BmpEncoderCore : IImageEncoderInternals
internal sealed class BmpEncoderCore
{
/// <summary>
/// The amount to pad each row by.
@ -146,10 +146,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
int colorPaletteSize = this.bitsPerPixel switch
{
BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit,
BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit,
BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit,
BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit,
BmpBitsPerPixel.Bit8 => ColorPaletteSize8Bit,
BmpBitsPerPixel.Bit4 => ColorPaletteSize4Bit,
BmpBitsPerPixel.Bit2 => ColorPaletteSize2Bit,
BmpBitsPerPixel.Bit1 => ColorPaletteSize1Bit,
_ => 0
};
@ -248,7 +248,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
clrUsed: 0,
clrImportant: 0);
if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Bit32)
{
infoHeader.AlphaMask = Rgba32AlphaMask;
infoHeader.RedMask = Rgba32RedMask;
@ -351,31 +351,31 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer;
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
case BmpBitsPerPixel.Bit32:
this.Write32BitPixelData(configuration, stream, pixels);
break;
case BmpBitsPerPixel.Pixel24:
case BmpBitsPerPixel.Bit24:
this.Write24BitPixelData(configuration, stream, pixels);
break;
case BmpBitsPerPixel.Pixel16:
case BmpBitsPerPixel.Bit16:
this.Write16BitPixelData(configuration, stream, pixels);
break;
case BmpBitsPerPixel.Pixel8:
case BmpBitsPerPixel.Bit8:
this.Write8BitPixelData(configuration, stream, image);
break;
case BmpBitsPerPixel.Pixel4:
case BmpBitsPerPixel.Bit4:
this.Write4BitPixelData(configuration, stream, image);
break;
case BmpBitsPerPixel.Pixel2:
case BmpBitsPerPixel.Bit2:
this.Write2BitPixelData(configuration, stream, image);
break;
case BmpBitsPerPixel.Pixel1:
case BmpBitsPerPixel.Bit1:
this.Write1BitPixelData(configuration, stream, image);
break;
}

113
src/ImageSharp/Formats/Bmp/BmpMetadata.cs

@ -1,12 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
// TODO: Add color table information.
namespace SixLabors.ImageSharp.Formats.Bmp;
/// <summary>
/// Provides Bmp specific metadata information for the image.
/// </summary>
public class BmpMetadata : IDeepCloneable
public class BmpMetadata : IFormatMetadata<BmpMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="BmpMetadata"/> class.
@ -38,7 +41,7 @@ public class BmpMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Bit24;
/// <summary>
/// Gets or sets the color table, if any.
@ -46,5 +49,109 @@ public class BmpMetadata : IDeepCloneable
public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new BmpMetadata(this);
public static BmpMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
return bpp switch
{
1 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit1 },
2 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit2 },
<= 4 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit4 },
<= 8 => new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Bit8 },
<= 16 => new BmpMetadata
{
BitsPerPixel = BmpBitsPerPixel.Bit16,
InfoHeaderType = BmpInfoHeaderType.WinVersion3
},
<= 24 => new BmpMetadata
{
BitsPerPixel = BmpBitsPerPixel.Bit24,
InfoHeaderType = BmpInfoHeaderType.WinVersion4
},
_ => new BmpMetadata
{
BitsPerPixel = BmpBitsPerPixel.Bit32,
InfoHeaderType = BmpInfoHeaderType.WinVersion5
}
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BitsPerPixel;
PixelAlphaRepresentation alpha = this.InfoHeaderType switch
{
BmpInfoHeaderType.WinVersion2 or
BmpInfoHeaderType.Os2Version2Short or
BmpInfoHeaderType.WinVersion3 or
BmpInfoHeaderType.AdobeVersion3 or
BmpInfoHeaderType.Os2Version2 => PixelAlphaRepresentation.None,
BmpInfoHeaderType.AdobeVersion3WithAlpha or
BmpInfoHeaderType.WinVersion4 or
BmpInfoHeaderType.WinVersion5 or
_ => bpp < 32 ? PixelAlphaRepresentation.None : PixelAlphaRepresentation.Unassociated
};
PixelComponentInfo info;
PixelColorType color;
switch (this.BitsPerPixel)
{
case BmpBitsPerPixel.Bit1:
info = PixelComponentInfo.Create(1, bpp, 1);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit2:
info = PixelComponentInfo.Create(1, bpp, 2);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit4:
info = PixelComponentInfo.Create(1, bpp, 4);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit8:
info = PixelComponentInfo.Create(1, bpp, 8);
color = PixelColorType.Indexed;
break;
// Could be 555 with padding but 565 is more common in newer bitmaps and offers
// greater accuracy due to extra green precision.
case BmpBitsPerPixel.Bit16:
info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit24:
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit32 or _:
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
break;
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
EncodingType = this.BitsPerPixel <= BmpBitsPerPixel.Bit8
? EncodingType.Lossy
: EncodingType.Lossless,
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public BmpMetadata DeepClone() => new(this);
}

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

@ -1,20 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the bmp format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="BmpMetadata"/>.</returns>
public static BmpMetadata GetBmpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(BmpFormat.Instance);
}

18
src/ImageSharp/Formats/Cur/CurDecoderCore.cs

@ -15,16 +15,30 @@ internal sealed class CurDecoderCore : IconDecoderCore
}
protected override void SetFrameMetadata(
ImageFrameMetadata metadata,
ImageMetadata imageMetadata,
ImageFrameMetadata frameMetadata,
int index,
in IconDirEntry entry,
IconFrameCompression compression,
BmpBitsPerPixel bitsPerPixel,
ReadOnlyMemory<Color>? colorTable)
{
CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata();
CurFrameMetadata curFrameMetadata = frameMetadata.GetCurMetadata();
curFrameMetadata.FromIconDirEntry(entry);
curFrameMetadata.Compression = compression;
curFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
curFrameMetadata.ColorTable = colorTable;
if (index == 0)
{
CurMetadata curMetadata = imageMetadata.GetCurMetadata();
curMetadata.Compression = compression;
curMetadata.BmpBitsPerPixel = bitsPerPixel;
curMetadata.ColorTable = colorTable;
curMetadata.EncodingWidth = curFrameMetadata.EncodingWidth;
curMetadata.EncodingHeight = curFrameMetadata.EncodingHeight;
curMetadata.HotspotX = curFrameMetadata.HotspotX;
curMetadata.HotspotY = curFrameMetadata.HotspotY;
}
}
}

133
src/ImageSharp/Formats/Cur/CurFrameMetadata.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Cur;
/// <summary>
/// IcoFrameMetadata.
/// </summary>
public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
public class CurFrameMetadata : IFormatFrameMetadata<CurFrameMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="CurFrameMetadata"/> class.
@ -60,7 +60,7 @@ public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
/// Gets or sets the number of bits per pixel.<br/>
/// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
/// </summary>
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
/// <summary>
/// Gets or sets the color table, if any.
@ -69,11 +69,75 @@ public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <inheritdoc/>
public CurFrameMetadata DeepClone() => new(this);
public static CurFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
{
if (!metadata.PixelTypeInfo.HasValue)
{
return new CurFrameMetadata
{
BmpBitsPerPixel = BmpBitsPerPixel.Bit32,
Compression = IconFrameCompression.Png
};
}
byte encodingWidth = metadata.EncodingWidth switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingWidth,
_ => 0
};
byte encodingHeight = metadata.EncodingHeight switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingHeight,
_ => 0
};
int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel;
BmpBitsPerPixel bbpp = bpp switch
{
1 => BmpBitsPerPixel.Bit1,
2 => BmpBitsPerPixel.Bit2,
<= 4 => BmpBitsPerPixel.Bit4,
<= 8 => BmpBitsPerPixel.Bit8,
<= 16 => BmpBitsPerPixel.Bit16,
<= 24 => BmpBitsPerPixel.Bit24,
_ => BmpBitsPerPixel.Bit32
};
IconFrameCompression compression = IconFrameCompression.Bmp;
if (bbpp is BmpBitsPerPixel.Bit32)
{
compression = IconFrameCompression.Png;
}
return new CurFrameMetadata
{
BmpBitsPerPixel = bbpp,
Compression = compression,
EncodingWidth = encodingWidth,
EncodingHeight = encodingHeight,
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
};
}
/// <inheritdoc/>
public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo(),
ColorTable = this.ColorTable,
EncodingWidth = this.EncodingWidth,
EncodingHeight = this.EncodingHeight
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public CurFrameMetadata DeepClone() => new(this);
internal void FromIconDirEntry(IconDirEntry entry)
{
this.EncodingWidth = entry.Width;
@ -84,7 +148,7 @@ public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
internal IconDirEntry ToIconDirEntry()
{
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Pixel8
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
? (byte)0
: (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel);
@ -97,4 +161,65 @@ public class CurFrameMetadata : IDeepCloneable<CurFrameMetadata>, IDeepCloneable
ColorCount = colorCount
};
}
private PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BmpBitsPerPixel;
PixelComponentInfo info;
PixelColorType color;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
if (this.Compression is IconFrameCompression.Png)
{
bpp = 32;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
}
else
{
switch (this.BmpBitsPerPixel)
{
case BmpBitsPerPixel.Bit1:
info = PixelComponentInfo.Create(1, bpp, 1);
color = PixelColorType.Binary;
break;
case BmpBitsPerPixel.Bit2:
info = PixelComponentInfo.Create(1, bpp, 2);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit4:
info = PixelComponentInfo.Create(1, bpp, 4);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit8:
info = PixelComponentInfo.Create(1, bpp, 8);
color = PixelColorType.Indexed;
break;
// Could be 555 with padding but 565 is more common in newer bitmaps and offers
// greater accuracy due to extra green precision.
case BmpBitsPerPixel.Bit16:
info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit24:
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit32 or _:
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
}
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
}

173
src/ImageSharp/Formats/Cur/CurMetadata.cs

@ -1,16 +1,183 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Cur;
/// <summary>
/// Provides Ico specific metadata information for the image.
/// Provides Cur specific metadata information for the image.
/// </summary>
public class CurMetadata : IDeepCloneable<CurMetadata>, IDeepCloneable
public class CurMetadata : IFormatMetadata<CurMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="CurMetadata"/> class.
/// </summary>
public CurMetadata()
{
}
private CurMetadata(CurMetadata other)
{
this.Compression = other.Compression;
this.HotspotX = other.HotspotX;
this.HotspotY = other.HotspotY;
this.EncodingWidth = other.EncodingWidth;
this.EncodingHeight = other.EncodingHeight;
this.BmpBitsPerPixel = other.BmpBitsPerPixel;
if (other.ColorTable?.Length > 0)
{
this.ColorTable = other.ColorTable.Value.ToArray();
}
}
/// <summary>
/// Gets or sets the frame compressions format. Derived from the root frame.
/// </summary>
public IconFrameCompression Compression { get; set; }
/// <summary>
/// Gets or sets the horizontal coordinates of the hotspot in number of pixels from the left. Derived from the root frame.
/// </summary>
public ushort HotspotX { get; set; }
/// <summary>
/// Gets or sets the vertical coordinates of the hotspot in number of pixels from the top. Derived from the root frame.
/// </summary>
public ushort HotspotY { get; set; }
/// <summary>
/// Gets or sets the encoding width. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
/// </summary>
public byte EncodingWidth { get; set; }
/// <summary>
/// Gets or sets the encoding height. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
/// </summary>
public byte EncodingHeight { get; set; }
/// <summary>
/// Gets or sets the number of bits per pixel.<br/>
/// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
/// </summary>
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
/// <summary>
/// Gets or sets the color table, if any. Derived from the root frame.<br/>
/// The underlying pixel format is represented by <see cref="Bgr24"/>.
/// </summary>
public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <inheritdoc/>
public CurMetadata DeepClone() => new();
public static CurMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
BmpBitsPerPixel bbpp = bpp switch
{
1 => BmpBitsPerPixel.Bit1,
2 => BmpBitsPerPixel.Bit2,
<= 4 => BmpBitsPerPixel.Bit4,
<= 8 => BmpBitsPerPixel.Bit8,
<= 16 => BmpBitsPerPixel.Bit16,
<= 24 => BmpBitsPerPixel.Bit24,
_ => BmpBitsPerPixel.Bit32
};
IconFrameCompression compression = IconFrameCompression.Bmp;
if (bbpp is BmpBitsPerPixel.Bit32)
{
compression = IconFrameCompression.Png;
}
return new CurMetadata
{
BmpBitsPerPixel = bbpp,
Compression = compression,
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BmpBitsPerPixel;
PixelComponentInfo info;
PixelColorType color;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
if (this.Compression is IconFrameCompression.Png)
{
bpp = 32;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
}
else
{
switch (this.BmpBitsPerPixel)
{
case BmpBitsPerPixel.Bit1:
info = PixelComponentInfo.Create(1, bpp, 1);
color = PixelColorType.Binary;
break;
case BmpBitsPerPixel.Bit2:
info = PixelComponentInfo.Create(1, bpp, 2);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit4:
info = PixelComponentInfo.Create(1, bpp, 4);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit8:
info = PixelComponentInfo.Create(1, bpp, 8);
color = PixelColorType.Indexed;
break;
// Could be 555 with padding but 565 is more common in newer bitmaps and offers
// greater accuracy due to extra green precision.
case BmpBitsPerPixel.Bit16:
info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit24:
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit32 or _:
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
}
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8
? EncodingType.Lossy
: EncodingType.Lossless,
PixelTypeInfo = this.GetPixelTypeInfo(),
ColorTable = this.ColorTable
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public CurMetadata DeepClone() => new(this);
}

45
src/ImageSharp/Formats/Cur/MetadataExtensions.cs

@ -1,45 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the Icon format specific metadata for the image.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="CurMetadata"/>.</returns>
public static CurMetadata GetCurMetadata(this ImageMetadata source)
=> source.GetFormatMetadata(CurFormat.Instance);
/// <summary>
/// Gets the Icon format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="CurFrameMetadata"/>.</returns>
public static CurFrameMetadata GetCurMetadata(this ImageFrameMetadata source)
=> source.GetFormatMetadata(CurFormat.Instance);
/// <summary>
/// Gets the Icon format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">
/// When this method returns, contains the metadata associated with the specified frame,
/// if found; otherwise, the default value for the type of the metadata parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true"/> if the Icon frame metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetCurMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out CurFrameMetadata? metadata)
=> source.TryGetFormatMetadata(CurFormat.Instance, out metadata);
}

20
src/ImageSharp/Formats/EncodingType.cs

@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Provides a way to specify the type of encoding to be used.
/// </summary>
public enum EncodingType
{
/// <summary>
/// Lossless encoding, which compresses data without any loss of information.
/// </summary>
Lossless,
/// <summary>
/// Lossy encoding, which compresses data by discarding some of it.
/// </summary>
Lossy
}

54
src/ImageSharp/Formats/FormatConnectingFrameMetadata.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// A metadata format designed to allow conversion between different image format frames.
/// </summary>
public class FormatConnectingFrameMetadata
{
/// <summary>
/// Gets information about the encoded pixel type if any.
/// </summary>
public PixelTypeInfo? PixelTypeInfo { get; init; }
/// <summary>
/// Gets the frame color table if any.
/// </summary>
public ReadOnlyMemory<Color>? ColorTable { get; init; }
/// <summary>
/// Gets the frame color table mode.
/// </summary>
public FrameColorTableMode ColorTableMode { get; init; }
/// <summary>
/// Gets the duration of the frame.
/// </summary>
public TimeSpan Duration { get; init; }
/// <summary>
/// Gets the frame alpha blending mode.
/// </summary>
public FrameBlendMode BlendMode { get; init; }
/// <summary>
/// Gets the frame disposal mode.
/// </summary>
public FrameDisposalMode DisposalMode { get; init; }
/// <summary>
/// Gets or sets the encoding width. <br />
/// Used for formats that require a specific frame size.
/// </summary>
public int? EncodingWidth { get; set; }
/// <summary>
/// Gets or sets the encoding height. <br />
/// Used for formats that require a specific frame size.
/// </summary>
public int? EncodingHeight { get; set; }
}

70
src/ImageSharp/Formats/FormatConnectingMetadata.cs

@ -0,0 +1,70 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// A metadata format designed to allow conversion between different image formats.
/// </summary>
public class FormatConnectingMetadata
{
/// <summary>
/// Gets the encoding type.
/// </summary>
public EncodingType EncodingType { get; init; }
/// <summary>
/// Gets the quality to use when <see cref="EncodingType"/> is <see cref="EncodingType.Lossy"/>.
/// </summary>
/// <remarks>
/// The value is usually between 1 and 100. Defaults to 100.
/// </remarks>
public int Quality { get; init; } = 100;
/// <summary>
/// Gets information about the encoded pixel type.
/// </summary>
public PixelTypeInfo PixelTypeInfo { get; init; }
/// <summary>
/// Gets the shared color table if any.
/// </summary>
public ReadOnlyMemory<Color>? ColorTable { get; init; }
/// <summary>
/// Gets the shared color table mode.
/// </summary>
/// <remarks>
/// Defaults to <see cref="FrameColorTableMode.Global"/>.
/// </remarks>
public FrameColorTableMode ColorTableMode { get; init; } = FrameColorTableMode.Global;
/// <summary>
/// Gets the default background color of the canvas when animating.
/// This color may be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when the disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
/// </summary>
/// <remarks>
/// Defaults to <see cref="Color.Transparent"/>.
/// </remarks>
public Color BackgroundColor { get; init; } = Color.Transparent;
/// <summary>
/// Gets the number of times any animation is repeated.
/// </summary>
/// <remarks>
/// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1.
/// </remarks>
public ushort RepeatCount { get; init; } = 1;
/// <summary>
/// Gets a value indicating whether the root frame is shown as part of the animated sequence.
/// </summary>
/// <remarks>
/// Defaults to <see langword="true"/>.
/// </remarks>
public bool AnimateRootFrame { get; init; } = true;
}

23
src/ImageSharp/Formats/FrameBlendMode.cs

@ -0,0 +1,23 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Provides a way to specify how the current frame should be blended with the previous frame in the animation sequence.
/// </summary>
public enum FrameBlendMode
{
/// <summary>
/// Do not blend. Render the current frame on the canvas by overwriting the rectangle covered by the current frame.
/// </summary>
Source = 0,
/// <summary>
/// Blend the current frame with the previous frame in the animation sequence within the rectangle covered
/// by the current frame.
/// If the current has any transparent areas, the corresponding areas of the previous frame will be visible
/// through these transparent regions.
/// </summary>
Over = 1
}

20
src/ImageSharp/Formats/FrameColorTableMode.cs

@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Provides a way to specify how the color table is used by the frame.
/// </summary>
public enum FrameColorTableMode
{
/// <summary>
/// The frame uses the shared color table specified by the image metadata.
/// </summary>
Global,
/// <summary>
/// The frame uses a color table specified by the frame metadata.
/// </summary>
Local
}

38
src/ImageSharp/Formats/FrameDisposalMode.cs

@ -0,0 +1,38 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Provides a way to specify how the current frame should be disposed of before rendering the next frame.
/// </summary>
public enum FrameDisposalMode
{
/// <summary>
/// No disposal specified.
/// The decoder is not required to take any action.
/// </summary>
Unspecified = 0,
/// <summary>
/// Do not dispose. The current frame is not disposed of, or in other words, not cleared or altered when moving to
/// the next frame. This means that the next frame is drawn over the current frame, and if the next frame contains
/// transparency, the previous frame will be visible through these transparent areas.
/// </summary>
DoNotDispose = 1,
/// <summary>
/// Restore to background color. When transitioning to the next frame, the area occupied by the current frame is
/// filled with the background color specified in the image metadata.
/// This effectively erases the current frame by replacing it with the background color before the next frame is displayed.
/// </summary>
RestoreToBackground = 2,
/// <summary>
/// Restore to previous. This method restores the area affected by the current frame to what it was before the
/// current frame was displayed. It essentially "undoes" the current frame, reverting to the state of the image
/// before the frame was displayed, then the next frame is drawn. This is useful for animations where only a small
/// part of the image changes from frame to frame.
/// </summary>
RestoreToPrevious = 3
}

20
src/ImageSharp/Formats/Gif/GifColorTableMode.cs

@ -1,20 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Gif;
/// <summary>
/// Provides enumeration for the available color table modes.
/// </summary>
public enum GifColorTableMode
{
/// <summary>
/// A single color table is calculated from the first frame and reused for subsequent frames.
/// </summary>
Global,
/// <summary>
/// A unique color table is calculated for each frame.
/// </summary>
Local
}

32
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
/// <summary>
/// Performs the gif decoding operation.
/// </summary>
internal sealed class GifDecoderCore : IImageDecoderInternals
internal sealed class GifDecoderCore : ImageDecoderCore
{
/// <summary>
/// The temp buffer used to reduce allocations.
@ -94,8 +94,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="options">The decoder options.</param>
public GifDecoderCore(DecoderOptions options)
: base(options)
{
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.maxFrames = options.MaxFrames;
@ -103,14 +103,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public DecoderOptions Options { get; }
/// <inheritdoc />
public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
uint frameCount = 0;
Image<TPixel>? image = null;
@ -181,7 +174,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
uint frameCount = 0;
ImageFrameMetadata? previousFrame = null;
@ -249,7 +242,6 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
return new ImageInfo(
new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel),
new(this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height),
this.metadata,
framesMetadata);
@ -287,6 +279,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
{
GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0");
}
this.Dimensions = new(this.imageDescriptor.Width, this.imageDescriptor.Height);
}
/// <summary>
@ -498,7 +492,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
else
{
if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious)
if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToPrevious)
{
prevFrame = previousFrame;
}
@ -615,7 +609,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
previousFrame = currentFrame ?? image.Frames.RootFrame;
if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground)
if (this.graphicsControlExtension.DisposalMethod == FrameDisposalMode.RestoreToBackground)
{
this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
}
@ -690,14 +684,14 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
&& this.logicalScreenDescriptor.GlobalColorTableSize > 0)
{
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.ColorTableMode = GifColorTableMode.Global;
gifMeta.ColorTableMode = FrameColorTableMode.Global;
}
if (this.imageDescriptor.LocalColorTableFlag
&& this.imageDescriptor.LocalColorTableSize > 0)
{
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.ColorTableMode = GifColorTableMode.Local;
gifMeta.ColorTableMode = FrameColorTableMode.Local;
Color[] colorTable = new Color[this.imageDescriptor.LocalColorTableSize];
ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize]);
@ -713,7 +707,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
gifMeta.HasTransparency = this.graphicsControlExtension.TransparencyFlag;
gifMeta.TransparencyIndex = this.graphicsControlExtension.TransparencyIndex;
gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime;
gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
gifMeta.DisposalMode = this.graphicsControlExtension.DisposalMethod;
}
}
@ -760,8 +754,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
this.metadata = meta;
this.gifMetadata = meta.GetGifMetadata();
this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag
? GifColorTableMode.Global
: GifColorTableMode.Local;
? FrameColorTableMode.Global
: FrameColorTableMode.Local;
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{

37
src/ImageSharp/Formats/Gif/GifDisposalMethod.cs

@ -1,37 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Gif;
/// <summary>
/// Provides enumeration for instructing the decoder what to do with the last image
/// in an animation sequence.
/// <see href="http://www.w3.org/Graphics/GIF/spec-gif89a.txt"/> section 23
/// </summary>
public enum GifDisposalMethod
{
/// <summary>
/// No disposal specified.
/// The decoder is not required to take any action.
/// </summary>
Unspecified = 0,
/// <summary>
/// Do not dispose.
/// The graphic is to be left in place.
/// </summary>
NotDispose = 1,
/// <summary>
/// Restore to background color.
/// The area used by the graphic must be restored to the background color.
/// </summary>
RestoreToBackground = 2,
/// <summary>
/// Restore to previous.
/// The decoder is required to restore the area overwritten by the
/// graphic with what was there prior to rendering the graphic.
/// </summary>
RestoreToPrevious = 3
}

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

@ -11,7 +11,7 @@ public sealed class GifEncoder : QuantizingImageEncoder
/// <summary>
/// Gets the color table mode: Global or local.
/// </summary>
public GifColorTableMode? ColorTableMode { get; init; }
public FrameColorTableMode? ColorTableMode { get; init; }
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)

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

@ -5,8 +5,6 @@ using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@ -19,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
/// <summary>
/// Implements the GIF encoding protocol.
/// </summary>
internal sealed class GifEncoderCore : IImageEncoderInternals
internal sealed class GifEncoderCore
{
/// <summary>
/// Used for allocating memory during processing operations.
@ -49,7 +47,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
/// <summary>
/// The color table mode: Global or local.
/// </summary>
private GifColorTableMode? colorTableMode;
private FrameColorTableMode? colorTableMode;
/// <summary>
/// The pixel sampling strategy for global quantization.
@ -85,9 +83,9 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
GifMetadata gifMetadata = GetGifMetadata(image);
GifMetadata gifMetadata = image.Metadata.CloneGifMetadata();
this.colorTableMode ??= gifMetadata.ColorTableMode;
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
bool useGlobalTable = this.colorTableMode == FrameColorTableMode.Global;
// Quantize the first image frame returning a palette.
IndexedImageFrame<TPixel>? quantized = null;
@ -99,7 +97,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
if (this.quantizer is null)
{
// Is this a gif with color information. If so use that, otherwise use octree.
if (gifMetadata.ColorTableMode == GifColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0)
if (gifMetadata.ColorTableMode == FrameColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0)
{
// We avoid dithering by default to preserve the original colors.
int transparencyIndex = GetTransparentIndex(quantized, frameMetadata);
@ -171,63 +169,24 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
// Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMethod);
this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMode);
stream.WriteByte(GifConstants.EndIntroducer);
quantized?.Dispose();
}
private static GifMetadata GetGifMetadata<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
{
return (GifMetadata)gif.DeepClone();
}
if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
{
AnimatedImageMetadata ani = png.ToAnimatedImageMetadata();
return GifMetadata.FromAnimatedMetadata(ani);
}
if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp))
{
AnimatedImageMetadata ani = webp.ToAnimatedImageMetadata();
return GifMetadata.FromAnimatedMetadata(ani);
}
// Return explicit new instance so we do not mutate the original metadata.
return new();
}
private static GifFrameMetadata GetGifFrameMetadata<TPixel>(ImageFrame<TPixel> frame, int transparencyIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
GifFrameMetadata? metadata = null;
if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
{
metadata = (GifFrameMetadata)gif.DeepClone();
}
else if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
{
AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata();
metadata = GifFrameMetadata.FromAnimatedMetadata(ani);
}
else if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp))
{
AnimatedImageFrameMetadata ani = webp.ToAnimatedImageFrameMetadata();
metadata = GifFrameMetadata.FromAnimatedMetadata(ani);
}
if (metadata?.ColorTableMode == GifColorTableMode.Global && transparencyIndex > -1)
GifFrameMetadata metadata = frame.Metadata.CloneGifMetadata();
if (metadata.ColorTableMode == FrameColorTableMode.Global && transparencyIndex > -1)
{
metadata.HasTransparency = true;
metadata.TransparencyIndex = ClampIndex(transparencyIndex);
}
return metadata ?? new();
return metadata;
}
private void EncodeAdditionalFrames<TPixel>(
@ -235,7 +194,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
Image<TPixel> image,
ReadOnlyMemory<TPixel> globalPalette,
int globalTransparencyIndex,
GifDisposalMethod previousDisposalMethod)
FrameDisposalMode previousDisposalMode)
where TPixel : unmanaged, IPixel<TPixel>
{
if (image.Frames.Count == 1)
@ -258,7 +217,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
ImageFrame<TPixel> currentFrame = image.Frames[i];
ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
GifFrameMetadata gifMetadata = GetGifFrameMetadata(currentFrame, globalTransparencyIndex);
bool useLocal = this.colorTableMode == GifColorTableMode.Local || (gifMetadata.ColorTableMode == GifColorTableMode.Local);
bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (gifMetadata.ColorTableMode == FrameColorTableMode.Local);
if (!useLocal && !hasPaletteQuantizer && i > 0)
{
@ -279,10 +238,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
useLocal,
gifMetadata,
paletteQuantizer,
previousDisposalMethod);
previousDisposalMode);
previousFrame = currentFrame;
previousDisposalMethod = gifMetadata.DisposalMethod;
previousDisposalMode = gifMetadata.DisposalMode;
}
if (hasPaletteQuantizer)
@ -301,7 +260,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
Buffer2D<byte> indices = ((IPixelSource)quantized).PixelBuffer;
Rectangle interest = indices.FullRectangle();
bool useLocal = this.colorTableMode == GifColorTableMode.Local || (metadata.ColorTableMode == GifColorTableMode.Local);
bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (metadata.ColorTableMode == FrameColorTableMode.Local);
int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length);
this.WriteImageDescriptor(interest, useLocal, bitDepth, stream);
@ -323,14 +282,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
bool useLocal,
GifFrameMetadata metadata,
PaletteQuantizer<TPixel> globalPaletteQuantizer,
GifDisposalMethod previousDisposal)
FrameDisposalMode previousDisposalMode)
where TPixel : unmanaged, IPixel<TPixel>
{
// Capture any explicit transparency index from the metadata.
// We use it to determine the value to use to replace duplicate pixels.
int transparencyIndex = metadata.HasTransparency ? metadata.TransparencyIndex : -1;
ImageFrame<TPixel>? previous = previousDisposal == GifDisposalMethod.RestoreToBackground ? null : previousFrame;
ImageFrame<TPixel>? previous = previousDisposalMode == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
// Deduplicate and quantize the frame capturing only required parts.
(bool difference, Rectangle bounds) =
@ -664,7 +623,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
bool hasTransparency = metadata.HasTransparency;
byte packedValue = GifGraphicControlExtension.GetPackedValue(
disposalMethod: metadata.DisposalMethod,
disposalMode: metadata.DisposalMode,
transparencyFlag: hasTransparency);
GifGraphicControlExtension extension = new(

76
src/ImageSharp/Formats/Gif/GifFrameMetadata.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
/// <summary>
/// Provides Gif specific metadata information for the image frame.
/// </summary>
public class GifFrameMetadata : IDeepCloneable
public class GifFrameMetadata : IFormatFrameMetadata<GifFrameMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="GifFrameMetadata"/> class.
@ -26,7 +26,7 @@ public class GifFrameMetadata : IDeepCloneable
{
this.ColorTableMode = other.ColorTableMode;
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.DisposalMode = other.DisposalMode;
if (other.LocalColorTable?.Length > 0)
{
@ -40,7 +40,7 @@ public class GifFrameMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the color table mode.
/// </summary>
public GifColorTableMode ColorTableMode { get; set; }
public FrameColorTableMode ColorTableMode { get; set; }
/// <summary>
/// Gets or sets the local color table, if any.
@ -73,10 +73,64 @@ public class GifFrameMetadata : IDeepCloneable
/// Primarily used in Gif animation, this field indicates the way in which the graphic is to
/// be treated after being displayed.
/// </summary>
public GifDisposalMethod DisposalMethod { get; set; }
public FrameDisposalMode DisposalMode { get; set; }
/// <inheritdoc />
public static GifFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
{
int index = -1;
const float background = 1f;
if (metadata.ColorTable.HasValue)
{
ReadOnlySpan<Color> colorTable = metadata.ColorTable.Value.Span;
for (int i = 0; i < colorTable.Length; i++)
{
Vector4 vector = colorTable[i].ToScaledVector4();
if (vector.W < background)
{
index = i;
}
}
}
bool hasTransparency = index >= 0;
return new()
{
LocalColorTable = metadata.ColorTable,
ColorTableMode = metadata.ColorTableMode,
FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10),
DisposalMode = metadata.DisposalMode,
HasTransparency = hasTransparency,
TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue,
};
}
/// <inheritdoc />
public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
{
// For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or
// has a local palette with 256 colors and is not transparent we should use 'Source'.
bool blendSource = this.DisposalMode == FrameDisposalMode.RestoreToBackground || (this.LocalColorTable?.Length == 256 && !this.HasTransparency);
// If the color table is global and frame has no transparency. Consider it 'Source' also.
blendSource |= this.ColorTableMode == FrameColorTableMode.Global && !this.HasTransparency;
return new()
{
ColorTable = this.LocalColorTable,
ColorTableMode = this.ColorTableMode,
Duration = TimeSpan.FromMilliseconds(this.FrameDelay * 10),
DisposalMode = this.DisposalMode,
BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over,
};
}
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new GifFrameMetadata(this);
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public GifFrameMetadata DeepClone() => new(this);
internal static GifFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata)
{
@ -101,19 +155,11 @@ public class GifFrameMetadata : IDeepCloneable
return new()
{
LocalColorTable = metadata.ColorTable,
ColorTableMode = metadata.ColorTableMode == FrameColorTableMode.Global ? GifColorTableMode.Global : GifColorTableMode.Local,
ColorTableMode = metadata.ColorTableMode,
FrameDelay = (int)Math.Round(metadata.Duration.TotalMilliseconds / 10),
DisposalMethod = GetMode(metadata.DisposalMode),
DisposalMode = metadata.DisposalMode,
HasTransparency = hasTransparency,
TransparencyIndex = hasTransparency ? unchecked((byte)index) : byte.MinValue,
};
}
private static GifDisposalMethod GetMode(FrameDisposalMode mode) => mode switch
{
FrameDisposalMode.DoNotDispose => GifDisposalMethod.NotDispose,
FrameDisposalMode.RestoreToBackground => GifDisposalMethod.RestoreToBackground,
FrameDisposalMode.RestoreToPrevious => GifDisposalMethod.RestoreToPrevious,
_ => GifDisposalMethod.Unspecified,
};
}

58
src/ImageSharp/Formats/Gif/GifMetadata.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
/// <summary>
/// Provides Gif specific metadata information for the image.
/// </summary>
public class GifMetadata : IDeepCloneable
public class GifMetadata : IFormatMetadata<GifMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="GifMetadata"/> class.
@ -49,7 +49,7 @@ public class GifMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the color table mode.
/// </summary>
public GifColorTableMode ColorTableMode { get; set; }
public FrameColorTableMode ColorTableMode { get; set; }
/// <summary>
/// Gets or sets the global color table, if any.
@ -67,12 +67,10 @@ public class GifMetadata : IDeepCloneable
/// Gets or sets the collection of comments about the graphics, credits, descriptions or any
/// other type of non-control and non-graphic data.
/// </summary>
public IList<string> Comments { get; set; } = new List<string>();
public IList<string> Comments { get; set; } = [];
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new GifMetadata(this);
internal static GifMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
public static GifMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
int index = 0;
Color background = metadata.BackgroundColor;
@ -81,20 +79,60 @@ public class GifMetadata : IDeepCloneable
ReadOnlySpan<Color> colorTable = metadata.ColorTable.Value.Span;
for (int i = 0; i < colorTable.Length; i++)
{
if (background == colorTable[i])
if (background != colorTable[i])
{
index = i;
break;
continue;
}
index = i;
break;
}
}
return new()
{
GlobalColorTable = metadata.ColorTable,
ColorTableMode = metadata.ColorTableMode == FrameColorTableMode.Global ? GifColorTableMode.Global : GifColorTableMode.Local,
ColorTableMode = metadata.ColorTableMode,
RepeatCount = metadata.RepeatCount,
BackgroundColorIndex = (byte)Numerics.Clamp(index, 0, 255),
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = this.GlobalColorTable.HasValue
? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.GlobalColorTable.Value.Length), 1, 8)
: 8;
return new PixelTypeInfo(bpp)
{
ColorType = PixelColorType.Indexed,
ComponentInfo = PixelComponentInfo.Create(1, bpp, bpp),
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
{
Color color = this.GlobalColorTable.HasValue && this.GlobalColorTable.Value.Span.Length > this.BackgroundColorIndex
? this.GlobalColorTable.Value.Span[this.BackgroundColorIndex]
: Color.Transparent;
return new()
{
AnimateRootFrame = true,
BackgroundColor = color,
ColorTable = this.GlobalColorTable,
ColorTableMode = this.ColorTableMode,
PixelTypeInfo = this.GetPixelTypeInfo(),
RepeatCount = this.RepeatCount,
};
}
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public GifMetadata DeepClone() => new(this);
}

105
src/ImageSharp/Formats/Gif/MetadataExtensions.cs

@ -1,105 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the gif format specific metadata for the image.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="GifMetadata"/>.</returns>
public static GifMetadata GetGifMetadata(this ImageMetadata source)
=> source.GetFormatMetadata(GifFormat.Instance);
/// <summary>
/// Gets the gif format specific metadata for the image.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">
/// When this method returns, contains the metadata associated with the specified image,
/// if found; otherwise, the default value for the type of the metadata parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true"/> if the gif metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetGifMetadata(this ImageMetadata source, [NotNullWhen(true)] out GifMetadata? metadata)
=> source.TryGetFormatMetadata(GifFormat.Instance, out metadata);
/// <summary>
/// Gets the gif format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="GifFrameMetadata"/>.</returns>
public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata source)
=> source.GetFormatMetadata(GifFormat.Instance);
/// <summary>
/// Gets the gif format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">
/// When this method returns, contains the metadata associated with the specified frame,
/// if found; otherwise, the default value for the type of the metadata parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true"/> if the gif frame metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetGifMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out GifFrameMetadata? metadata)
=> source.TryGetFormatMetadata(GifFormat.Instance, out metadata);
internal static AnimatedImageMetadata ToAnimatedImageMetadata(this GifMetadata source)
{
Color background = Color.Transparent;
if (source.GlobalColorTable != null)
{
background = source.GlobalColorTable.Value.Span[source.BackgroundColorIndex];
}
return new()
{
ColorTable = source.GlobalColorTable,
ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local,
RepeatCount = source.RepeatCount,
BackgroundColor = background,
};
}
internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this GifFrameMetadata source)
{
// For most scenarios we would consider the blend method to be 'Over' however if a frame has a disposal method of 'RestoreToBackground' or
// has a local palette with 256 colors and is not transparent we should use 'Source'.
bool blendSource = source.DisposalMethod == GifDisposalMethod.RestoreToBackground || (source.LocalColorTable?.Length == 256 && !source.HasTransparency);
// If the color table is global and frame has no transparency. Consider it 'Source' also.
blendSource |= source.ColorTableMode == GifColorTableMode.Global && !source.HasTransparency;
return new()
{
ColorTable = source.LocalColorTable,
ColorTableMode = source.ColorTableMode == GifColorTableMode.Global ? FrameColorTableMode.Global : FrameColorTableMode.Local,
Duration = TimeSpan.FromMilliseconds(source.FrameDelay * 10),
DisposalMode = GetMode(source.DisposalMethod),
BlendMode = blendSource ? FrameBlendMode.Source : FrameBlendMode.Over,
};
}
private static FrameDisposalMode GetMode(GifDisposalMethod method) => method switch
{
GifDisposalMethod.NotDispose => FrameDisposalMode.DoNotDispose,
GifDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
GifDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious,
_ => FrameDisposalMode.Unspecified,
};
}

6
src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs

@ -52,7 +52,7 @@ internal readonly struct GifGraphicControlExtension : IGifExtension, IEquatable<
/// Gets the disposal method which indicates the way in which the
/// graphic is to be treated after being displayed.
/// </summary>
public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2);
public FrameDisposalMode DisposalMethod => (FrameDisposalMode)((this.Packed & 0x1C) >> 2);
/// <summary>
/// Gets a value indicating whether transparency flag is to be set.
@ -80,7 +80,7 @@ internal readonly struct GifGraphicControlExtension : IGifExtension, IEquatable<
public static GifGraphicControlExtension Parse(ReadOnlySpan<byte> buffer)
=> MemoryMarshal.Cast<byte, GifGraphicControlExtension>(buffer)[0];
public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
public static byte GetPackedValue(FrameDisposalMode disposalMode, bool userInputFlag = false, bool transparencyFlag = false)
{
/*
Reserved | 3 Bits
@ -91,7 +91,7 @@ internal readonly struct GifGraphicControlExtension : IGifExtension, IEquatable<
byte value = 0;
value |= (byte)((int)disposalMethod << 2);
value |= (byte)((int)disposalMode << 2);
if (userInputFlag)
{

33
src/ImageSharp/Formats/IFormatFrameMetadata.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// An interface that provides metadata for a specific image format frames.
/// </summary>
public interface IFormatFrameMetadata : IDeepCloneable
{
/// <summary>
/// Converts the metadata to a <see cref="FormatConnectingFrameMetadata"/> instance.
/// </summary>
/// <returns>The <see cref="FormatConnectingFrameMetadata"/>.</returns>
FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata();
}
/// <summary>
/// An interface that provides metadata for a specific image format frames.
/// </summary>
/// <typeparam name="TSelf">The metadata type implementing this interface.</typeparam>
public interface IFormatFrameMetadata<TSelf> : IFormatFrameMetadata, IDeepCloneable<TSelf>
where TSelf : class, IFormatFrameMetadata
{
/// <summary>
/// Creates a new instance of the <typeparamref name="TSelf"/> class from the given <see cref="FormatConnectingFrameMetadata"/>.
/// </summary>
/// <param name="metadata">The <see cref="FormatConnectingFrameMetadata"/>.</param>
/// <returns>The <typeparamref name="TSelf"/>.</returns>
#pragma warning disable CA1000 // Do not declare static members on generic types
static abstract TSelf FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata);
#pragma warning restore CA1000 // Do not declare static members on generic types
}

41
src/ImageSharp/Formats/IFormatMetadata.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// An interface that provides metadata for a specific image format.
/// </summary>
public interface IFormatMetadata : IDeepCloneable
{
/// <summary>
/// Converts the metadata to a <see cref="PixelTypeInfo"/> instance.
/// </summary>
/// <returns>The pixel type info.</returns>
PixelTypeInfo GetPixelTypeInfo();
/// <summary>
/// Converts the metadata to a <see cref="FormatConnectingMetadata"/> instance.
/// </summary>
/// <returns>The <see cref="FormatConnectingMetadata"/>.</returns>
FormatConnectingMetadata ToFormatConnectingMetadata();
}
/// <summary>
/// An interface that provides metadata for a specific image format.
/// </summary>
/// <typeparam name="TSelf">The metadata type implementing this interface.</typeparam>
public interface IFormatMetadata<TSelf> : IFormatMetadata, IDeepCloneable<TSelf>
where TSelf : class, IFormatMetadata
{
/// <summary>
/// Creates a new instance of the <typeparamref name="TSelf"/> class from the given <see cref="FormatConnectingMetadata"/>.
/// </summary>
/// <param name="metadata">The <see cref="FormatConnectingMetadata"/>.</param>
/// <returns>The <typeparamref name="TSelf"/>.</returns>
#pragma warning disable CA1000 // Do not declare static members on generic types
static abstract TSelf FromFormatConnectingMetadata(FormatConnectingMetadata metadata);
#pragma warning restore CA1000 // Do not declare static members on generic types
}

50
src/ImageSharp/Formats/IImageDecoderInternals.cs

@ -1,50 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Abstraction for shared internals for XXXDecoderCore implementations to be used with <see cref="ImageDecoderUtilities"/>.
/// </summary>
internal interface IImageDecoderInternals
{
/// <summary>
/// Gets the general decoder options.
/// </summary>
DecoderOptions Options { get; }
/// <summary>
/// Gets the dimensions of the image being decoded.
/// </summary>
Size Dimensions { get; }
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>The decoded image.</returns>
/// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="ImageInfo"/>.</returns>
/// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
}

22
src/ImageSharp/Formats/IImageEncoderInternals.cs

@ -1,22 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Abstraction for shared internals for ***DecoderCore implementations.
/// </summary>
internal interface IImageEncoderInternals
{
/// <summary>
/// Encodes the image.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
}

6
src/ImageSharp/Formats/IImageFormat.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
@ -14,12 +14,12 @@ public interface IImageFormat
string Name { get; }
/// <summary>
/// Gets the default mimetype that the image format uses
/// Gets the default mime type that the image format uses
/// </summary>
string DefaultMimeType { get; }
/// <summary>
/// Gets all the mimetypes that have been used by this image format.
/// Gets all the mime types that have been used by this image format.
/// </summary>
IEnumerable<string> MimeTypes { get; }

16
src/ImageSharp/Formats/Ico/IcoDecoderCore.cs

@ -15,16 +15,28 @@ internal sealed class IcoDecoderCore : IconDecoderCore
}
protected override void SetFrameMetadata(
ImageFrameMetadata metadata,
ImageMetadata imageMetadata,
ImageFrameMetadata frameMetadata,
int index,
in IconDirEntry entry,
IconFrameCompression compression,
BmpBitsPerPixel bitsPerPixel,
ReadOnlyMemory<Color>? colorTable)
{
IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata();
IcoFrameMetadata icoFrameMetadata = frameMetadata.GetIcoMetadata();
icoFrameMetadata.FromIconDirEntry(entry);
icoFrameMetadata.Compression = compression;
icoFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
icoFrameMetadata.ColorTable = colorTable;
if (index == 0)
{
IcoMetadata curMetadata = imageMetadata.GetIcoMetadata();
curMetadata.Compression = compression;
curMetadata.BmpBitsPerPixel = bitsPerPixel;
curMetadata.ColorTable = colorTable;
curMetadata.EncodingWidth = icoFrameMetadata.EncodingWidth;
curMetadata.EncodingHeight = icoFrameMetadata.EncodingHeight;
}
}
}

133
src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Ico;
/// <summary>
/// Provides Ico specific metadata information for the image frame.
/// </summary>
public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
public class IcoFrameMetadata : IFormatFrameMetadata<IcoFrameMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="IcoFrameMetadata"/> class.
@ -53,7 +53,7 @@ public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
/// Gets or sets the number of bits per pixel.<br/>
/// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
/// </summary>
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
/// <summary>
/// Gets or sets the color table, if any.
@ -62,11 +62,75 @@ public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <inheritdoc/>
public IcoFrameMetadata DeepClone() => new(this);
public static IcoFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
{
if (!metadata.PixelTypeInfo.HasValue)
{
return new IcoFrameMetadata
{
BmpBitsPerPixel = BmpBitsPerPixel.Bit32,
Compression = IconFrameCompression.Png
};
}
byte encodingWidth = metadata.EncodingWidth switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingWidth,
_ => 0
};
byte encodingHeight = metadata.EncodingHeight switch
{
> 255 => 0,
<= 255 and >= 1 => (byte)metadata.EncodingHeight,
_ => 0
};
int bpp = metadata.PixelTypeInfo.Value.BitsPerPixel;
BmpBitsPerPixel bbpp = bpp switch
{
1 => BmpBitsPerPixel.Bit1,
2 => BmpBitsPerPixel.Bit2,
<= 4 => BmpBitsPerPixel.Bit4,
<= 8 => BmpBitsPerPixel.Bit8,
<= 16 => BmpBitsPerPixel.Bit16,
<= 24 => BmpBitsPerPixel.Bit24,
_ => BmpBitsPerPixel.Bit32
};
IconFrameCompression compression = IconFrameCompression.Bmp;
if (bbpp is BmpBitsPerPixel.Bit32)
{
compression = IconFrameCompression.Png;
}
return new IcoFrameMetadata
{
BmpBitsPerPixel = bbpp,
Compression = compression,
EncodingWidth = encodingWidth,
EncodingHeight = encodingHeight,
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
};
}
/// <inheritdoc/>
public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo(),
ColorTable = this.ColorTable,
EncodingWidth = this.EncodingWidth,
EncodingHeight = this.EncodingHeight
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public IcoFrameMetadata DeepClone() => new(this);
internal void FromIconDirEntry(IconDirEntry entry)
{
this.EncodingWidth = entry.Width;
@ -75,7 +139,7 @@ public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
internal IconDirEntry ToIconDirEntry()
{
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Pixel8
byte colorCount = this.Compression == IconFrameCompression.Png || this.BmpBitsPerPixel > BmpBitsPerPixel.Bit8
? (byte)0
: (byte)ColorNumerics.GetColorCountForBitDepth((int)this.BmpBitsPerPixel);
@ -92,4 +156,65 @@ public class IcoFrameMetadata : IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
},
};
}
private PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BmpBitsPerPixel;
PixelComponentInfo info;
PixelColorType color;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
if (this.Compression is IconFrameCompression.Png)
{
bpp = 32;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
}
else
{
switch (this.BmpBitsPerPixel)
{
case BmpBitsPerPixel.Bit1:
info = PixelComponentInfo.Create(1, bpp, 1);
color = PixelColorType.Binary;
break;
case BmpBitsPerPixel.Bit2:
info = PixelComponentInfo.Create(1, bpp, 2);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit4:
info = PixelComponentInfo.Create(1, bpp, 4);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit8:
info = PixelComponentInfo.Create(1, bpp, 8);
color = PixelColorType.Indexed;
break;
// Could be 555 with padding but 565 is more common in newer bitmaps and offers
// greater accuracy due to extra green precision.
case BmpBitsPerPixel.Bit16:
info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit24:
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit32 or _:
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
}
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
}

159
src/ImageSharp/Formats/Ico/IcoMetadata.cs

@ -1,16 +1,171 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Icon;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Ico;
/// <summary>
/// Provides Ico specific metadata information for the image.
/// </summary>
public class IcoMetadata : IDeepCloneable<IcoMetadata>, IDeepCloneable
public class IcoMetadata : IFormatMetadata<IcoMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="IcoMetadata"/> class.
/// </summary>
public IcoMetadata()
{
}
private IcoMetadata(IcoMetadata other)
{
this.Compression = other.Compression;
this.EncodingWidth = other.EncodingWidth;
this.EncodingHeight = other.EncodingHeight;
this.BmpBitsPerPixel = other.BmpBitsPerPixel;
if (other.ColorTable?.Length > 0)
{
this.ColorTable = other.ColorTable.Value.ToArray();
}
}
/// <summary>
/// Gets or sets the frame compressions format. Derived from the root frame.
/// </summary>
public IconFrameCompression Compression { get; set; }
/// <summary>
/// Gets or sets the encoding width. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
/// </summary>
public byte EncodingWidth { get; set; }
/// <summary>
/// Gets or sets the encoding height. <br />
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater. Derived from the root frame.
/// </summary>
public byte EncodingHeight { get; set; }
/// <summary>
/// Gets or sets the number of bits per pixel.<br/>
/// Used when <see cref="Compression"/> is <see cref="IconFrameCompression.Bmp"/>
/// </summary>
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Bit32;
/// <summary>
/// Gets or sets the color table, if any. Derived from the root frame.<br/>
/// The underlying pixel format is represented by <see cref="Bgr24"/>.
/// </summary>
public ReadOnlyMemory<Color>? ColorTable { get; set; }
/// <inheritdoc/>
public static IcoMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
BmpBitsPerPixel bbpp = bpp switch
{
1 => BmpBitsPerPixel.Bit1,
2 => BmpBitsPerPixel.Bit2,
<= 4 => BmpBitsPerPixel.Bit4,
<= 8 => BmpBitsPerPixel.Bit8,
<= 16 => BmpBitsPerPixel.Bit16,
<= 24 => BmpBitsPerPixel.Bit24,
_ => BmpBitsPerPixel.Bit32
};
IconFrameCompression compression = IconFrameCompression.Bmp;
if (bbpp is BmpBitsPerPixel.Bit32)
{
compression = IconFrameCompression.Png;
}
return new IcoMetadata
{
BmpBitsPerPixel = bbpp,
Compression = compression,
ColorTable = compression == IconFrameCompression.Bmp ? metadata.ColorTable : null
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BmpBitsPerPixel;
PixelComponentInfo info;
PixelColorType color;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
if (this.Compression is IconFrameCompression.Png)
{
bpp = 32;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
}
else
{
switch (this.BmpBitsPerPixel)
{
case BmpBitsPerPixel.Bit1:
info = PixelComponentInfo.Create(1, bpp, 1);
color = PixelColorType.Binary;
break;
case BmpBitsPerPixel.Bit2:
info = PixelComponentInfo.Create(1, bpp, 2);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit4:
info = PixelComponentInfo.Create(1, bpp, 4);
color = PixelColorType.Indexed;
break;
case BmpBitsPerPixel.Bit8:
info = PixelComponentInfo.Create(1, bpp, 8);
color = PixelColorType.Indexed;
break;
// Could be 555 with padding but 565 is more common in newer bitmaps and offers
// greater accuracy due to extra green precision.
case BmpBitsPerPixel.Bit16:
info = PixelComponentInfo.Create(3, bpp, 5, 6, 5);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit24:
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
color = PixelColorType.RGB;
break;
case BmpBitsPerPixel.Bit32 or _:
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
}
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
/// <inheritdoc/>
public IcoMetadata DeepClone() => new();
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
EncodingType = this.Compression == IconFrameCompression.Bmp && this.BmpBitsPerPixel <= BmpBitsPerPixel.Bit8
? EncodingType.Lossy
: EncodingType.Lossless,
PixelTypeInfo = this.GetPixelTypeInfo(),
ColorTable = this.ColorTable
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public IcoMetadata DeepClone() => new(this);
}

45
src/ImageSharp/Formats/Ico/MetadataExtensions.cs

@ -1,45 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the Ico format specific metadata for the image.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="IcoMetadata"/>.</returns>
public static IcoMetadata GetIcoMetadata(this ImageMetadata source)
=> source.GetFormatMetadata(IcoFormat.Instance);
/// <summary>
/// Gets the Ico format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="IcoFrameMetadata"/>.</returns>
public static IcoFrameMetadata GetIcoMetadata(this ImageFrameMetadata source)
=> source.GetFormatMetadata(IcoFormat.Instance);
/// <summary>
/// Gets the Ico format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">
/// When this method returns, contains the metadata associated with the specified frame,
/// if found; otherwise, the default value for the type of the metadata parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true"/> if the Ico frame metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetIcoMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out IcoFrameMetadata? metadata)
=> source.TryGetFormatMetadata(IcoFormat.Instance, out metadata);
}

46
src/ImageSharp/Formats/Icon/IconDecoderCore.cs

@ -6,24 +6,21 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Icon;
internal abstract class IconDecoderCore : IImageDecoderInternals
internal abstract class IconDecoderCore : ImageDecoderCore
{
private IconDir fileHeader;
private IconDirEntry[]? entries;
protected IconDecoderCore(DecoderOptions options)
=> this.Options = options;
public DecoderOptions Options { get; }
public Size Dimensions { get; private set; }
: base(options)
{
}
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
/// <inheritdoc />
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
// Stream may not at 0.
long basePosition = stream.Position;
@ -61,7 +58,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes);
// Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result.
Image<TPixel> temp = this.GetDecoder(isPng).Decode<TPixel>(stream, cancellationToken);
Image<TPixel> temp = this.GetDecoder(isPng).Decode<TPixel>(this.Options.Configuration, stream, cancellationToken);
decodedEntries.Add((temp, isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, i));
// Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data
@ -74,7 +71,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
PngMetadata? pngMetadata = null;
Image<TPixel> result = new(this.Options.Configuration, metadata, decodedEntries.Select(x =>
{
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Bit32;
ReadOnlyMemory<Color>? colorTable = null;
ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions);
ImageFrame<TPixel> source = x.Image.Frames.RootFrameUnsafe;
@ -106,7 +103,9 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
}
this.SetFrameMetadata(
metadata,
target.Metadata,
x.Index,
this.entries[x.Index],
x.Compression,
bitsPerPixel,
@ -131,7 +130,8 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
return result;
}
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
/// <inheritdoc />
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
// Stream may not at 0.
long basePosition = stream.Position;
@ -146,7 +146,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
int bpp = 0;
for (int i = 0; i < frames.Length; i++)
{
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Bit32;
ReadOnlyMemory<Color>? colorTable = null;
ref IconDirEntry entry = ref this.entries[i];
@ -168,7 +168,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes);
// Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result.
ImageInfo temp = this.GetDecoder(isPng).Identify(stream, cancellationToken);
ImageInfo frameInfo = this.GetDecoder(isPng).Identify(this.Options.Configuration, stream, cancellationToken);
ImageFrameMetadata frameMetadata = new();
@ -176,14 +176,14 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
{
if (i == 0)
{
pngMetadata = temp.Metadata.GetPngMetadata();
pngMetadata = frameInfo.Metadata.GetPngMetadata();
}
frameMetadata.SetFormatMetadata(PngFormat.Instance, temp.FrameMetadataCollection[0].GetPngMetadata());
frameMetadata.SetFormatMetadata(PngFormat.Instance, frameInfo.FrameMetadataCollection[0].GetPngMetadata());
}
else
{
BmpMetadata meta = temp.Metadata.GetBmpMetadata();
BmpMetadata meta = frameInfo.Metadata.GetBmpMetadata();
bitsPerPixel = meta.BitsPerPixel;
colorTable = meta.ColorTable;
@ -198,7 +198,9 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
frames[i] = frameMetadata;
this.SetFrameMetadata(
metadata,
frames[i],
i,
this.entries[i],
isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp,
bitsPerPixel,
@ -206,7 +208,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
// Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data
// which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft.
this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height));
this.Dimensions = new(Math.Max(this.Dimensions.Width, frameInfo.Size.Width), Math.Max(this.Dimensions.Height, frameInfo.Size.Height));
}
// Copy the format specific metadata to the image.
@ -220,11 +222,13 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata);
}
return new(new(bpp), this.Dimensions, metadata, frames);
return new(this.Dimensions, metadata, frames);
}
protected abstract void SetFrameMetadata(
ImageFrameMetadata metadata,
ImageMetadata imageMetadata,
ImageFrameMetadata frameMetadata,
int index,
in IconDirEntry entry,
IconFrameCompression compression,
BmpBitsPerPixel bitsPerPixel,
@ -275,7 +279,7 @@ internal abstract class IconDecoderCore : IImageDecoderInternals
this.Dimensions = new(width, height);
}
private IImageDecoderInternals GetDecoder(bool isPng)
private ImageDecoderCore GetDecoder(bool isPng)
{
if (isPng)
{

6
src/ImageSharp/Formats/Icon/IconEncoderCore.cs

@ -11,7 +11,7 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Icon;
internal abstract class IconEncoderCore : IImageEncoderInternals
internal abstract class IconEncoderCore
{
private readonly QuantizingImageEncoder encoder;
private readonly IconFileType iconFileType;
@ -43,6 +43,8 @@ internal abstract class IconEncoderCore : IImageEncoderInternals
for (int i = 0; i < image.Frames.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
// Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data
// which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft.
ImageFrame<TPixel> frame = image.Frames[i];
@ -167,7 +169,7 @@ internal abstract class IconEncoderCore : IImageEncoderInternals
{
this.Compression = compression;
this.BmpBitsPerPixel = compression == IconFrameCompression.Png
? BmpBitsPerPixel.Pixel32
? BmpBitsPerPixel.Bit32
: bmpBitsPerPixel;
this.ColorTable = colorTable;
this.iconDirEntry = iconDirEntry;

26
src/ImageSharp/Formats/ImageDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -189,7 +190,7 @@ public abstract class ImageDecoder : IImageDecoder
throw new NotSupportedException("Cannot read from the stream.");
}
T PeformActionAndResetPosition(Stream s, long position)
T PerformActionAndResetPosition(Stream s, long position)
{
T result = action(s);
@ -206,7 +207,7 @@ public abstract class ImageDecoder : IImageDecoder
if (stream.CanSeek)
{
return PeformActionAndResetPosition(stream, stream.Position);
return PerformActionAndResetPosition(stream, stream.Position);
}
Configuration configuration = options.Configuration;
@ -231,7 +232,7 @@ public abstract class ImageDecoder : IImageDecoder
throw new NotSupportedException("Cannot read from the stream.");
}
Task<T> PeformActionAndResetPosition(Stream s, long position, CancellationToken ct)
Task<T> PerformActionAndResetPosition(Stream s, long position, CancellationToken ct)
{
try
{
@ -263,15 +264,15 @@ public abstract class ImageDecoder : IImageDecoder
// code below to copy the stream to an in-memory buffer before invoking the action.
if (stream is MemoryStream ms)
{
return PeformActionAndResetPosition(ms, ms.Position, cancellationToken);
return PerformActionAndResetPosition(ms, ms.Position, cancellationToken);
}
if (stream is ChunkedMemoryStream cms)
{
return PeformActionAndResetPosition(cms, cms.Position, cancellationToken);
return PerformActionAndResetPosition(cms, cms.Position, cancellationToken);
}
return CopyToMemoryStreamAndActionAsync(options, stream, PeformActionAndResetPosition, cancellationToken);
return CopyToMemoryStreamAndActionAsync(options, stream, PerformActionAndResetPosition, cancellationToken);
}
private static async Task<T> CopyToMemoryStreamAndActionAsync<T>(
@ -282,7 +283,7 @@ public abstract class ImageDecoder : IImageDecoder
{
long position = stream.CanSeek ? stream.Position : 0;
Configuration configuration = options.Configuration;
using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
await using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
return await action(memoryStream, position, cancellationToken).ConfigureAwait(false);
@ -293,6 +294,11 @@ public abstract class ImageDecoder : IImageDecoder
if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format))
{
image.Metadata.DecodedImageFormat = format;
foreach (ImageFrame frame in image.Frames)
{
frame.Metadata.DecodedImageFormat = format;
}
}
}
@ -301,6 +307,12 @@ public abstract class ImageDecoder : IImageDecoder
if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format))
{
info.Metadata.DecodedImageFormat = format;
info.PixelType = info.Metadata.GetDecodedPixelTypeInfo();
foreach (ImageFrameMetadata frame in info.FrameMetadataCollection)
{
frame.DecodedImageFormat = format;
}
}
}
}

127
src/ImageSharp/Formats/ImageDecoderCore.cs

@ -0,0 +1,127 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// The base class for all stateful image decoders.
/// </summary>
internal abstract class ImageDecoderCore
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageDecoderCore"/> class.
/// </summary>
/// <param name="options">The general decoder options.</param>
protected ImageDecoderCore(DecoderOptions options)
=> this.Options = options;
/// <summary>
/// Gets the general decoder options.
/// </summary>
public DecoderOptions Options { get; }
/// <summary>
/// Gets or sets the dimensions of the image being decoded.
/// </summary>
public Size Dimensions { get; protected internal set; }
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="configuration">The shared configuration.</param>
/// <param name="stream">The <see cref="Stream" /> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="ImageInfo" />.</returns>
/// <exception cref="InvalidImageContentException">Thrown if the encoded image contains errors.</exception>
public ImageInfo Identify(
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
{
using BufferedReadStream bufferedReadStream = new(configuration, stream, cancellationToken);
try
{
return this.Identify(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
throw new InvalidImageContentException(this.Dimensions, ex);
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The shared configuration.</param>
/// <param name="stream">The <see cref="Stream" /> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image{TPixel}" />.</returns>
/// <exception cref="InvalidImageContentException">Thrown if the encoded image contains errors.</exception>
public Image<TPixel> Decode<TPixel>(
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// Test may pass a BufferedReadStream in order to monitor EOF hits, if so, use the existing instance.
BufferedReadStream bufferedReadStream =
stream as BufferedReadStream ?? new BufferedReadStream(configuration, stream, cancellationToken);
try
{
return this.Decode<TPixel>(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
throw new InvalidImageContentException(this.Dimensions, ex);
}
catch (Exception)
{
throw;
}
finally
{
if (bufferedReadStream != stream)
{
bufferedReadStream.Dispose();
}
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="ImageInfo"/>.</returns>
/// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
protected abstract ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>The decoded image.</returns>
/// <remarks>
/// Cancellable synchronous method. In case of cancellation, an <see cref="OperationCanceledException"/> shall
/// be thrown which will be handled on the call site.
/// </remarks>
protected abstract Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
}

81
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -1,81 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Utility methods for <see cref="IImageDecoderInternals"/>.
/// </summary>
internal static class ImageDecoderUtilities
{
internal static ImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
{
using BufferedReadStream bufferedReadStream = new(configuration, stream, cancellationToken);
try
{
return decoder.Identify(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
throw new InvalidImageContentException(decoder.Dimensions, ex);
}
catch (Exception)
{
throw;
}
}
internal static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
internal static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// Test may pass a BufferedReadStream in order to monitor EOF hits, if so, use the existing instance.
BufferedReadStream bufferedReadStream = stream as BufferedReadStream ?? new BufferedReadStream(configuration, stream, cancellationToken);
try
{
return decoder.Decode<TPixel>(bufferedReadStream, cancellationToken);
}
catch (InvalidMemoryOperationException ex)
{
throw largeImageExceptionFactory(ex, decoder.Dimensions);
}
catch (Exception)
{
throw;
}
finally
{
if (bufferedReadStream != stream)
{
bufferedReadStream.Dispose();
}
}
}
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
InvalidMemoryOperationException memoryOperationException,
Size dimensions) =>
new(dimensions, memoryOperationException);
}

7
src/ImageSharp/Formats/ImageEncoder.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
@ -42,6 +41,8 @@ public abstract class ImageEncoder : IImageEncoder
private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
image.SynchronizeMetadata();
Configuration configuration = image.Configuration;
if (stream.CanSeek)
{
@ -59,6 +60,8 @@ public abstract class ImageEncoder : IImageEncoder
private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
image.SynchronizeMetadata();
Configuration configuration = image.Configuration;
if (stream.CanSeek)
{
@ -66,7 +69,7 @@ public abstract class ImageEncoder : IImageEncoder
}
else
{
using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
await using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
await DoEncodeAsync(ms);
ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)

4
src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs

@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
internal class JpegFrameConfig
{
public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables)
public JpegFrameConfig(JpegColorSpace colorType, JpegColorType encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables)
{
this.ColorType = colorType;
this.EncodingColor = encodingColor;
@ -25,7 +25,7 @@ internal class JpegFrameConfig
public JpegColorSpace ColorType { get; }
public JpegEncodingColor EncodingColor { get; }
public JpegColorType EncodingColor { get; }
public JpegComponentConfig[] Components { get; }

6
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -139,13 +139,13 @@ internal class HuffmanScanEncoder
/// <param name="frame">Frame to encode.</param>
/// <param name="converter">Converter from color to spectral.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void EncodeScanBaselineInterleaved<TPixel>(JpegEncodingColor color, JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
public void EncodeScanBaselineInterleaved<TPixel>(JpegColorType color, JpegFrame frame, SpectralConverter<TPixel> converter, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
switch (color)
{
case JpegEncodingColor.YCbCrRatio444:
case JpegEncodingColor.Rgb:
case JpegColorType.YCbCrRatio444:
case JpegColorType.Rgb:
this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken);
break;
default:

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
/// <summary>
/// The Huffman encoding specifications.
/// </summary>
public readonly struct HuffmanSpec
internal readonly struct HuffmanSpec
{
/// <summary>
/// Huffman talbe specification for luminance DC.

2
src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs → src/ImageSharp/Formats/Jpeg/JpegColorType.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
/// <summary>
/// Provides enumeration of available JPEG color types.
/// </summary>
public enum JpegEncodingColor : byte
public enum JpegColorType : byte
{
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.

42
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
/// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
/// with additional fixes for both performance and common encoding errors.
/// </summary>
internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
{
/// <summary>
/// Whether the image has an EXIF marker.
@ -117,8 +117,8 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
/// </summary>
/// <param name="options">The decoder options.</param>
public JpegDecoderCore(JpegDecoderOptions options)
: base(options.GeneralOptions)
{
this.Options = options.GeneralOptions;
this.resizeMode = options.ResizeMode;
this.configuration = options.GeneralOptions.Configuration;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
@ -130,12 +130,6 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
// Refers to assembly's static data segment, no allocation occurs.
private static ReadOnlySpan<byte> SupportedPrecisions => new byte[] { 8, 12 };
/// <inheritdoc />
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => this.Frame.PixelSize;
/// <summary>
/// Gets the frame
/// </summary>
@ -198,8 +192,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
using SpectralConverter<TPixel> spectralConverter = new(this.configuration, this.resizeMode == JpegDecoderResizeMode.ScaleOnly ? null : this.Options.TargetSize);
this.ParseStream(stream, spectralConverter, cancellationToken);
@ -216,7 +209,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
/// <inheritdoc/>
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ParseStream(stream, spectralConverter: null, cancellationToken);
this.InitExifProfile();
@ -226,7 +219,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
this.InitDerivedMetadataProperties();
Size pixelSize = this.Frame.PixelSize;
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), new(pixelSize.Width, pixelSize.Height), this.Metadata);
return new ImageInfo(new(pixelSize.Width, pixelSize.Height), this.Metadata);
}
/// <summary>
@ -603,58 +596,58 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
/// Returns the jpeg color type based on the colorspace and subsampling used.
/// </summary>
/// <returns>Jpeg color type.</returns>
private JpegEncodingColor DeduceJpegColorType()
private JpegColorType DeduceJpegColorType()
{
switch (this.ColorSpace)
{
case JpegColorSpace.Grayscale:
return JpegEncodingColor.Luminance;
return JpegColorType.Luminance;
case JpegColorSpace.RGB:
return JpegEncodingColor.Rgb;
return JpegColorType.Rgb;
case JpegColorSpace.YCbCr:
if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegEncodingColor.YCbCrRatio444;
return JpegColorType.YCbCrRatio444;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegEncodingColor.YCbCrRatio422;
return JpegColorType.YCbCrRatio422;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegEncodingColor.YCbCrRatio420;
return JpegColorType.YCbCrRatio420;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegEncodingColor.YCbCrRatio411;
return JpegColorType.YCbCrRatio411;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegEncodingColor.YCbCrRatio410;
return JpegColorType.YCbCrRatio410;
}
else
{
return JpegEncodingColor.YCbCrRatio420;
return JpegColorType.YCbCrRatio420;
}
case JpegColorSpace.Cmyk:
return JpegEncodingColor.Cmyk;
return JpegColorType.Cmyk;
case JpegColorSpace.Ycck:
return JpegEncodingColor.Ycck;
return JpegColorType.Ycck;
default:
return JpegEncodingColor.YCbCrRatio420;
return JpegColorType.YCbCrRatio420;
}
}
@ -1237,6 +1230,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
this.Dimensions = new(frameWidth, frameHeight);
this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive;
remaining -= length;

2
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -45,7 +45,7 @@ public sealed class JpegEncoder : ImageEncoder
/// <summary>
/// Gets the jpeg color for encoding.
/// </summary>
public JpegEncodingColor? ColorType { get; init; }
public JpegColorType? ColorType { get; init; }
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)

18
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs

@ -40,7 +40,7 @@ internal sealed unsafe partial class JpegEncoderCore
// YCbCr 4:4:4
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio444,
JpegColorType.YCbCrRatio444,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -53,7 +53,7 @@ internal sealed unsafe partial class JpegEncoderCore
// YCbCr 4:2:2
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio422,
JpegColorType.YCbCrRatio422,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -66,7 +66,7 @@ internal sealed unsafe partial class JpegEncoderCore
// YCbCr 4:2:0
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio420,
JpegColorType.YCbCrRatio420,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -79,7 +79,7 @@ internal sealed unsafe partial class JpegEncoderCore
// YCbCr 4:1:1
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio411,
JpegColorType.YCbCrRatio411,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -92,7 +92,7 @@ internal sealed unsafe partial class JpegEncoderCore
// YCbCr 4:1:0
new JpegFrameConfig(
JpegColorSpace.YCbCr,
JpegEncodingColor.YCbCrRatio410,
JpegColorType.YCbCrRatio410,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -105,7 +105,7 @@ internal sealed unsafe partial class JpegEncoderCore
// Luminance
new JpegFrameConfig(
JpegColorSpace.Grayscale,
JpegEncodingColor.Luminance,
JpegColorType.Luminance,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -123,7 +123,7 @@ internal sealed unsafe partial class JpegEncoderCore
// Rgb
new JpegFrameConfig(
JpegColorSpace.RGB,
JpegEncodingColor.Rgb,
JpegColorType.Rgb,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -146,7 +146,7 @@ internal sealed unsafe partial class JpegEncoderCore
// Cmyk
new JpegFrameConfig(
JpegColorSpace.Cmyk,
JpegEncodingColor.Cmyk,
JpegColorType.Cmyk,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),
@ -170,7 +170,7 @@ internal sealed unsafe partial class JpegEncoderCore
// YccK
new JpegFrameConfig(
JpegColorSpace.Ycck,
JpegEncodingColor.Ycck,
JpegColorType.Ycck,
new JpegComponentConfig[]
{
new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0),

12
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
/// <summary>
/// Image encoder for writing an image to a stream as a jpeg.
/// </summary>
internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
internal sealed unsafe partial class JpegEncoderCore
{
/// <summary>
/// The available encodable frame configs.
@ -72,7 +72,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata);
bool interleaved = this.encoder.Interleaved ?? jpegMetadata.Interleaved ?? true;
bool interleaved = this.encoder.Interleaved ?? jpegMetadata.Interleaved;
using JpegFrame frame = new(image, frameConfig, interleaved);
// Write the Start Of Image marker.
@ -539,17 +539,11 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// <param name="buffer">Temporary buffer.</param>
private void WriteProfiles(ImageMetadata metadata, Span<byte> buffer)
{
if (metadata is null)
{
return;
}
// For compatibility, place the profiles in the following order:
// - APP1 EXIF
// - APP1 XMP
// - APP2 ICC
// - APP13 IPTC
metadata.SyncProfiles();
this.WriteExifProfile(metadata.ExifProfile, buffer);
this.WriteXmpProfile(metadata.XmpProfile, buffer);
this.WriteIccProfile(metadata.IccProfile, buffer);
@ -780,7 +774,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
private JpegFrameConfig GetFrameConfig(JpegMetadata metadata)
{
JpegEncodingColor color = this.encoder.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420;
JpegColorType color = this.encoder.ColorType ?? metadata.ColorType;
JpegFrameConfig frameConfig = Array.Find(
FrameConfigs,
cfg => cfg.EncodingColor == color);

138
src/ImageSharp/Formats/Jpeg/JpegMetadata.cs

@ -2,20 +2,20 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg;
/// <summary>
/// Provides Jpeg specific metadata information for the image.
/// </summary>
public class JpegMetadata : IDeepCloneable
public class JpegMetadata : IFormatMetadata<JpegMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="JpegMetadata"/> class.
/// </summary>
public JpegMetadata()
{
this.Comments = new List<JpegComData>();
}
/// <summary>
@ -36,7 +36,7 @@ public class JpegMetadata : IDeepCloneable
/// </summary>
/// <remarks>
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// with non-compliant ITU quantization tables.
/// </remarks>
internal int? LuminanceQuality { get; set; }
@ -45,16 +45,17 @@ public class JpegMetadata : IDeepCloneable
/// </summary>
/// <remarks>
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// with non-compliant ITU quantization tables.
/// </remarks>
internal int? ChrominanceQuality { get; set; }
/// <summary>
/// Gets the encoded quality.
/// Gets or sets the encoded quality.
/// </summary>
/// <remarks>
/// Note that jpeg image can have different quality for luminance and chrominance components.
/// This property returns maximum value of luma/chroma qualities if both are present.
/// Setting the quality will update both values.
/// </remarks>
public int Quality
{
@ -69,45 +70,138 @@ public class JpegMetadata : IDeepCloneable
return this.LuminanceQuality.Value;
}
else
{
if (this.ChrominanceQuality.HasValue)
{
return this.ChrominanceQuality.Value;
}
return Quantization.DefaultQualityFactor;
}
return this.ChrominanceQuality ?? Quantization.DefaultQualityFactor;
}
set
{
this.LuminanceQuality = value;
this.ChrominanceQuality = value;
}
}
/// <summary>
/// Gets the color type.
/// Gets or sets the color type.
/// </summary>
public JpegEncodingColor? ColorType { get; internal set; }
public JpegColorType ColorType { get; set; } = JpegColorType.YCbCrRatio420;
/// <summary>
/// Gets the component encoding mode.
/// Gets or sets a value indicating whether the component encoding mode should be interleaved.
/// </summary>
/// <remarks>
/// Interleaved encoding mode encodes all color components in a single scan.
/// Non-interleaved encoding mode encodes each color component in a separate scan.
/// </remarks>
public bool? Interleaved { get; internal set; }
public bool Interleaved { get; set; } = true;
/// <summary>
/// Gets the scan encoding mode.
/// Gets or sets a value indicating whether the scan encoding mode is progressive.
/// </summary>
/// <remarks>
/// Progressive jpeg images encode component data across multiple scans.
/// </remarks>
public bool? Progressive { get; internal set; }
public bool Progressive { get; set; }
/// <summary>
/// Gets the comments.
/// Gets or sets collection of comments.
/// </summary>
public IList<JpegComData> Comments { get; }
public IList<JpegComData> Comments { get; set; } = [];
/// <inheritdoc/>
public static JpegMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
JpegColorType color;
PixelColorType colorType = metadata.PixelTypeInfo.ColorType;
switch (colorType)
{
case PixelColorType.Luminance:
color = JpegColorType.Luminance;
break;
case PixelColorType.CMYK:
color = JpegColorType.Cmyk;
break;
case PixelColorType.YCCK:
color = JpegColorType.Ycck;
break;
default:
if (colorType.HasFlag(PixelColorType.RGB) || colorType.HasFlag(PixelColorType.BGR))
{
color = JpegColorType.Rgb;
}
else
{
color = metadata.Quality <= Quantization.DefaultQualityFactor
? JpegColorType.YCbCrRatio420
: JpegColorType.YCbCrRatio444;
}
break;
}
return new JpegMetadata
{
ColorType = color,
ChrominanceQuality = metadata.Quality,
LuminanceQuality = metadata.Quality,
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
PixelComponentInfo info;
switch (this.ColorType)
{
case JpegColorType.Luminance:
bpp = 8;
colorType = PixelColorType.Luminance;
info = PixelComponentInfo.Create(1, bpp, 8);
break;
case JpegColorType.Cmyk:
bpp = 32;
colorType = PixelColorType.CMYK;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
break;
case JpegColorType.Ycck:
bpp = 32;
colorType = PixelColorType.YCCK;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
break;
case JpegColorType.Rgb:
bpp = 24;
colorType = PixelColorType.RGB;
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
break;
default:
bpp = 24;
colorType = PixelColorType.YCbCr;
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
break;
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = PixelAlphaRepresentation.None,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
EncodingType = EncodingType.Lossy,
PixelTypeInfo = this.GetPixelTypeInfo(),
Quality = this.Quality,
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new JpegMetadata(this);
public JpegMetadata DeepClone() => new(this);
}

21
src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs

@ -1,21 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the jpeg format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="JpegMetadata"/>.</returns>
public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance);
}

25
src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Pbm;
/// <summary>
/// Configuration options for use during PBM encoding.
/// </summary>
internal interface IPbmEncoderOptions
{
/// <summary>
/// Gets the encoding of the pixels.
/// </summary>
PbmEncoding? Encoding { get; }
/// <summary>
/// Gets the Color type of the resulting image.
/// </summary>
PbmColorType? ColorType { get; }
/// <summary>
/// Gets the Data Type of the pixel components.
/// </summary>
PbmComponentType? ComponentType { get; }
}

20
src/ImageSharp/Formats/Pbm/MetadataExtensions.cs

@ -1,20 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the pbm format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="PbmMetadata"/>.</returns>
public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance);
}

26
src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
/// <summary>
/// Performs the PBM decoding operation.
/// </summary>
internal sealed class PbmDecoderCore : IImageDecoderInternals
internal sealed class PbmDecoderCore : ImageDecoderCore
{
private int maxPixelValue;
@ -52,24 +52,17 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="options">The decoder options.</param>
public PbmDecoderCore(DecoderOptions options)
: base(options)
{
this.Options = options;
this.configuration = options.Configuration;
}
/// <inheritdoc/>
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => this.pixelSize;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ProcessHeader(stream);
var image = new Image<TPixel>(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata);
Image<TPixel> image = new(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
@ -83,13 +76,12 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
}
/// <inheritdoc/>
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ProcessHeader(stream);
// BlackAndWhite pixels are encoded into a byte.
int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata);
return new ImageInfo(
new(this.pixelSize.Width, this.pixelSize.Height),
this.metadata);
}
/// <summary>
@ -97,6 +89,7 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="stream">The input stream.</param>
/// <exception cref="InvalidImageContentException">An EOF marker has been read before the image has been decoded.</exception>
[MemberNotNull(nameof(metadata))]
private void ProcessHeader(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[2];
@ -179,6 +172,7 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
}
this.pixelSize = new Size(width, height);
this.Dimensions = this.pixelSize;
this.metadata = new ImageMetadata();
PbmMetadata meta = this.metadata.GetPbmMetadata();
meta.Encoding = this.encoding;

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

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
namespace SixLabors.ImageSharp.Formats.Pbm;
/// <summary>

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

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm;
@ -10,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
/// <summary>
/// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap.
/// </summary>
internal sealed class PbmEncoderCore : IImageEncoderInternals
internal sealed class PbmEncoderCore
{
private const byte NewLine = (byte)'\n';
private const byte Space = (byte)' ';

96
src/ImageSharp/Formats/Pbm/PbmMetadata.cs

@ -1,12 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Pbm;
/// <summary>
/// Provides PBM specific metadata information for the image.
/// </summary>
public class PbmMetadata : IDeepCloneable
public class PbmMetadata : IFormatMetadata<PbmMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="PbmMetadata"/> class.
@ -41,5 +43,95 @@ public class PbmMetadata : IDeepCloneable
public PbmComponentType ComponentType { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PbmMetadata(this);
public static PbmMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
PbmColorType color;
PixelColorType colorType = metadata.PixelTypeInfo.ColorType;
switch (colorType)
{
case PixelColorType.Binary:
color = PbmColorType.BlackAndWhite;
break;
case PixelColorType.Luminance:
color = PbmColorType.Grayscale;
break;
default:
if (colorType.HasFlag(PixelColorType.RGB) || colorType.HasFlag(PixelColorType.BGR))
{
color = PbmColorType.Rgb;
}
else
{
color = PbmColorType.Grayscale;
}
break;
}
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
PbmComponentType componentType = bpp switch
{
1 => PbmComponentType.Bit,
<= 8 => PbmComponentType.Byte,
_ => PbmComponentType.Short
};
return new PbmMetadata
{
ColorType = color,
ComponentType = componentType
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
PixelComponentInfo info;
switch (this.ColorType)
{
case PbmColorType.BlackAndWhite:
bpp = 1;
colorType = PixelColorType.Binary;
info = PixelComponentInfo.Create(1, bpp, 1);
break;
case PbmColorType.Rgb:
bpp = this.ComponentType == PbmComponentType.Short ? 48 : 24;
colorType = PixelColorType.RGB;
info = this.ComponentType == PbmComponentType.Short
? PixelComponentInfo.Create(3, bpp, 16, 16, 16)
: PixelComponentInfo.Create(3, bpp, 8, 8, 8);
break;
case PbmColorType.Grayscale:
default:
bpp = this.ComponentType == PbmComponentType.Short ? 16 : 8;
colorType = PixelColorType.Luminance;
info = this.ComponentType == PbmComponentType.Short
? PixelComponentInfo.Create(1, bpp, bpp)
: PixelComponentInfo.Create(1, bpp, bpp);
break;
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = PixelAlphaRepresentation.None,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo(),
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public PbmMetadata DeepClone() => new(this);
}

20
src/ImageSharp/Formats/Png/Chunks/FrameControl.cs

@ -22,8 +22,8 @@ internal readonly struct FrameControl
uint yOffset,
ushort delayNumerator,
ushort delayDenominator,
PngDisposalMethod disposeOperation,
PngBlendMethod blendOperation)
FrameDisposalMode disposalMode,
FrameBlendMode blendMode)
{
this.SequenceNumber = sequenceNumber;
this.Width = width;
@ -32,8 +32,8 @@ internal readonly struct FrameControl
this.YOffset = yOffset;
this.DelayNumerator = delayNumerator;
this.DelayDenominator = delayDenominator;
this.DisposeOperation = disposeOperation;
this.BlendOperation = blendOperation;
this.DisposalMode = disposalMode;
this.BlendMode = blendMode;
}
/// <summary>
@ -84,12 +84,12 @@ internal readonly struct FrameControl
/// <summary>
/// Gets the type of frame area disposal to be done after rendering this frame
/// </summary>
public PngDisposalMethod DisposeOperation { get; }
public FrameDisposalMode DisposalMode { get; }
/// <summary>
/// Gets the type of frame area rendering for this frame
/// </summary>
public PngBlendMethod BlendOperation { get; }
public FrameBlendMode BlendMode { get; }
public Rectangle Bounds => new((int)this.XOffset, (int)this.YOffset, (int)this.Width, (int)this.Height);
@ -137,8 +137,8 @@ internal readonly struct FrameControl
BinaryPrimitives.WriteUInt16BigEndian(buffer[20..22], this.DelayNumerator);
BinaryPrimitives.WriteUInt16BigEndian(buffer[22..24], this.DelayDenominator);
buffer[24] = (byte)this.DisposeOperation;
buffer[25] = (byte)this.BlendOperation;
buffer[24] = (byte)(this.DisposalMode - 1);
buffer[25] = (byte)this.BlendMode;
}
/// <summary>
@ -155,6 +155,6 @@ internal readonly struct FrameControl
yOffset: BinaryPrimitives.ReadUInt32BigEndian(data[16..20]),
delayNumerator: BinaryPrimitives.ReadUInt16BigEndian(data[20..22]),
delayDenominator: BinaryPrimitives.ReadUInt16BigEndian(data[22..24]),
disposeOperation: (PngDisposalMethod)data[24],
blendOperation: (PngBlendMethod)data[25]);
disposalMode: (FrameDisposalMode)(data[24] + 1),
blendMode: (FrameBlendMode)data[25]);
}

84
src/ImageSharp/Formats/Png/MetadataExtensions.cs

@ -1,84 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the png format specific metadata for the image.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="PngMetadata"/>.</returns>
public static PngMetadata GetPngMetadata(this ImageMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
/// <summary>
/// Gets the png format specific metadata for the image.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">The metadata.</param>
/// <returns>
/// <see langword="true"/> if the png metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetPngMetadata(this ImageMetadata source, [NotNullWhen(true)] out PngMetadata? metadata)
=> source.TryGetFormatMetadata(PngFormat.Instance, out metadata);
/// <summary>
/// Gets the png format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <returns>The <see cref="PngFrameMetadata"/>.</returns>
public static PngFrameMetadata GetPngMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
/// <summary>
/// Gets the png format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">The metadata.</param>
/// <returns>
/// <see langword="true"/> if the png frame metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetPngMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out PngFrameMetadata? metadata)
=> source.TryGetFormatMetadata(PngFormat.Instance, out metadata);
internal static AnimatedImageMetadata ToAnimatedImageMetadata(this PngMetadata source)
=> new()
{
ColorTable = source.ColorTable,
ColorTableMode = FrameColorTableMode.Global,
RepeatCount = (ushort)Numerics.Clamp(source.RepeatCount, 0, ushort.MaxValue),
};
internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this PngFrameMetadata source)
{
double delay = source.FrameDelay.ToDouble();
if (double.IsNaN(delay))
{
delay = 0;
}
return new()
{
ColorTableMode = FrameColorTableMode.Global,
Duration = TimeSpan.FromMilliseconds(delay * 1000),
DisposalMode = GetMode(source.DisposalMethod),
BlendMode = source.BlendMethod == PngBlendMethod.Source ? FrameBlendMode.Source : FrameBlendMode.Over,
};
}
private static FrameDisposalMode GetMode(PngDisposalMethod method) => method switch
{
PngDisposalMethod.DoNotDispose => FrameDisposalMode.DoNotDispose,
PngDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
PngDisposalMethod.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious,
_ => FrameDisposalMode.Unspecified,
};
}

22
src/ImageSharp/Formats/Png/PngBlendMethod.cs

@ -1,22 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Specifies whether the frame is to be alpha blended into the current output buffer content,
/// or whether it should completely replace its region in the output buffer.
/// </summary>
public enum PngBlendMethod
{
/// <summary>
/// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
/// </summary>
Source,
/// <summary>
/// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as
/// described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2].
/// </summary>
Over
}

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

@ -53,8 +53,8 @@ public sealed class PngDecoder : SpecializedImageDecoder<PngDecoderOptions>
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
PngColorType color = meta.ColorType;
PngBitDepth bits = meta.BitDepth;
switch (color)
{
@ -101,5 +101,5 @@ public sealed class PngDecoder : SpecializedImageDecoder<PngDecoderOptions>
}
/// <inheritdoc/>
protected override PngDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) => new PngDecoderOptions() { GeneralOptions = options };
protected override PngDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) => new() { GeneralOptions = options };
}

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

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Performs the png decoding operation.
/// </summary>
internal sealed class PngDecoderCore : IImageDecoderInternals
internal sealed class PngDecoderCore : ImageDecoderCore
{
/// <summary>
/// The general decoder options.
@ -136,8 +136,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="options">The decoder options.</param>
public PngDecoderCore(PngDecoderOptions options)
: base(options.GeneralOptions)
{
this.Options = options.GeneralOptions;
this.configuration = options.GeneralOptions.Configuration;
this.maxFrames = options.GeneralOptions.MaxFrames;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
@ -147,8 +147,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
: base(options.GeneralOptions)
{
this.Options = options.GeneralOptions;
this.colorMetadataOnly = colorMetadataOnly;
this.maxFrames = options.GeneralOptions.MaxFrames;
this.skipMetadata = true;
@ -159,14 +159,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
/// <inheritdoc/>
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => new(this.header.Width, this.header.Height);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
uint frameCount = 0;
ImageMetadata metadata = new();
@ -241,7 +234,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
cancellationToken);
// if current frame dispose is restore to previous, then from future frame's perspective, it never happened
if (currentFrameControl.Value.DisposeOperation != PngDisposalMethod.RestoreToPrevious)
if (currentFrameControl.Value.DisposalMode != FrameDisposalMode.RestoreToPrevious)
{
previousFrame = currentFrame;
previousFrameControl = currentFrameControl;
@ -341,7 +334,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
/// <inheritdoc/>
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
uint frameCount = 0;
ImageMetadata metadata = new();
@ -527,10 +520,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngThrowHelper.ThrowInvalidHeader();
}
// 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, framesMetadata);
return new ImageInfo(new(this.header.Width, this.header.Height), metadata, framesMetadata);
}
finally
{
@ -674,12 +664,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
// If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND.
// So, if restoring to before first frame, clear entire area. Same if first frame (previousFrameControl null).
if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToPrevious))
if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToPrevious))
{
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion();
pixelRegion.Clear();
}
else if (previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToBackground)
else if (previousFrameControl.Value.DisposalMode == FrameDisposalMode.RestoreToBackground)
{
Rectangle restoreArea = previousFrameControl.Value.Bounds;
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(restoreArea);
@ -703,29 +693,6 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
imageFrameMetadata.Add(meta);
}
/// <summary>
/// Calculates the correct number of bits per pixel for the given color type.
/// </summary>
/// <returns>The <see cref="int"/></returns>
private int CalculateBitsPerPixel()
{
switch (this.pngColorType)
{
case PngColorType.Grayscale:
case PngColorType.Palette:
return this.header.BitDepth;
case PngColorType.GrayscaleWithAlpha:
return this.header.BitDepth * 2;
case PngColorType.Rgb:
return this.header.BitDepth * 3;
case PngColorType.RgbWithAlpha:
return this.header.BitDepth * 4;
default:
PngThrowHelper.ThrowNotSupportedColor();
return -1;
}
}
/// <summary>
/// Calculates the correct number of bytes per pixel for the given color type.
/// </summary>
@ -817,8 +784,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int height = (int)frameControl.YMax;
IMemoryOwner<TPixel>? blendMemory = null;
Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
if (frameControl.BlendOperation == PngBlendMethod.Over)
Span<TPixel> blendRowBuffer = [];
if (frameControl.BlendMode == FrameBlendMode.Over)
{
blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
blendRowBuffer = blendMemory.Memory.Span;
@ -910,8 +877,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
Buffer2D<TPixel> imageBuffer = imageFrame.PixelBuffer;
IMemoryOwner<TPixel>? blendMemory = null;
Span<TPixel> blendRowBuffer = Span<TPixel>.Empty;
if (frameControl.BlendOperation == PngBlendMethod.Over)
Span<TPixel> blendRowBuffer = [];
if (frameControl.BlendMode == FrameBlendMode.Over)
{
blendMemory = this.memoryAllocator.Allocate<TPixel>(imageFrame.Width, AllocationOptions.Clean);
blendRowBuffer = blendMemory.Memory.Span;
@ -1036,7 +1003,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
bool blend = frameControl.BlendMode == FrameBlendMode.Over;
Span<TPixel> rowSpan = blend
? blendRowBuffer
: destination;
@ -1149,7 +1116,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int increment = 1)
where TPixel : unmanaged, IPixel<TPixel>
{
bool blend = frameControl.BlendOperation == PngBlendMethod.Over;
bool blend = frameControl.BlendMode == FrameBlendMode.Over;
Span<TPixel> rowSpan = blend
? blendRowBuffer
: destination;
@ -1365,6 +1332,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
pngMetadata.InterlaceMethod = this.header.InterlaceMethod;
this.pngColorType = this.header.ColorType;
this.Dimensions = new(this.header.Width, this.header.Height);
}
/// <summary>
@ -1483,7 +1451,20 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
byte colorPrimaries = data[0];
byte transferFunction = data[1];
byte matrixCoefficients = data[2];
bool? fullRange = data[3] == 1 ? true : data[3] == 0 ? false : null;
bool? fullRange;
if (data[3] == 1)
{
fullRange = true;
}
else if (data[3] == 0)
{
fullRange = false;
}
else
{
fullRange = null;
}
metadata.CicpProfile = new CicpProfile(colorPrimaries, transferFunction, matrixCoefficients, fullRange);
}
@ -1515,7 +1496,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
// Sequence of bytes for the exif header ("Exif" ASCII and two zero bytes).
// This doesn't actually allocate.
ReadOnlySpan<byte> exifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
ReadOnlySpan<byte> exifHeader = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00];
if (dataLength < exifHeader.Length)
{
@ -1626,7 +1607,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
Span<byte> destUncompressedData = destBuffer.GetSpan();
if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
{
uncompressedBytesArray = Array.Empty<byte>();
uncompressedBytesArray = [];
return false;
}
@ -1635,7 +1616,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
if (memoryStreamOutput.Length > maxLength)
{
uncompressedBytesArray = Array.Empty<byte>();
uncompressedBytesArray = [];
return false;
}
@ -2002,7 +1983,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
if (length == 0)
{
return new BasicArrayBuffer<byte>(Array.Empty<byte>());
return new BasicArrayBuffer<byte>([]);
}
// We rent the buffer here to return it afterwards in Decode()

25
src/ImageSharp/Formats/Png/PngDisposalMethod.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
/// </summary>
public enum PngDisposalMethod
{
/// <summary>
/// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
/// </summary>
DoNotDispose,
/// <summary>
/// The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
/// </summary>
RestoreToBackground,
/// <summary>
/// The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
/// </summary>
RestoreToPrevious
}

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

@ -9,10 +9,8 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -23,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Performs the png encoding operation.
/// </summary>
internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
internal sealed class PngEncoderCore : IDisposable
{
/// <summary>
/// The maximum block size, defaults at 64k for uncompressed blocks.
@ -160,7 +158,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
this.height = image.Height;
ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = GetPngMetadata(image);
PngMetadata pngMetadata = metadata.ClonePngMetadata();
this.SanitizeAndSetEncoderOptions<TPixel>(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
stream.Write(PngConstants.HeaderBytes);
@ -211,8 +209,8 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{
// Write the first animated frame.
currentFrame = image.Frames[currentFrameIndex];
PngFrameMetadata frameMetadata = GetPngFrameMetadata(currentFrame);
PngDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
PngFrameMetadata frameMetadata = currentFrame.Metadata.GetPngMetadata();
FrameDisposalMode previousDisposal = frameMetadata.DisposalMode;
FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds(), 0);
uint sequenceNumber = 1;
if (pngMetadata.AnimateRootFrame)
@ -237,12 +235,12 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++)
{
ImageFrame<TPixel>? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
ImageFrame<TPixel>? prev = previousDisposal == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
currentFrame = image.Frames[currentFrameIndex];
ImageFrame<TPixel>? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null;
frameMetadata = GetPngFrameMetadata(currentFrame);
bool blend = frameMetadata.BlendMethod == PngBlendMethod.Over;
frameMetadata = currentFrame.Metadata.GetPngMetadata();
bool blend = frameMetadata.BlendMode == FrameBlendMode.Over;
(bool difference, Rectangle bounds) =
AnimationUtilities.DeDuplicatePixels(
@ -268,7 +266,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
sequenceNumber += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true) + 1;
previousFrame = currentFrame;
previousDisposal = frameMetadata.DisposalMethod;
previousDisposal = frameMetadata.DisposalMode;
}
}
@ -288,54 +286,6 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
this.currentScanline?.Dispose();
}
private static PngMetadata GetPngMetadata<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
{
return (PngMetadata)png.DeepClone();
}
if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
{
AnimatedImageMetadata ani = gif.ToAnimatedImageMetadata();
return PngMetadata.FromAnimatedMetadata(ani);
}
if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp))
{
AnimatedImageMetadata ani = webp.ToAnimatedImageMetadata();
return PngMetadata.FromAnimatedMetadata(ani);
}
// Return explicit new instance so we do not mutate the original metadata.
return new();
}
private static PngFrameMetadata GetPngFrameMetadata<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>
{
if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
{
return (PngFrameMetadata)png.DeepClone();
}
if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
{
AnimatedImageFrameMetadata ani = gif.ToAnimatedImageFrameMetadata();
return PngFrameMetadata.FromAnimatedMetadata(ani);
}
if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp))
{
AnimatedImageFrameMetadata ani = webp.ToAnimatedImageFrameMetadata();
return PngFrameMetadata.FromAnimatedMetadata(ani);
}
// Return explicit new instance so we do not mutate the original metadata.
return new();
}
/// <summary>
/// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases.
/// </summary>
@ -826,7 +776,6 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return;
}
meta.SyncProfiles();
this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray());
}
@ -881,6 +830,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="metaData">The image meta data.</param>
/// <exception cref="NotSupportedException">CICP matrix coefficients other than Identity are not supported in PNG.</exception>
private void WriteCicpChunk(Stream stream, ImageMetadata metaData)
{
if (metaData.CicpProfile is null)
@ -1125,8 +1075,8 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
yOffset: (uint)bounds.Top,
delayNumerator: (ushort)frameMetadata.FrameDelay.Numerator,
delayDenominator: (ushort)frameMetadata.FrameDelay.Denominator,
disposeOperation: frameMetadata.DisposalMethod,
blendOperation: frameMetadata.BlendMethod);
disposalMode: frameMetadata.DisposalMode,
blendMode: frameMetadata.BlendMode);
fcTL.WriteTo(this.chunkDataBuffer.Span);
@ -1483,41 +1433,19 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
// Use options, then check metadata, if nothing set there then we suggest
// a sensible default based upon the pixel format.
PngColorType? colorType = encoder.ColorType ?? pngMetadata.ColorType;
byte? bits = (byte?)(encoder.BitDepth ?? pngMetadata.BitDepth);
if (colorType is null || bits is null)
{
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
PixelComponentInfo? componentInfo = info.ComponentInfo;
colorType ??= SuggestColorType<TPixel>(in info);
if (bits is null)
{
// TODO: Update once we stop abusing PixelTypeInfo in decoders.
if (componentInfo.HasValue)
{
PixelComponentInfo c = componentInfo.Value;
bits = (byte)SuggestBitDepth<TPixel>(in c);
}
else
{
bits = (byte)PngBitDepth.Bit8;
}
}
}
PngColorType color = encoder.ColorType ?? pngMetadata.ColorType;
byte bits = (byte)(encoder.BitDepth ?? pngMetadata.BitDepth);
// Ensure bit depth and color type are a supported combination.
// Ensure the bit depth and color type are a supported combination.
// Bit8 is the only bit depth supported by all color types.
byte[] validBitDepths = PngConstants.ColorTypes[colorType.Value];
byte[] validBitDepths = PngConstants.ColorTypes[color];
if (Array.IndexOf(validBitDepths, bits) == -1)
{
bits = (byte)PngBitDepth.Bit8;
}
this.colorType = colorType.Value;
this.bitDepth = bits.Value;
this.colorType = color;
this.bitDepth = bits;
if (encoder.FilterMethod.HasValue)
{
@ -1532,7 +1460,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
use16Bit = bits == (byte)PngBitDepth.Bit16;
bytesPerPixel = CalculateBytesPerPixel(this.colorType, use16Bit);
this.interlaceMode = (encoder.InterlaceMethod ?? pngMetadata.InterlaceMethod)!.Value;
this.interlaceMode = encoder.InterlaceMethod ?? pngMetadata.InterlaceMethod;
this.chunkFilter = encoder.SkipMetadata ? PngChunkFilter.ExcludeAll : encoder.ChunkFilter ?? PngChunkFilter.None;
}
@ -1672,47 +1600,6 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
_ => use16Bit ? 8 : 4,
};
/// <summary>
/// Returns a suggested <see cref="PngColorType"/> for the given <typeparamref name="TPixel"/>
/// </summary>
/// <param name="info">The pixel type info.</param>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
private static PngColorType SuggestColorType<TPixel>(in PixelTypeInfo info)
where TPixel : unmanaged, IPixel<TPixel>
{
if (info.AlphaRepresentation == PixelAlphaRepresentation.None)
{
return info.ColorType switch
{
PixelColorType.Grayscale => PngColorType.Grayscale,
_ => PngColorType.Rgb,
};
}
return info.ColorType switch
{
PixelColorType.Grayscale | PixelColorType.Alpha or PixelColorType.Alpha => PngColorType.GrayscaleWithAlpha,
_ => PngColorType.RgbWithAlpha,
};
}
/// <summary>
/// Returns a suggested <see cref="PngBitDepth"/> for the given <typeparamref name="TPixel"/>
/// </summary>
/// <param name="info">The pixel type info.</param>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
private static PngBitDepth SuggestBitDepth<TPixel>(in PixelComponentInfo info)
where TPixel : unmanaged, IPixel<TPixel>
{
int bits = info.GetMaximumComponentPrecision();
if (bits > (int)PixelComponentBitDepth.Bit8)
{
return PngBitDepth.Bit16;
}
return PngBitDepth.Bit8;
}
private unsafe struct ScratchBuffer
{
private const int Size = 26;

56
src/ImageSharp/Formats/Png/PngFrameMetadata.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Provides APng specific metadata information for the image frame.
/// </summary>
public class PngFrameMetadata : IDeepCloneable
public class PngFrameMetadata : IFormatFrameMetadata<PngFrameMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="PngFrameMetadata"/> class.
@ -24,8 +24,8 @@ public class PngFrameMetadata : IDeepCloneable
private PngFrameMetadata(PngFrameMetadata other)
{
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.BlendMethod = other.BlendMethod;
this.DisposalMode = other.DisposalMode;
this.BlendMode = other.BlendMode;
}
/// <summary>
@ -39,12 +39,12 @@ public class PngFrameMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the type of frame area disposal to be done after rendering this frame
/// </summary>
public PngDisposalMethod DisposalMethod { get; set; }
public FrameDisposalMode DisposalMode { get; set; }
/// <summary>
/// Gets or sets the type of frame area rendering for this frame
/// </summary>
public PngBlendMethod BlendMethod { get; set; }
public FrameBlendMode BlendMode { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PngFrameMetadata"/> class.
@ -53,26 +53,48 @@ public class PngFrameMetadata : IDeepCloneable
internal void FromChunk(in FrameControl frameControl)
{
this.FrameDelay = new Rational(frameControl.DelayNumerator, frameControl.DelayDenominator);
this.DisposalMethod = frameControl.DisposeOperation;
this.BlendMethod = frameControl.BlendOperation;
this.DisposalMode = frameControl.DisposalMode;
this.BlendMode = frameControl.BlendMode;
}
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PngFrameMetadata(this);
internal static PngFrameMetadata FromAnimatedMetadata(AnimatedImageFrameMetadata metadata)
public static PngFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
=> new()
{
FrameDelay = new(metadata.Duration.TotalMilliseconds / 1000),
DisposalMethod = GetMode(metadata.DisposalMode),
BlendMethod = metadata.BlendMode == FrameBlendMode.Source ? PngBlendMethod.Source : PngBlendMethod.Over,
DisposalMode = GetMode(metadata.DisposalMode),
BlendMode = metadata.BlendMode,
};
private static PngDisposalMethod GetMode(FrameDisposalMode mode) => mode switch
/// <inheritdoc/>
public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
{
double delay = this.FrameDelay.ToDouble();
if (double.IsNaN(delay))
{
delay = 0;
}
return new()
{
ColorTableMode = FrameColorTableMode.Global,
Duration = TimeSpan.FromMilliseconds(delay * 1000),
DisposalMode = this.DisposalMode,
BlendMode = this.BlendMode,
};
}
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public PngFrameMetadata DeepClone() => new(this);
private static FrameDisposalMode GetMode(FrameDisposalMode mode) => mode switch
{
FrameDisposalMode.RestoreToBackground => PngDisposalMethod.RestoreToBackground,
FrameDisposalMode.RestoreToPrevious => PngDisposalMethod.RestoreToPrevious,
FrameDisposalMode.DoNotDispose => PngDisposalMethod.DoNotDispose,
_ => PngDisposalMethod.DoNotDispose,
FrameDisposalMode.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
FrameDisposalMode.RestoreToPrevious => FrameDisposalMode.RestoreToPrevious,
FrameDisposalMode.DoNotDispose => FrameDisposalMode.DoNotDispose,
_ => FrameDisposalMode.DoNotDispose,
};
}

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

@ -2,13 +2,14 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Provides Png specific metadata information for the image.
/// </summary>
public class PngMetadata : IDeepCloneable
public class PngMetadata : IFormatMetadata<PngMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="PngMetadata"/> class.
@ -46,17 +47,17 @@ public class PngMetadata : IDeepCloneable
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary>
public PngBitDepth? BitDepth { get; set; }
public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
/// <summary>
/// Gets or sets the color type.
/// </summary>
public PngColorType? ColorType { get; set; }
public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image.
/// </summary>
public PngInterlaceMode? InterlaceMethod { get; set; } = PngInterlaceMode.None;
public PngInterlaceMode InterlaceMethod { get; set; } = PngInterlaceMode.None;
/// <summary>
/// Gets or sets the gamma value for the image.
@ -77,7 +78,7 @@ public class PngMetadata : IDeepCloneable
/// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks.
/// Used for conveying textual information associated with the image.
/// </summary>
public IList<PngTextData> TextData { get; set; } = new List<PngTextData>();
public IList<PngTextData> TextData { get; set; } = [];
/// <summary>
/// Gets or sets the number of times to loop this APNG. 0 indicates infinite looping.
@ -90,35 +91,165 @@ public class PngMetadata : IDeepCloneable
public bool AnimateRootFrame { get; set; } = true;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PngMetadata(this);
internal static PngMetadata FromAnimatedMetadata(AnimatedImageMetadata metadata)
public static PngMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
// Should the conversion be from a format that uses a 24bit palette entries (gif)
// we need to clone and adjust the color table to allow for transparency.
Color[]? colorTable = metadata.ColorTable.HasValue ? metadata.ColorTable.Value.ToArray() : null;
Color[]? colorTable = metadata.ColorTable?.ToArray();
if (colorTable != null)
{
for (int i = 0; i < colorTable.Length; i++)
{
ref Color c = ref colorTable[i];
if (c == metadata.BackgroundColor)
if (c != metadata.BackgroundColor)
{
// Png treats background as fully empty
c = Color.Transparent;
break;
continue;
}
// Png treats background as fully empty
c = Color.Transparent;
break;
}
}
PngColorType color;
PixelColorType colorType = metadata.PixelTypeInfo.ColorType;
switch (colorType)
{
case PixelColorType.Binary:
case PixelColorType.Indexed:
color = PngColorType.Palette;
break;
case PixelColorType.Luminance:
color = PngColorType.Grayscale;
break;
case PixelColorType.RGB:
case PixelColorType.BGR:
color = PngColorType.Rgb;
break;
default:
if (colorType.HasFlag(PixelColorType.Luminance))
{
color = PngColorType.GrayscaleWithAlpha;
break;
}
color = PngColorType.RgbWithAlpha;
break;
}
// PNG uses bits per component not per pixel.
int bpc = metadata.PixelTypeInfo.ComponentInfo?.GetMaximumComponentPrecision() ?? 8;
PngBitDepth bitDepth = bpc switch
{
1 => PngBitDepth.Bit1,
2 => PngBitDepth.Bit2,
4 => PngBitDepth.Bit4,
_ => (bpc <= 8) ? PngBitDepth.Bit8 : PngBitDepth.Bit16,
};
return new()
{
ColorType = colorTable != null ? PngColorType.Palette : null,
BitDepth = colorTable != null
? (PngBitDepth)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(colorTable.Length), 1, 8)
: null,
ColorType = color,
BitDepth = bitDepth,
ColorTable = colorTable,
RepeatCount = metadata.RepeatCount,
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
PixelComponentInfo info;
switch (this.ColorType)
{
case PngColorType.Palette:
bpp = this.ColorTable.HasValue
? Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.ColorTable.Value.Length), 1, 8)
: 8;
colorType = PixelColorType.Indexed;
info = PixelComponentInfo.Create(1, bpp, bpp);
break;
case PngColorType.Grayscale:
bpp = (int)this.BitDepth;
colorType = PixelColorType.Luminance;
info = PixelComponentInfo.Create(1, bpp, bpp);
break;
case PngColorType.GrayscaleWithAlpha:
alpha = PixelAlphaRepresentation.Unassociated;
if (this.BitDepth == PngBitDepth.Bit16)
{
bpp = 32;
colorType = PixelColorType.Luminance | PixelColorType.Alpha;
info = PixelComponentInfo.Create(2, bpp, 16, 16);
break;
}
bpp = 16;
colorType = PixelColorType.Luminance | PixelColorType.Alpha;
info = PixelComponentInfo.Create(2, bpp, 8, 8);
break;
case PngColorType.Rgb:
if (this.BitDepth == PngBitDepth.Bit16)
{
bpp = 48;
colorType = PixelColorType.RGB;
info = PixelComponentInfo.Create(3, bpp, 16, 16, 16);
break;
}
bpp = 24;
colorType = PixelColorType.RGB;
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
break;
case PngColorType.RgbWithAlpha:
default:
alpha = PixelAlphaRepresentation.Unassociated;
if (this.BitDepth == PngBitDepth.Bit16)
{
bpp = 64;
colorType = PixelColorType.RGB | PixelColorType.Alpha;
info = PixelComponentInfo.Create(4, bpp, 16, 16, 16, 16);
break;
}
bpp = 32;
colorType = PixelColorType.RGB | PixelColorType.Alpha;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
break;
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
ColorTable = this.ColorTable,
ColorTableMode = FrameColorTableMode.Global,
PixelTypeInfo = this.GetPixelTypeInfo(),
RepeatCount = (ushort)Numerics.Clamp(this.RepeatCount, 0, ushort.MaxValue),
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public PngMetadata DeepClone() => new(this);
}

20
src/ImageSharp/Formats/Qoi/MetadataExtensions.cs

@ -1,20 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the qoi format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="QoiMetadata"/>.</returns>
public static QoiMetadata GetQoiMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(QoiFormat.Instance);
}

23
src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs

@ -13,7 +13,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Qoi;
internal class QoiDecoderCore : IImageDecoderInternals
internal class QoiDecoderCore : ImageDecoderCore
{
/// <summary>
/// The global configuration.
@ -31,31 +31,20 @@ internal class QoiDecoderCore : IImageDecoderInternals
private QoiHeader header;
public QoiDecoderCore(DecoderOptions options)
: base(options)
{
this.Options = options;
this.configuration = options.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
public DecoderOptions Options { get; }
public Size Dimensions { get; }
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
// Process the header to get metadata
this.ProcessHeader(stream);
// Create Image object
ImageMetadata metadata = new()
{
DecodedImageFormat = QoiFormat.Instance,
HorizontalResolution = this.header.Width,
VerticalResolution = this.header.Height,
ResolutionUnits = PixelResolutionUnit.AspectRatio
};
ImageMetadata metadata = new();
QoiMetadata qoiMetadata = metadata.GetQoiMetadata();
qoiMetadata.Channels = this.header.Channels;
qoiMetadata.ColorSpace = this.header.ColorSpace;
@ -68,7 +57,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ProcessHeader(stream);
PixelTypeInfo pixelType = new(8 * (int)this.header.Channels);
@ -79,7 +68,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
qoiMetadata.Channels = this.header.Channels;
qoiMetadata.ColorSpace = this.header.ColorSpace;
return new ImageInfo(pixelType, size, metadata);
return new ImageInfo(size, metadata);
}
/// <summary>

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

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Qoi;
/// <summary>
/// Image encoder for writing an image to a stream as a QOi image
/// </summary>
internal class QoiEncoderCore : IImageEncoderInternals
internal class QoiEncoderCore
{
/// <summary>
/// The encoder with options
@ -41,7 +41,13 @@ internal class QoiEncoderCore : IImageEncoderInternals
this.memoryAllocator = configuration.MemoryAllocator;
}
/// <inheritdoc />
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{

60
src/ImageSharp/Formats/Qoi/QoiMetadata.cs

@ -1,12 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Qoi;
/// <summary>
/// Provides Qoi specific metadata information for the image.
/// </summary>
public class QoiMetadata : IDeepCloneable
public class QoiMetadata : IFormatMetadata<QoiMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="QoiMetadata"/> class.
@ -36,5 +38,59 @@ public class QoiMetadata : IDeepCloneable
public QoiColorSpace ColorSpace { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new QoiMetadata(this);
public static QoiMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
PixelColorType color = metadata.PixelTypeInfo.ColorType;
if (color.HasFlag(PixelColorType.Alpha))
{
return new QoiMetadata { Channels = QoiChannels.Rgba };
}
return new QoiMetadata { Channels = QoiChannels.Rgb };
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp;
PixelColorType colorType;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
PixelComponentInfo info;
switch (this.Channels)
{
case QoiChannels.Rgb:
bpp = 24;
colorType = PixelColorType.RGB;
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
break;
default:
bpp = 32;
colorType = PixelColorType.RGB | PixelColorType.Alpha;
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
alpha = PixelAlphaRepresentation.Unassociated;
break;
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ColorType = colorType,
ComponentInfo = info,
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public QoiMetadata DeepClone() => new(this);
}

20
src/ImageSharp/Formats/Tga/MetadataExtensions.cs

@ -1,20 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the tga format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="TgaMetadata"/>.</returns>
public static TgaMetadata GetTgaMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TgaFormat.Instance);
}

8
src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs

@ -11,20 +11,20 @@ public enum TgaBitsPerPixel : byte
/// <summary>
/// 8 bits per pixel. Each pixel consists of 1 byte.
/// </summary>
Pixel8 = 8,
Bit8 = 8,
/// <summary>
/// 16 bits per pixel. Each pixel consists of 2 bytes.
/// </summary>
Pixel16 = 16,
Bit16 = 16,
/// <summary>
/// 24 bits per pixel. Each pixel consists of 3 bytes.
/// </summary>
Pixel24 = 24,
Bit24 = 24,
/// <summary>
/// 32 bits per pixel. Each pixel consists of 4 bytes.
/// </summary>
Pixel32 = 32
Bit32 = 32
}

20
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Tga;
/// <summary>
/// Performs the tga decoding operation.
/// </summary>
internal sealed class TgaDecoderCore : IImageDecoderInternals
internal sealed class TgaDecoderCore : ImageDecoderCore
{
/// <summary>
/// General configuration options.
@ -52,21 +52,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="options">The options.</param>
public TgaDecoderCore(DecoderOptions options)
: base(options)
{
this.Options = options;
this.configuration = options.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc />
public DecoderOptions Options { get; }
/// <inheritdoc />
public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
try
{
@ -642,11 +635,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadFileHeader(stream);
return new ImageInfo(
new PixelTypeInfo(this.fileHeader.PixelDepth),
new(this.fileHeader.Width, this.fileHeader.Height),
this.metadata);
}
@ -915,6 +907,8 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
stream.Read(buffer, 0, TgaFileHeader.Size);
this.fileHeader = TgaFileHeader.Parse(buffer);
this.Dimensions = new Size(this.fileHeader.Width, this.fileHeader.Height);
this.metadata = new ImageMetadata();
this.tgaMetadata = this.metadata.GetTgaMetadata();
this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
@ -934,7 +928,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
return (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4);
}
private bool IsTrueColor32BitPerPixel(TgaBitsPerPixel bitsPerPixel) => bitsPerPixel == TgaBitsPerPixel.Pixel32 &&
private bool IsTrueColor32BitPerPixel(TgaBitsPerPixel bitsPerPixel) => bitsPerPixel == TgaBitsPerPixel.Bit32 &&
(this.fileHeader.ImageType == TgaImageType.TrueColor ||
this.fileHeader.ImageType == TgaImageType.RleTrueColor);
}

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

@ -3,8 +3,6 @@
using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -14,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tga;
/// <summary>
/// Image encoder for writing an image to a stream as a truevision targa image.
/// </summary>
internal sealed class TgaEncoderCore : IImageEncoderInternals
internal sealed class TgaEncoderCore
{
/// <summary>
/// Used for allocating memory during processing operations.
@ -61,7 +59,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
this.bitsPerPixel ??= tgaMetadata.BitsPerPixel;
TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor;
if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8)
if (this.bitsPerPixel == TgaBitsPerPixel.Bit8)
{
imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite;
}
@ -73,13 +71,13 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
imageDescriptor |= 0x20;
}
if (this.bitsPerPixel is TgaBitsPerPixel.Pixel32)
if (this.bitsPerPixel is TgaBitsPerPixel.Bit32)
{
// Indicate, that 8 bit are used for the alpha channel.
imageDescriptor |= 0x8;
}
if (this.bitsPerPixel is TgaBitsPerPixel.Pixel16)
if (this.bitsPerPixel is TgaBitsPerPixel.Bit16)
{
// Indicate, that 1 bit is used for the alpha channel.
imageDescriptor |= 0x1;
@ -107,11 +105,11 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
stream.Write(buffer, 0, TgaFileHeader.Size);
if (this.compression is TgaCompression.RunLength)
{
this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame);
this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame, cancellationToken);
}
else
{
this.WriteImage(image.Configuration, stream, image.Frames.RootFrame);
this.WriteImage(image.Configuration, stream, image.Frames.RootFrame, cancellationToken);
}
stream.Flush();
@ -123,29 +121,28 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
/// <param name="image"> /// The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
private void WriteImage<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.PixelBuffer;
switch (this.bitsPerPixel)
{
case TgaBitsPerPixel.Pixel8:
this.Write8Bit(configuration, stream, pixels);
case TgaBitsPerPixel.Bit8:
this.Write8Bit(configuration, stream, pixels, cancellationToken);
break;
case TgaBitsPerPixel.Pixel16:
this.Write16Bit(configuration, stream, pixels);
case TgaBitsPerPixel.Bit16:
this.Write16Bit(configuration, stream, pixels, cancellationToken);
break;
case TgaBitsPerPixel.Pixel24:
this.Write24Bit(configuration, stream, pixels);
case TgaBitsPerPixel.Bit24:
this.Write24Bit(configuration, stream, pixels, cancellationToken);
break;
case TgaBitsPerPixel.Pixel32:
this.Write32Bit(configuration, stream, pixels);
case TgaBitsPerPixel.Bit32:
this.Write32Bit(configuration, stream, pixels, cancellationToken);
break;
}
}
@ -156,23 +153,33 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The stream to write the image to.</param>
/// <param name="image">The image to encode.</param>
private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
/// <param name="cancellationToken">The token to request cancellation.</param>
private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.PixelBuffer;
using IMemoryOwner<Rgba32> rgbaOwner = this.memoryAllocator.Allocate<Rgba32>(image.Width);
Span<Rgba32> rgbaRow = rgbaOwner.GetSpan();
for (int y = 0; y < image.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgba32(image.Configuration, pixelRow, rgbaRow);
for (int x = 0; x < image.Width;)
{
TPixel currentPixel = pixelRow[x];
Rgba32 rgba = rgbaRow[x];
byte equalPixelCount = FindEqualPixels(pixelRow, x);
if (equalPixelCount > 0)
{
// Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
// Write the number of equal pixels, with the high bit set, indicating it's a compressed pixel run.
stream.WriteByte((byte)(equalPixelCount | 128));
this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
this.WritePixel(stream, rgba);
x += equalPixelCount + 1;
}
else
@ -180,12 +187,13 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
// Write Raw Packet (i.e., Non-Run-Length Encoded):
byte unEqualPixelCount = FindUnEqualPixels(pixelRow, x);
stream.WriteByte(unEqualPixelCount);
this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
this.WritePixel(stream, rgba);
x++;
for (int i = 0; i < unEqualPixelCount; i++)
{
currentPixel = pixelRow[x];
this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
rgba = rgbaRow[x];
this.WritePixel(stream, rgba);
x++;
}
}
@ -196,22 +204,19 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// <summary>
/// Writes a the pixel to the stream.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The stream to write to.</param>
/// <param name="currentPixel">The current pixel.</param>
/// <param name="color">The color of the pixel to write.</param>
private void WritePixel<TPixel>(Stream stream, TPixel currentPixel, Rgba32 color)
where TPixel : unmanaged, IPixel<TPixel>
private void WritePixel(Stream stream, Rgba32 color)
{
switch (this.bitsPerPixel)
{
case TgaBitsPerPixel.Pixel8:
int luminance = GetLuminance(currentPixel);
stream.WriteByte((byte)luminance);
case TgaBitsPerPixel.Bit8:
L8 l8 = L8.FromRgba32(color);
stream.WriteByte(l8.PackedValue);
break;
case TgaBitsPerPixel.Pixel16:
Bgra5551 bgra5551 = new(color.ToVector4());
case TgaBitsPerPixel.Bit16:
Bgra5551 bgra5551 = Bgra5551.FromRgba32(color);
Span<byte> buffer = stackalloc byte[2];
BinaryPrimitives.WriteInt16LittleEndian(buffer, (short)bgra5551.PackedValue);
stream.WriteByte(buffer[0]);
@ -219,13 +224,13 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
break;
case TgaBitsPerPixel.Pixel24:
case TgaBitsPerPixel.Bit24:
stream.WriteByte(color.B);
stream.WriteByte(color.G);
stream.WriteByte(color.R);
break;
case TgaBitsPerPixel.Pixel32:
case TgaBitsPerPixel.Bit32:
stream.WriteByte(color.B);
stream.WriteByte(color.G);
stream.WriteByte(color.R);
@ -310,7 +315,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
/// <param name="cancellationToken">The token to request cancellation.</param>
private void Write8Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 1);
@ -318,6 +324,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
for (int y = pixels.Height - 1; y >= 0; y--)
{
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
configuration,
@ -335,7 +343,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
/// <param name="cancellationToken">The token to request cancellation.</param>
private void Write16Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 2);
@ -343,6 +352,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
for (int y = pixels.Height - 1; y >= 0; y--)
{
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
configuration,
@ -360,7 +371,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
/// <param name="cancellationToken">The token to request cancellation.</param>
private void Write24Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 3);
@ -368,6 +380,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
for (int y = pixels.Height - 1; y >= 0; y--)
{
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
configuration,
@ -385,7 +399,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
/// <param name="cancellationToken">The token to request cancellation.</param>
private void Write32Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
@ -393,6 +408,8 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
for (int y = pixels.Height - 1; y >= 0; y--)
{
cancellationToken.ThrowIfCancellationRequested();
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
configuration,
@ -402,17 +419,4 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
stream.Write(rowSpan);
}
}
/// <summary>
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
/// <param name="sourcePixel">The pixel to get the luminance from.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetLuminance<TPixel>(TPixel sourcePixel)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 vector = sourcePixel.ToVector4();
return ColorNumerics.GetBT709Luminance(ref vector, 256);
}
}

71
src/ImageSharp/Formats/Tga/TgaMetadata.cs

@ -1,12 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga;
/// <summary>
/// Provides TGA specific metadata information for the image.
/// </summary>
public class TgaMetadata : IDeepCloneable
public class TgaMetadata : IFormatMetadata<TgaMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="TgaMetadata"/> class.
@ -25,7 +27,7 @@ public class TgaMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the number of bits per pixel.
/// </summary>
public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24;
public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Bit24;
/// <summary>
/// Gets or sets the number of alpha bits per pixel.
@ -33,5 +35,68 @@ public class TgaMetadata : IDeepCloneable
public byte AlphaChannelBits { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TgaMetadata(this);
public static TgaMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
// TODO: AlphaChannelBits is not used during encoding.
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
return bpp switch
{
<= 8 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit8 },
<= 16 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit16 },
<= 24 => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit24 },
_ => new TgaMetadata { BitsPerPixel = TgaBitsPerPixel.Bit32 }
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BitsPerPixel;
PixelComponentInfo info;
PixelColorType color;
PixelAlphaRepresentation alpha;
switch (this.BitsPerPixel)
{
case TgaBitsPerPixel.Bit8:
info = PixelComponentInfo.Create(1, bpp, 8);
color = PixelColorType.Luminance;
alpha = PixelAlphaRepresentation.None;
break;
case TgaBitsPerPixel.Bit16:
info = PixelComponentInfo.Create(1, bpp, 5, 5, 5, 1);
color = PixelColorType.BGR | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
case TgaBitsPerPixel.Bit24:
info = PixelComponentInfo.Create(3, bpp, 8, 8, 8);
color = PixelColorType.RGB;
alpha = PixelAlphaRepresentation.None;
break;
case TgaBitsPerPixel.Bit32 or _:
info = PixelComponentInfo.Create(4, bpp, 8, 8, 8, 8);
color = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
}
return new PixelTypeInfo(bpp)
{
AlphaRepresentation = alpha,
ComponentInfo = info,
ColorType = color
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public TgaMetadata DeepClone() => new(this);
}

2
src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs

@ -33,7 +33,7 @@ internal class TiffJpegCompressor : TiffBaseCompressor
var image = Image.LoadPixelData<Rgb24>(rows, width, height);
image.Save(memoryStream, new JpegEncoder()
{
ColorType = JpegEncodingColor.Rgb
ColorType = JpegColorType.Rgb
});
memoryStream.Position = 0;
memoryStream.WriteTo(this.Output);

4
src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs

@ -32,8 +32,8 @@ internal class WebpTiffCompression : TiffBaseDecompressor
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{
using WebpDecoderCore decoder = new(new WebpDecoderOptions());
using Image<Rgb24> image = decoder.Decode<Rgb24>(stream, cancellationToken);
using WebpDecoderCore decoder = new(new WebpDecoderOptions() { GeneralOptions = this.options });
using Image<Rgb24> image = decoder.Decode<Rgb24>(this.options.Configuration, stream, cancellationToken);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
}

45
src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs

@ -39,9 +39,9 @@ internal static class TiffConstants
public const ushort BigTiffHeaderMagicNumber = 43;
/// <summary>
/// The big tiff bytesize of offsets value.
/// The big tiff byte size of offsets value.
/// </summary>
public const ushort BigTiffBytesize = 8;
public const ushort BigTiffByteSize = 8;
/// <summary>
/// RowsPerStrip default value, which is effectively infinity.
@ -58,38 +58,63 @@ internal static class TiffConstants
/// </summary>
public const int DefaultStripSize = 8 * 1024;
/// <summary>
/// The default predictor is None.
/// </summary>
public const TiffPredictor DefaultPredictor = TiffPredictor.None;
/// <summary>
/// The default bits per pixel is Bit24.
/// </summary>
public const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24;
/// <summary>
/// The default bits per sample for color images with 8 bits for each color channel.
/// </summary>
public static readonly TiffBitsPerSample DefaultBitsPerSample = BitsPerSampleRgb8Bit;
/// <summary>
/// The default compression is None.
/// </summary>
public const TiffCompression DefaultCompression = TiffCompression.None;
/// <summary>
/// The default photometric interpretation is Rgb.
/// </summary>
public const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
/// <summary>
/// The bits per sample for 1 bit bicolor images.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample1Bit = new(1, 0, 0);
/// <summary>
/// The bits per sample for images with a 4 color palette.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample4Bit = new(4, 0, 0);
/// <summary>
/// The bits per sample for 8 bit images.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample8Bit = new(8, 0, 0);
/// <summary>
/// The bits per sample for 16-bit grayscale images.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample16Bit = new TiffBitsPerSample(16, 0, 0);
public static readonly TiffBitsPerSample BitsPerSample16Bit = new(16, 0, 0);
/// <summary>
/// The bits per sample for color images with 8 bits for each color channel.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8);
public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new(8, 8, 8);
/// <summary>
/// The list of mimetypes that equate to a tiff.
/// The list of mime types that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" };
public static readonly IEnumerable<string> MimeTypes = ["image/tiff", "image/tiff-fx"];
/// <summary>
/// The list of file extensions that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" };
public static readonly IEnumerable<string> FileExtensions = ["tiff", "tif"];
}

2
src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

@ -64,7 +64,7 @@ internal class HeaderReader : BaseExifReader
ushort bytesize = this.ReadUInt16();
ushort reserve = this.ReadUInt16();
if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0)
if (bytesize == TiffConstants.BigTiffByteSize && reserve == 0)
{
this.FirstIfdOffset = this.ReadUInt64();
return;

27
src/ImageSharp/Formats/Tiff/MetadataExtensions.cs

@ -1,27 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the tiff format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="TiffMetadata"/>.</returns>
public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance);
/// <summary>
/// Gets the tiff format specific metadata for the image frame.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="TiffFrameMetadata"/>.</returns>
public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance);
}

8
src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs

@ -147,20 +147,20 @@ public readonly struct TiffBitsPerSample : IEquatable<TiffBitsPerSample>
{
if (this.Channel1 == 0)
{
return new[] { this.Channel0 };
return [this.Channel0];
}
if (this.Channel2 == 0)
{
return new[] { this.Channel0, this.Channel1 };
return [this.Channel0, this.Channel1];
}
if (this.Channel3 == 0)
{
return new[] { this.Channel0, this.Channel1, this.Channel2 };
return [this.Channel0, this.Channel1, this.Channel2];
}
return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 };
return [this.Channel0, this.Channel1, this.Channel2, this.Channel3];
}
/// <summary>

17
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
/// <summary>
/// Performs the tiff decoding operation.
/// </summary>
internal class TiffDecoderCore : IImageDecoderInternals
internal class TiffDecoderCore : ImageDecoderCore
{
/// <summary>
/// General configuration options.
@ -60,8 +60,8 @@ internal class TiffDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="options">The decoder options.</param>
public TiffDecoderCore(DecoderOptions options)
: base(options)
{
this.Options = options;
this.configuration = options.Configuration;
this.skipMetadata = options.SkipMetadata;
this.maxFrames = options.MaxFrames;
@ -154,14 +154,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
public TiffPredictor Predictor { get; set; }
/// <inheritdoc/>
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions { get; private set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
List<ImageFrame<TPixel>> frames = [];
List<ImageFrameMetadata> framesMetadata = [];
@ -215,7 +208,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
}
/// <inheritdoc/>
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.inputStream = stream;
DirectoryReader reader = new(stream, this.configuration.MemoryAllocator);
@ -234,7 +227,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
int width = GetImageWidth(rootFrameExifProfile);
int height = GetImageHeight(rootFrameExifProfile);
return new ImageInfo(new PixelTypeInfo((int)framesMetadata[0].GetTiffMetadata().BitsPerPixel), new(width, height), metadata, framesMetadata);
return new ImageInfo(new(width, height), metadata, framesMetadata);
}
/// <summary>

16
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

@ -23,7 +23,7 @@ internal static class TiffDecoderMetadataCreator
TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
}
ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].ExifProfile);
ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0]);
if (!ignoreMetadata)
{
@ -50,14 +50,22 @@ internal static class TiffDecoderMetadataCreator
return imageMetaData;
}
private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile)
private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ImageFrameMetadata rootFrameMetadata)
{
ImageMetadata imageMetaData = new();
SetResolution(imageMetaData, exifProfile);
SetResolution(imageMetaData, rootFrameMetadata.ExifProfile);
TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata();
tiffMetadata.ByteOrder = byteOrder;
tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default;
TiffFrameMetadata tiffFrameMetadata = rootFrameMetadata.GetTiffMetadata();
tiffMetadata.BitsPerPixel = tiffFrameMetadata.BitsPerPixel;
tiffMetadata.BitsPerSample = tiffFrameMetadata.BitsPerSample;
tiffMetadata.Compression = tiffFrameMetadata.Compression;
tiffMetadata.PhotometricInterpretation = tiffFrameMetadata.PhotometricInterpretation;
tiffMetadata.Predictor = tiffFrameMetadata.Predictor;
return imageMetaData;
}
@ -109,7 +117,7 @@ internal static class TiffDecoderMetadataCreator
return false;
}
// Probably wrong endianess, swap byte order.
// Probably wrong endianness, swap byte order.
Span<byte> iptcBytesSpan = iptcBytes.AsSpan();
Span<byte> buffer = stackalloc byte[4];
for (int i = 0; i < iptcBytes.Length; i += 4)

78
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -63,7 +63,7 @@ internal static class TiffDecoderOptionsParser
}
TiffSampleFormat? sampleFormat = null;
if (exifProfile.TryGetValue(ExifTag.SampleFormat, out var formatValue))
if (exifProfile.TryGetValue(ExifTag.SampleFormat, out IExifValue<ushort[]> formatValue))
{
TiffSampleFormat[] sampleFormats = formatValue.Value.Select(a => (TiffSampleFormat)a).ToArray();
sampleFormat = sampleFormats[0];
@ -106,11 +106,11 @@ internal static class TiffDecoderOptionsParser
options.PlanarConfiguration = DefaultPlanarConfiguration;
}
options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None;
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
options.Predictor = frameMetadata.Predictor;
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation;
options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger;
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
options.BitsPerPixel = (int)frameMetadata.BitsPerPixel;
options.BitsPerSample = frameMetadata.BitsPerSample;
if (exifProfile.TryGetValue(ExifTag.ReferenceBlackWhite, out IExifValue<Rational[]> blackWhiteValue))
{
@ -142,9 +142,7 @@ internal static class TiffDecoderOptionsParser
options.ParseCompression(frameMetadata.Compression, exifProfile);
options.ParseColorType(exifProfile);
bool isTiled = VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration);
return isTiled;
return VerifyRequiredFieldsArePresent(exifProfile, frameMetadata, options.PlanarConfiguration);
}
/// <summary>
@ -194,13 +192,6 @@ internal static class TiffDecoderOptionsParser
}
}
// For BiColor compressed images, the BitsPerPixel value will be set explicitly to 1, so we don't throw in those cases.
// See: https://github.com/SixLabors/ImageSharp/issues/2587
if (frameMetadata.BitsPerPixel == null && !IsBiColorCompression(frameMetadata.Compression))
{
TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!");
}
return isTiled;
}
@ -224,7 +215,6 @@ internal static class TiffDecoderOptionsParser
switch (bitsPerChannel)
{
case 32:
{
if (options.SampleFormat == TiffSampleFormat.Float)
{
options.ColorType = TiffColorType.WhiteIsZero32Float;
@ -233,43 +223,30 @@ internal static class TiffDecoderOptionsParser
options.ColorType = TiffColorType.WhiteIsZero32;
break;
}
case 24:
{
options.ColorType = TiffColorType.WhiteIsZero24;
break;
}
case 16:
{
options.ColorType = TiffColorType.WhiteIsZero16;
break;
}
case 8:
{
options.ColorType = TiffColorType.WhiteIsZero8;
break;
}
case 4:
{
options.ColorType = TiffColorType.WhiteIsZero4;
break;
}
case 1:
{
options.ColorType = TiffColorType.WhiteIsZero1;
break;
}
default:
{
options.ColorType = TiffColorType.WhiteIsZero;
break;
}
}
break;
@ -291,7 +268,6 @@ internal static class TiffDecoderOptionsParser
switch (bitsPerChannel)
{
case 32:
{
if (options.SampleFormat == TiffSampleFormat.Float)
{
options.ColorType = TiffColorType.BlackIsZero32Float;
@ -300,43 +276,30 @@ internal static class TiffDecoderOptionsParser
options.ColorType = TiffColorType.BlackIsZero32;
break;
}
case 24:
{
options.ColorType = TiffColorType.BlackIsZero24;
break;
}
case 16:
{
options.ColorType = TiffColorType.BlackIsZero16;
break;
}
case 8:
{
options.ColorType = TiffColorType.BlackIsZero8;
break;
}
case 4:
{
options.ColorType = TiffColorType.BlackIsZero4;
break;
}
case 1:
{
options.ColorType = TiffColorType.BlackIsZero1;
break;
}
default:
{
options.ColorType = TiffColorType.BlackIsZero;
break;
}
}
break;
@ -535,29 +498,21 @@ internal static class TiffDecoderOptionsParser
switch (compression ?? TiffCompression.None)
{
case TiffCompression.None:
{
options.CompressionType = TiffDecoderCompressionType.None;
break;
}
case TiffCompression.PackBits:
{
options.CompressionType = TiffDecoderCompressionType.PackBits;
break;
}
case TiffCompression.Deflate:
case TiffCompression.OldDeflate:
{
options.CompressionType = TiffDecoderCompressionType.Deflate;
break;
}
case TiffCompression.Lzw:
{
options.CompressionType = TiffDecoderCompressionType.Lzw;
break;
}
case TiffCompression.CcittGroup3Fax:
{
@ -599,16 +554,13 @@ internal static class TiffDecoderOptionsParser
}
case TiffCompression.Ccitt1D:
{
options.CompressionType = TiffDecoderCompressionType.HuffmanRle;
options.BitsPerSample = new TiffBitsPerSample(1, 0, 0);
options.BitsPerPixel = 1;
break;
}
case TiffCompression.OldJpeg:
{
if (!options.OldJpegCompressionStartOfImageMarker.HasValue)
{
TiffThrowHelper.ThrowNotSupported("Missing SOI marker offset for tiff with old jpeg compression");
@ -629,10 +581,8 @@ internal static class TiffDecoderOptionsParser
}
break;
}
case TiffCompression.Jpeg:
{
options.CompressionType = TiffDecoderCompressionType.Jpeg;
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null)
@ -643,30 +593,14 @@ internal static class TiffDecoderOptionsParser
}
break;
}
case TiffCompression.Webp:
{
options.CompressionType = TiffDecoderCompressionType.Webp;
break;
}
default:
{
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported");
break;
}
}
}
private static bool IsBiColorCompression(TiffCompression? compression)
{
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or
TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
}

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

@ -47,7 +47,7 @@ public class TiffEncoder : QuantizingImageEncoder
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
TiffEncoderCore encode = new(this, image.Configuration.MemoryAllocator);
TiffEncoderCore encode = new(this, image.Configuration);
encode.Encode(image, stream, cancellationToken);
}
}

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

@ -1,8 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Advanced;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
/// <summary>
/// Performs the TIFF encoding operation.
/// </summary>
internal sealed class TiffEncoderCore : IImageEncoderInternals
internal sealed class TiffEncoderCore
{
private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian
? TiffConstants.ByteOrderLittleEndianShort
@ -50,41 +49,22 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// </summary>
private readonly DeflateCompressionLevel compressionLevel;
/// <summary>
/// The default predictor is None.
/// </summary>
private const TiffPredictor DefaultPredictor = TiffPredictor.None;
/// <summary>
/// The default bits per pixel is Bit24.
/// </summary>
private const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24;
/// <summary>
/// The default compression is None.
/// </summary>
private const TiffCompression DefaultCompression = TiffCompression.None;
/// <summary>
/// The default photometric interpretation is Rgb.
/// </summary>
private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb;
/// <summary>
/// Whether to skip metadata during encoding.
/// </summary>
private readonly bool skipMetadata;
private readonly List<(long, uint)> frameMarkers = new();
private readonly List<(long, uint)> frameMarkers = [];
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
public TiffEncoderCore(TiffEncoder options, MemoryAllocator memoryAllocator)
/// <param name="configuration">The global configuration.</param>
public TiffEncoderCore(TiffEncoder options, Configuration configuration)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.PhotometricInterpretation = options.PhotometricInterpretation;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.pixelSamplingStrategy = options.PixelSamplingStrategy;
@ -135,35 +115,29 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
// Determine the correct values to encode with.
// EncoderOptions > Metadata > Default.
TiffBitsPerPixel? bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel;
TiffBitsPerPixel bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel;
TiffPhotometricInterpretation? photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation;
TiffPhotometricInterpretation photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation;
TiffPredictor predictor =
this.HorizontalPredictor
?? rootFrameTiffMetaData.Predictor
?? DefaultPredictor;
TiffPredictor predictor = this.HorizontalPredictor ?? rootFrameTiffMetaData.Predictor;
TiffCompression compression =
this.CompressionType
?? rootFrameTiffMetaData.Compression
?? DefaultCompression;
TiffCompression compression = this.CompressionType ?? rootFrameTiffMetaData.Compression;
// Make sure, the Encoder options makes sense in combination with each other.
this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor);
// Make sure the Encoder options makes sense in combination with each other.
this.SanitizeAndSetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor);
using TiffStreamWriter writer = new(stream);
Span<byte> buffer = stackalloc byte[4];
long ifdMarker = WriteHeader(writer, buffer);
Image<TPixel> metadataImage = image;
Image<TPixel>? metadataImage = image;
foreach (ImageFrame<TPixel> frame in image.Frames)
{
cancellationToken.ThrowIfCancellationRequested();
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, this.BitsPerPixel.Value, this.CompressionType.Value, ifdMarker);
metadataImage = null;
}
@ -199,6 +173,8 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
/// <param name="frame">The tiff frame.</param>
/// <param name="imageMetadata">The image metadata (resolution values for each frame).</param>
/// <param name="image">The image (common metadata for root frame).</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="compression">The compression type.</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
/// <returns>
/// The next IFD offset value.
@ -207,16 +183,18 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
TiffStreamWriter writer,
ImageFrame<TPixel> frame,
ImageMetadata imageMetadata,
Image<TPixel> image,
Image<TPixel>? image,
TiffBitsPerPixel bitsPerPixel,
TiffCompression compression,
long ifdOffset)
where TPixel : unmanaged, IPixel<TPixel>
{
using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
this.CompressionType ?? TiffCompression.None,
compression,
writer.BaseStream,
this.memoryAllocator,
frame.Width,
(int)this.BitsPerPixel,
(int)bitsPerPixel,
this.compressionLevel,
this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None);
@ -229,7 +207,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
this.memoryAllocator,
this.configuration,
entriesCollector,
(int)this.BitsPerPixel);
(int)bitsPerPixel);
int rowsPerStrip = CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType);
@ -307,7 +285,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
}
uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12));
List<byte[]> largeDataBlocks = new();
List<byte[]> largeDataBlocks = [];
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
@ -354,135 +332,87 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
return nextIfdMarker;
}
[MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
private void SanitizeAndSetEncoderOptions(
TiffBitsPerPixel? bitsPerPixel,
int inputBitsPerPixel,
TiffPhotometricInterpretation? photometricInterpretation,
TiffBitsPerPixel bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
TiffPredictor predictor)
{
// BitsPerPixel should be the primary source of truth for the encoder options.
if (bitsPerPixel.HasValue)
// Ensure 1 Bit compression is only used with 1 bit pixel type.
// Choose a sensible default based on the bits per pixel.
if (IsOneBitCompression(compression) && bitsPerPixel != TiffBitsPerPixel.Bit1)
{
switch (bitsPerPixel)
compression = bitsPerPixel switch
{
case TiffBitsPerPixel.Bit1:
if (IsOneBitCompression(compression))
{
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
break;
}
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit4:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit8:
this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break;
case TiffBitsPerPixel.Bit16:
// Assume desire to encode as L16 grayscale
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break;
case TiffBitsPerPixel.Bit6:
case TiffBitsPerPixel.Bit10:
case TiffBitsPerPixel.Bit12:
case TiffBitsPerPixel.Bit14:
case TiffBitsPerPixel.Bit30:
case TiffBitsPerPixel.Bit36:
case TiffBitsPerPixel.Bit42:
case TiffBitsPerPixel.Bit48:
// Encoding not yet supported bits per pixel will default to 24 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
break;
case TiffBitsPerPixel.Bit64:
// Encoding not yet supported bits per pixel will default to 32 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
break;
default:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
break;
}
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
this.CompressionType = DefaultCompression;
}
return;
< TiffBitsPerPixel.Bit8 => TiffCompression.None,
_ => TiffCompression.Deflate,
};
}
// If no photometric interpretation was chosen, the input image bit per pixel should be preserved.
if (!photometricInterpretation.HasValue)
{
if (IsOneBitCompression(this.CompressionType))
{
// We need to make sure bits per pixel is set to Bit1 now. WhiteIsZero is set because its the default for bilevel compressed data.
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
return;
}
// Ensure predictor is only used with compression that supports it.
predictor = HasPredictor(compression) ? predictor : TiffPredictor.None;
// At the moment only 8, 16 and 32 bits per pixel can be preserved by the tiff encoder.
if (inputBitsPerPixel == 8)
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
return;
}
if (inputBitsPerPixel == 16)
{
// Assume desire to encode as L16 grayscale
this.SetEncoderOptions(TiffBitsPerPixel.Bit16, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
return;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor);
return;
}
switch (photometricInterpretation)
// BitsPerPixel should be the primary source of truth for the encoder options.
switch (bitsPerPixel)
{
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (IsOneBitCompression(this.CompressionType))
case TiffBitsPerPixel.Bit1:
if (IsOneBitCompression(compression))
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None);
return;
}
if (inputBitsPerPixel == 16)
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit16, photometricInterpretation, compression, predictor);
return;
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, predictor);
break;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
case TiffPhotometricInterpretation.PaletteColor:
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
case TiffPhotometricInterpretation.Rgb:
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType))
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break;
case TiffBitsPerPixel.Bit4:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor);
break;
case TiffBitsPerPixel.Bit8:
// Allow any combination of the below for 8 bit images.
if (photometricInterpretation is TiffPhotometricInterpretation.BlackIsZero
or TiffPhotometricInterpretation.WhiteIsZero
or TiffPhotometricInterpretation.PaletteColor)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
compression = DefaultCompression;
this.SetEncoderOptions(bitsPerPixel, photometricInterpretation, compression, predictor);
break;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor);
return;
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, predictor);
break;
case TiffBitsPerPixel.Bit16:
// Assume desire to encode as L16 grayscale
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break;
case TiffBitsPerPixel.Bit6:
case TiffBitsPerPixel.Bit10:
case TiffBitsPerPixel.Bit12:
case TiffBitsPerPixel.Bit14:
case TiffBitsPerPixel.Bit30:
case TiffBitsPerPixel.Bit36:
case TiffBitsPerPixel.Bit42:
case TiffBitsPerPixel.Bit48:
// Encoding not yet supported bits per pixel will default to 24 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor);
break;
case TiffBitsPerPixel.Bit64:
// Encoding not yet supported bits per pixel will default to 32 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, predictor);
break;
default:
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor);
break;
}
this.SetEncoderOptions(DefaultBitsPerPixel, DefaultPhotometricInterpretation, compression, predictor);
}
private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor)
[MemberNotNull(nameof(BitsPerPixel), nameof(PhotometricInterpretation), nameof(CompressionType), nameof(HorizontalPredictor))]
private void SetEncoderOptions(
TiffBitsPerPixel bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
TiffPredictor predictor)
{
this.BitsPerPixel = bitsPerPixel;
this.PhotometricInterpretation = photometricInterpretation;
@ -492,4 +422,7 @@ internal sealed class TiffEncoderCore : IImageEncoderInternals
public static bool IsOneBitCompression(TiffCompression? compression)
=> compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax;
public static bool HasPredictor(TiffCompression? compression)
=> compression is TiffCompression.Deflate or TiffCompression.Lzw;
}

32
src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
/// <summary>
/// Provides Tiff specific metadata information for the frame.
/// </summary>
public class TiffFrameMetadata : IDeepCloneable
public class TiffFrameMetadata : IFormatFrameMetadata<TiffFrameMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
@ -34,33 +34,47 @@ public class TiffFrameMetadata : IDeepCloneable
/// <summary>
/// Gets or sets the bits per pixel.
/// </summary>
public TiffBitsPerPixel? BitsPerPixel { get; set; }
public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel;
/// <summary>
/// Gets or sets number of bits per component.
/// </summary>
public TiffBitsPerSample? BitsPerSample { get; set; }
public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample;
/// <summary>
/// Gets or sets the compression scheme used on the image data.
/// </summary>
public TiffCompression? Compression { get; set; }
public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression;
/// <summary>
/// Gets or sets the color space of the image data.
/// </summary>
public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; }
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation;
/// <summary>
/// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
/// </summary>
public TiffPredictor? Predictor { get; set; }
public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor;
/// <summary>
/// Gets or sets the set of inks used in a separated (<see cref="TiffPhotometricInterpretation.Separated"/>) image.
/// </summary>
public TiffInkSet? InkSet { get; set; }
/// <inheritdoc/>
public static TiffFrameMetadata FromFormatConnectingFrameMetadata(FormatConnectingFrameMetadata metadata)
=> new();
/// <inheritdoc/>
public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
=> new();
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public TiffFrameMetadata DeepClone() => new(this);
/// <summary>
/// Returns a new <see cref="TiffFrameMetadata"/> instance parsed from the given Exif profile.
/// </summary>
@ -89,7 +103,7 @@ public class TiffFrameMetadata : IDeepCloneable
meta.BitsPerSample = bitsPerSample;
}
meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel();
meta.BitsPerPixel = meta.BitsPerSample.BitsPerPixel();
if (profile.TryGetValue(ExifTag.Compression, out IExifValue<ushort>? compressionValue))
{
@ -111,13 +125,11 @@ public class TiffFrameMetadata : IDeepCloneable
meta.InkSet = (TiffInkSet)inkSetValue.Value;
}
// TODO: Why do we remove this? Encoding should overwrite.
profile.RemoveValue(ExifTag.BitsPerSample);
profile.RemoveValue(ExifTag.Compression);
profile.RemoveValue(ExifTag.PhotometricInterpretation);
profile.RemoveValue(ExifTag.Predictor);
}
}
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffFrameMetadata(this);
}

152
src/ImageSharp/Formats/Tiff/TiffMetadata.cs

@ -1,12 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff;
/// <summary>
/// Provides Tiff specific metadata information for the image.
/// </summary>
public class TiffMetadata : IDeepCloneable
public class TiffMetadata : IFormatMetadata<TiffMetadata>
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffMetadata"/> class.
@ -23,6 +26,11 @@ public class TiffMetadata : IDeepCloneable
{
this.ByteOrder = other.ByteOrder;
this.FormatType = other.FormatType;
this.BitsPerPixel = other.BitsPerPixel;
this.BitsPerSample = other.BitsPerSample;
this.Compression = other.Compression;
this.PhotometricInterpretation = other.PhotometricInterpretation;
this.Predictor = other.Predictor;
}
/// <summary>
@ -35,6 +43,146 @@ public class TiffMetadata : IDeepCloneable
/// </summary>
public TiffFormatType FormatType { get; set; }
/// <summary>
/// Gets or sets the bits per pixel. Derived from the root frame.
/// </summary>
public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffConstants.DefaultBitsPerPixel;
/// <summary>
/// Gets or sets number of bits per component. Derived from the root frame.
/// </summary>
public TiffBitsPerSample BitsPerSample { get; set; } = TiffConstants.DefaultBitsPerSample;
/// <summary>
/// Gets or sets the compression scheme used on the image data. Derived from the root frame.
/// </summary>
public TiffCompression Compression { get; set; } = TiffConstants.DefaultCompression;
/// <summary>
/// Gets or sets the color space of the image data. Derived from the root frame.
/// </summary>
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } = TiffConstants.DefaultPhotometricInterpretation;
/// <summary>
/// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied.
/// Derived from the root frame.
/// </summary>
public TiffPredictor Predictor { get; set; } = TiffConstants.DefaultPredictor;
/// <inheritdoc/>
public static TiffMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata)
{
int bpp = metadata.PixelTypeInfo.BitsPerPixel;
return bpp switch
{
1 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit1,
BitsPerSample = TiffConstants.BitsPerSample1Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero,
Compression = TiffCompression.CcittGroup4Fax,
Predictor = TiffPredictor.None
},
<= 4 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit4,
BitsPerSample = TiffConstants.BitsPerSample4Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.None // Best match for low bit depth
},
8 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit8,
BitsPerSample = TiffConstants.BitsPerSample8Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
},
16 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit16,
BitsPerSample = TiffConstants.BitsPerSample16Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
},
32 or 64 => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit32,
BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
},
_ => new TiffMetadata
{
BitsPerPixel = TiffBitsPerPixel.Bit24,
BitsPerSample = TiffConstants.BitsPerSampleRgb8Bit,
PhotometricInterpretation = TiffPhotometricInterpretation.Rgb,
Compression = TiffCompression.Deflate,
Predictor = TiffPredictor.Horizontal
}
};
}
/// <inheritdoc/>
public PixelTypeInfo GetPixelTypeInfo()
{
int bpp = (int)this.BitsPerPixel;
TiffBitsPerSample samples = this.BitsPerSample;
PixelComponentInfo info = samples.Channels switch
{
1 => PixelComponentInfo.Create(1, bpp, bpp),
2 => PixelComponentInfo.Create(2, bpp, bpp, samples.Channel0, samples.Channel1),
3 => PixelComponentInfo.Create(3, bpp, samples.Channel0, samples.Channel1, samples.Channel2),
_ => PixelComponentInfo.Create(4, bpp, samples.Channel0, samples.Channel1, samples.Channel2, samples.Channel3)
};
PixelColorType colorType;
PixelAlphaRepresentation alpha = PixelAlphaRepresentation.None;
switch (this.BitsPerPixel)
{
case TiffBitsPerPixel.Bit1:
colorType = PixelColorType.Binary;
break;
case TiffBitsPerPixel.Bit4:
case TiffBitsPerPixel.Bit6:
case TiffBitsPerPixel.Bit8:
colorType = PixelColorType.Indexed;
break;
case TiffBitsPerPixel.Bit16:
colorType = PixelColorType.Luminance;
break;
case TiffBitsPerPixel.Bit32:
case TiffBitsPerPixel.Bit64:
colorType = PixelColorType.RGB | PixelColorType.Alpha;
alpha = PixelAlphaRepresentation.Unassociated;
break;
default:
colorType = PixelColorType.RGB;
break;
}
return new PixelTypeInfo(bpp)
{
ColorType = colorType,
ComponentInfo = info,
AlphaRepresentation = alpha
};
}
/// <inheritdoc/>
public FormatConnectingMetadata ToFormatConnectingMetadata()
=> new()
{
PixelTypeInfo = this.GetPixelTypeInfo()
};
/// <inheritdoc/>
IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone();
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffMetadata(this);
public TiffMetadata DeepClone() => new(this);
}

20
src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs

@ -10,7 +10,7 @@ internal readonly struct WebpFrameData
/// </summary>
public const uint HeaderSize = 16;
public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod)
public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, FrameBlendMode blendingMethod, FrameDisposalMode disposalMethod)
{
this.DataSize = dataSize;
this.X = x;
@ -30,12 +30,12 @@ internal readonly struct WebpFrameData
width,
height,
duration,
(flags & 2) == 0 ? WebpBlendMethod.Over : WebpBlendMethod.Source,
(flags & 1) == 1 ? WebpDisposalMethod.RestoreToBackground : WebpDisposalMethod.DoNotDispose)
(flags & 2) == 0 ? FrameBlendMode.Over : FrameBlendMode.Source,
(flags & 1) == 1 ? FrameDisposalMode.RestoreToBackground : FrameDisposalMode.DoNotDispose)
{
}
public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendMethod blendingMethod, WebpDisposalMethod disposalMethod)
public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, FrameBlendMode blendingMethod, FrameDisposalMode disposalMethod)
: this(0, x, y, width, height, duration, blendingMethod, disposalMethod)
{
}
@ -74,12 +74,12 @@ internal readonly struct WebpFrameData
/// <summary>
/// Gets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public WebpBlendMethod BlendingMethod { get; }
public FrameBlendMode BlendingMethod { get; }
/// <summary>
/// Gets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
/// </summary>
public WebpDisposalMethod DisposalMethod { get; }
public FrameDisposalMode DisposalMethod { get; }
public Rectangle Bounds => new((int)this.X, (int)this.Y, (int)this.Width, (int)this.Height);
@ -91,13 +91,13 @@ internal readonly struct WebpFrameData
{
byte flags = 0;
if (this.BlendingMethod is WebpBlendMethod.Source)
if (this.BlendingMethod is FrameBlendMode.Source)
{
// Set blending flag.
flags |= 2;
}
if (this.DisposalMethod is WebpDisposalMethod.RestoreToBackground)
if (this.DisposalMethod is FrameDisposalMode.RestoreToBackground)
{
// Set disposal flag.
flags |= 1;
@ -124,7 +124,7 @@ internal readonly struct WebpFrameData
{
Span<byte> buffer = stackalloc byte[4];
WebpFrameData data = new(
return new(
dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) * 2,
@ -132,7 +132,5 @@ internal readonly struct WebpFrameData
height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
flags: stream.ReadByte());
return data;
}
}

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

@ -241,8 +241,6 @@ internal class Vp8LEncoder : IDisposable
{
// Write bytes from the bit-writer buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
@ -259,7 +257,7 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata();
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
}

8
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -315,8 +315,6 @@ internal class Vp8Encoder : IDisposable
{
// Write bytes from the bitwriter buffer to the stream.
ImageMetadata metadata = image.Metadata;
metadata.SyncProfiles();
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
@ -332,7 +330,7 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation)
{
WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata();
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
}
@ -376,7 +374,7 @@ internal class Vp8Encoder : IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
ImageFrame<TPixel> frame = image.Frames.RootFrame;
this.Encode(stream, frame, image.Bounds, WebpCommonUtils.GetWebpFrameMetadata(frame), false, image);
this.Encode(stream, frame, image.Bounds, frame.Metadata.GetWebpMetadata(), false, image);
}
/// <summary>
@ -462,7 +460,7 @@ internal class Vp8Encoder : IDisposable
// Extract and encode alpha channel data, if present.
int alphaDataSize = 0;
bool alphaCompressionSucceeded = false;
Span<byte> alphaData = Span<byte>.Empty;
Span<byte> alphaData = [];
IMemoryOwner<byte> encodedAlphaData = null;
try
{

75
src/ImageSharp/Formats/Webp/MetadataExtensions.cs

@ -1,75 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the webp format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="WebpMetadata"/>.</returns>
public static WebpMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance);
/// <summary>
/// Gets the webp format specific metadata for the image.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">The metadata.</param>
/// <returns>
/// <see langword="true"/> if the webp metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetWebpMetadata(this ImageMetadata source, [NotNullWhen(true)] out WebpMetadata? metadata)
=> source.TryGetFormatMetadata(WebpFormat.Instance, out metadata);
/// <summary>
/// Gets the webp format specific metadata for the image frame.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="WebpFrameMetadata"/>.</returns>
public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance);
/// <summary>
/// Gets the webp format specific metadata for the image frame.
/// </summary>
/// <param name="source">The metadata this method extends.</param>
/// <param name="metadata">The metadata.</param>
/// <returns>
/// <see langword="true"/> if the webp frame metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetWebpFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out WebpFrameMetadata? metadata)
=> source.TryGetFormatMetadata(WebpFormat.Instance, out metadata);
internal static AnimatedImageMetadata ToAnimatedImageMetadata(this WebpMetadata source)
=> new()
{
ColorTableMode = FrameColorTableMode.Global,
RepeatCount = source.RepeatCount,
BackgroundColor = source.BackgroundColor
};
internal static AnimatedImageFrameMetadata ToAnimatedImageFrameMetadata(this WebpFrameMetadata source)
=> new()
{
ColorTableMode = FrameColorTableMode.Global,
Duration = TimeSpan.FromMilliseconds(source.FrameDelay),
DisposalMode = GetMode(source.DisposalMethod),
BlendMode = source.BlendMethod == WebpBlendMethod.Over ? FrameBlendMode.Over : FrameBlendMode.Source,
};
private static FrameDisposalMode GetMode(WebpDisposalMethod method) => method switch
{
WebpDisposalMethod.RestoreToBackground => FrameDisposalMode.RestoreToBackground,
WebpDisposalMethod.DoNotDispose => FrameDisposalMode.DoNotDispose,
_ => FrameDisposalMode.DoNotDispose,
};
}

4
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -195,14 +195,14 @@ internal class WebpAnimationDecoder : IDisposable
Rectangle regionRectangle = frameData.Bounds;
if (frameData.DisposalMethod is WebpDisposalMethod.RestoreToBackground)
if (frameData.DisposalMethod is FrameDisposalMode.RestoreToBackground)
{
this.RestoreToBackground(imageFrame, backgroundColor);
}
using Buffer2D<TPixel> decodedImageFrame = this.DecodeImageFrameData<TPixel>(frameData, webpInfo);
bool blend = previousFrame != null && frameData.BlendingMethod == WebpBlendMethod.Over;
bool blend = previousFrame != null && frameData.BlendingMethod == FrameBlendMode.Over;
DrawDecodedImageFrameOnCanvas(decodedImageFrame, imageFrame, regionRectangle, blend);
previousFrame = currentFrame ?? image.Frames.RootFrame;

4
src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs

@ -11,10 +11,10 @@ public enum WebpBitsPerPixel : short
/// <summary>
/// 24 bits per pixel. Each pixel consists of 3 bytes.
/// </summary>
Pixel24 = 24,
Bit24 = 24,
/// <summary>
/// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present).
/// </summary>
Pixel32 = 32
Bit32 = 32
}

22
src/ImageSharp/Formats/Webp/WebpBlendMethod.cs

@ -1,22 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public enum WebpBlendMethod
{
/// <summary>
/// Do not blend. After disposing of the previous frame,
/// render the current frame on the canvas by overwriting the rectangle covered by the current frame.
/// </summary>
Source = 0,
/// <summary>
/// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending.
/// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle.
/// </summary>
Over = 1,
}

33
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -17,6 +17,10 @@ internal static class WebpChunkParsingUtils
/// <summary>
/// Reads the header of a lossy webp image.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="stream">The buffered read stream.</param>
/// <param name="buffer">The scratch buffer to use while reading.</param>
/// <param name="features">The webp features to parse.</param>
/// <returns>Information about this webp image.</returns>
public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
{
@ -114,13 +118,15 @@ internal static class WebpChunkParsingUtils
Vp8BitReader bitReader = new(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining };
return new WebpImageInfo
return new()
{
Width = width,
Height = height,
XScale = xScale,
YScale = yScale,
BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Pixel32 : WebpBitsPerPixel.Pixel24,
// Vp8 header can be parsed during the processing of the Vp8X header.
BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Bit32 : WebpBitsPerPixel.Bit24,
IsLossless = false,
Features = features,
Vp8Profile = (sbyte)version,
@ -132,7 +138,10 @@ internal static class WebpChunkParsingUtils
/// <summary>
/// Reads the header of a lossless webp image.
/// </summary>
/// <returns>Information about this image.</returns>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="stream">The buffered read stream.</param>
/// <param name="buffer">The scratch buffer to use while reading.</param>
/// <param name="features">The webp features to parse.</param>
public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
{
// VP8 data size.
@ -156,8 +165,8 @@ internal static class WebpChunkParsingUtils
}
// The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise.
// TODO: this flag value is not used yet
bool alphaIsUsed = bitReader.ReadBit();
// Alpha may have already been set by the VP8X chunk.
features.Alpha |= bitReader.ReadBit();
// The next 3 bits are the version. The version number is a 3 bit code that must be set to 0.
// Any other value should be treated as an error.
@ -167,11 +176,11 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header");
}
return new WebpImageInfo
return new()
{
Width = width,
Height = height,
BitsPerPixel = WebpBitsPerPixel.Pixel32,
BitsPerPixel = features.Alpha ? WebpBitsPerPixel.Bit32 : WebpBitsPerPixel.Bit24,
IsLossless = true,
Features = features,
Vp8LBitReader = bitReader
@ -187,6 +196,9 @@ internal static class WebpChunkParsingUtils
/// - An optional 'ALPH' chunk with alpha channel data.
/// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow.
/// </summary>
/// <param name="stream">The buffered read stream.</param>
/// <param name="buffer">The scratch buffer to use while reading.</param>
/// <param name="features">The webp features to parse.</param>
/// <returns>Information about this webp image.</returns>
public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, Span<byte> buffer, WebpFeatures features)
{
@ -217,6 +229,7 @@ internal static class WebpChunkParsingUtils
features.Animation = (imageFeatures & (1 << 1)) != 0;
// 3 reserved bytes should follow which are supposed to be zero.
// No other decoder actually checks this though.
stream.Read(buffer, 0, 3);
// 3 bytes for the width.
@ -226,14 +239,14 @@ internal static class WebpChunkParsingUtils
uint height = ReadUInt24LittleEndian(stream, buffer) + 1;
// Read all the chunks in the order they occur.
WebpImageInfo info = new()
return new()
{
Width = width,
Height = height,
Features = features
};
return info;
// Additional properties are set during the parsing of the VP8 or VP8L headers.
};
}
/// <summary>

25
src/ImageSharp/Formats/Webp/WebpColorType.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Provides enumeration of the various webp color types.
/// </summary>
public enum WebpColorType
{
/// <summary>
/// Yuv (luminance, blue chroma, red chroma) as defined in the ITU-R Rec. BT.709 specification.
/// </summary>
Yuv,
/// <summary>
/// Rgb color space.
/// </summary>
Rgb,
/// <summary>
/// Rgba color space.
/// </summary>
Rgba
}

50
src/ImageSharp/Formats/Webp/WebpCommonUtils.cs

@ -4,8 +4,6 @@
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp;
@ -15,54 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary>
internal static class WebpCommonUtils
{
public static WebpMetadata GetWebpMetadata<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
if (image.Metadata.TryGetWebpMetadata(out WebpMetadata? webp))
{
return (WebpMetadata)webp.DeepClone();
}
if (image.Metadata.TryGetGifMetadata(out GifMetadata? gif))
{
AnimatedImageMetadata ani = gif.ToAnimatedImageMetadata();
return WebpMetadata.FromAnimatedMetadata(ani);
}
if (image.Metadata.TryGetPngMetadata(out PngMetadata? png))
{
AnimatedImageMetadata ani = png.ToAnimatedImageMetadata();
return WebpMetadata.FromAnimatedMetadata(ani);
}
// Return explicit new instance so we do not mutate the original metadata.
return new();
}
public static WebpFrameMetadata GetWebpFrameMetadata<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>
{
if (frame.Metadata.TryGetWebpFrameMetadata(out WebpFrameMetadata? webp))
{
return (WebpFrameMetadata)webp.DeepClone();
}
if (frame.Metadata.TryGetGifMetadata(out GifFrameMetadata? gif))
{
AnimatedImageFrameMetadata ani = gif.ToAnimatedImageFrameMetadata();
return WebpFrameMetadata.FromAnimatedMetadata(ani);
}
if (frame.Metadata.TryGetPngMetadata(out PngFrameMetadata? png))
{
AnimatedImageFrameMetadata ani = png.ToAnimatedImageFrameMetadata();
return WebpFrameMetadata.FromAnimatedMetadata(ani);
}
// Return explicit new instance so we do not mutate the original metadata.
return new();
}
/// <summary>
/// Checks if the pixel row is not opaque.
/// </summary>

37
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Performs the webp decoding operation.
/// </summary>
internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
{
/// <summary>
/// General configuration options.
@ -61,8 +61,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
/// <param name="options">The decoder options.</param>
public WebpDecoderCore(WebpDecoderOptions options)
: base(options.GeneralOptions)
{
this.Options = options.GeneralOptions;
this.backgroundColorHandling = options.BackgroundColorHandling;
this.configuration = options.GeneralOptions.Configuration;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
@ -70,15 +70,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <inheritdoc/>
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
{
Image<TPixel>? image = null;
try
@ -136,7 +129,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
}
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
ReadImageHeader(stream, stackalloc byte[4]);
@ -144,7 +137,6 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
{
return new ImageInfo(
new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel),
new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height),
metadata);
}
@ -186,36 +178,43 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
Span<byte> buffer = stackalloc byte[4];
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
WebpImageInfo? info = null;
WebpFeatures features = new();
switch (chunkType)
{
case WebpChunkType.Vp8:
info = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
webpMetadata.ColorType = WebpColorType.Yuv;
return info;
case WebpChunkType.Vp8L:
info = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb;
return info;
case WebpChunkType.Vp8X:
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features);
info = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features);
while (stream.Position < stream.Length)
{
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
if (chunkType == WebpChunkType.Vp8)
{
info = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features);
webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb;
}
else if (chunkType == WebpChunkType.Vp8L)
{
info = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features);
webpMetadata.ColorType = info.Features?.Alpha == true ? WebpColorType.Rgba : WebpColorType.Rgb;
}
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
{
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer);
if (isAnimationChunk)
{
return webpInfos;
return info;
}
}
else
@ -226,7 +225,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
}
}
return webpInfos;
return info;
default:
WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header");
return

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save