Browse Source

cleanup

af/merge-core
Anton Firszov 9 years ago
parent
commit
a8657d9bd1
  1. 51
      src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs
  2. 21
      src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
  3. 517
      src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs

51
src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs

@ -7,22 +7,67 @@ namespace ImageSharp.Formats.Jpg
{
using System.Buffers;
/// <summary>
/// A structure to store unprocessed <see cref="Block8x8F"/> instances and their coordinates while scanning the image.
/// </summary>
internal struct DecodedBlockMemento
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> used to pool data in <see cref="JpegDecoderCore.DecodedBlocks"/>.
/// Should always clean arrays when returning!
/// A value indicating whether the <see cref="DecodedBlockMemento"/> instance is initialized.
/// </summary>
public static readonly ArrayPool<DecodedBlockMemento> ArrayPool = ArrayPool<DecodedBlockMemento>.Create();
public bool Initialized;
/// <summary>
/// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
/// </summary>
public int Bx;
/// <summary>
/// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
/// </summary>
public int By;
/// <summary>
/// The <see cref="Block8x8F"/>
/// </summary>
public Block8x8F Block;
/// <summary>
/// The <see cref="ArrayPool{T}"/> used to pool data in <see cref="JpegDecoderCore.DecodedBlocks"/>.
/// Should always clean arrays when returning!
/// </summary>
private static readonly ArrayPool<DecodedBlockMemento> ArrayPool = ArrayPool<DecodedBlockMemento>.Create();
/// <summary>
/// Rent an array of <see cref="DecodedBlockMemento"/>-s from the pool.
/// </summary>
/// <param name="size">The requested array size</param>
/// <returns>An array of <see cref="DecodedBlockMemento"/>-s</returns>
public static DecodedBlockMemento[] RentArray(int size)
{
return ArrayPool.Rent(size);
}
/// <summary>
/// Returns the <see cref="DecodedBlockMemento"/> array to the pool.
/// </summary>
/// <param name="blockArray">The <see cref="DecodedBlockMemento"/> array</param>
public static void ReturnArray(DecodedBlockMemento[] blockArray)
{
ArrayPool.Return(blockArray, true);
}
/// <summary>
/// Store the block data into a <see cref="DecodedBlockMemento"/> at the given index.
/// </summary>
/// <param name="blockArray">The array of <see cref="DecodedBlockMemento"/></param>
/// <param name="index">The index in the array</param>
/// <param name="bx">X coordinate of the block</param>
/// <param name="by">Y coordinate of the block</param>
/// <param name="block">The <see cref="Block8x8F"/></param>
public static void Store(DecodedBlockMemento[] blockArray, int index, int bx, int by, ref Block8x8F block)
{
blockArray[index].Initialized = true;
blockArray[index].Bx = bx;
blockArray[index].By = by;
blockArray[index].Block = block;

21
src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs

@ -30,12 +30,12 @@ namespace ImageSharp.Formats.Jpg
/// <summary>
/// The AC table index
/// </summary>
private const int AcTableIndex = 1;
public const int AcTableIndex = 1;
/// <summary>
/// The DC table index
/// </summary>
private const int DcTableIndex = 0;
public const int DcTableIndex = 0;
/// <summary>
/// The current component index
@ -98,7 +98,7 @@ namespace ImageSharp.Formats.Jpg
private ComputationData data;
/// <summary>
/// Initializes the default instance after creation.
/// Initializes a default-constructed <see cref="JpegScanDecoder"/> instance for reading data from <see cref="JpegDecoderCore"/>-s stream.
/// </summary>
/// <param name="p">Pointer to <see cref="JpegScanDecoder"/> on the stack</param>
/// <param name="decoder">The <see cref="JpegDecoderCore"/> instance</param>
@ -109,12 +109,20 @@ namespace ImageSharp.Formats.Jpg
p->InitStreamReadingImpl(decoder, remaining);
}
/// <summary>
/// Initializes a default-constructed <see cref="JpegScanDecoder"/> instance, filling the data and setting the pointers.
/// </summary>
/// <param name="p">Pointer to <see cref="JpegScanDecoder"/> on the stack</param>
public static void Init(JpegScanDecoder* p)
{
p->data = ComputationData.Create();
p->pointers = new DataPointers(&p->data);
}
/// <summary>
/// Loads the data from the given <see cref="DecodedBlockMemento"/> into the block.
/// </summary>
/// <param name="memento">The <see cref="DecodedBlockMemento"/></param>
public void LoadMemento(ref DecodedBlockMemento memento)
{
this.bx = memento.Bx;
@ -184,7 +192,6 @@ namespace ImageSharp.Formats.Jpg
}
this.ReadBlock(decoder, scanIndex);
//this.ProcessBlock(decoder);
}
// for j
@ -415,10 +422,6 @@ namespace ImageSharp.Formats.Jpg
DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref *b);
}
private bool IsProgressiveBlockFinished(JpegDecoderCore decoder)
=> decoder.IsProgressive && (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0);
private DecoderErrorCode DecodeEobRun(int count, JpegDecoderCore decoder)
{
uint bitsResult;
@ -663,7 +666,5 @@ namespace ImageSharp.Formats.Jpg
return zig;
}
}
}

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

