diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs
index 2c08284a35..12acdfc6cb 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs
@@ -8,69 +8,77 @@ namespace ImageSharp.Formats.Jpg
using System.Buffers;
///
- /// Represents a Huffman tree
+ /// Represents a Huffman tree
///
internal struct HuffmanTree : IDisposable
{
- public static HuffmanTree[] CreateHuffmanTrees()
- {
- HuffmanTree[] result = new HuffmanTree[(MaxTc + 1) * (MaxTh + 1)];
- for (int i = 0; i < MaxTc + 1; i++)
- {
- for (int j = 0; j < MaxTh + 1; j++)
- {
- result[(i * ThRowSize) + j].Init();
- }
- }
- return result;
- }
///
- /// The maximum (inclusive) number of codes in a Huffman tree.
+ /// The maximum (inclusive) number of codes in a Huffman tree.
+ ///
+ public const int MaxNCodes = 256;
+
+ ///
+ /// The maximum (inclusive) number of bits in a Huffman code.
+ ///
+ public const int MaxCodeLength = 16;
+
+ ///
+ /// The maximum number of Huffman table classes
+ ///
+ public const int MaxTc = 1;
+
+ ///
+ /// The maximum number of Huffman table identifiers
+ ///
+ public const int MaxTh = 3;
+
+ ///
+ /// Row size of the Huffman table
///
- internal const int MaxNCodes = 256;
+ public const int ThRowSize = MaxTh + 1;
///
- /// The maximum (inclusive) number of bits in a Huffman code.
+ /// Number of Hufman Trees in the Huffman table
///
- internal const int MaxCodeLength = 16;
+ public const int NumberOfTrees = (MaxTc + 1) * (MaxTh + 1);
///
- /// The log-2 size of the Huffman decoder's look-up table.
+ /// The log-2 size of the Huffman decoder's look-up table.
///
- internal const int LutSize = 8;
+ public const int LutSize = 8;
///
- /// Gets or sets the number of codes in the tree.
+ /// Gets or sets the number of codes in the tree.
///
public int Length;
///
- /// Gets the look-up table for the next LutSize bits in the bit-stream.
- /// The high 8 bits of the uint16 are the encoded value. The low 8 bits
- /// are 1 plus the code length, or 0 if the value is too large to fit in
- /// lutSize bits.
+ /// Gets the look-up table for the next LutSize bits in the bit-stream.
+ /// The high 8 bits of the uint16 are the encoded value. The low 8 bits
+ /// are 1 plus the code length, or 0 if the value is too large to fit in
+ /// lutSize bits.
///
public ushort[] Lut;
///
- /// Gets the the decoded values, sorted by their encoding.
+ /// Gets the the decoded values, sorted by their encoding.
///
public byte[] Values;
///
- /// Gets the array of minimum codes.
- /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
+ /// Gets the array of minimum codes.
+ /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
///
public int[] MinCodes;
///
- /// Gets the array of maximum codes.
- /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
+ /// Gets the array of maximum codes.
+ /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
///
public int[] MaxCodes;
///
- /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
+ /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
///
public int[] Indices;
@@ -81,7 +89,25 @@ namespace ImageSharp.Formats.Jpg
private static readonly ArrayPool IntBuffer = ArrayPool.Create(MaxCodeLength, 50);
///
- /// Initializes the Huffman tree
+ /// Creates and initializes an array of instances of size
+ ///
+ /// An array of instances representing the Huffman table
+ public static HuffmanTree[] CreateHuffmanTrees()
+ {
+ HuffmanTree[] result = new HuffmanTree[NumberOfTrees];
+ for (int i = 0; i < MaxTc + 1; i++)
+ {
+ for (int j = 0; j < MaxTh + 1; j++)
+ {
+ result[(i * ThRowSize) + j].Init();
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Initializes the Huffman tree
///
public void Init()
{
@@ -93,7 +119,7 @@ namespace ImageSharp.Formats.Jpg
}
///
- /// Disposes the underlying buffers
+ /// Disposes the underlying buffers
///
public void Dispose()
{
@@ -105,12 +131,15 @@ namespace ImageSharp.Formats.Jpg
}
///
- /// Internal part of the DHT processor, whatever does it mean
+ /// Internal part of the DHT processor, whatever does it mean
///
/// The decoder instance
/// The temporal buffer that holds the data that has been read from stream
/// Remaining bits
- internal void ProcessDefineHuffmanTablesMarkerLoop(JpegDecoderCore decoder, byte[] defineHuffmanTablesData, ref int remaining)
+ internal void ProcessDefineHuffmanTablesMarkerLoop(
+ JpegDecoderCore decoder,
+ byte[] defineHuffmanTablesData,
+ ref int remaining)
{
// Read nCodes and huffman.Valuess (and derive h.Length).
// nCodes[i] is the number of codes with code length i.
@@ -197,17 +226,5 @@ namespace ImageSharp.Formats.Jpg
c <<= 1;
}
}
-
- ///
- /// The maximum number of Huffman table classes
- ///
- internal const int MaxTc = 1;
-
- ///
- /// The maximum number of Huffman table identifiers
- ///
- internal const int MaxTh = 3;
-
- internal const int ThRowSize = MaxTh + 1;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs
index 92d0f34294..00e2b0e13a 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/JpegPixelArea.cs
@@ -1,89 +1,127 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageSharp.Formats.Jpg
{
-
- using System;
- using System.Buffers;
using System.Runtime.CompilerServices;
///
- /// Represents a grayscale image
+ /// Represents an area of a Jpeg subimage (channel)
///
internal struct JpegPixelArea
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the struct from existing data.
///
- /// The width.
- /// The height.
- public static JpegPixelArea CreatePooled(int width, int height)
- {
- int size = width * height;
- var pixels = CleanPooler.RentCleanArray(size);
- return new JpegPixelArea(pixels, width, 0);
- }
-
- public JpegPixelArea(byte[] pixels, int widthOrStride, int offset)
+ /// The pixel array
+ /// The stride
+ /// The offset
+ public JpegPixelArea(byte[] pixels, int striede, int offset)
{
- this.Stride = widthOrStride;
+ this.Stride = striede;
this.Pixels = pixels;
this.Offset = offset;
}
- public void ReturnPooled()
- {
- if (this.Pixels == null) return;
- CleanPooler.ReturnArray(this.Pixels);
- this.Pixels = null;
- }
-
///
- /// Gets or sets the pixels.
+ /// Gets the pixels.
///
public byte[] Pixels { get; private set; }
- public bool Created => this.Pixels != null;
+ ///
+ /// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea))
+ ///
+ public bool IsInitialized => this.Pixels != null;
///
- /// Gets or sets the width.
+ /// Gets or the stride.
///
- public int Stride { get; private set; }
+ public int Stride { get; }
///
- /// Gets or sets the offset
+ /// Gets or the offset.
///
- public int Offset { get; private set; }
-
+ public int Offset { get; }
+
///
- /// Get the subarea that belongs to the Block8x8 defined by block indices
+ /// Gets a of bytes to the pixel area
+ ///
+ public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset);
+
+ ///
+ /// Returns the pixel at (x, y)
+ ///
+ /// The x index
+ /// The y index
+ /// The pixel value
+ public byte this[int x, int y]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return this.Pixels[(y * this.Stride) + x];
+ }
+ }
+
+ ///
+ /// Creates a new instance of the struct.
+ /// Pixel array will be taken from a pool, this instance will be the owner of it's pixel data, therefore
+ /// should be called when the instance is no longer needed.
+ ///
+ /// The width.
+ /// The height.
+ /// A with pooled data
+ public static JpegPixelArea CreatePooled(int width, int height)
+ {
+ int size = width * height;
+ var pixels = CleanPooler.RentCleanArray(size);
+ return new JpegPixelArea(pixels, width, 0);
+ }
+
+ ///
+ /// Returns to the pool
+ ///
+ public void ReturnPooled()
+ {
+ if (this.Pixels == null)
+ {
+ return;
+ }
+
+ CleanPooler.ReturnArray(this.Pixels);
+ this.Pixels = null;
+ }
+
+ ///
+ /// Gets the subarea that belongs to the Block8x8 defined by block indices
///
/// The block X index
/// The block Y index
- ///
- public JpegPixelArea GetOffsetedAreaForBlock(int bx, int by)
+ /// The subarea offseted by block indices
+ public JpegPixelArea GetOffsetedSubAreaForBlock(int bx, int by)
{
- int offset = this.Offset + 8 * (by * this.Stride + bx);
+ int offset = this.Offset + (8 * ((by * this.Stride) + bx));
return new JpegPixelArea(this.Pixels, this.Stride, offset);
}
- public byte this[int x, int y] => this.Pixels[y * this.Stride + x];
-
///
- /// Gets the row offset at the given position
+ /// Gets the row offset at the given position
///
/// The y-coordinate of the image.
- /// The
+ /// The
public int GetRowOffset(int y)
{
return this.Offset + (y * this.Stride);
}
- public MutableSpan Span => new MutableSpan(this.Pixels, this.Offset);
-
+ ///
+ /// Load values to the pixel area from the given .
+ /// Level shift [-128.0, 128.0] floating point color values by +128, clip them to [0, 255], and convert them to
+ /// values
+ ///
+ /// The block holding the color values
+ /// Temporal block provided by the caller
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp)
{
@@ -91,4 +129,4 @@ namespace ImageSharp.Formats.Jpg
block->CopyColorsTo(new MutableSpan(this.Pixels, this.Offset), this.Stride, temp);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
index bd0d58dd28..f842a295c9 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
@@ -2,24 +2,22 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageSharp.Formats.Jpg
{
using System;
- using System.Buffers;
///
- /// Represents an image made up of three color components (luminance, blue chroma, red chroma)
+ /// Represents an image made up of three color components (luminance, blue chroma, red chroma)
///
internal class YCbCrImage : IDisposable
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The width.
/// The height.
/// The ratio.
- public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio, int yOffset, int cOffset)
+ public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio)
{
int cw, ch;
YCbCrSize(width, height, ratio, out cw, out ch);
@@ -27,134 +25,120 @@ namespace ImageSharp.Formats.Jpg
this.CbPixels = CleanPooler.RentCleanArray(cw * ch);
this.CrPixels = CleanPooler.RentCleanArray(cw * ch);
this.Ratio = ratio;
- this.YOffset = yOffset;
- this.COffset = cOffset;
+ this.YOffset = 0;
+ this.COffset = 0;
this.YStride = width;
this.CStride = cw;
- this.X = 0;
- this.Y = 0;
}
- public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset);
-
- public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset);
-
- public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset);
-
- ///
- /// Prevents a default instance of the class from being created.
- ///
- private YCbCrImage(int yOffset, int cOffset)
- {
- this.YOffset = yOffset;
- this.COffset = cOffset;
- }
-
///
- /// Provides enumeration of the various available subsample ratios.
+ /// Provides enumeration of the various available subsample ratios.
///
public enum YCbCrSubsampleRatio
{
///
- /// YCbCrSubsampleRatio444
+ /// YCbCrSubsampleRatio444
///
YCbCrSubsampleRatio444,
///
- /// YCbCrSubsampleRatio422
+ /// YCbCrSubsampleRatio422
///
YCbCrSubsampleRatio422,
///
- /// YCbCrSubsampleRatio420
+ /// YCbCrSubsampleRatio420
///
YCbCrSubsampleRatio420,
///
- /// YCbCrSubsampleRatio440
+ /// YCbCrSubsampleRatio440
///
YCbCrSubsampleRatio440,
///
- /// YCbCrSubsampleRatio411
+ /// YCbCrSubsampleRatio411
///
YCbCrSubsampleRatio411,
///
- /// YCbCrSubsampleRatio410
+ /// YCbCrSubsampleRatio410
///
YCbCrSubsampleRatio410,
}
///
- /// Gets or sets the luminance components channel.
+ /// Gets an offseted to the Cb channel
///
- public byte[] YPixels { get; }
+ public JpegPixelArea CbChannel => new JpegPixelArea(this.CbPixels, this.CStride, this.COffset);
///
- /// Gets or sets the blue chroma components channel.
+ /// Gets the blue chroma components channel.
///
public byte[] CbPixels { get; }
///
- /// Gets or sets the red chroma components channel.
+ /// Gets the index of the first element of red or blue chroma.
///
- public byte[] CrPixels { get; }
+ public int COffset { get; }
///
- /// Gets or sets the Y slice index delta between vertically adjacent pixels.
+ /// Gets an offseted to the Cr channel
///
- public int YStride { get; }
+ public JpegPixelArea CrChannel => new JpegPixelArea(this.CrPixels, this.CStride, this.COffset);
///
- /// Gets or sets the red and blue chroma slice index delta between vertically adjacent pixels
- /// that map to separate chroma samples.
+ /// Gets the red chroma components channel.
+ ///
+ public byte[] CrPixels { get; }
+
+ ///
+ /// Gets the red and blue chroma slice index delta between vertically adjacent pixels
+ /// that map to separate chroma samples.
///
public int CStride { get; }
///
- /// Gets or sets the index of the first luminance element.
+ /// Gets or sets the subsampling ratio.
///
- public int YOffset { get; }
+ public YCbCrSubsampleRatio Ratio { get; set; }
///
- /// Gets or sets the index of the first element of red or blue chroma.
+ /// Gets an offseted to the Y channel
///
- public int COffset { get; }
+ public JpegPixelArea YChannel => new JpegPixelArea(this.YPixels, this.YStride, this.YOffset);
///
- /// Gets or sets the horizontal position.
+ /// Gets the index of the first luminance element.
///
- public int X { get; set; }
+ public int YOffset { get; }
///
- /// Gets or sets the vertical position.
+ /// Gets the luminance components channel.
///
- public int Y { get; set; }
-
+ public byte[] YPixels { get; }
+
///
- /// Gets or sets the subsampling ratio.
+ /// Gets the Y slice index delta between vertically adjacent pixels.
///
- public YCbCrSubsampleRatio Ratio { get; set; }
+ public int YStride { get; }
///
- /// Returns the offset of the first luminance component at the given row
+ /// Disposes the returning rented arrays to the pools.
///
- /// The row number.
- ///
- /// The .
- ///
- public int GetRowYOffset(int y)
+ public void Dispose()
{
- return y * this.YStride;
+ CleanPooler.ReturnArray(this.YPixels);
+ CleanPooler.ReturnArray(this.CrPixels);
+ CleanPooler.ReturnArray(this.CbPixels);
}
///
- /// Returns the offset of the first chroma component at the given row
+ /// Returns the offset of the first chroma component at the given row
///
/// The row number.
///
- /// The .
+ /// The .
///
public int GetRowCOffset(int y)
{
@@ -176,14 +160,31 @@ namespace ImageSharp.Formats.Jpg
}
///
- /// Returns the height and width of the chroma components
+ /// Returns the offset of the first luminance component at the given row
+ ///
+ /// The row number.
+ ///
+ /// The .
+ ///
+ public int GetRowYOffset(int y)
+ {
+ return y * this.YStride;
+ }
+
+ ///
+ /// Returns the height and width of the chroma components
///
/// The width.
/// The height.
/// The subsampling ratio.
/// The chroma width.
/// The chroma height.
- private static void YCbCrSize(int width, int height, YCbCrSubsampleRatio ratio, out int chromaWidth, out int chromaHeight)
+ private static void YCbCrSize(
+ int width,
+ int height,
+ YCbCrSubsampleRatio ratio,
+ out int chromaWidth,
+ out int chromaHeight)
{
switch (ratio)
{
@@ -215,12 +216,5 @@ namespace ImageSharp.Formats.Jpg
break;
}
}
-
- public void Dispose()
- {
- CleanPooler.ReturnArray(this.YPixels);
- CleanPooler.ReturnArray(this.CrPixels);
- CleanPooler.ReturnArray(this.CbPixels);
- }
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
index 9e5990d2df..1696d0811a 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
@@ -2,153 +2,153 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageSharp.Formats
{
using System;
using System.IO;
using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
using System.Threading.Tasks;
+
using ImageSharp.Formats.Jpg;
///
- /// Performs the jpeg decoding operation.
+ /// Performs the jpeg decoding operation.
///
internal unsafe class JpegDecoderCore : IDisposable
{
///
- /// The maximum number of color components
+ /// The AC table index
///
- private const int MaxComponents = 4;
+ private const int AcTable = 1;
///
- /// The maximum number of quantization tables
+ /// The DC table index
///
- private const int MaxTq = 3;
+ private const int DcTable = 0;
///
- /// The DC table index
+ /// The maximum number of color components
///
- private const int DcTable = 0;
+ private const int MaxComponents = 4;
///
- /// The AC table index
+ /// The maximum number of quantization tables
///
- private const int AcTable = 1;
+ private const int MaxTq = 3;
///
- /// The component array
+ /// The component array
///
private readonly Component[] componentArray;
///
- /// Saved state between progressive-mode scans.
+ /// The huffman trees
///
- private readonly Block8x8F[][] progCoeffs;
+ private readonly HuffmanTree[] huffmanTrees;
///
- /// The huffman trees
+ /// Saved state between progressive-mode scans.
///
- private readonly HuffmanTree[] huffmanTrees;
+ private readonly Block8x8F[][] progCoeffs;
///
- /// Quantization tables, in zigzag order.
+ /// Quantization tables, in zigzag order.
///
private readonly Block8x8F[] quantizationTables;
///
- /// A temporary buffer for holding pixels
+ /// A temporary buffer for holding pixels
///
- private readonly byte[] temp; // TODO: the usage of this buffer is unclean + need to move it to the stack for performance
+ private readonly byte[] temp;
+
+ // TODO: the usage of this buffer is unclean + need to move it to the stack for performance
///
- /// The byte buffer.
+ /// The App14 marker color-space
///
- private Bytes bytes;
+ private byte adobeTransform;
///
- /// The byte buffer.
+ /// Whether the image is in CMYK format with an App14 marker
///
- private Stream inputStream;
+ private bool adobeTransformValid;
///
- /// Holds the unprocessed bits that have been taken from the byte-stream.
+ /// Holds the unprocessed bits that have been taken from the byte-stream.
///
private Bits bits;
- ///
- /// The image width
- ///
- private int imageWidth;
+ private JpegPixelArea blackImage;
+
+ private int blockIndex;
///
- /// The image height
+ /// The byte buffer.
///
- private int imageHeight;
+ private Bytes bytes;
///
- /// The number of color components within the image.
+ /// The number of color components within the image.
///
private int componentCount;
///
- /// A grayscale image to decode to.
+ /// End-of-Band run, specified in section G.1.2.2.
///
- private JpegPixelArea grayImage;
+ private ushort eobRun;
///
- /// The full color image to decode to.
+ /// A grayscale image to decode to.
///
- private YCbCrImage ycbcrImage;
-
-
- private JpegPixelArea blackImage;
+ private JpegPixelArea grayImage;
///
- /// The restart interval
+ /// The horizontal resolution. Calculated if the image has a JFIF header.
///
- private int restartInterval;
+ private short horizontalResolution;
///
- /// Whether the image is interlaced (progressive)
+ /// The image height
///
- private bool isProgressive;
+ private int imageHeight;
///
- /// Whether the image has a JFIF header
+ /// The image width
///
- private bool isJfif;
+ private int imageWidth;
///
- /// Whether the image is in CMYK format with an App14 marker
+ /// The byte buffer.
///
- private bool adobeTransformValid;
+ private Stream inputStream;
///
- /// The App14 marker color-space
+ /// Whether the image has a JFIF header
///
- private byte adobeTransform;
+ private bool isJfif;
///
- /// End-of-Band run, specified in section G.1.2.2.
+ /// Whether the image is interlaced (progressive)
///
- private ushort eobRun;
+ private bool isProgressive;
///
- /// The horizontal resolution. Calculated if the image has a JFIF header.
+ /// The restart interval
///
- private short horizontalResolution;
+ private int restartInterval;
///
- /// The vertical resolution. Calculated if the image has a JFIF header.
+ /// The vertical resolution. Calculated if the image has a JFIF header.
///
private short verticalResolution;
- private int blockIndex;
+ ///
+ /// The full color image to decode to.
+ ///
+ private YCbCrImage ycbcrImage;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
public JpegDecoderCore()
{
@@ -162,24 +162,24 @@ namespace ImageSharp.Formats
}
///
- /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent)
- /// It's better tho have an error code for this!
+ /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent)
+ /// It's better tho have an error code for this!
///
internal enum ErrorCodes
{
///
- /// NoError
+ /// NoError
///
NoError,
///
- /// MissingFF00
+ /// MissingFF00
///
MissingFF00
}
///
- /// Gets or sets the byte buffer.
+ /// Gets or sets the byte buffer.
///
public Bytes Bytes
{
@@ -195,7 +195,7 @@ namespace ImageSharp.Formats
}
///
- /// Gets the input stream.
+ /// Gets the input stream.
///
public Stream InputStream
{
@@ -206,8 +206,8 @@ namespace ImageSharp.Formats
}
///
- /// Decodes the image from the specified this._stream and sets
- /// the data to image.
+ /// Decodes the image from the specified this._stream and sets
+ /// the data to image.
///
/// The pixel format.
/// The image, where the data should be set to.
@@ -375,7 +375,7 @@ namespace ImageSharp.Formats
}
}
- if (this.grayImage.Created)
+ if (this.grayImage.IsInitialized)
{
this.ConvertFromGrayScale(this.imageWidth, this.imageHeight, image);
}
@@ -385,7 +385,8 @@ namespace ImageSharp.Formats
{
if (!this.adobeTransformValid)
{
- throw new ImageFormatException("Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
+ throw new ImageFormatException(
+ "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
}
// See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
@@ -425,7 +426,7 @@ namespace ImageSharp.Formats
}
///
- /// Dispose
+ /// Dispose
///
public void Dispose()
{
@@ -441,9 +442,9 @@ namespace ImageSharp.Formats
}
///
- /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
+ /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
///
- /// The
+ /// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal byte ReadByte()
{
@@ -451,8 +452,47 @@ namespace ImageSharp.Formats
}
///
- /// Optimized method to pack bytes to the image from the YCbCr color space.
- /// This is faster than implicit casting as it avoids double packing.
+ /// Reads exactly length bytes into data. It does not care about byte stuffing.
+ ///
+ /// The data to write to.
+ /// The offset in the source buffer
+ /// The number of bytes to read
+ internal void ReadFull(byte[] data, int offset, int length)
+ {
+ // Unread the overshot bytes, if any.
+ if (this.bytes.UnreadableBytes != 0)
+ {
+ if (this.bits.UnreadBits >= 8)
+ {
+ this.UnreadByteStuffedByte();
+ }
+
+ this.bytes.UnreadableBytes = 0;
+ }
+
+ while (length > 0)
+ {
+ if (this.bytes.J - this.bytes.I >= length)
+ {
+ Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, length);
+ this.bytes.I += length;
+ length -= length;
+ }
+ else
+ {
+ Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, this.bytes.J - this.bytes.I);
+ offset += this.bytes.J - this.bytes.I;
+ length -= this.bytes.J - this.bytes.I;
+ this.bytes.I += this.bytes.J - this.bytes.I;
+
+ this.bytes.Fill(this.inputStream);
+ }
+ }
+ }
+
+ ///
+ /// Optimized method to pack bytes to the image from the YCbCr color space.
+ /// This is faster than implicit casting as it avoids double packing.
///
/// The pixel format.
/// The packed pixel.
@@ -474,440 +514,794 @@ namespace ImageSharp.Formats
}
///
- /// Processes a Define Huffman Table marker, and initializes a huffman
- /// struct from its contents. Specified in section B.2.4.2.
+ /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header.
///
- /// The remaining bytes in the segment block.
- private void ProcessDefineHuffmanTablesMarker(int remaining)
+ /// The pixel format.
+ /// The image to assign the resolution to.
+ private void AssignResolution(Image image)
+ where TColor : struct, IPackedPixel, IEquatable
{
- while (remaining > 0)
+ if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0)
{
- if (remaining < 17)
- {
- throw new ImageFormatException("DHT has wrong length");
- }
-
- this.ReadFull(this.temp, 0, 17);
-
- int tc = this.temp[0] >> 4;
- if (tc > HuffmanTree.MaxTc)
- {
- throw new ImageFormatException("Bad Tc value");
- }
-
- int th = this.temp[0] & 0x0f;
- if (th > HuffmanTree.MaxTh || (!this.isProgressive && (th > 1)))
- {
- throw new ImageFormatException("Bad Th value");
- }
-
- int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th;
- this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.temp, ref remaining);
+ image.HorizontalResolution = this.horizontalResolution;
+ image.VerticalResolution = this.verticalResolution;
}
}
-
///
- /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
+ /// Converts the image from the original CMYK image pixels.
///
- /// The huffman value
- /// The
- private byte DecodeHuffman(ref HuffmanTree huffmanTree)
+ /// The pixel format.
+ /// The image width.
+ /// The image height.
+ /// The image.
+ private void ConvertFromCmyk(int width, int height, Image image)
+ where TColor : struct, IPackedPixel, IEquatable
{
- // Copy stuff to the stack:
- if (huffmanTree.Length == 0)
- {
- throw new ImageFormatException("Uninitialized Huffman table");
- }
-
- if (this.bits.UnreadBits < 8)
- {
- ErrorCodes errorCode = this.bits.EnsureNBits(8, this);
-
- if (errorCode == ErrorCodes.NoError)
- {
- ushort v = huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - HuffmanTree.LutSize)) & 0xff];
+ int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
- if (v != 0)
- {
- byte n = (byte)((v & 0xff) - 1);
- this.bits.UnreadBits -= n;
- this.bits.Mask >>= n;
- return (byte)(v >> 8);
- }
- }
- else
- {
- this.UnreadByteStuffedByte();
- }
- }
+ image.InitPixels(width, height);
- int code = 0;
- for (int i = 0; i < HuffmanTree.MaxCodeLength; i++)
+ using (PixelAccessor pixels = image.Lock())
{
- if (this.bits.UnreadBits == 0)
- {
- ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
- if (errorCode != ErrorCodes.NoError)
- {
- throw new MissingFF00Exception();
- }
- }
-
- if ((this.bits.Accumulator & this.bits.Mask) != 0)
- {
- code |= 1;
- }
-
- this.bits.UnreadBits--;
- this.bits.Mask >>= 1;
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
- if (code <= huffmanTree.MaxCodes[i])
- {
- return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]];
- }
+ for (int x = 0; x < width; x++)
+ {
+ byte cyan = this.ycbcrImage.YPixels[yo + x];
+ byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)];
- code <<= 1;
+ TColor packed = default(TColor);
+ this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
+ pixels[x, y] = packed;
+ }
+ });
}
- throw new ImageFormatException("Bad Huffman code");
+ this.AssignResolution(image);
}
///
- /// Decodes a single bit
+ /// Converts the image from the original grayscale image pixels.
///
- /// The
- private bool DecodeBit()
+ /// The pixel format.
+ /// The image width.
+ /// The image height.
+ /// The image.
+ private void ConvertFromGrayScale(int width, int height, Image image)
+ where TColor : struct, IPackedPixel, IEquatable
{
- if (this.bits.UnreadBits == 0)
+ image.InitPixels(width, height);
+
+ using (PixelAccessor pixels = image.Lock())
{
- ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
- if (errorCode != ErrorCodes.NoError)
- {
- throw new MissingFF00Exception();
- }
+ Parallel.For(
+ 0,
+ height,
+ Bootstrapper.ParallelOptions,
+ y =>
+ {
+ int yoff = this.grayImage.GetRowOffset(y);
+ for (int x = 0; x < width; x++)
+ {
+ byte rgb = this.grayImage.Pixels[yoff + x];
+
+ TColor packed = default(TColor);
+ packed.PackFromBytes(rgb, rgb, rgb, 255);
+ pixels[x, y] = packed;
+ }
+ });
}
- bool ret = (this.bits.Accumulator & this.bits.Mask) != 0;
- this.bits.UnreadBits--;
- this.bits.Mask >>= 1;
- return ret;
+ this.AssignResolution(image);
}
///
- /// Decodes the given number of bits
+ /// Converts the image from the original RBG image pixels.
///
- /// The number of bits to decode.
- /// The
- private uint DecodeBits(int count)
+ /// The pixel format.
+ /// The image width.
+ /// The height.
+ /// The image.
+ private void ConvertFromRGB(int width, int height, Image image)
+ where TColor : struct, IPackedPixel, IEquatable
{
- if (this.bits.UnreadBits < count)
- {
- ErrorCodes errorCode = this.bits.EnsureNBits(count, this);
- if (errorCode != ErrorCodes.NoError)
- {
- throw new MissingFF00Exception();
- }
- }
+ int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
+ image.InitPixels(width, height);
- uint ret = this.bits.Accumulator >> (this.bits.UnreadBits - count);
- ret = (uint)(ret & ((1 << count) - 1));
- this.bits.UnreadBits -= count;
- this.bits.Mask >>= count;
- return ret;
- }
+ using (PixelAccessor pixels = image.Lock())
+ {
+ Parallel.For(
+ 0,
+ height,
+ Bootstrapper.ParallelOptions,
+ y =>
+ {
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ byte red = this.ycbcrImage.YPixels[yo + x];
+ byte green = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte blue = this.ycbcrImage.CrPixels[co + (x / scale)];
+
+ TColor packed = default(TColor);
+ packed.PackFromBytes(red, green, blue, 255);
+ pixels[x, y] = packed;
+ }
+ });
+ }
+
+ this.AssignResolution(image);
+ }
///
- /// Undoes the most recent ReadByteStuffedByte call,
- /// giving a byte of data back from bits to bytes. The Huffman look-up table
- /// requires at least 8 bits for look-up, which means that Huffman decoding can
- /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot
- /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte.
+ /// Converts the image from the original YCbCr image pixels.
///
- private void UnreadByteStuffedByte()
+ /// The pixel format.
+ /// The image width.
+ /// The image height.
+ /// The image.
+ private void ConvertFromYCbCr(int width, int height, Image image)
+ where TColor : struct, IPackedPixel, IEquatable
{
- this.bytes.I -= this.bytes.UnreadableBytes;
- this.bytes.UnreadableBytes = 0;
- if (this.bits.UnreadBits >= 8)
+ int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
+ image.InitPixels(width, height);
+
+ using (PixelAccessor pixels = image.Lock())
{
- this.bits.Accumulator >>= 8;
- this.bits.UnreadBits -= 8;
- this.bits.Mask >>= 8;
+ Parallel.For(
+ 0,
+ height,
+ Bootstrapper.ParallelOptions,
+ y =>
+ {
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
+
+ for (int x = 0; x < width; x++)
+ {
+ byte yy = this.ycbcrImage.YPixels[yo + x];
+ byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
+
+ TColor packed = default(TColor);
+ PackYcbCr(ref packed, yy, cb, cr);
+ pixels[x, y] = packed;
+ }
+ });
}
+
+ this.AssignResolution(image);
}
///
- /// Reads exactly length bytes into data. It does not care about byte stuffing.
+ /// Converts the image from the original YCCK image pixels.
///
- /// The data to write to.
- /// The offset in the source buffer
- /// The number of bytes to read
- internal void ReadFull(byte[] data, int offset, int length)
+ /// The pixel format.
+ /// The image width.
+ /// The image height.
+ /// The image.
+ private void ConvertFromYcck(int width, int height, Image image)
+ where TColor : struct, IPackedPixel, IEquatable
{
- // Unread the overshot bytes, if any.
- if (this.bytes.UnreadableBytes != 0)
- {
- if (this.bits.UnreadBits >= 8)
- {
- this.UnreadByteStuffedByte();
- }
+ int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
- this.bytes.UnreadableBytes = 0;
- }
+ image.InitPixels(width, height);
- while (length > 0)
+ using (PixelAccessor pixels = image.Lock())
{
- if (this.bytes.J - this.bytes.I >= length)
- {
- Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, length);
- this.bytes.I += length;
- length -= length;
- }
- else
- {
- Array.Copy(this.bytes.Buffer, this.bytes.I, data, offset, this.bytes.J - this.bytes.I);
- offset += this.bytes.J - this.bytes.I;
- length -= this.bytes.J - this.bytes.I;
- this.bytes.I += this.bytes.J - this.bytes.I;
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
- this.bytes.Fill(this.inputStream);
- }
+ for (int x = 0; x < width; x++)
+ {
+ byte yy = this.ycbcrImage.YPixels[yo + x];
+ byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
+
+ TColor packed = default(TColor);
+ this.PackYcck(ref packed, yy, cb, cr, x, y);
+ pixels[x, y] = packed;
+ }
+ });
}
+
+ this.AssignResolution(image);
}
///
- /// Skips the next n bytes.
+ /// Decodes a single bit
///
- /// The number of bytes to ignore.
- private void Skip(int count)
+ /// The
+ private bool DecodeBit()
{
- // Unread the overshot bytes, if any.
- if (this.bytes.UnreadableBytes != 0)
+ if (this.bits.UnreadBits == 0)
{
- if (this.bits.UnreadBits >= 8)
+ ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
+ if (errorCode != ErrorCodes.NoError)
{
- this.UnreadByteStuffedByte();
+ throw new MissingFF00Exception();
}
-
- this.bytes.UnreadableBytes = 0;
}
- while (true)
- {
- int m = this.bytes.J - this.bytes.I;
- if (m > count)
- {
- m = count;
- }
+ bool ret = (this.bits.Accumulator & this.bits.Mask) != 0;
+ this.bits.UnreadBits--;
+ this.bits.Mask >>= 1;
+ return ret;
+ }
- this.bytes.I += m;
- count -= m;
- if (count == 0)
+ ///
+ /// Decodes the given number of bits
+ ///
+ /// The number of bits to decode.
+ /// The
+ private uint DecodeBits(int count)
+ {
+ if (this.bits.UnreadBits < count)
+ {
+ ErrorCodes errorCode = this.bits.EnsureNBits(count, this);
+ if (errorCode != ErrorCodes.NoError)
{
- break;
+ throw new MissingFF00Exception();
}
-
- this.bytes.Fill(this.inputStream);
}
+
+ uint ret = this.bits.Accumulator >> (this.bits.UnreadBits - count);
+ ret = (uint)(ret & ((1 << count) - 1));
+ this.bits.UnreadBits -= count;
+ this.bits.Mask >>= count;
+ return ret;
}
///
- /// Processes the Start of Frame marker. Specified in section B.2.2.
+ /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
///
- /// The remaining bytes in the segment block.
- private void ProcessStartOfFrameMarker(int remaining)
+ /// The huffman value
+ /// The
+ private byte DecodeHuffman(ref HuffmanTree huffmanTree)
{
- if (this.componentCount != 0)
+ // Copy stuff to the stack:
+ if (huffmanTree.Length == 0)
{
- throw new ImageFormatException("Multiple SOF markers");
+ throw new ImageFormatException("Uninitialized Huffman table");
}
- switch (remaining)
+ if (this.bits.UnreadBits < 8)
{
- case 6 + (3 * 1): // Grayscale image.
- this.componentCount = 1;
- break;
- case 6 + (3 * 3): // YCbCr or RGB image.
- this.componentCount = 3;
- break;
- case 6 + (3 * 4): // YCbCrK or CMYK image.
- this.componentCount = 4;
- break;
- default:
- throw new ImageFormatException("Incorrect number of components");
- }
-
- this.ReadFull(this.temp, 0, remaining);
+ ErrorCodes errorCode = this.bits.EnsureNBits(8, this);
- // We only support 8-bit precision.
- if (this.temp[0] != 8)
- {
- throw new ImageFormatException("Only 8-Bit precision supported.");
- }
+ if (errorCode == ErrorCodes.NoError)
+ {
+ ushort v =
+ huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - HuffmanTree.LutSize)) & 0xff];
- this.imageHeight = (this.temp[1] << 8) + this.temp[2];
- this.imageWidth = (this.temp[3] << 8) + this.temp[4];
- if (this.temp[5] != this.componentCount)
- {
- throw new ImageFormatException("SOF has wrong length");
+ if (v != 0)
+ {
+ byte n = (byte)((v & 0xff) - 1);
+ this.bits.UnreadBits -= n;
+ this.bits.Mask >>= n;
+ return (byte)(v >> 8);
+ }
+ }
+ else
+ {
+ this.UnreadByteStuffedByte();
+ }
}
- for (int i = 0; i < this.componentCount; i++)
+ int code = 0;
+ for (int i = 0; i < HuffmanTree.MaxCodeLength; i++)
{
- this.componentArray[i].Identifier = this.temp[6 + (3 * i)];
-
- // Section B.2.2 states that "the value of C_i shall be different from
- // the values of C_1 through C_(i-1)".
- for (int j = 0; j < i; j++)
+ if (this.bits.UnreadBits == 0)
{
- if (this.componentArray[i].Identifier == this.componentArray[j].Identifier)
+ ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
+ if (errorCode != ErrorCodes.NoError)
{
- throw new ImageFormatException("Repeated component identifier");
+ throw new MissingFF00Exception();
}
}
- this.componentArray[i].Selector = this.temp[8 + (3 * i)];
- if (this.componentArray[i].Selector > MaxTq)
+ if ((this.bits.Accumulator & this.bits.Mask) != 0)
{
- throw new ImageFormatException("Bad Tq value");
+ code |= 1;
}
- byte hv = this.temp[7 + (3 * i)];
- int h = hv >> 4;
- int v = hv & 0x0f;
- if (h < 1 || h > 4 || v < 1 || v > 4)
- {
- throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio");
- }
+ this.bits.UnreadBits--;
+ this.bits.Mask >>= 1;
- if (h == 3 || v == 3)
+ if (code <= huffmanTree.MaxCodes[i])
{
- throw new ImageFormatException("Lnsupported subsampling ratio");
+ return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]];
}
- switch (this.componentCount)
- {
- case 1:
+ code <<= 1;
+ }
- // If a JPEG image has only one component, section A.2 says "this data
- // is non-interleaved by definition" and section A.2.2 says "[in this
- // case...] the order of data units within a scan shall be left-to-right
- // and top-to-bottom... regardless of the values of H_1 and V_1". Section
- // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be
- // one data unit". Similarly, section A.1.1 explains that it is the ratio
- // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale
- // images, H_1 is the maximum H_j for all components j, so that ratio is
- // always 1. The component's (h, v) is effectively always (1, 1): even if
- // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
- // MCUs, not two 16x8 MCUs.
- h = 1;
- v = 1;
+ throw new ImageFormatException("Bad Huffman code");
+ }
+
+ private JpegPixelArea GetDestinationChannel(int compIndex)
+ {
+ if (this.componentCount == 1)
+ {
+ return this.grayImage;
+ }
+ else
+ {
+ switch (compIndex)
+ {
+ case 0:
+ return this.ycbcrImage.YChannel;
+ case 1:
+ return this.ycbcrImage.CbChannel;
+ case 2:
+ return this.ycbcrImage.CrChannel;
+ case 3:
+ return this.blackImage;
+ default:
+ throw new ImageFormatException("Too many components");
+ }
+ }
+ }
+
+ ///
+ /// Returns a value indicating whether the image in an RGB image.
+ ///
+ ///
+ /// The .
+ ///
+ private bool IsRGB()
+ {
+ if (this.isJfif)
+ {
+ return false;
+ }
+
+ if (this.adobeTransformValid && this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown)
+ {
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
+ // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr.
+ return true;
+ }
+
+ return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G'
+ && this.componentArray[2].Identifier == 'B';
+ }
+
+ ///
+ /// Makes the image from the buffer.
+ ///
+ /// The horizontal MCU count
+ /// The vertical MCU count
+ private void MakeImage(int mxx, int myy)
+ {
+ if (this.grayImage.IsInitialized || this.ycbcrImage != null)
+ {
+ return;
+ }
+
+ if (this.componentCount == 1)
+ {
+ this.grayImage = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy);
+ }
+ else
+ {
+ int h0 = this.componentArray[0].HorizontalFactor;
+ int v0 = this.componentArray[0].VerticalFactor;
+ int horizontalRatio = h0 / this.componentArray[1].HorizontalFactor;
+ int verticalRatio = v0 / this.componentArray[1].VerticalFactor;
+
+ YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444;
+ switch ((horizontalRatio << 4) | verticalRatio)
+ {
+ case 0x11:
+ ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444;
+ break;
+ case 0x12:
+ ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440;
+ break;
+ case 0x21:
+ ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422;
+ break;
+ case 0x22:
+ ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420;
+ break;
+ case 0x41:
+ ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411;
break;
+ case 0x42:
+ ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410;
+ break;
+ }
+
+ this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio);
+
+ if (this.componentCount == 4)
+ {
+ int h3 = this.componentArray[3].HorizontalFactor;
+ int v3 = this.componentArray[3].VerticalFactor;
+
+ this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * mxx, 8 * v3 * myy);
+ }
+ }
+ }
+
+ ///
+ /// Optimized method to pack bytes to the image from the CMYK color space.
+ /// This is faster than implicit casting as it avoids double packing.
+ ///
+ /// The pixel format.
+ /// The packed pixel.
+ /// The cyan component.
+ /// The magenta component.
+ /// The yellow component.
+ /// The x-position within the image.
+ /// The y-position within the image.
+ private void PackCmyk(ref TColor packed, byte c, byte m, byte y, int xx, int yy)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ // Get keyline
+ float keyline = (255 - this.blackImage[xx, yy]) / 255F;
+
+ // Convert back to RGB. CMY are not inverted
+ byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
+ byte g = (byte)(((m / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
+ byte b = (byte)(((y / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
+
+ packed.PackFromBytes(r, g, b, 255);
+ }
+
+ ///
+ /// Optimized method to pack bytes to the image from the YCCK color space.
+ /// This is faster than implicit casting as it avoids double packing.
+ ///
+ /// The pixel format.
+ /// The packed pixel.
+ /// The y luminance component.
+ /// The cb chroma component.
+ /// The cr chroma component.
+ /// The x-position within the image.
+ /// The y-position within the image.
+ private void PackYcck(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get
+ // CMY, and patch in the original K. The RGB to CMY inversion cancels
+ // out the 'Adobe inversion' described in the applyBlack doc comment
+ // above, so in practice, only the fourth channel (black) is inverted.
+ // TODO: We can speed this up further with Vector4
+ int ccb = cb - 128;
+ int ccr = cr - 128;
+
+ // First convert from YCbCr to CMY
+ float cyan = (y + (1.402F * ccr)).Clamp(0, 255) / 255F;
+ float magenta = (y - (0.34414F * ccb) - (0.71414F * ccr)).Clamp(0, 255) / 255F;
+ float yellow = (y + (1.772F * ccb)).Clamp(0, 255) / 255F;
+
+ // Get keyline
+ float keyline = (255 - this.blackImage[xx, yy]) / 255F;
+
+ // Convert back to RGB
+ byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255);
+ byte g = (byte)(((1 - magenta) * (1 - keyline)).Clamp(0, 1) * 255);
+ byte b = (byte)(((1 - yellow) * (1 - keyline)).Clamp(0, 1) * 255);
+
+ packed.PackFromBytes(r, g, b, 255);
+ }
+
+ ///
+ /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters.
+ /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not
+ /// deleted by default when deleting all metadata because it may affect the appearance of the image.
+ ///
+ /// The remaining number of bytes in the stream.
+ private void ProcessApp14Marker(int remaining)
+ {
+ if (remaining < 12)
+ {
+ this.Skip(remaining);
+ return;
+ }
+
+ this.ReadFull(this.temp, 0, 12);
+ remaining -= 12;
+
+ if (this.temp[0] == 'A' && this.temp[1] == 'd' && this.temp[2] == 'o' && this.temp[3] == 'b'
+ && this.temp[4] == 'e')
+ {
+ this.adobeTransformValid = true;
+ this.adobeTransform = this.temp[11];
+ }
+
+ if (remaining > 0)
+ {
+ this.Skip(remaining);
+ }
+ }
+
+ ///
+ /// Processes the App1 marker retrieving any stored metadata
+ ///
+ /// The pixel format.
+ /// The remaining bytes in the segment block.
+ /// The image.
+ private void ProcessApp1Marker(int remaining, Image image)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ if (remaining < 6)
+ {
+ this.Skip(remaining);
+ return;
+ }
+
+ byte[] profile = new byte[remaining];
+ this.ReadFull(profile, 0, remaining);
+
+ if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
+ && profile[5] == '\0')
+ {
+ image.ExifProfile = new ExifProfile(profile);
+ }
+ }
+
+ ///
+ /// Processes the application header containing the JFIF identifier plus extra data.
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessApplicationHeader(int remaining)
+ {
+ if (remaining < 5)
+ {
+ this.Skip(remaining);
+ return;
+ }
+
+ this.ReadFull(this.temp, 0, 13);
+ remaining -= 13;
+
+ // TODO: We should be using constants for this.
+ this.isJfif = this.temp[0] == 'J' && this.temp[1] == 'F' && this.temp[2] == 'I' && this.temp[3] == 'F'
+ && this.temp[4] == '\x00';
+
+ if (this.isJfif)
+ {
+ this.horizontalResolution = (short)(this.temp[9] + (this.temp[10] << 8));
+ this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8));
+ }
+
+ if (remaining > 0)
+ {
+ this.Skip(remaining);
+ }
+ }
+
+ private void ProcessBlockImpl(
+ int ah,
+ Block8x8F* b,
+ Block8x8F* temp1,
+ Block8x8F* temp2,
+ int* unzigPtr,
+ Scan[] scan,
+ int i,
+ int zigStart,
+ int zigEnd,
+ int al,
+ int[] dc,
+ int compIndex,
+ int @by,
+ int mxx,
+ int hi,
+ int bx,
+ Block8x8F* qt)
+ {
+ int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector;
+ if (ah != 0)
+ {
+ this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al);
+ }
+ else
+ {
+ int zig = zigStart;
+ if (zig == 0)
+ {
+ zig++;
+
+ // Decode the DC coefficient, as specified in section F.2.2.1.
+ byte value =
+ this.DecodeHuffman(
+ ref this.huffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]);
+ if (value > 16)
+ {
+ throw new ImageFormatException("Excessive DC component");
+ }
+
+ int deltaDC = this.bits.ReceiveExtend(value, this);
+ dc[compIndex] += deltaDC;
+
+ // b[0] = dc[compIndex] << al;
+ Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al);
+ }
+
+ if (zig <= zigEnd && this.eobRun > 0)
+ {
+ this.eobRun--;
+ }
+ else
+ {
+ // Decode the AC coefficients, as specified in section F.2.2.2.
+ // Huffman huffv = ;
+ for (; zig <= zigEnd; zig++)
+ {
+ byte value = this.DecodeHuffman(ref this.huffmanTrees[huffmannIdx]);
+ byte val0 = (byte)(value >> 4);
+ byte val1 = (byte)(value & 0x0f);
+ if (val1 != 0)
+ {
+ zig += val0;
+ if (zig > zigEnd)
+ {
+ break;
+ }
- case 3:
+ int ac = this.bits.ReceiveExtend(val1, this);
- // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
- // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the
- // (h, v) values for the Y component are either (1, 1), (1, 2),
- // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values
- // must be a multiple of the Cb and Cr component's values. We also
- // assume that the two chroma components have the same subsampling
- // ratio.
- switch (i)
+ // b[Unzig[zig]] = ac << al;
+ Block8x8F.SetScalarAt(b, unzigPtr[zig], ac << al);
+ }
+ else
{
- case 0:
+ if (val0 != 0x0f)
+ {
+ this.eobRun = (ushort)(1 << val0);
+ if (val0 != 0)
{
- // Y.
- // We have already verified, above, that h and v are both
- // either 1, 2 or 4, so invalid (h, v) combinations are those
- // with v == 4.
- if (v == 4)
- {
- throw new ImageFormatException("Unsupported subsampling ratio");
- }
-
- break;
+ this.eobRun |= (ushort)this.DecodeBits(val0);
}
- case 1:
- {
- // Cb.
- if (this.componentArray[0].HorizontalFactor % h != 0
- || this.componentArray[0].VerticalFactor % v != 0)
- {
- throw new ImageFormatException("Unsupported subsampling ratio");
- }
+ this.eobRun--;
+ break;
+ }
- break;
- }
+ zig += 0x0f;
+ }
+ }
+ }
+ }
- case 2:
- {
- // Cr.
- if (this.componentArray[1].HorizontalFactor != h
- || this.componentArray[1].VerticalFactor != v)
- {
- throw new ImageFormatException("Unsupported subsampling ratio");
- }
+ if (this.isProgressive)
+ {
+ if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0)
+ {
+ // We haven't completely decoded this 8x8 block. Save the coefficients.
- break;
- }
- }
+ // TODO!!!
+ // throw new NotImplementedException();
+ // this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone();
+ this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = *b;
- break;
+ // At this point, we could execute the rest of the loop body to dequantize and
+ // perform the inverse DCT, to save early stages of a progressive image to the
+ // *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
+ // the jpeg.Decode function does not return until the entire image is decoded,
+ // so we "continue" here to avoid wasted computation.
+ return;
+ }
+ }
- case 4:
+ // Dequantize, perform the inverse DCT and store the block to the image.
+ Block8x8F.UnZig(b, qt, unzigPtr);
- // For 4-component images (either CMYK or YCbCrK), we only support two
- // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
- // Theoretically, 4-component JPEG images could mix and match hv values
- // but in practice, those two combinations are the only ones in use,
- // and it simplifies the applyBlack code below if we can assume that:
- // - for CMYK, the C and K channels have full samples, and if the M
- // and Y channels subsample, they subsample both horizontally and
- // vertically.
- // - for YCbCrK, the Y and K channels have full samples.
- switch (i)
- {
- case 0:
- if (hv != 0x11 && hv != 0x22)
- {
- throw new ImageFormatException("Unsupported subsampling ratio");
- }
+ DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2);
- break;
- case 1:
- case 2:
- if (hv != 0x11)
- {
- throw new ImageFormatException("Unsupported subsampling ratio");
- }
+ var destChannel = this.GetDestinationChannel(compIndex);
+ var destArea = destChannel.GetOffsetedSubAreaForBlock(bx, by);
+ destArea.LoadColorsFrom(temp1, temp2);
+ }
- break;
- case 3:
- if (this.componentArray[0].HorizontalFactor != h
- || this.componentArray[0].VerticalFactor != v)
- {
- throw new ImageFormatException("Unsupported subsampling ratio");
- }
+ private void ProcessComponentImpl(
+ int i,
+ ref Scan currentScan,
+ Scan[] scan,
+ ref int totalHv,
+ ref Component currentComponent)
+ {
+ // Section B.2.3 states that "the value of Cs_j shall be different from
+ // the values of Cs_1 through Cs_(j-1)". Since we have previously
+ // verified that a frame's component identifiers (C_i values in section
+ // B.2.2) are unique, it suffices to check that the implicit indexes
+ // into comp are unique.
+ for (int j = 0; j < i; j++)
+ {
+ if (currentScan.Index == scan[j].Index)
+ {
+ throw new ImageFormatException("Repeated component selector");
+ }
+ }
- break;
- }
+ totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor;
- break;
+ currentScan.DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4);
+ if (currentScan.DcTableSelector > HuffmanTree.MaxTh)
+ {
+ throw new ImageFormatException("Bad DC table selector value");
+ }
+
+ currentScan.AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f);
+ if (currentScan.AcTableSelector > HuffmanTree.MaxTh)
+ {
+ throw new ImageFormatException("Bad AC table selector value");
+ }
+ }
+
+ ///
+ /// Processes a Define Huffman Table marker, and initializes a huffman
+ /// struct from its contents. Specified in section B.2.4.2.
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessDefineHuffmanTablesMarker(int remaining)
+ {
+ while (remaining > 0)
+ {
+ if (remaining < 17)
+ {
+ throw new ImageFormatException("DHT has wrong length");
}
- this.componentArray[i].HorizontalFactor = h;
- this.componentArray[i].VerticalFactor = v;
+ this.ReadFull(this.temp, 0, 17);
+
+ int tc = this.temp[0] >> 4;
+ if (tc > HuffmanTree.MaxTc)
+ {
+ throw new ImageFormatException("Bad Tc value");
+ }
+
+ int th = this.temp[0] & 0x0f;
+ if (th > HuffmanTree.MaxTh || (!this.isProgressive && (th > 1)))
+ {
+ throw new ImageFormatException("Bad Th value");
+ }
+
+ int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th;
+ this.huffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.temp, ref remaining);
+ }
+ }
+
+ ///
+ /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
+ /// macroblocks
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessDefineRestartIntervalMarker(int remaining)
+ {
+ if (remaining != 2)
+ {
+ throw new ImageFormatException("DRI has wrong length");
}
+
+ this.ReadFull(this.temp, 0, 2);
+ this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1];
}
///
- /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
+ /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
///
/// The remaining bytes in the segment block.
///
- /// Thrown if the tables do not match the header
+ /// Thrown if the tables do not match the header
///
private void ProcessDqt(int remaining)
{
@@ -973,327 +1367,231 @@ namespace ImageSharp.Formats
}
}
- ///
- /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in macroblocks
- ///
- /// The remaining bytes in the segment block.
- private void ProcessDefineRestartIntervalMarker(int remaining)
- {
- if (remaining != 2)
- {
- throw new ImageFormatException("DRI has wrong length");
- }
-
- this.ReadFull(this.temp, 0, 2);
- this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1];
- }
-
- ///
- /// Processes the application header containing the JFIF identifier plus extra data.
- ///
- /// The remaining bytes in the segment block.
- private void ProcessApplicationHeader(int remaining)
- {
- if (remaining < 5)
- {
- this.Skip(remaining);
- return;
- }
-
- this.ReadFull(this.temp, 0, 13);
- remaining -= 13;
-
- // TODO: We should be using constants for this.
- this.isJfif = this.temp[0] == 'J' && this.temp[1] == 'F' && this.temp[2] == 'I' && this.temp[3] == 'F'
- && this.temp[4] == '\x00';
-
- if (this.isJfif)
- {
- this.horizontalResolution = (short)(this.temp[9] + (this.temp[10] << 8));
- this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8));
- }
-
- if (remaining > 0)
- {
- this.Skip(remaining);
- }
- }
-
- ///
- /// Processes the App1 marker retrieving any stored metadata
- ///
- /// The pixel format.
- /// The remaining bytes in the segment block.
- /// The image.
- private void ProcessApp1Marker(int remaining, Image image)
- where TColor : struct, IPackedPixel, IEquatable
+ private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv)
{
- if (remaining < 6)
+ // Component selector.
+ int cs = this.temp[1 + (2 * i)];
+ int compIndex = -1;
+ for (int j = 0; j < this.componentCount; j++)
{
- this.Skip(remaining);
- return;
+ // Component compv = ;
+ if (cs == this.componentArray[j].Identifier)
+ {
+ compIndex = j;
+ }
}
- byte[] profile = new byte[remaining];
- this.ReadFull(profile, 0, remaining);
-
- if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
- && profile[5] == '\0')
+ if (compIndex < 0)
{
- image.ExifProfile = new ExifProfile(profile);
+ throw new ImageFormatException("Unknown component selector");
}
+
+ currentScan.Index = (byte)compIndex;
+
+ this.ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]);
}
///
- /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters.
- /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not
- /// deleted by default when deleting all metadata because it may affect the appearance of the image.
+ /// Processes the Start of Frame marker. Specified in section B.2.2.
///
- /// The remaining number of bytes in the stream.
- private void ProcessApp14Marker(int remaining)
+ /// The remaining bytes in the segment block.
+ private void ProcessStartOfFrameMarker(int remaining)
{
- if (remaining < 12)
+ if (this.componentCount != 0)
{
- this.Skip(remaining);
- return;
+ throw new ImageFormatException("Multiple SOF markers");
}
- this.ReadFull(this.temp, 0, 12);
- remaining -= 12;
-
- if (this.temp[0] == 'A' && this.temp[1] == 'd' && this.temp[2] == 'o' && this.temp[3] == 'b'
- && this.temp[4] == 'e')
+ switch (remaining)
{
- this.adobeTransformValid = true;
- this.adobeTransform = this.temp[11];
+ case 6 + (3 * 1): // Grayscale image.
+ this.componentCount = 1;
+ break;
+ case 6 + (3 * 3): // YCbCr or RGB image.
+ this.componentCount = 3;
+ break;
+ case 6 + (3 * 4): // YCbCrK or CMYK image.
+ this.componentCount = 4;
+ break;
+ default:
+ throw new ImageFormatException("Incorrect number of components");
}
- if (remaining > 0)
+ this.ReadFull(this.temp, 0, remaining);
+
+ // We only support 8-bit precision.
+ if (this.temp[0] != 8)
{
- this.Skip(remaining);
+ throw new ImageFormatException("Only 8-Bit precision supported.");
}
- }
- ///
- /// Converts the image from the original YCCK image pixels.
- ///
- /// The pixel format.
- /// The image width.
- /// The image height.
- /// The image.
- private void ConvertFromYcck(int width, int height, Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
-
- image.InitPixels(width, height);
-
- using (PixelAccessor pixels = image.Lock())
+ this.imageHeight = (this.temp[1] << 8) + this.temp[2];
+ this.imageWidth = (this.temp[3] << 8) + this.temp[4];
+ if (this.temp[5] != this.componentCount)
{
- Parallel.For(
- 0,
- height,
- y =>
- {
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
-
- for (int x = 0; x < width; x++)
- {
- byte yy = this.ycbcrImage.YPixels[yo + x];
- byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
- byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
-
- TColor packed = default(TColor);
- this.PackYcck(ref packed, yy, cb, cr, x, y);
- pixels[x, y] = packed;
- }
- });
+ throw new ImageFormatException("SOF has wrong length");
}
- this.AssignResolution(image);
- }
-
- ///
- /// Converts the image from the original CMYK image pixels.
- ///
- /// The pixel format.
- /// The image width.
- /// The image height.
- /// The image.
- private void ConvertFromCmyk(int width, int height, Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
-
- image.InitPixels(width, height);
-
- using (PixelAccessor pixels = image.Lock())
+ for (int i = 0; i < this.componentCount; i++)
{
- Parallel.For(
- 0,
- height,
- y =>
+ this.componentArray[i].Identifier = this.temp[6 + (3 * i)];
+
+ // Section B.2.2 states that "the value of C_i shall be different from
+ // the values of C_1 through C_(i-1)".
+ for (int j = 0; j < i; j++)
+ {
+ if (this.componentArray[i].Identifier == this.componentArray[j].Identifier)
{
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
+ throw new ImageFormatException("Repeated component identifier");
+ }
+ }
- for (int x = 0; x < width; x++)
- {
- byte cyan = this.ycbcrImage.YPixels[yo + x];
- byte magenta = this.ycbcrImage.CbPixels[co + (x / scale)];
- byte yellow = this.ycbcrImage.CrPixels[co + (x / scale)];
+ this.componentArray[i].Selector = this.temp[8 + (3 * i)];
+ if (this.componentArray[i].Selector > MaxTq)
+ {
+ throw new ImageFormatException("Bad Tq value");
+ }
- TColor packed = default(TColor);
- this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
- pixels[x, y] = packed;
- }
- });
- }
+ byte hv = this.temp[7 + (3 * i)];
+ int h = hv >> 4;
+ int v = hv & 0x0f;
+ if (h < 1 || h > 4 || v < 1 || v > 4)
+ {
+ throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio");
+ }
- this.AssignResolution(image);
- }
+ if (h == 3 || v == 3)
+ {
+ throw new ImageFormatException("Lnsupported subsampling ratio");
+ }
- ///
- /// Converts the image from the original grayscale image pixels.
- ///
- /// The pixel format.
- /// The image width.
- /// The image height.
- /// The image.
- private void ConvertFromGrayScale(int width, int height, Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- image.InitPixels(width, height);
+ switch (this.componentCount)
+ {
+ case 1:
- using (PixelAccessor pixels = image.Lock())
- {
- Parallel.For(
- 0,
- height,
- Bootstrapper.ParallelOptions,
- y =>
- {
- int yoff = this.grayImage.GetRowOffset(y);
- for (int x = 0; x < width; x++)
- {
- byte rgb = this.grayImage.Pixels[yoff + x];
+ // If a JPEG image has only one component, section A.2 says "this data
+ // is non-interleaved by definition" and section A.2.2 says "[in this
+ // case...] the order of data units within a scan shall be left-to-right
+ // and top-to-bottom... regardless of the values of H_1 and V_1". Section
+ // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be
+ // one data unit". Similarly, section A.1.1 explains that it is the ratio
+ // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale
+ // images, H_1 is the maximum H_j for all components j, so that ratio is
+ // always 1. The component's (h, v) is effectively always (1, 1): even if
+ // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
+ // MCUs, not two 16x8 MCUs.
+ h = 1;
+ v = 1;
+ break;
- TColor packed = default(TColor);
- packed.PackFromBytes(rgb, rgb, rgb, 255);
- pixels[x, y] = packed;
- }
- });
- }
+ case 3:
- this.AssignResolution(image);
- }
+ // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
+ // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the
+ // (h, v) values for the Y component are either (1, 1), (1, 2),
+ // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values
+ // must be a multiple of the Cb and Cr component's values. We also
+ // assume that the two chroma components have the same subsampling
+ // ratio.
+ switch (i)
+ {
+ case 0:
+ {
+ // Y.
+ // We have already verified, above, that h and v are both
+ // either 1, 2 or 4, so invalid (h, v) combinations are those
+ // with v == 4.
+ if (v == 4)
+ {
+ throw new ImageFormatException("Unsupported subsampling ratio");
+ }
- ///
- /// Converts the image from the original YCbCr image pixels.
- ///
- /// The pixel format.
- /// The image width.
- /// The image height.
- /// The image.
- private void ConvertFromYCbCr(int width, int height, Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
- image.InitPixels(width, height);
+ break;
+ }
- using (PixelAccessor pixels = image.Lock())
- {
- Parallel.For(
- 0,
- height,
- Bootstrapper.ParallelOptions,
- y =>
- {
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
+ case 1:
+ {
+ // Cb.
+ if (this.componentArray[0].HorizontalFactor % h != 0
+ || this.componentArray[0].VerticalFactor % v != 0)
+ {
+ throw new ImageFormatException("Unsupported subsampling ratio");
+ }
- for (int x = 0; x < width; x++)
- {
- byte yy = this.ycbcrImage.YPixels[yo + x];
- byte cb = this.ycbcrImage.CbPixels[co + (x / scale)];
- byte cr = this.ycbcrImage.CrPixels[co + (x / scale)];
+ break;
+ }
- TColor packed = default(TColor);
- PackYcbCr(ref packed, yy, cb, cr);
- pixels[x, y] = packed;
- }
- });
- }
+ case 2:
+ {
+ // Cr.
+ if (this.componentArray[1].HorizontalFactor != h
+ || this.componentArray[1].VerticalFactor != v)
+ {
+ throw new ImageFormatException("Unsupported subsampling ratio");
+ }
- this.AssignResolution(image);
- }
+ break;
+ }
+ }
- ///
- /// Converts the image from the original RBG image pixels.
- ///
- /// The pixel format.
- /// The image width.
- /// The height.
- /// The image.
- private void ConvertFromRGB(int width, int height, Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
- image.InitPixels(width, height);
+ break;
- using (PixelAccessor pixels = image.Lock())
- {
- Parallel.For(
- 0,
- height,
- Bootstrapper.ParallelOptions,
- y =>
+ case 4:
+
+ // For 4-component images (either CMYK or YCbCrK), we only support two
+ // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
+ // Theoretically, 4-component JPEG images could mix and match hv values
+ // but in practice, those two combinations are the only ones in use,
+ // and it simplifies the applyBlack code below if we can assume that:
+ // - for CMYK, the C and K channels have full samples, and if the M
+ // and Y channels subsample, they subsample both horizontally and
+ // vertically.
+ // - for YCbCrK, the Y and K channels have full samples.
+ switch (i)
{
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
+ case 0:
+ if (hv != 0x11 && hv != 0x22)
+ {
+ throw new ImageFormatException("Unsupported subsampling ratio");
+ }
- for (int x = 0; x < width; x++)
- {
- byte red = this.ycbcrImage.YPixels[yo + x];
- byte green = this.ycbcrImage.CbPixels[co + (x / scale)];
- byte blue = this.ycbcrImage.CrPixels[co + (x / scale)];
+ break;
+ case 1:
+ case 2:
+ if (hv != 0x11)
+ {
+ throw new ImageFormatException("Unsupported subsampling ratio");
+ }
- TColor packed = default(TColor);
- packed.PackFromBytes(red, green, blue, 255);
- pixels[x, y] = packed;
- }
- });
- }
+ break;
+ case 3:
+ if (this.componentArray[0].HorizontalFactor != h
+ || this.componentArray[0].VerticalFactor != v)
+ {
+ throw new ImageFormatException("Unsupported subsampling ratio");
+ }
- this.AssignResolution(image);
- }
+ break;
+ }
- ///
- /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header.
- ///
- /// The pixel format.
- /// The image to assign the resolution to.
- private void AssignResolution(Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0)
- {
- image.HorizontalResolution = this.horizontalResolution;
- image.VerticalResolution = this.verticalResolution;
+ break;
+ }
+
+ this.componentArray[i].HorizontalFactor = h;
+ this.componentArray[i].VerticalFactor = v;
}
}
///
- /// Processes the SOS (Start of scan marker).
+ /// Processes the SOS (Start of scan marker).
///
///
- /// TODO: This also needs some significant refactoring to follow a more OO format.
+ /// TODO: This also needs some significant refactoring to follow a more OO format.
///
/// The remaining bytes in the segment block.
///
- /// Missing SOF Marker
- /// SOS has wrong length
+ /// Missing SOF Marker
+ /// SOS has wrong length
///
private void ProcessStartOfScan(int remaining)
{
@@ -1378,7 +1676,7 @@ namespace ImageSharp.Formats
int v0 = this.componentArray[0].VerticalFactor;
int mxx = (this.imageWidth + (8 * h0) - 1) / (8 * h0);
int myy = (this.imageHeight + (8 * v0) - 1) / (8 * v0);
-
+
this.MakeImage(mxx, myy);
if (this.isProgressive)
@@ -1410,7 +1708,6 @@ namespace ImageSharp.Formats
int bx, by, blockCount = 0;
// TODO: A DecoderScanProcessor struct could clean up this mess
-
Block8x8F b = default(Block8x8F);
Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F);
@@ -1473,7 +1770,7 @@ namespace ImageSharp.Formats
int qtIndex = this.componentArray[compIndex].Selector;
// TODO: A DecoderScanProcessor struct could clean up this mess
- // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. (The first one could be async)
+ // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async.
fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex])
{
// Load the previous partially decoded coefficients, if applicable.
@@ -1516,264 +1813,59 @@ namespace ImageSharp.Formats
i,
zigStart,
zigEnd,
- al,
- dc,
- compIndex,
- @by,
- mxx,
- hi,
- bx,
- qtp);
- }
- }
- }
-
- // for j
- }
-
- // for i
- mcu++;
-
- if (this.restartInterval > 0 && mcu % this.restartInterval == 0 && mcu < mxx * myy)
- {
- // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input,
- // but this one assumes well-formed input, and hence the restart marker follows immediately.
- this.ReadFull(this.temp, 0, 2);
- if (this.temp[0] != 0xff || this.temp[1] != expectedRst)
- {
- throw new ImageFormatException("Bad RST marker");
- }
-
- expectedRst++;
- if (expectedRst == JpegConstants.Markers.RST7 + 1)
- {
- expectedRst = JpegConstants.Markers.RST0;
- }
-
- // Reset the Huffman decoder.
- this.bits = default(Bits);
-
- // Reset the DC components, as per section F.2.1.3.1.
- dc = new int[MaxComponents];
-
- // Reset the progressive decoder state, as per section G.1.2.2.
- this.eobRun = 0;
- }
- }
-
- // for mx
- }
-
- // for my
- }
-
- private void ProcessBlockImpl(
- int ah,
- Block8x8F* b,
- Block8x8F* temp1,
- Block8x8F* temp2,
- int* unzigPtr,
- Scan[] scan,
- int i,
- int zigStart,
- int zigEnd,
- int al,
- int[] dc,
- int compIndex,
- int @by,
- int mxx,
- int hi,
- int bx,
- Block8x8F* qt)
- {
- int huffmannIdx = (AcTable * HuffmanTree.ThRowSize) + scan[i].AcTableSelector;
- if (ah != 0)
- {
- this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al);
- }
- else
- {
- int zig = zigStart;
- if (zig == 0)
- {
- zig++;
-
- // Decode the DC coefficient, as specified in section F.2.2.1.
- byte value = this.DecodeHuffman(
- ref this.huffmanTrees[(DcTable * HuffmanTree.ThRowSize) + scan[i].DcTableSelector]);
- if (value > 16)
- {
- throw new ImageFormatException("Excessive DC component");
- }
-
- int deltaDC = this.bits.ReceiveExtend(value, this);
- dc[compIndex] += deltaDC;
-
- // b[0] = dc[compIndex] << al;
- Block8x8F.SetScalarAt(b, 0, dc[compIndex] << al);
- }
-
- if (zig <= zigEnd && this.eobRun > 0)
- {
- this.eobRun--;
- }
- else
- {
- // Decode the AC coefficients, as specified in section F.2.2.2.
- // Huffman huffv = ;
- for (; zig <= zigEnd; zig++)
- {
- byte value = this.DecodeHuffman(ref this.huffmanTrees[huffmannIdx]);
- byte val0 = (byte)(value >> 4);
- byte val1 = (byte)(value & 0x0f);
- if (val1 != 0)
- {
- zig += val0;
- if (zig > zigEnd)
- {
- break;
- }
-
- int ac = this.bits.ReceiveExtend(val1, this);
-
- // b[Unzig[zig]] = ac << al;
- Block8x8F.SetScalarAt(b, unzigPtr[zig], ac << al);
- }
- else
- {
- if (val0 != 0x0f)
- {
- this.eobRun = (ushort)(1 << val0);
- if (val0 != 0)
- {
- this.eobRun |= (ushort)this.DecodeBits(val0);
- }
-
- this.eobRun--;
- break;
- }
-
- zig += 0x0f;
- }
- }
- }
- }
-
- if (this.isProgressive)
- {
- if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0)
- {
- // We haven't completely decoded this 8x8 block. Save the coefficients.
-
- // TODO!!!
- // throw new NotImplementedException();
- // this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone();
- this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = *b;
-
- // At this point, we could execute the rest of the loop body to dequantize and
- // perform the inverse DCT, to save early stages of a progressive image to the
- // *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
- // the jpeg.Decode function does not return until the entire image is decoded,
- // so we "continue" here to avoid wasted computation.
- return;
- }
- }
-
- // Dequantize, perform the inverse DCT and store the block to the image.
- Block8x8F.UnZig(b, qt, unzigPtr);
-
- DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2);
+ al,
+ dc,
+ compIndex,
+ @by,
+ mxx,
+ hi,
+ bx,
+ qtp);
+ }
+ }
+ }
- var destChannel = this.GetDestinationChannel(compIndex);
- var destArea = destChannel.GetOffsetedAreaForBlock(bx, by);
- destArea.LoadColorsFrom(temp1, temp2);
- }
+ // for j
+ }
- private JpegPixelArea GetDestinationChannel(int compIndex)
- {
- if (this.componentCount == 1)
- {
- return this.grayImage;
- }
- else
- {
- switch (compIndex)
- {
- case 0:
- return this.ycbcrImage.YChannel;
- case 1:
- return this.ycbcrImage.CbChannel;
- case 2:
- return this.ycbcrImage.CrChannel;
- case 3:
- return this.blackImage;
- default:
- throw new ImageFormatException("Too many components");
- }
- }
- }
+ // for i
+ mcu++;
- private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv)
- {
- // Component selector.
- int cs = this.temp[1 + (2 * i)];
- int compIndex = -1;
- for (int j = 0; j < this.componentCount; j++)
- {
- // Component compv = ;
- if (cs == this.componentArray[j].Identifier)
- {
- compIndex = j;
- }
- }
+ if (this.restartInterval > 0 && mcu % this.restartInterval == 0 && mcu < mxx * myy)
+ {
+ // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input,
+ // but this one assumes well-formed input, and hence the restart marker follows immediately.
+ this.ReadFull(this.temp, 0, 2);
+ if (this.temp[0] != 0xff || this.temp[1] != expectedRst)
+ {
+ throw new ImageFormatException("Bad RST marker");
+ }
- if (compIndex < 0)
- {
- throw new ImageFormatException("Unknown component selector");
- }
+ expectedRst++;
+ if (expectedRst == JpegConstants.Markers.RST7 + 1)
+ {
+ expectedRst = JpegConstants.Markers.RST0;
+ }
- currentScan.Index = (byte)compIndex;
+ // Reset the Huffman decoder.
+ this.bits = default(Bits);
- this.ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]);
- }
+ // Reset the DC components, as per section F.2.1.3.1.
+ dc = new int[MaxComponents];
- private void ProcessComponentImpl(
- int i,
- ref Scan currentScan,
- Scan[] scan,
- ref int totalHv,
- ref Component currentComponent)
- {
- // Section B.2.3 states that "the value of Cs_j shall be different from
- // the values of Cs_1 through Cs_(j-1)". Since we have previously
- // verified that a frame's component identifiers (C_i values in section
- // B.2.2) are unique, it suffices to check that the implicit indexes
- // into comp are unique.
- for (int j = 0; j < i; j++)
- {
- if (currentScan.Index == scan[j].Index)
- {
- throw new ImageFormatException("Repeated component selector");
+ // Reset the progressive decoder state, as per section G.1.2.2.
+ this.eobRun = 0;
+ }
}
- }
- totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor;
-
- currentScan.DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4);
- if (currentScan.DcTableSelector > HuffmanTree.MaxTh)
- {
- throw new ImageFormatException("Bad DC table selector value");
+ // for mx
}
- currentScan.AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f);
- if (currentScan.AcTableSelector > HuffmanTree.MaxTh)
- {
- throw new ImageFormatException("Bad AC table selector value");
- }
+ // for my
}
///
- /// Decodes a successive approximation refinement block, as specified in section G.1.2.
+ /// Decodes a successive approximation refinement block, as specified in section G.1.2.
///
/// The block of coefficients
/// The Huffman tree
@@ -1875,16 +1967,16 @@ namespace ImageSharp.Formats
}
///
- /// Refines non-zero entries of b in zig-zag order.
- /// If >= 0, the first zero entries are skipped over.
+ /// Refines non-zero entries of b in zig-zag order.
+ /// If >= 0, the first zero entries are skipped over.
///
/// The block of coefficients
/// The zig-zag start index
/// The zig-zag end index
/// The non-zero entry
/// The low transform offset
- /// Pointer to the Jpeg Unzig data (data part of )
- /// The
+ /// Pointer to the Jpeg Unzig data (data part of )
+ /// The
private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta, int* unzigPtr)
{
for (; zig <= zigEnd; zig++)
@@ -1924,192 +2016,103 @@ namespace ImageSharp.Formats
return zig;
}
-
+
///
- /// Makes the image from the buffer.
+ /// Skips the next n bytes.
///
- /// The horizontal MCU count
- /// The vertical MCU count
- private void MakeImage(int mxx, int myy)
+ /// The number of bytes to ignore.
+ private void Skip(int count)
{
- if (this.grayImage.Created || this.ycbcrImage != null) return;
-
- if (this.componentCount == 1)
+ // Unread the overshot bytes, if any.
+ if (this.bytes.UnreadableBytes != 0)
{
- this.grayImage = JpegPixelArea.CreatePooled(8 * mxx, 8 * myy);
- //this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight);
+ if (this.bits.UnreadBits >= 8)
+ {
+ this.UnreadByteStuffedByte();
+ }
+
+ this.bytes.UnreadableBytes = 0;
}
- else
- {
- int h0 = this.componentArray[0].HorizontalFactor;
- int v0 = this.componentArray[0].VerticalFactor;
- int horizontalRatio = h0 / this.componentArray[1].HorizontalFactor;
- int verticalRatio = v0 / this.componentArray[1].VerticalFactor;
- YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444;
- switch ((horizontalRatio << 4) | verticalRatio)
+ while (true)
+ {
+ int m = this.bytes.J - this.bytes.I;
+ if (m > count)
{
- case 0x11:
- ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444;
- break;
- case 0x12:
- ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440;
- break;
- case 0x21:
- ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422;
- break;
- case 0x22:
- ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420;
- break;
- case 0x41:
- ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411;
- break;
- case 0x42:
- ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410;
- break;
+ m = count;
}
- this.ycbcrImage = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio, 0, 0);
-
- if (this.componentCount == 4)
+ this.bytes.I += m;
+ count -= m;
+ if (count == 0)
{
- int h3 = this.componentArray[3].HorizontalFactor;
- int v3 = this.componentArray[3].VerticalFactor;
-
- this.blackImage = JpegPixelArea.CreatePooled(8 * h3 * mxx, 8 * v3 * myy);
-
+ break;
}
+
+ this.bytes.Fill(this.inputStream);
}
}
///
- /// Returns a value indicating whether the image in an RGB image.
+ /// Undoes the most recent ReadByteStuffedByte call,
+ /// giving a byte of data back from bits to bytes. The Huffman look-up table
+ /// requires at least 8 bits for look-up, which means that Huffman decoding can
+ /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot
+ /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte.
///
- ///
- /// The .
- ///
- private bool IsRGB()
+ private void UnreadByteStuffedByte()
{
- if (this.isJfif)
- {
- return false;
- }
-
- if (this.adobeTransformValid && this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown)
+ this.bytes.I -= this.bytes.UnreadableBytes;
+ this.bytes.UnreadableBytes = 0;
+ if (this.bits.UnreadBits >= 8)
{
- // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
- // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr.
- return true;
+ this.bits.Accumulator >>= 8;
+ this.bits.UnreadBits -= 8;
+ this.bits.Mask >>= 8;
}
-
- return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G'
- && this.componentArray[2].Identifier == 'B';
- }
-
- ///
- /// Optimized method to pack bytes to the image from the YCCK color space.
- /// This is faster than implicit casting as it avoids double packing.
- ///
- /// The pixel format.
- /// The packed pixel.
- /// The y luminance component.
- /// The cb chroma component.
- /// The cr chroma component.
- /// The x-position within the image.
- /// The y-position within the image.
- private void PackYcck(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy)
- where TColor : struct, IPackedPixel, IEquatable
- {
- // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get
- // CMY, and patch in the original K. The RGB to CMY inversion cancels
- // out the 'Adobe inversion' described in the applyBlack doc comment
- // above, so in practice, only the fourth channel (black) is inverted.
- // TODO: We can speed this up further with Vector4
- int ccb = cb - 128;
- int ccr = cr - 128;
-
- // First convert from YCbCr to CMY
- float cyan = (y + (1.402F * ccr)).Clamp(0, 255) / 255F;
- float magenta = (y - (0.34414F * ccb) - (0.71414F * ccr)).Clamp(0, 255) / 255F;
- float yellow = (y + (1.772F * ccb)).Clamp(0, 255) / 255F;
-
- // Get keyline
- float keyline = (255 - this.blackImage[xx, yy]) / 255F;
-
- // Convert back to RGB
- byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255);
- byte g = (byte)(((1 - magenta) * (1 - keyline)).Clamp(0, 1) * 255);
- byte b = (byte)(((1 - yellow) * (1 - keyline)).Clamp(0, 1) * 255);
-
- packed.PackFromBytes(r, g, b, 255);
- }
-
- ///
- /// Optimized method to pack bytes to the image from the CMYK color space.
- /// This is faster than implicit casting as it avoids double packing.
- ///
- /// The pixel format.
- /// The packed pixel.
- /// The cyan component.
- /// The magenta component.
- /// The yellow component.
- /// The x-position within the image.
- /// The y-position within the image.
- private void PackCmyk(ref TColor packed, byte c, byte m, byte y, int xx, int yy)
- where TColor : struct, IPackedPixel, IEquatable
- {
- // Get keyline
- float keyline = (255 - this.blackImage[xx, yy]) / 255F;
-
- // Convert back to RGB. CMY are not inverted
- byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
- byte g = (byte)(((m / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
- byte b = (byte)(((y / 255F) * (1F - keyline)).Clamp(0, 1) * 255);
-
- packed.PackFromBytes(r, g, b, 255);
}
///
- /// Represents a component scan
+ /// Represents a component scan
///
private struct Scan
{
///
- /// Gets or sets the component index.
+ /// Gets or sets the component index.
///
public byte Index { get; set; }
///
- /// Gets or sets the DC table selector
+ /// Gets or sets the DC table selector
///
public byte DcTableSelector { get; set; }
///
- /// Gets or sets the AC table selector
+ /// Gets or sets the AC table selector
///
public byte AcTableSelector { get; set; }
}
///
- /// The missing ff00 exception.
+ /// The EOF (End of File exception).
+ /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker
///
- internal class MissingFF00Exception : Exception
+ internal class EOFException : Exception
{
}
///
- /// The EOF (End of File exception).
- /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker
+ /// The missing ff00 exception.
///
- internal class EOFException : Exception
+ internal class MissingFF00Exception : Exception
{
}
///
- /// The short huffman data exception.
+ /// The short huffman data exception.
///
private class ShortHuffmanDataException : Exception
{
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs b/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs
index a181eeee0a..7369d658f2 100644
--- a/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs
+++ b/src/ImageSharp/Formats/Jpg/Utils/CleanPooler.cs
@@ -1,13 +1,30 @@
-namespace ImageSharp.Formats
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats
{
using System.Buffers;
+ ///
+ /// Wraps to always provide arrays initialized with default(T)
+ ///
+ /// The element type
internal class CleanPooler
{
private static readonly ArrayPool Pool = ArrayPool.Create();
+ ///
+ /// Rents a clean array
+ ///
+ /// The minimum array length
+ /// A clean array of T
public static T[] RentCleanArray(int minimumLength) => Pool.Rent(minimumLength);
+ ///
+ /// Retursn array to the pool
+ ///
+ /// The array
public static void ReturnArray(T[] array) => Pool.Return(array, true);
}
}
\ No newline at end of file