Browse Source

Optimize and cleanup the jpeg decoder

TODO: Test single vs multiple threaded decoding.
af/merge-core
James Jackson-South 10 years ago
parent
commit
bcfc74a182
  1. 1
      Settings.StyleCop
  2. 411
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  3. 6
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  4. 2
      tests/ImageSharp.Tests/FileTestBase.cs

1
Settings.StyleCop

@ -31,6 +31,7 @@
<Value>ss</Value> <Value>ss</Value>
<Value>Vol</Value> <Value>Vol</Value>
<Value>pp</Value> <Value>pp</Value>
<Value>cmyk</Value>
</CollectionProperty> </CollectionProperty>
</GlobalSettings> </GlobalSettings>
<Analyzers> <Analyzers>

411
src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs

@ -34,15 +34,24 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private const int MaxComponents = 4; private const int MaxComponents = 4;
private const int maxTc = 1; private const int MaxTc = 1;
private const int maxTh = 3; private const int MaxTh = 3;
private const int maxTq = 3; /// <summary>
/// The maximum number of quantization tables
/// </summary>
private const int MaxTq = 3;
private const int dcTable = 0; /// <summary>
/// The DC table index
/// </summary>
private const int DcTable = 0;
private const int acTable = 1; /// <summary>
/// The AC table index
/// </summary>
private const int AcTable = 1;
/// <summary> /// <summary>
/// Unzig maps from the zigzag ordering to the natural ordering. For example, /// Unzig maps from the zigzag ordering to the natural ordering. For example,
@ -77,6 +86,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly Block[] quantizationTables; private readonly Block[] quantizationTables;
/// <summary>
/// A temporary buffer for holding pixels
/// </summary>
private readonly byte[] temp;
/// <summary> /// <summary>
/// The byte buffer. /// The byte buffer.
/// </summary> /// </summary>
@ -142,8 +156,14 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private bool isJfif; private bool isJfif;
/// <summary>
/// Whether the image is in CMYK format with an App14 marker
/// </summary>
private bool adobeTransformValid; private bool adobeTransformValid;
/// <summary>
/// The App14 marker color-space
/// </summary>
private byte adobeTransform; private byte adobeTransform;
/// <summary> /// <summary>
@ -151,11 +171,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private ushort eobRun; private ushort eobRun;
/// <summary>
/// A temporary buffer for holding pixels
/// </summary>
private byte[] temp;
/// <summary> /// <summary>
/// The horizontal resolution. Calculated if the image has a JFIF header. /// The horizontal resolution. Calculated if the image has a JFIF header.
/// </summary> /// </summary>
@ -171,17 +186,17 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
public JpegDecoderCore() public JpegDecoderCore()
{ {
this.huffmanTrees = new Huffman[maxTc + 1, maxTh + 1]; this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
this.quantizationTables = new Block[maxTq + 1]; this.quantizationTables = new Block[MaxTq + 1];
this.temp = new byte[2 * Block.BlockSize]; this.temp = new byte[2 * Block.BlockSize];
this.componentArray = new Component[MaxComponents]; this.componentArray = new Component[MaxComponents];
this.progCoeffs = new Block[MaxComponents][]; this.progCoeffs = new Block[MaxComponents][];
this.bits = new Bits(); this.bits = new Bits();
this.bytes = new Bytes(); this.bytes = new Bytes();
for (int i = 0; i < maxTc + 1; i++) for (int i = 0; i < MaxTc + 1; i++)
{ {
for (int j = 0; j < maxTh + 1; j++) for (int j = 0; j < MaxTh + 1; j++)
{ {
this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength); this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength);
} }
@ -204,10 +219,8 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="stream">The stream, where the image should be /// <param name="image">The image, where the data should be set to.</param>
/// decoded from. Cannot be null (Nothing in Visual Basic).</param> /// <param name="stream">The stream, where the image should be.</param>
/// <param name="image">The image, where the data should be set to.
/// Cannot be null (Nothing in Visual Basic).</param>
/// <param name="configOnly">Whether to decode metadata only.</param> /// <param name="configOnly">Whether to decode metadata only.</param>
public void Decode<TColor, TPacked>(Image<TColor, TPacked> image, Stream stream, bool configOnly) public void Decode<TColor, TPacked>(Image<TColor, TPacked> image, Stream stream, bool configOnly)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
@ -283,7 +296,7 @@ namespace ImageSharp.Formats
// Read the 16-bit length of the segment. The value includes the 2 bytes for the // 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. // length itself, so we subtract 2 to get the number of remaining bytes.
this.ReadFull(this.temp, 0, 2); this.ReadFull(this.temp, 0, 2);
int remaining = ((int)this.temp[0] << 8) + (int)this.temp[1] - 2; int remaining = (this.temp[0] << 8) + this.temp[1] - 2;
if (remaining < 0) if (remaining < 0)
{ {
throw new ImageFormatException("Short segment length."); throw new ImageFormatException("Short segment length.");
@ -295,7 +308,7 @@ namespace ImageSharp.Formats
case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2: case JpegConstants.Markers.SOF2:
this.isProgressive = marker == JpegConstants.Markers.SOF2; this.isProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessSOF(remaining); this.ProcessStartOfFrameMarker(remaining);
if (configOnly && this.isJfif) if (configOnly && this.isJfif)
{ {
return; return;
@ -309,7 +322,7 @@ namespace ImageSharp.Formats
} }
else else
{ {
this.ProcessDht(remaining); this.ProcessDefineHuffmanTablesMarker(remaining);
} }
break; break;
@ -335,12 +348,12 @@ namespace ImageSharp.Formats
} }
else else
{ {
this.ProcessDri(remaining); this.ProcessDefineRestartIntervalMarker(remaining);
} }
break; break;
case JpegConstants.Markers.APP0: case JpegConstants.Markers.APP0:
this.ProcessApp0Marker(remaining); this.ProcessApplicationHeader(remaining);
break; break;
case JpegConstants.Markers.APP1: case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining, image); this.ProcessApp1Marker(remaining, image);
@ -457,12 +470,12 @@ namespace ImageSharp.Formats
/// Processes a Define Huffman Table marker, and initializes a huffman /// Processes a Define Huffman Table marker, and initializes a huffman
/// struct from its contents. Specified in section B.2.4.2. /// struct from its contents. Specified in section B.2.4.2.
/// </summary> /// </summary>
/// <param name="n"></param> /// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDht(int n) private void ProcessDefineHuffmanTablesMarker(int remaining)
{ {
while (n > 0) while (remaining > 0)
{ {
if (n < 17) if (remaining < 17)
{ {
throw new ImageFormatException("DHT has wrong length"); throw new ImageFormatException("DHT has wrong length");
} }
@ -470,13 +483,13 @@ namespace ImageSharp.Formats
this.ReadFull(this.temp, 0, 17); this.ReadFull(this.temp, 0, 17);
int tc = this.temp[0] >> 4; int tc = this.temp[0] >> 4;
if (tc > maxTc) if (tc > MaxTc)
{ {
throw new ImageFormatException("Bad Tc value"); throw new ImageFormatException("Bad Tc value");
} }
int th = this.temp[0] & 0x0f; int th = this.temp[0] & 0x0f;
if (th > maxTh || (!this.isProgressive && (th > 1))) if (th > MaxTh || (!this.isProgressive && (th > 1)))
{ {
throw new ImageFormatException("Bad Th value"); throw new ImageFormatException("Bad Th value");
} }
@ -505,8 +518,8 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Huffman table has excessive length"); throw new ImageFormatException("Huffman table has excessive length");
} }
n -= huffman.Length + 17; remaining -= huffman.Length + 17;
if (n < 0) if (remaining < 0)
{ {
throw new ImageFormatException("DHT has wrong length"); throw new ImageFormatException("DHT has wrong length");
} }
@ -571,8 +584,7 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// Returns the next Huffman-coded value from the bit-stream, /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
/// decoded according to the given value.
/// </summary> /// </summary>
/// <param name="huffman">The huffman value</param> /// <param name="huffman">The huffman value</param>
/// <returns>The <see cref="byte"/></returns> /// <returns>The <see cref="byte"/></returns>
@ -868,15 +880,18 @@ namespace ImageSharp.Formats
} }
} }
// Specified in section B.2.2. /// <summary>
private void ProcessSOF(int n) /// Processes the Start of Frame marker. Specified in section B.2.2.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessStartOfFrameMarker(int remaining)
{ {
if (this.componentCount != 0) if (this.componentCount != 0)
{ {
throw new ImageFormatException("multiple SOF markers"); throw new ImageFormatException("Multiple SOF markers");
} }
switch (n) switch (remaining)
{ {
case 6 + (3 * 1): // Grayscale image. case 6 + (3 * 1): // Grayscale image.
this.componentCount = 1; this.componentCount = 1;
@ -891,7 +906,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Incorrect number of components"); throw new ImageFormatException("Incorrect number of components");
} }
this.ReadFull(this.temp, 0, n); this.ReadFull(this.temp, 0, remaining);
// We only support 8-bit precision. // We only support 8-bit precision.
if (this.temp[0] != 8) if (this.temp[0] != 8)
@ -921,7 +936,7 @@ namespace ImageSharp.Formats
} }
this.componentArray[i].Selector = this.temp[8 + (3 * i)]; this.componentArray[i].Selector = this.temp[8 + (3 * i)];
if (this.componentArray[i].Selector > maxTq) if (this.componentArray[i].Selector > MaxTq)
{ {
throw new ImageFormatException("Bad Tq value"); throw new ImageFormatException("Bad Tq value");
} }
@ -1053,17 +1068,23 @@ namespace ImageSharp.Formats
} }
} }
// Specified in section B.2.4.1. /// <summary>
private void ProcessDqt(int n) /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the tables do not match the header
/// </exception>
private void ProcessDqt(int remaining)
{ {
while (n > 0) while (remaining > 0)
{ {
bool done = false; bool done = false;
n--; remaining--;
byte x = this.ReadByte(); byte x = this.ReadByte();
byte tq = (byte)(x & 0x0f); byte tq = (byte)(x & 0x0f);
if (tq > maxTq) if (tq > MaxTq)
{ {
throw new ImageFormatException("Bad Tq value"); throw new ImageFormatException("Bad Tq value");
} }
@ -1071,13 +1092,13 @@ namespace ImageSharp.Formats
switch (x >> 4) switch (x >> 4)
{ {
case 0: case 0:
if (n < Block.BlockSize) if (remaining < Block.BlockSize)
{ {
done = true; done = true;
break; break;
} }
n -= Block.BlockSize; remaining -= Block.BlockSize;
this.ReadFull(this.temp, 0, Block.BlockSize); this.ReadFull(this.temp, 0, Block.BlockSize);
for (int i = 0; i < Block.BlockSize; i++) for (int i = 0; i < Block.BlockSize; i++)
@ -1087,13 +1108,13 @@ namespace ImageSharp.Formats
break; break;
case 1: case 1:
if (n < 2 * Block.BlockSize) if (remaining < 2 * Block.BlockSize)
{ {
done = true; done = true;
break; break;
} }
n -= 2 * Block.BlockSize; remaining -= 2 * Block.BlockSize;
this.ReadFull(this.temp, 0, 2 * Block.BlockSize); this.ReadFull(this.temp, 0, 2 * Block.BlockSize);
for (int i = 0; i < Block.BlockSize; i++) for (int i = 0; i < Block.BlockSize; i++)
@ -1112,16 +1133,19 @@ namespace ImageSharp.Formats
} }
} }
if (n != 0) if (remaining != 0)
{ {
throw new ImageFormatException("DQT has wrong length"); throw new ImageFormatException("DQT has wrong length");
} }
} }
// Specified in section B.2.4.4. /// <summary>
private void ProcessDri(int n) /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in macroblocks
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineRestartIntervalMarker(int remaining)
{ {
if (n != 2) if (remaining != 2)
{ {
throw new ImageFormatException("DRI has wrong length"); throw new ImageFormatException("DRI has wrong length");
} }
@ -1130,16 +1154,20 @@ namespace ImageSharp.Formats
this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1]; this.restartInterval = ((int)this.temp[0] << 8) + (int)this.temp[1];
} }
private void ProcessApp0Marker(int n) /// <summary>
/// Processes the application header containing the JFIF identifier plus extra data.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeader(int remaining)
{ {
if (n < 5) if (remaining < 5)
{ {
this.Skip(n); this.Skip(remaining);
return; return;
} }
this.ReadFull(this.temp, 0, 13); this.ReadFull(this.temp, 0, 13);
n -= 13; remaining -= 13;
// TODO: We should be using constants for this. // TODO: We should be using constants for this.
this.isJfif = this.temp[0] == 'J' && this.isJfif = this.temp[0] == 'J' &&
@ -1154,9 +1182,9 @@ namespace ImageSharp.Formats
this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8)); this.verticalResolution = (short)(this.temp[11] + (this.temp[12] << 8));
} }
if (n > 0) if (remaining > 0)
{ {
this.Skip(n); this.Skip(remaining);
} }
} }
@ -1165,20 +1193,20 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="n">The position in the stream.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
private void ProcessApp1Marker<TColor, TPacked>(int n, Image<TColor, TPacked> image) private void ProcessApp1Marker<TColor, TPacked>(int remaining, Image<TColor, TPacked> image)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
if (n < 6) if (remaining < 6)
{ {
this.Skip(n); this.Skip(remaining);
return; return;
} }
byte[] profile = new byte[n]; byte[] profile = new byte[remaining];
this.ReadFull(profile, 0, n); this.ReadFull(profile, 0, remaining);
if (profile[0] == 'E' && if (profile[0] == 'E' &&
profile[1] == 'x' && profile[1] == 'x' &&
@ -1191,16 +1219,22 @@ namespace ImageSharp.Formats
} }
} }
private void ProcessApp14Marker(int n) /// <summary>
/// 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.
/// </summary>
/// <param name="remaining">The remaining number of bytes in the stream.</param>
private void ProcessApp14Marker(int remaining)
{ {
if (n < 12) if (remaining < 12)
{ {
this.Skip(n); this.Skip(remaining);
return; return;
} }
this.ReadFull(this.temp, 0, 12); this.ReadFull(this.temp, 0, 12);
n -= 12; remaining -= 12;
if (this.temp[0] == 'A' && if (this.temp[0] == 'A' &&
this.temp[1] == 'd' && this.temp[1] == 'd' &&
@ -1212,14 +1246,14 @@ namespace ImageSharp.Formats
this.adobeTransform = this.temp[11]; this.adobeTransform = this.temp[11];
} }
if (n > 0) if (remaining > 0)
{ {
this.Skip(n); this.Skip(remaining);
} }
} }
/// <summary> /// <summary>
/// Converts the image from the original Cmyk image pixels. /// Converts the image from the original CMYK image pixels.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
@ -1241,40 +1275,41 @@ namespace ImageSharp.Formats
{ {
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
TColor[] pixels = new TColor[width * height]; image.InitPixels(width, height);
// 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.
Parallel.For(
0,
height,
y =>
{
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < width; x++) using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{
// 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.
Parallel.For(
0,
height,
y =>
{ {
byte yy = this.ycbcrImage.YChannel[yo + x]; int yo = this.ycbcrImage.GetRowYOffset(y);
byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; int co = this.ycbcrImage.GetRowCOffset(y);
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
int index = (y * width) + x; for (int x = 0; x < width; x++)
{
// Implicit casting FTW byte yy = this.ycbcrImage.YChannel[yo + x];
Color color = new YCbCr(yy, cb, cr); byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
int keyline = 255 - this.blackPixels[(y * this.blackStride) + x]; byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
Color final = new Cmyk(color.R / 255F, color.G / 255F, color.B / 255F, keyline / 255F);
// Implicit casting FTW
TColor packed = default(TColor); Color color = new YCbCr(yy, cb, cr);
packed.PackFromVector4(final.ToVector4()); int keyline = 255 - this.blackPixels[(y * this.blackStride) + x];
pixels[index] = packed; Color final = new Cmyk(color.R / 255F, color.G / 255F, color.B / 255F, keyline / 255F);
}
}); TColor packed = default(TColor);
packed.PackFromBytes(final.R, final.G, final.B, final.A);
pixels[x, y] = packed;
}
});
}
image.SetPixels(width, height, pixels); this.AssignResolution(image);
} }
} }
@ -1290,27 +1325,28 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
TColor[] pixels = new TColor[width * height]; image.InitPixels(width, height);
Parallel.For( using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
0, {
height, Parallel.For(
Bootstrapper.Instance.ParallelOptions, 0,
y => height,
{ Bootstrapper.Instance.ParallelOptions,
int yoff = this.grayImage.GetRowOffset(y); y =>
for (int x = 0; x < width; x++)
{ {
int offset = (y * width) + x; int yoff = this.grayImage.GetRowOffset(y);
byte rgb = this.grayImage.Pixels[yoff + x]; for (int x = 0; x < width; x++)
{
byte rgb = this.grayImage.Pixels[yoff + x];
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(new Color(rgb, rgb, rgb).ToVector4()); packed.PackFromBytes(rgb, rgb, rgb, 255);
pixels[offset] = packed; pixels[x, y] = packed;
} }
}); });
}
image.SetPixels(width, height, pixels);
this.AssignResolution(image); this.AssignResolution(image);
} }
@ -1327,14 +1363,15 @@ namespace ImageSharp.Formats
where TPacked : struct where TPacked : struct
{ {
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
image.InitPixels(width, height);
TColor[] pixels = new TColor[width * height]; using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{
Parallel.For( Parallel.For(
0, 0,
height, height,
Bootstrapper.Instance.ParallelOptions, Bootstrapper.Instance.ParallelOptions,
y => y =>
{ {
int yo = this.ycbcrImage.GetRowYOffset(y); int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y); int co = this.ycbcrImage.GetRowCOffset(y);
@ -1345,17 +1382,15 @@ namespace ImageSharp.Formats
byte cb = this.ycbcrImage.CbChannel[co + (x / scale)]; byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
int index = (y * width) + x;
// Implicit casting FTW // Implicit casting FTW
Color color = new YCbCr(yy, cb, cr); Color color = new YCbCr(yy, cb, cr);
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(color.ToVector4()); packed.PackFromBytes(color.R, color.G, color.B, color.A);
pixels[index] = packed; pixels[x, y] = packed;
} }
}); });
}
image.SetPixels(width, height, pixels);
this.AssignResolution(image); this.AssignResolution(image);
} }
@ -1372,13 +1407,15 @@ namespace ImageSharp.Formats
where TPacked : struct where TPacked : struct
{ {
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor; int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
TColor[] pixels = new TColor[width * height]; image.InitPixels(width, height);
Parallel.For( using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
0, {
height, Parallel.For(
Bootstrapper.Instance.ParallelOptions, 0,
y => height,
Bootstrapper.Instance.ParallelOptions,
y =>
{ {
int yo = this.ycbcrImage.GetRowYOffset(y); int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y); int co = this.ycbcrImage.GetRowCOffset(y);
@ -1389,15 +1426,13 @@ namespace ImageSharp.Formats
byte green = this.ycbcrImage.CbChannel[co + (x / scale)]; byte green = this.ycbcrImage.CbChannel[co + (x / scale)];
byte blue = this.ycbcrImage.CrChannel[co + (x / scale)]; byte blue = this.ycbcrImage.CrChannel[co + (x / scale)];
int index = (y * width) + x;
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(new Color(red, green, blue).ToVector4()); packed.PackFromBytes(red, green, blue, 255);
pixels[x, y] = packed;
pixels[index] = packed;
} }
}); });
}
image.SetPixels(width, height, pixels);
this.AssignResolution(image); this.AssignResolution(image);
} }
@ -1424,29 +1459,27 @@ namespace ImageSharp.Formats
/// <remarks> /// <remarks>
/// 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.
/// </remarks> /// </remarks>
/// <param name="n"> /// <param name="remaining">The remaining bytes in the segment block.</param>
/// The first byte of the current image marker.
/// </param>
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Missing SOF Marker /// Missing SOF Marker
/// SOS has wrong length /// SOS has wrong length
/// </exception> /// </exception>
private void ProcessStartOfScan(int n) private void ProcessStartOfScan(int remaining)
{ {
if (this.componentCount == 0) if (this.componentCount == 0)
{ {
throw new ImageFormatException("Missing SOF marker"); throw new ImageFormatException("Missing SOF marker");
} }
if (n < 6 || 4 + (2 * this.componentCount) < n || n % 2 != 0) if (remaining < 6 || 4 + (2 * this.componentCount) < remaining || remaining % 2 != 0)
{ {
throw new ImageFormatException("SOS has wrong length"); throw new ImageFormatException("SOS has wrong length");
} }
this.ReadFull(this.temp, 0, n); this.ReadFull(this.temp, 0, remaining);
byte lnComp = this.temp[0]; byte scanComponentCount = this.temp[0];
if (n != 4 + (2 * lnComp)) if (remaining != 4 + (2 * scanComponentCount))
{ {
throw new ImageFormatException("SOS length inconsistent with number of components"); throw new ImageFormatException("SOS length inconsistent with number of components");
} }
@ -1454,7 +1487,7 @@ namespace ImageSharp.Formats
Scan[] scan = new Scan[MaxComponents]; Scan[] scan = new Scan[MaxComponents];
int totalHv = 0; int totalHv = 0;
for (int i = 0; i < lnComp; i++) for (int i = 0; i < scanComponentCount; i++)
{ {
// Component selector. // Component selector.
int cs = this.temp[1 + (2 * i)]; int cs = this.temp[1 + (2 * i)];
@ -1488,17 +1521,16 @@ namespace ImageSharp.Formats
} }
} }
totalHv += this.componentArray[compIndex].HorizontalFactor totalHv += this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor;
* this.componentArray[compIndex].VerticalFactor;
scan[i].DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); scan[i].DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4);
if (scan[i].DcTableSelector > maxTh) if (scan[i].DcTableSelector > MaxTh)
{ {
throw new ImageFormatException("Bad DC table selector value"); throw new ImageFormatException("Bad DC table selector value");
} }
scan[i].AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); scan[i].AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f);
if (scan[i].AcTableSelector > maxTh) if (scan[i].AcTableSelector > MaxTh)
{ {
throw new ImageFormatException("Bad AC table selector value"); throw new ImageFormatException("Bad AC table selector value");
} }
@ -1532,17 +1564,17 @@ namespace ImageSharp.Formats
if (this.isProgressive) if (this.isProgressive)
{ {
zigStart = (int)this.temp[1 + (2 * lnComp)]; zigStart = this.temp[1 + (2 * scanComponentCount)];
zigEnd = (int)this.temp[2 + (2 * lnComp)]; zigEnd = this.temp[2 + (2 * scanComponentCount)];
ah = (int)(this.temp[3 + (2 * lnComp)] >> 4); ah = this.temp[3 + (2 * scanComponentCount)] >> 4;
al = (int)(this.temp[3 + (2 * lnComp)] & 0x0f); al = this.temp[3 + (2 * scanComponentCount)] & 0x0f;
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd) if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd)
{ {
throw new ImageFormatException("Bad spectral selection bounds"); throw new ImageFormatException("Bad spectral selection bounds");
} }
if (zigStart != 0 && lnComp != 1) if (zigStart != 0 && scanComponentCount != 1)
{ {
throw new ImageFormatException("Progressive AC coefficients for more than one component"); throw new ImageFormatException("Progressive AC coefficients for more than one component");
} }
@ -1566,7 +1598,7 @@ namespace ImageSharp.Formats
if (this.isProgressive) if (this.isProgressive)
{ {
for (int i = 0; i < lnComp; i++) for (int i = 0; i < scanComponentCount; i++)
{ {
int compIndex = scan[i].Index; int compIndex = scan[i].Index;
if (this.progCoeffs[compIndex] == null) if (this.progCoeffs[compIndex] == null)
@ -1586,7 +1618,7 @@ namespace ImageSharp.Formats
int mcu = 0; int mcu = 0;
byte expectedRst = JpegConstants.Markers.RST0; byte expectedRst = JpegConstants.Markers.RST0;
// b is the decoded coefficients, in natural (not zig-zag) order. // b is the decoded coefficients block, in natural (not zig-zag) order.
Block b; Block b;
int[] dc = new int[MaxComponents]; int[] dc = new int[MaxComponents];
@ -1598,7 +1630,7 @@ namespace ImageSharp.Formats
{ {
for (int mx = 0; mx < mxx; mx++) for (int mx = 0; mx < mxx; mx++)
{ {
for (int i = 0; i < lnComp; i++) for (int i = 0; i < scanComponentCount; i++)
{ {
int compIndex = scan[i].Index; int compIndex = scan[i].Index;
int hi = this.componentArray[compIndex].HorizontalFactor; int hi = this.componentArray[compIndex].HorizontalFactor;
@ -1612,7 +1644,7 @@ namespace ImageSharp.Formats
// For a baseline 32x16 pixel image, the Y blocks visiting order is: // For a baseline 32x16 pixel image, the Y blocks visiting order is:
// 0 1 4 5 // 0 1 4 5
// 2 3 6 7 // 2 3 6 7
// For progressive images, the interleaved scans (those with nComp > 1) // For progressive images, the interleaved scans (those with component count > 1)
// are traversed as above, but non-interleaved scans are traversed left // are traversed as above, but non-interleaved scans are traversed left
// to right, top to bottom: // to right, top to bottom:
// 0 1 2 3 // 0 1 2 3
@ -1629,7 +1661,7 @@ namespace ImageSharp.Formats
// The non-interleaved scans will process only 6 Y blocks: // The non-interleaved scans will process only 6 Y blocks:
// 0 1 2 // 0 1 2
// 3 4 5 // 3 4 5
if (lnComp != 1) if (scanComponentCount != 1)
{ {
bx = (hi * mx) + (j % hi); bx = (hi * mx) + (j % hi);
by = (vi * my) + (j / hi); by = (vi * my) + (j / hi);
@ -1651,7 +1683,7 @@ namespace ImageSharp.Formats
if (ah != 0) if (ah != 0)
{ {
this.Refine(b, this.huffmanTrees[acTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al); this.Refine(b, this.huffmanTrees[AcTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al);
} }
else else
{ {
@ -1661,14 +1693,14 @@ namespace ImageSharp.Formats
zig++; zig++;
// Decode the DC coefficient, as specified in section F.2.2.1. // Decode the DC coefficient, as specified in section F.2.2.1.
byte value = this.DecodeHuffman(this.huffmanTrees[dcTable, scan[i].DcTableSelector]); byte value = this.DecodeHuffman(this.huffmanTrees[DcTable, scan[i].DcTableSelector]);
if (value > 16) if (value > 16)
{ {
throw new ImageFormatException("Excessive DC component"); throw new ImageFormatException("Excessive DC component");
} }
int dcDelta = this.ReceiveExtend(value); int deltaDC = this.ReceiveExtend(value);
dc[compIndex] += dcDelta; dc[compIndex] += deltaDC;
b[0] = dc[compIndex] << al; b[0] = dc[compIndex] << al;
} }
@ -1679,7 +1711,7 @@ namespace ImageSharp.Formats
else else
{ {
// Decode the AC coefficients, as specified in section F.2.2.2. // Decode the AC coefficients, as specified in section F.2.2.2.
Huffman huffv = this.huffmanTrees[acTable, scan[i].AcTableSelector]; Huffman huffv = this.huffmanTrees[AcTable, scan[i].AcTableSelector];
for (; zig <= zigEnd; zig++) for (; zig <= zigEnd; zig++)
{ {
byte value = this.DecodeHuffman(huffv); byte value = this.DecodeHuffman(huffv);
@ -1853,11 +1885,11 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="b"></param> /// <param name="b">The block of coefficients</param>
/// <param name="h"></param> /// <param name="h">The Huffman tree</param>
/// <param name="zigStart"></param> /// <param name="zigStart">The zig-zag start index</param>
/// <param name="zigEnd"></param> /// <param name="zigEnd">The zig-zag end index</param>
/// <param name="delta"></param> /// <param name="delta">The low transform offset</param>
private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta) private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta)
{ {
// Refining a DC component is trivial. // Refining a DC component is trivial.
@ -1897,8 +1929,7 @@ namespace ImageSharp.Formats
this.eobRun = (ushort)(1 << val0); this.eobRun = (ushort)(1 << val0);
if (val0 != 0) if (val0 != 0)
{ {
uint bits = this.DecodeBits(val0); this.eobRun |= (ushort)this.DecodeBits(val0);
this.eobRun |= (ushort)bits;
} }
done = true; done = true;
@ -1943,8 +1974,16 @@ namespace ImageSharp.Formats
} }
} }
// refineNonZeroes refines non-zero entries of b in zig-zag order. If nz >= 0, /// <summary>
// the first nz zero entries are skipped over. /// Refines non-zero entries of b in zig-zag order.
/// If <paramref name="nz"/> >= 0, the first <paramref name="nz"/> zero entries are skipped over.
/// </summary>
/// <param name="b">The block of coefficients</param>
/// <param name="zig">The zig-zag start index</param>
/// <param name="zigEnd">The zig-zag end index</param>
/// <param name="nz">The non-zero entry</param>
/// <param name="delta">The low transform offset</param>
/// <returns>The <see cref="int"/></returns>
private int RefineNonZeroes(Block b, int zig, int zigEnd, int nz, int delta) private int RefineNonZeroes(Block b, int zig, int zigEnd, int nz, int delta)
{ {
for (; zig <= zigEnd; zig++) for (; zig <= zigEnd; zig++)
@ -1983,14 +2022,14 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Makes the image from the buffer. /// Makes the image from the buffer.
/// </summary> /// </summary>
/// <param name="mxx"></param> /// <param name="mxx">The horizontal MCU count</param>
/// <param name="myy"></param> /// <param name="myy">The vertical MCU count</param>
private void MakeImage(int mxx, int myy) private void MakeImage(int mxx, int myy)
{ {
if (this.componentCount == 1) if (this.componentCount == 1)
{ {
GrayImage m = new GrayImage(8 * mxx, 8 * myy); GrayImage gray = new GrayImage(8 * mxx, 8 * myy);
this.grayImage = m.Subimage(0, 0, this.imageWidth, this.imageHeight); this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight);
} }
else else
{ {
@ -2022,8 +2061,8 @@ namespace ImageSharp.Formats
break; break;
} }
YCbCrImage m = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio); YCbCrImage ycbcr = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio);
this.ycbcrImage = m.Subimage(0, 0, this.imageWidth, this.imageHeight); this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight);
if (this.componentCount == 4) if (this.componentCount == 4)
{ {
@ -2093,6 +2132,10 @@ namespace ImageSharp.Formats
{ {
} }
/// <summary>
/// The EOF (End of File exception).
/// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker
/// </summary>
private class EOFException : Exception private class EOFException : Exception
{ {
} }

6
src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs

@ -348,7 +348,7 @@ namespace ImageSharp.Formats
this.WriteProfiles(image); this.WriteProfiles(image);
// Write the quantization tables. // Write the quantization tables.
this.WriteDescreteQuantizationTables(); this.WriteDefineQuantizationTables();
// Write the image dimensions. // Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount); this.WriteStartOfFrame(image.Width, image.Height, componentCount);
@ -661,9 +661,9 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// Writes the Define Quantization Marker and tables. /// Writes the Define Quantization Marker and tables.
/// </summary> /// </summary>
private void WriteDescreteQuantizationTables() private void WriteDefineQuantizationTables()
{ {
int markerlen = 2 + (NQuantIndex * (1 + Block.BlockSize)); int markerlen = 2 + (NQuantIndex * (1 + Block.BlockSize));
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);

2
tests/ImageSharp.Tests/FileTestBase.cs

@ -22,7 +22,7 @@ namespace ImageSharp.Tests
//new TestFile(TestImages.Png.Pd), //new TestFile(TestImages.Png.Pd),
new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Calliphora), new TestFile(TestImages.Jpeg.Calliphora),
//new TestFile(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only new TestFile(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Turtle), new TestFile(TestImages.Jpeg.Turtle),
//new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only //new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only
//new TestFile(TestImages.Jpeg.Progress), // Perf: Enable for local testing only //new TestFile(TestImages.Jpeg.Progress), // Perf: Enable for local testing only

Loading…
Cancel
Save