From bcfc74a182d8ecee9daa97992355aa822e303a6f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 3 Nov 2016 01:06:55 +1100 Subject: [PATCH] Optimize and cleanup the jpeg decoder TODO: Test single vs multiple threaded decoding. --- Settings.StyleCop | 1 + src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 411 ++++++++++-------- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 6 +- tests/ImageSharp.Tests/FileTestBase.cs | 2 +- 4 files changed, 232 insertions(+), 188 deletions(-) diff --git a/Settings.StyleCop b/Settings.StyleCop index 0eabc6cf3..b7a5355e0 100644 --- a/Settings.StyleCop +++ b/Settings.StyleCop @@ -31,6 +31,7 @@ ss Vol pp + cmyk diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 39a24db00..ede58a5f4 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -34,15 +34,24 @@ namespace ImageSharp.Formats /// 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; + /// + /// The maximum number of quantization tables + /// + private const int MaxTq = 3; - private const int dcTable = 0; + /// + /// The DC table index + /// + private const int DcTable = 0; - private const int acTable = 1; + /// + /// The AC table index + /// + private const int AcTable = 1; /// /// Unzig maps from the zigzag ordering to the natural ordering. For example, @@ -77,6 +86,11 @@ namespace ImageSharp.Formats /// private readonly Block[] quantizationTables; + /// + /// A temporary buffer for holding pixels + /// + private readonly byte[] temp; + /// /// The byte buffer. /// @@ -142,8 +156,14 @@ namespace ImageSharp.Formats /// private bool isJfif; + /// + /// Whether the image is in CMYK format with an App14 marker + /// private bool adobeTransformValid; + /// + /// The App14 marker color-space + /// private byte adobeTransform; /// @@ -151,11 +171,6 @@ namespace ImageSharp.Formats /// private ushort eobRun; - /// - /// A temporary buffer for holding pixels - /// - private byte[] temp; - /// /// The horizontal resolution. Calculated if the image has a JFIF header. /// @@ -171,17 +186,17 @@ namespace ImageSharp.Formats /// 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 /// /// The pixel format. /// The packed format. uint, long, float. - /// The stream, where the image should be - /// decoded from. Cannot be null (Nothing in Visual Basic). - /// The image, where the data should be set to. - /// Cannot be null (Nothing in Visual Basic). + /// The image, where the data should be set to. + /// The stream, where the image should be. /// Whether to decode metadata only. public void Decode(Image image, Stream stream, bool configOnly) where TColor : struct, IPackedPixel @@ -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. /// - /// - private void ProcessDht(int n) + /// The remaining bytes in the segment block. + 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 } /// - /// 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. /// /// The huffman value /// The @@ -868,15 +880,18 @@ namespace ImageSharp.Formats } } - // Specified in section B.2.2. - private void ProcessSOF(int n) + /// + /// Processes the Start of Frame marker. Specified in section B.2.2. + /// + /// The remaining bytes in the segment block. + 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) + /// + /// 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 + /// + 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) + /// + /// 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 (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) + /// + /// Processes the application header containing the JFIF identifier plus extra data. + /// + /// The remaining bytes in the segment block. + 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 /// /// The pixel format. /// The packed format. uint, long, float. - /// The position in the stream. + /// The remaining bytes in the segment block. /// The image. - private void ProcessApp1Marker(int n, Image image) + private void ProcessApp1Marker(int remaining, Image image) where TColor : struct, IPackedPixel 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) + /// + /// 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 (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); } } /// - /// Converts the image from the original Cmyk image pixels. + /// Converts the image from the original CMYK image pixels. /// /// The pixel format. /// The packed format. uint, long, float. @@ -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 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 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 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 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 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 /// /// TODO: This also needs some significant refactoring to follow a more OO format. /// - /// - /// The first byte of the current image marker. - /// + /// The remaining bytes in the segment block. /// /// Missing SOF Marker /// SOS has wrong length /// - 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 /// /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// - /// - /// - /// - /// - /// + /// The block of coefficients + /// The Huffman tree + /// The zig-zag start index + /// The zig-zag end index + /// The low transform offset 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. + /// + /// 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 + /// The private int RefineNonZeroes(Block b, int zig, int zigEnd, int nz, int delta) { for (; zig <= zigEnd; zig++) @@ -1983,14 +2022,14 @@ namespace ImageSharp.Formats /// /// Makes the image from the buffer. /// - /// - /// + /// The horizontal MCU count + /// The vertical MCU count 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 { } + /// + /// The EOF (End of File exception). + /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker + /// private class EOFException : Exception { } diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index a5c9be547..4e35ed8de 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/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 } /// - /// Writes the Define Quantization Marker and tables. + /// Writes the Define Quantization Marker and tables. /// - private void WriteDescreteQuantizationTables() + private void WriteDefineQuantizationTables() { int markerlen = 2 + (NQuantIndex * (1 + Block.BlockSize)); this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 855c3a7af..2b53d0253 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/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