@ -40,8 +40,6 @@ namespace ImageSharp.Formats
public Bytes Bytes;
#pragma warning restore SA401
/// <summary>
/// The App14 marker color-space
/// </summary>
@ -180,254 +178,10 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
this.ProcessStream(image, stream, metadataOnly);
if (metadataOnly) return;
this.ConvertBlocksToImagePixels(image);
}
private void ConvertBlocksToImagePixels<TColor>(Image<TColor> image)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
this.ProcessBlocks<TColor>();
if (this.grayImage.IsInitialized)
{
this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image);
}
else if (this.ycbcrImage != null)
{
if (this.ComponentCount == 4)
{
if (!this.adobeTransformValid)
{
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
// See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
// TODO: YCbCrA?
if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck)
{
this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image);
}
else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
// Assume CMYK
this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image);
}
return;
}
if (this.ComponentCount == 3)
{
if (this.IsRGB())
{
this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image);
return;
}
this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image);
return;
}
throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces.");
}
else
{
throw new ImageFormatException("Missing SOS marker.");
}
}
private void ProcessBlocks<TColor>()
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
JpegScanDecoder scanDecoder = default(JpegScanDecoder);
JpegScanDecoder.Init(&scanDecoder);
for(int componentIndex = 0; componentIndex < this.ComponentCount; componentIndex++)
if (!metadataOnly)
{
scanDecoder.ComponentIndex = componentIndex;
DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex];
for (int i = 0; i < blockArray.Length; i++)
{
scanDecoder.LoadMemento(ref blockArray[i]);
scanDecoder.ProcessBlock(this);
}
}
}
private void ProcessStream<TColor>(Image<TColor> image, Stream stream, bool metadataOnly)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
this.InputStream = stream;
// Check for the Start Of Image marker.
this.ReadFull(this.Temp, 0, 2);
if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
// Process the remaining segments until the End Of Image marker.
bool processBytes = true;
// we can't currently short circute progressive images so don't try.
while (processBytes)
{
this.ReadFull(this.Temp, 0, 2);
while (this.Temp[0] != 0xff)
{
// Strictly speaking, this is a format error. However, libjpeg is
// liberal in what it accepts. As of version 9, next_marker in
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
// continues to decode the stream. Even before next_marker sees
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
// bytes as it can, possibly past the end of a scan's data. It
// effectively puts back any markers that it overscanned (e.g. an
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
// and thus it can silently ignore a small number of extraneous
// non-marker bytes before next_marker has a chance to see them (and
// print a warning).
// We are therefore also liberal in what we accept. Extraneous data
// is silently ignore
// This is similar to, but not exactly the same as, the restart
// mechanism within a scan (the RST[0-7] markers).
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
// "\xff\x00", and so are detected a little further down below.
this.Temp[0] = this.Temp[1];
this.Temp[1] = this.ReadByte();
}
byte marker = this.Temp[1];
if (marker == 0)
{
// Treat "\xff\x00" as extraneous data.
continue;
}
while (marker == 0xff)
{
// Section B.1.1.2 says, "Any marker may optionally be preceded by any
// number of fill bytes, which are bytes assigned code X'FF'".
marker = this.ReadByte();
}
// End Of Image.
if (marker == JpegConstants.Markers.EOI)
{
break;
}
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
{
// Figures B.2 and B.16 of the specification suggest that restart markers should
// only occur between Entropy Coded Segments and not after the final ECS.
// However, some encoders may generate incorrect JPEGs with a final restart
// marker. That restart marker will be seen here instead of inside the ProcessSOS
// method, and is ignored as a harmless error. Restart markers have no extra data,
// so we check for this before we read the 16-bit length of the segment.
continue;
}
// Read the 16-bit length of the segment. The value includes the 2 bytes for the
// length itself, so we subtract 2 to get the number of remaining bytes.
this.ReadFull(this.Temp, 0, 2);
int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2;
if (remaining < 0)
{
throw new ImageFormatException("Short segment length.");
}
switch (marker)
{
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.IsProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining);
if (metadataOnly && this.isJfif)
{
return;
}
break;
case JpegConstants.Markers.DHT:
if (metadataOnly)
{
this.Skip(remaining);
}
else
{
this.ProcessDefineHuffmanTablesMarker(remaining);
}
break;
case JpegConstants.Markers.DQT:
if (metadataOnly)
{
this.Skip(remaining);
}
else
{
this.ProcessDqt(remaining);
}
break;
case JpegConstants.Markers.SOS:
if (metadataOnly)
{
return;
}
// when this is a progressive image this gets called a number of times
// need to know how many times this should be called in total.
this.ProcessStartOfScan(remaining);
if (!this.IsProgressive)
{
// if this is not a progressive image we can stop processing bytes as we now have the image data.
processBytes = false;
}
break;
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
this.Skip(remaining);
}
else
{
this.ProcessDefineRestartIntervalMarker(remaining);
}
break;
case JpegConstants.Markers.APP0:
this.ProcessApplicationHeader(remaining);
break;
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining, image);
break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
default:
if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
|| marker == JpegConstants.Markers.COM)
{
this.Skip(remaining);
}
else if (marker < JpegConstants.Markers.SOF0)
{
// See Table B.1 "Marker code assignments".
throw new ImageFormatException("Unknown marker");
}
else
{
throw new ImageFormatException("Unknown marker");
}
break;
}
this.DecodeBlocksIntoJpegImageChannels<TColor>();
this.ConvertJpegPixelsToImagePixels(image);
}
}
@ -445,7 +199,7 @@ namespace ImageSharp.Formats
{
if (blockArray != null)
{
DecodedBlockMemento.ArrayPool.Return(blockArray, true);
DecodedBlockMemento.ReturnArray(blockArray);
}
}
@ -701,6 +455,267 @@ namespace ImageSharp.Formats
packed.PackFromBytes(r, g, b, 255);
}
/// <summary>
/// Read metadata from stream and read the blocks in the scans into <see cref="DecodedBlocks"/>.
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/></param>
/// <param name="stream">The stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param>
private void ProcessStream<TColor>(Image<TColor> image, Stream stream, bool metadataOnly)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
this.InputStream = stream;
// Check for the Start Of Image marker.
this.ReadFull(this.Temp, 0, 2);
if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
// Process the remaining segments until the End Of Image marker.
bool processBytes = true;
// we can't currently short circute progressive images so don't try.
while (processBytes)
{
this.ReadFull(this.Temp, 0, 2);
while (this.Temp[0] != 0xff)
{
// Strictly speaking, this is a format error. However, libjpeg is
// liberal in what it accepts. As of version 9, next_marker in
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
// continues to decode the stream. Even before next_marker sees
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
// bytes as it can, possibly past the end of a scan's data. It
// effectively puts back any markers that it overscanned (e.g. an
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
// and thus it can silently ignore a small number of extraneous
// non-marker bytes before next_marker has a chance to see them (and
// print a warning).
// We are therefore also liberal in what we accept. Extraneous data
// is silently ignore
// This is similar to, but not exactly the same as, the restart
// mechanism within a scan (the RST[0-7] markers).
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
// "\xff\x00", and so are detected a little further down below.
this.Temp[0] = this.Temp[1];
this.Temp[1] = this.ReadByte();
}
byte marker = this.Temp[1];
if (marker == 0)
{
// Treat "\xff\x00" as extraneous data.
continue;
}
while (marker == 0xff)
{
// Section B.1.1.2 says, "Any marker may optionally be preceded by any
// number of fill bytes, which are bytes assigned code X'FF'".
marker = this.ReadByte();
}
// End Of Image.
if (marker == JpegConstants.Markers.EOI)
{
break;
}
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
{
// Figures B.2 and B.16 of the specification suggest that restart markers should
// only occur between Entropy Coded Segments and not after the final ECS.
// However, some encoders may generate incorrect JPEGs with a final restart
// marker. That restart marker will be seen here instead of inside the ProcessSOS
// method, and is ignored as a harmless error. Restart markers have no extra data,
// so we check for this before we read the 16-bit length of the segment.
continue;
}
// Read the 16-bit length of the segment. The value includes the 2 bytes for the
// length itself, so we subtract 2 to get the number of remaining bytes.
this.ReadFull(this.Temp, 0, 2);
int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2;
if (remaining < 0)
{
throw new ImageFormatException("Short segment length.");
}
switch (marker)
{
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.IsProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining);
if (metadataOnly && this.isJfif)
{
return;
}
break;
case JpegConstants.Markers.DHT:
if (metadataOnly)
{
this.Skip(remaining);
}
else
{
this.ProcessDefineHuffmanTablesMarker(remaining);
}
break;
case JpegConstants.Markers.DQT:
if (metadataOnly)
{
this.Skip(remaining);
}
else
{
this.ProcessDqt(remaining);
}
break;
case JpegConstants.Markers.SOS:
if (metadataOnly)
{
return;
}
// when this is a progressive image this gets called a number of times
// need to know how many times this should be called in total.
this.ProcessStartOfScan(remaining);
if (!this.IsProgressive)
{
// if this is not a progressive image we can stop processing bytes as we now have the image data.
processBytes = false;
}
break;
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
this.Skip(remaining);
}
else
{
this.ProcessDefineRestartIntervalMarker(remaining);
}
break;
case JpegConstants.Markers.APP0:
this.ProcessApplicationHeader(remaining);
break;
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining, image);
break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
default:
if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
|| marker == JpegConstants.Markers.COM)
{
this.Skip(remaining);
}
else if (marker < JpegConstants.Markers.SOF0)
{
// See Table B.1 "Marker code assignments".
throw new ImageFormatException("Unknown marker");
}
else
{
throw new ImageFormatException("Unknown marker");
}
break;
}
}
}
/// <summary>
/// Process the blocks in <see cref="DecodedBlocks"/> into Jpeg image channels (<see cref="YCbCrImage"/> and <see cref="JpegPixelArea"/>)
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
private void DecodeBlocksIntoJpegImageChannels<TColor>()
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
JpegScanDecoder scanDecoder = default(JpegScanDecoder);
JpegScanDecoder.Init(&scanDecoder);
for (int componentIndex = 0; componentIndex < this.ComponentCount; componentIndex++)
{
scanDecoder.ComponentIndex = componentIndex;
DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex];
for (int i = 0; i < blockArray.Length; i++)
{
scanDecoder.LoadMemento(ref blockArray[i]);
scanDecoder.ProcessBlock(this);
}
}
}
/// <summary>
/// Convert the pixel data in <see cref="YCbCrImage"/> and/or <see cref="JpegPixelArea"/> into pixels of <see cref="Image{TColor}"/>
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
/// <param name="image">The destination image</param>
private void ConvertJpegPixelsToImagePixels<TColor>(Image<TColor> image)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
if (this.grayImage.IsInitialized)
{
this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image);
}
else if (this.ycbcrImage != null)
{
if (this.ComponentCount == 4)
{
if (!this.adobeTransformValid)
{
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
// See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
// TODO: YCbCrA?
if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck)
{
this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image);
}
else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
// Assume CMYK
this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image);
}
return;
}
if (this.ComponentCount == 3)
{
if (this.IsRGB())
{
this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image);
return;
}
this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image);
return;
}
throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces.");
}
else
{
throw new ImageFormatException("Missing SOS marker.");
}
}
/// <summary>
/// Assigns the horizontal and vertical resolution to the image if it has a JFIF header.
/// </summary>
@ -1470,7 +1485,7 @@ namespace ImageSharp.Formats
{
int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor
* this.ComponentArray[i].VerticalFactor;
this.DecodedBlocks[i] = DecodedBlockMemento.ArrayPool.Rent(size);
this.DecodedBlocks[i] = DecodedBlockMemento.RentArray(size);
}
}

Loading…
Cancel
Save