Browse Source

Optimize and cleanup the jpeg decoder

TODO: Test single vs multiple threaded decoding.
pull/20/head
James Jackson-South 9 years ago
parent
commit
0ed81b051a
  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>Vol</Value>
<Value>pp</Value>
<Value>cmyk</Value>
</CollectionProperty>
</GlobalSettings>
<Analyzers>

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

@ -34,15 +34,24 @@ namespace ImageSharp.Formats
/// </summary>
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>
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
@ -77,6 +86,11 @@ namespace ImageSharp.Formats
/// </summary>
private readonly Block[] quantizationTables;
/// <summary>
/// A temporary buffer for holding pixels
/// </summary>
private readonly byte[] temp;
/// <summary>
/// The byte buffer.
/// </summary>
@ -142,8 +156,14 @@ namespace ImageSharp.Formats
/// </summary>
private bool isJfif;
/// <summary>
/// Whether the image is in CMYK format with an App14 marker
/// </summary>
private bool adobeTransformValid;
/// <summary>
/// The App14 marker color-space
/// </summary>
private byte adobeTransform;
/// <summary>
@ -151,11 +171,6 @@ namespace ImageSharp.Formats
/// </summary>
private ushort eobRun;
/// <summary>
/// A temporary buffer for holding pixels
/// </summary>
private byte[] temp;
/// <summary>
/// The horizontal resolution. Calculated if the image has a JFIF header.
/// </summary>
@ -171,17 +186,17 @@ namespace ImageSharp.Formats
/// </summary>
public JpegDecoderCore()
{
this.huffmanTrees = new Huffman[maxTc + 1, maxTh + 1];
this.quantizationTables = new Block[maxTq + 1];
this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
this.quantizationTables = new Block[MaxTq + 1];
this.temp = new byte[2 * Block.BlockSize];
this.componentArray = new Component[MaxComponents];
this.progCoeffs = new Block[MaxComponents][];
this.bits = new Bits();
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);
}
@ -204,10 +219,8 @@ namespace ImageSharp.Formats
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="stream">The stream, where the image should be
/// decoded from. Cannot be null (Nothing in Visual Basic).</param>
/// <param name="image">The image, where the data should be set to.
/// Cannot be null (Nothing in Visual Basic).</param>
/// <param name="image">The image, where the data should be set to.</param>
/// <param name="stream">The stream, where the image should be.</param>
/// <param name="configOnly">Whether to decode metadata only.</param>
public void Decode<TColor, TPacked>(Image<TColor, TPacked> image, Stream stream, bool configOnly)
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
// length itself, so we subtract 2 to get the number of remaining bytes.
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)
{
throw new ImageFormatException("Short segment length.");
@ -295,7 +308,7 @@ namespace ImageSharp.Formats
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.isProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessSOF(remaining);
this.ProcessStartOfFrameMarker(remaining);
if (configOnly && this.isJfif)
{
return;
@ -309,7 +322,7 @@ namespace ImageSharp.Formats
}
else
{
this.ProcessDht(remaining);
this.ProcessDefineHuffmanTablesMarker(remaining);
}
break;
@ -335,12 +348,12 @@ namespace ImageSharp.Formats
}
else
{
this.ProcessDri(remaining);
this.ProcessDefineRestartIntervalMarker(remaining);
}
break;
case JpegConstants.Markers.APP0:
this.ProcessApp0Marker(remaining);
this.ProcessApplicationHeader(remaining);
break;
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining, image);
@ -457,12 +470,12 @@ namespace ImageSharp.Formats
/// Processes a Define Huffman Table marker, and initializes a huffman
/// struct from its contents. Specified in section B.2.4.2.
/// </summary>
/// <param name="n"></param>
private void ProcessDht(int n)
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineHuffmanTablesMarker(int remaining)
{
while (n > 0)
while (remaining > 0)
{
if (n < 17)
if (remaining < 17)
{
throw new ImageFormatException("DHT has wrong length");
}
@ -470,13 +483,13 @@ namespace ImageSharp.Formats
this.ReadFull(this.temp, 0, 17);
int tc = this.temp[0] >> 4;
if (tc > maxTc)
if (tc > MaxTc)
{
throw new ImageFormatException("Bad Tc value");
}
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");
}
@ -505,8 +518,8 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Huffman table has excessive length");
}
n -= huffman.Length + 17;
if (n < 0)
remaining -= huffman.Length + 17;
if (remaining < 0)
{
throw new ImageFormatException("DHT has wrong length");
}
@ -571,8 +584,7 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Returns the next Huffman-coded value from the bit-stream,
/// decoded according to the given value.
/// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
/// </summary>
/// <param name="huffman">The huffman value</param>
/// <returns>The <see cref="byte"/></returns>
@ -868,15 +880,18 @@ namespace ImageSharp.Formats
}
}
// Specified in section B.2.2.
private void ProcessSOF(int n)
/// <summary>
/// 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)
{
throw new ImageFormatException("multiple SOF markers");
throw new ImageFormatException("Multiple SOF markers");
}
switch (n)
switch (remaining)
{
case 6 + (3 * 1): // Grayscale image.
this.componentCount = 1;
@ -891,7 +906,7 @@ namespace ImageSharp.Formats
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.
if (this.temp[0] != 8)
@ -921,7 +936,7 @@ namespace ImageSharp.Formats
}
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");
}
@ -1053,17 +1068,23 @@ namespace ImageSharp.Formats
}
}
// Specified in section B.2.4.1.
private void ProcessDqt(int n)
/// <summary>
/// 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;
n--;
remaining--;
byte x = this.ReadByte();
byte tq = (byte)(x & 0x0f);
if (tq > maxTq)
if (tq > MaxTq)
{
throw new ImageFormatException("Bad Tq value");
}
@ -1071,13 +1092,13 @@ namespace ImageSharp.Formats
switch (x >> 4)
{
case 0:
if (n < Block.BlockSize)
if (remaining < Block.BlockSize)
{
done = true;
break;
}
n -= Block.BlockSize;
remaining -= Block.BlockSize;
this.ReadFull(this.temp, 0, Block.BlockSize);
for (int i = 0; i < Block.BlockSize; i++)
@ -1087,13 +1108,13 @@ namespace ImageSharp.Formats
break;
case 1:
if (n < 2 * Block.BlockSize)
if (remaining < 2 * Block.BlockSize)
{
done = true;
break;
}
n -= 2 * Block.BlockSize;
remaining -= 2 * Block.BlockSize;
this.ReadFull(this.temp, 0, 2 * Block.BlockSize);
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");
}
}
// Specified in section B.2.4.4.
private void ProcessDri(int n)
/// <summary>
/// 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");
}
@ -1130,16 +1154,20 @@ namespace ImageSharp.Formats
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;
}
this.ReadFull(this.temp, 0, 13);
n -= 13;
remaining -= 13;
// TODO: We should be using constants for this.
this.isJfif = this.temp[0] == 'J' &&
@ -1154,9 +1182,9 @@ namespace ImageSharp.Formats
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>
/// <typeparam name="TColor">The pixel format.</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>
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 TPacked : struct
{
if (n < 6)
if (remaining < 6)
{
this.Skip(n);
this.Skip(remaining);
return;
}
byte[] profile = new byte[n];
this.ReadFull(profile, 0, n);
byte[] profile = new byte[remaining];
this.ReadFull(profile, 0, remaining);
if (profile[0] == 'E' &&
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;
}
this.ReadFull(this.temp, 0, 12);
n -= 12;
remaining -= 12;
if (this.temp[0] == 'A' &&
this.temp[1] == 'd' &&
@ -1212,14 +1246,14 @@ namespace ImageSharp.Formats
this.adobeTransform = this.temp[11];
}
if (n > 0)
if (remaining > 0)
{
this.Skip(n);
this.Skip(remaining);
}
}
/// <summary>
/// Converts the image from the original Cmyk image pixels.
/// Converts the image from the original CMYK image pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</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;
TColor[] pixels = new TColor[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);
image.InitPixels(width, height);
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];
byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
int index = (y * width) + x;
// Implicit casting FTW
Color color = new YCbCr(yy, cb, cr);
int keyline = 255 - this.blackPixels[(y * this.blackStride) + x];
Color final = new Cmyk(color.R / 255F, color.G / 255F, color.B / 255F, keyline / 255F);
TColor packed = default(TColor);
packed.PackFromVector4(final.ToVector4());
pixels[index] = packed;
}
});
for (int x = 0; x < width; x++)
{
byte yy = this.ycbcrImage.YChannel[yo + x];
byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
// Implicit casting FTW
Color color = new YCbCr(yy, cb, cr);
int keyline = 255 - this.blackPixels[(y * this.blackStride) + x];
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 TPacked : struct
{
TColor[] pixels = new TColor[width * height];
image.InitPixels(width, height);
Parallel.For(
0,
height,
Bootstrapper.Instance.ParallelOptions,
y =>
{
int yoff = this.grayImage.GetRowOffset(y);
for (int x = 0; x < width; x++)
using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{
Parallel.For(
0,
height,
Bootstrapper.Instance.ParallelOptions,
y =>
{
int offset = (y * width) + x;
byte rgb = this.grayImage.Pixels[yoff + x];
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.PackFromVector4(new Color(rgb, rgb, rgb).ToVector4());
pixels[offset] = packed;
}
});
TColor packed = default(TColor);
packed.PackFromBytes(rgb, rgb, rgb, 255);
pixels[x, y] = packed;
}
});
}
image.SetPixels(width, height, pixels);
this.AssignResolution(image);
}
@ -1327,14 +1363,15 @@ namespace ImageSharp.Formats
where TPacked : struct
{
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
image.InitPixels(width, height);
TColor[] pixels = new TColor[width * height];
Parallel.For(
0,
height,
Bootstrapper.Instance.ParallelOptions,
y =>
using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{
Parallel.For(
0,
height,
Bootstrapper.Instance.ParallelOptions,
y =>
{
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
@ -1345,17 +1382,15 @@ namespace ImageSharp.Formats
byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
int index = (y * width) + x;
// Implicit casting FTW
Color color = new YCbCr(yy, cb, cr);
TColor packed = default(TColor);
packed.PackFromVector4(color.ToVector4());
pixels[index] = packed;
packed.PackFromBytes(color.R, color.G, color.B, color.A);
pixels[x, y] = packed;
}
});
}
image.SetPixels(width, height, pixels);
this.AssignResolution(image);
}
@ -1372,13 +1407,15 @@ namespace ImageSharp.Formats
where TPacked : struct
{
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
TColor[] pixels = new TColor[width * height];
image.InitPixels(width, height);
Parallel.For(
0,
height,
Bootstrapper.Instance.ParallelOptions,
y =>
using (PixelAccessor<TColor, TPacked> pixels = image.Lock())
{
Parallel.For(
0,
height,
Bootstrapper.Instance.ParallelOptions,
y =>
{
int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y);
@ -1389,15 +1426,13 @@ namespace ImageSharp.Formats
byte green = this.ycbcrImage.CbChannel[co + (x / scale)];
byte blue = this.ycbcrImage.CrChannel[co + (x / scale)];
int index = (y * width) + x;
TColor packed = default(TColor);
packed.PackFromVector4(new Color(red, green, blue).ToVector4());
pixels[index] = packed;
packed.PackFromBytes(red, green, blue, 255);
pixels[x, y] = packed;
}
});
}
image.SetPixels(width, height, pixels);
this.AssignResolution(image);
}
@ -1424,29 +1459,27 @@ namespace ImageSharp.Formats
/// <remarks>
/// TODO: This also needs some significant refactoring to follow a more OO format.
/// </remarks>
/// <param name="n">
/// The first byte of the current image marker.
/// </param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Missing SOF Marker
/// SOS has wrong length
/// </exception>
private void ProcessStartOfScan(int n)
private void ProcessStartOfScan(int remaining)
{
if (this.componentCount == 0)
{
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");
}
this.ReadFull(this.temp, 0, n);
byte lnComp = this.temp[0];
this.ReadFull(this.temp, 0, remaining);
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");
}
@ -1454,7 +1487,7 @@ namespace ImageSharp.Formats
Scan[] scan = new Scan[MaxComponents];
int totalHv = 0;
for (int i = 0; i < lnComp; i++)
for (int i = 0; i < scanComponentCount; i++)
{
// Component selector.
int cs = this.temp[1 + (2 * i)];
@ -1488,17 +1521,16 @@ namespace ImageSharp.Formats
}
}
totalHv += this.componentArray[compIndex].HorizontalFactor
* this.componentArray[compIndex].VerticalFactor;
totalHv += this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor;
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");
}
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");
}
@ -1532,17 +1564,17 @@ namespace ImageSharp.Formats
if (this.isProgressive)
{
zigStart = (int)this.temp[1 + (2 * lnComp)];
zigEnd = (int)this.temp[2 + (2 * lnComp)];
ah = (int)(this.temp[3 + (2 * lnComp)] >> 4);
al = (int)(this.temp[3 + (2 * lnComp)] & 0x0f);
zigStart = this.temp[1 + (2 * scanComponentCount)];
zigEnd = this.temp[2 + (2 * scanComponentCount)];
ah = this.temp[3 + (2 * scanComponentCount)] >> 4;
al = this.temp[3 + (2 * scanComponentCount)] & 0x0f;
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd)
{
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");
}
@ -1566,7 +1598,7 @@ namespace ImageSharp.Formats
if (this.isProgressive)
{
for (int i = 0; i < lnComp; i++)
for (int i = 0; i < scanComponentCount; i++)
{
int compIndex = scan[i].Index;
if (this.progCoeffs[compIndex] == null)
@ -1586,7 +1618,7 @@ namespace ImageSharp.Formats
int mcu = 0;
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;
int[] dc = new int[MaxComponents];
@ -1598,7 +1630,7 @@ namespace ImageSharp.Formats
{
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 hi = this.componentArray[compIndex].HorizontalFactor;
@ -1612,7 +1644,7 @@ namespace ImageSharp.Formats
// For a baseline 32x16 pixel image, the Y blocks visiting order is:
// 0 1 4 5
// 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
// to right, top to bottom:
// 0 1 2 3
@ -1629,7 +1661,7 @@ namespace ImageSharp.Formats
// The non-interleaved scans will process only 6 Y blocks:
// 0 1 2
// 3 4 5
if (lnComp != 1)
if (scanComponentCount != 1)
{
bx = (hi * mx) + (j % hi);
by = (vi * my) + (j / hi);
@ -1651,7 +1683,7 @@ namespace ImageSharp.Formats
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
{
@ -1661,14 +1693,14 @@ namespace ImageSharp.Formats
zig++;
// 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)
{
throw new ImageFormatException("Excessive DC component");
}
int dcDelta = this.ReceiveExtend(value);
dc[compIndex] += dcDelta;
int deltaDC = this.ReceiveExtend(value);
dc[compIndex] += deltaDC;
b[0] = dc[compIndex] << al;
}
@ -1679,7 +1711,7 @@ namespace ImageSharp.Formats
else
{
// 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++)
{
byte value = this.DecodeHuffman(huffv);
@ -1853,11 +1885,11 @@ namespace ImageSharp.Formats
/// <summary>
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
/// </summary>
/// <param name="b"></param>
/// <param name="h"></param>
/// <param name="zigStart"></param>
/// <param name="zigEnd"></param>
/// <param name="delta"></param>
/// <param name="b">The block of coefficients</param>
/// <param name="h">The Huffman tree</param>
/// <param name="zigStart">The zig-zag start index</param>
/// <param name="zigEnd">The zig-zag end index</param>
/// <param name="delta">The low transform offset</param>
private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta)
{
// Refining a DC component is trivial.
@ -1897,8 +1929,7 @@ namespace ImageSharp.Formats
this.eobRun = (ushort)(1 << val0);
if (val0 != 0)
{
uint bits = this.DecodeBits(val0);
this.eobRun |= (ushort)bits;
this.eobRun |= (ushort)this.DecodeBits(val0);
}
done = true;
@ -1943,8 +1974,16 @@ namespace ImageSharp.Formats
}
}
// refineNonZeroes refines non-zero entries of b in zig-zag order. If nz >= 0,
// the first nz zero entries are skipped over.
/// <summary>
/// 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)
{
for (; zig <= zigEnd; zig++)
@ -1983,14 +2022,14 @@ namespace ImageSharp.Formats
/// <summary>
/// Makes the image from the buffer.
/// </summary>
/// <param name="mxx"></param>
/// <param name="myy"></param>
/// <param name="mxx">The horizontal MCU count</param>
/// <param name="myy">The vertical MCU count</param>
private void MakeImage(int mxx, int myy)
{
if (this.componentCount == 1)
{
GrayImage m = new GrayImage(8 * mxx, 8 * myy);
this.grayImage = m.Subimage(0, 0, this.imageWidth, this.imageHeight);
GrayImage gray = new GrayImage(8 * mxx, 8 * myy);
this.grayImage = gray.Subimage(0, 0, this.imageWidth, this.imageHeight);
}
else
{
@ -2022,8 +2061,8 @@ namespace ImageSharp.Formats
break;
}
YCbCrImage m = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio);
this.ycbcrImage = m.Subimage(0, 0, this.imageWidth, this.imageHeight);
YCbCrImage ycbcr = new YCbCrImage(8 * h0 * mxx, 8 * v0 * myy, ratio);
this.ycbcrImage = ycbcr.Subimage(0, 0, this.imageWidth, this.imageHeight);
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
{
}

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

@ -348,7 +348,7 @@ namespace ImageSharp.Formats
this.WriteProfiles(image);
// Write the quantization tables.
this.WriteDescreteQuantizationTables();
this.WriteDefineQuantizationTables();
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount);
@ -661,9 +661,9 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Writes the Define Quantization Marker and tables.
/// Writes the Define Quantization Marker and tables.
/// </summary>
private void WriteDescreteQuantizationTables()
private void WriteDefineQuantizationTables()
{
int markerlen = 2 + (NQuantIndex * (1 + Block.BlockSize));
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.Jpeg.Floorplan), // Perf: Enable for local testing only
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.Fb), // Perf: Enable for local testing only
//new TestFile(TestImages.Jpeg.Progress), // Perf: Enable for local testing only

Loading…
Cancel
Save