From 00712a277e6966d41009868589dd1bcf061afdfa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 28 Jun 2017 02:23:43 +1000 Subject: [PATCH] Can now decode many images --- .../Formats/Jpeg/Port/Components/Adobe.cs | 40 +- .../Jpeg/Port/Components/FileMarker.cs | 6 + .../Jpeg/Port/Components/JpegPixelArea.cs | 146 ++++++ .../Jpeg/Port/Components/ScanDecoder.cs | 496 +++++++++++++----- .../Jpeg/Port/Components/YCbCrToRgbTables.cs | 128 +++++ .../Formats/Jpeg/Port/JpegDecoderCore.cs | 301 ++++++++--- tests/ImageSharp.Tests/TestFile.cs | 19 +- .../Formats/Jpg/baseline/ycck - Copy.jpg | 3 + 8 files changed, 935 insertions(+), 204 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs index 130b7bdb3..6ef128ccb 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs @@ -6,10 +6,12 @@ // ReSharper disable InconsistentNaming namespace ImageSharp.Formats.Jpeg.Port.Components { + using System; + /// /// Provides information about the Adobe marker segment /// - internal struct Adobe + internal struct Adobe : IEquatable { /// /// The DCT Encode Version @@ -34,5 +36,39 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// 02 : YCCK /// public byte ColorTransform; + + /// + public bool Equals(Adobe other) + { + return this.DCTEncodeVersion == other.DCTEncodeVersion + && this.APP14Flags0 == other.APP14Flags0 + && this.APP14Flags1 == other.APP14Flags1 + && this.ColorTransform == other.ColorTransform; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is Adobe && this.Equals((Adobe)obj); + } + + /// + public override int GetHashCode() + { + unchecked + { + // TODO: Merge and use HashCodeHelpers + int hashCode = this.DCTEncodeVersion.GetHashCode(); + hashCode = (hashCode * 397) ^ this.APP14Flags0.GetHashCode(); + hashCode = (hashCode * 397) ^ this.APP14Flags1.GetHashCode(); + hashCode = (hashCode * 397) ^ this.ColorTransform.GetHashCode(); + return hashCode; + } + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs index 39adba5cd..eaf3dafb9 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs @@ -49,5 +49,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// Gets the position of the marker within a stream /// public long Position { get; } + + /// + public override string ToString() + { + return this.Marker.ToString("X"); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs new file mode 100644 index 000000000..eafc6c33c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs @@ -0,0 +1,146 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using ImageSharp.Memory; + + /// + /// Represents a section of the jpeg component data laid out in pixel order. + /// + internal struct JpegPixelArea : IDisposable + { + private readonly int imageWidth; + + private readonly int imageHeight; + + private Buffer componentData; + + private int rowStride; + + /// + /// Initializes a new instance of the struct. + /// + /// The image width + /// The image height + /// The number of components + public JpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) + { + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.Width = 0; + this.Height = 0; + this.NumberOfComponents = numberOfComponents; + this.componentData = null; + this.rowStride = 0; + } + + /// + /// Gets the number of components + /// + public int NumberOfComponents { get; } + + /// + /// Gets the width + /// + public int Width { get; private set; } + + /// + /// Gets the height + /// + public int Height { get; private set; } + + /// + /// Organsizes the decoded jpeg components into a linear array ordered by component. + /// This must be called before attempting to retrieve the data. + /// + /// The jpeg component blocks + /// The pixel area width + /// The pixel area height + public void LinearizeBlockData(ComponentBlocks components, int width, int height) + { + this.Width = width; + this.Height = height; + int numberOfComponents = this.NumberOfComponents; + this.rowStride = width * numberOfComponents; + + float scaleX = this.imageWidth / (float)width; + float scaleY = this.imageHeight / (float)height; + this.componentData = new Buffer(width * height * numberOfComponents); + const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs + + using (var xScaleBlockOffset = new Buffer(width)) + { + for (int i = 0; i < numberOfComponents; i++) + { + ref Component component = ref components.Components[i]; + float componentScaleX = component.ScaleX * scaleX; + float componentScaleY = component.ScaleY * scaleY; + int offset = i; + Buffer output = component.Output; + int blocksPerScanline = (component.BlocksPerLine + 1) << 3; + + // Precalculate the xScaleBlockOffset + int j; + for (int x = 0; x < width; x++) + { + j = 0 | (int)(x * componentScaleX); + xScaleBlockOffset[x] = (int)((j & Mask3Lsb) << 3) | (j & 7); + } + + // Linearize the blocks of the component + for (int y = 0; y < height; y++) + { + j = 0 | (int)(y * componentScaleY); + int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3); + for (int x = 0; x < width; x++) + { + this.componentData[offset] = (byte)output[index + xScaleBlockOffset[x]]; + offset += numberOfComponents; + } + } + } + } + } + + /// + /// Gets a representing the row 'y' beginning from the the first byte on that row. + /// + /// The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the pixel area. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + this.CheckCoordinates(y); + return this.componentData.Slice(y * this.rowStride, this.rowStride); + } + + /// + public void Dispose() + { + this.componentData?.Dispose(); + this.componentData = null; + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the row. Must be greater than zero and less than the height of the area. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int y) + { + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the area bounds."); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 2ec9ea905..da7bb52a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -12,16 +12,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components using System.IO; using System.Runtime.CompilerServices; - /// - /// Encapsulates a decode method. TODO: This may well be a bottleneck - /// - /// The component - /// The offset - /// The DC Huffman tables - /// The AC Huffman tables - /// The input stream - internal delegate void DecodeAction(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream); - /// /// Provides the means to decode a spectral scan /// @@ -37,6 +27,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int eobrun; + private int compIndex; + private int successiveState; private int successiveACState; @@ -58,7 +50,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// The spectral selection end /// The successive approximation bit high end /// The successive approximation bit low end - public void DecodeScan( + /// The representing the processed length in bytes + public long DecodeScan( Frame frame, Stream stream, HuffmanTables dcHuffmanTables, @@ -72,179 +65,438 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int successivePrev, int successive) { + this.compIndex = componentIndex; this.specStart = spectralStart; this.specEnd = spectralEnd; this.successiveState = successive; bool progressive = frame.Progressive; int mcusPerLine = frame.McusPerLine; + long startPosition = stream.Position; - // TODO: Delegate action will not be fast - DecodeAction decodeFn; + int mcu = 0; + int mcuExpected; + if (componentsLength == 1) + { + mcuExpected = components[this.compIndex].BlocksPerLine * components[this.compIndex].BlocksPerColumn; + } + else + { + mcuExpected = mcusPerLine * frame.McusPerColumn; + } - if (progressive) + FileMarker fileMarker; + while (mcu < mcuExpected) { - if (this.specStart == 0) + // Reset interval stuff + int mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; + for (int i = 0; i < components.Length; i++) { - if (successivePrev == 0) - { - decodeFn = this.DecodeDCFirst; - } - else - { - decodeFn = this.DecodeDCSuccessive; - } + ref FrameComponent c = ref components[i]; + c.Pred = 0; + } + + this.eobrun = 0; - Debug.WriteLine(successivePrev == 0 ? "decodeDCFirst" : "decodeDCSuccessive"); + if (!progressive) + { + this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); } else { - if (successivePrev == 0) + if (this.specStart == 0) { - decodeFn = this.DecodeACFirst; + if (successivePrev == 0) + { + this.DecodeScanDCFirst(dcHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } + else + { + this.DecodeScanDCSuccessive(components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } } else { - decodeFn = this.DecodeACSuccessive; + if (successivePrev == 0) + { + this.DecodeScanACFirst(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } + else + { + this.DecodeScanACSuccessive(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); + } } + } + + // Find marker + this.bitsCount = 0; + long position = stream.Position; + fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); + + // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past + // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. + if (fileMarker.Invalid) + { +#if DEBUG + Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker}"); + +#endif + // stream.Position = fileMarker.Position; + } + + ushort marker = fileMarker.Marker; + + // if (marker <= 0xFF00) + // { + // throw new ImageFormatException("Marker was not found"); + // } + + // RSTn We've alread read the bytes and altered the position so no need to skip + if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) + { + continue; + } - Debug.WriteLine(successivePrev == 0 ? "decodeACFirst" : "decodeACSuccessive"); + if (!fileMarker.Invalid) + { + // We've found a valid marker. + // Rewind the stream to the position of the marker and beak + stream.Position = fileMarker.Position; + break; } + + // Rewind the stream + stream.Position = position; } - else + + fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); + + // Some images include more Scan blocks than expected, skip past those and + // attempt to find the next valid marker (fixes issue8182.pdf) in original code. + if (fileMarker.Invalid) { - decodeFn = this.DecodeBaseline; +#if DEBUG + Debug.WriteLine($"DecodeScan - Unexpected MCU data, next marker is: {fileMarker.Marker}"); +#endif + stream.Position = fileMarker.Position; } - int mcu = 0; - int mcuExpected; + return stream.Position - startPosition; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetBlockBufferOffset(FrameComponent component, int row, int col) + { + return 64 * (((component.BlocksPerLine + 1) * row) + col); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanBaseline( + HuffmanTables dcHuffmanTables, + HuffmanTables acHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { if (componentsLength == 1) { - mcuExpected = components[componentIndex].BlocksPerLine * components[componentIndex].BlocksPerColumn; + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcu, stream); + mcu++; + } } else { - mcuExpected = mcusPerLine * frame.McusPerColumn; - } + for (int n = 0; n < mcuToRead; n++) + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + } + } + } - Debug.WriteLine("mcuExpected = " + mcuExpected); + mcu++; + } + } + } - // FileMarker fileMarker; - while (mcu < mcuExpected) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanDCFirst( + HuffmanTables dcHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) { - // Reset interval - int mcuToRead = resetInterval > 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; - - // TODO: We might just be able to loop here. - // if (componentsLength == 1) - // { - // ref FrameComponent c = ref components[componentIndex]; - // c.Pred = 0; - // } - // else - // { - for (int i = 0; i < components.Length; i++) + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) { - ref FrameComponent c = ref components[i]; - c.Pred = 0; + this.DecodeBlockDCFirst(dcHuffmanTables, ref component, mcu, stream); + mcu++; } + } + else + { + for (int n = 0; n < mcuToRead; n++) + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuDCFirst(dcHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + } + } + } - // } - this.eobrun = 0; + mcu++; + } + } + } - if (componentsLength == 1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanDCSuccessive( + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) + { + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockDCSuccessive(ref component, mcu, stream); + mcu++; + } + } + else + { + for (int n = 0; n < mcuToRead; n++) { - ref FrameComponent component = ref components[componentIndex]; - for (int n = 0; n < mcuToRead; n++) + for (int i = 0; i < componentsLength; i++) { - DecodeBlock(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcu, stream); - mcu++; + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuDCSuccessive(ref component, mcusPerLine, mcu, j, k, stream); + } + } } + + mcu++; } - else + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanACFirst( + HuffmanTables acHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) + { + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockACFirst(acHuffmanTables, ref component, mcu, stream); + mcu++; + } + } + else + { + for (int n = 0; n < mcuToRead; n++) { - for (int n = 0; n < mcuToRead; n++) + for (int i = 0; i < componentsLength; i++) { - for (int i = 0; i < componentsLength; i++) + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) { - ref FrameComponent component = ref components[i]; - int h = component.HorizontalFactor; - int v = component.VerticalFactor; - for (int j = 0; j < v; j++) + for (int k = 0; k < h; k++) { - for (int k = 0; k < h; k++) - { - DecodeMcu(dcHuffmanTables, acHuffmanTables, ref component, decodeFn, mcusPerLine, mcu, j, k, stream); - } + this.DecodeMcuACFirst(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } - - mcu++; } + + mcu++; } + } + } - // Find marker - // this.bitsCount = 0; - // // TODO: We need to make sure we are not overwriting anything here. - // fileMarker = JpegDecoderCore.FindNextFileMarker(stream); - // // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past - // // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. - // if (fileMarker.Invalid) - // { - // #if DEBUG - // Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); - // #endif - // } - // ushort marker = fileMarker.Marker; - // if (marker <= 0xFF00) - // { - // throw new ImageFormatException("Marker was not found"); - // } - // if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) - // { - // // RSTx - // stream.Skip(2); - // } - // else - // { - // break; - // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeScanACSuccessive( + HuffmanTables acHuffmanTables, + FrameComponent[] components, + int componentsLength, + int mcusPerLine, + int mcuToRead, + ref int mcu, + Stream stream) + { + if (componentsLength == 1) + { + ref FrameComponent component = ref components[this.compIndex]; + for (int n = 0; n < mcuToRead; n++) + { + this.DecodeBlockACSuccessive(acHuffmanTables, ref component, mcu, stream); + mcu++; + } + } + else + { + for (int n = 0; n < mcuToRead; n++) + { + for (int i = 0; i < componentsLength; i++) + { + ref FrameComponent component = ref components[i]; + int h = component.HorizontalFactor; + int v = component.VerticalFactor; + for (int j = 0; j < v; j++) + { + for (int k = 0; k < h; k++) + { + this.DecodeMcuACSuccessive(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); + } + } + } + + mcu++; + } } + } - // fileMarker = JpegDecoderCore.FindNextFileMarker(stream); - // // Some images include more Scan blocks than expected, skip past those and - // // attempt to find the next valid marker (fixes issue8182.pdf) in original code. - // if (fileMarker.Invalid) - // { - // #if DEBUG - // Debug.WriteLine("DecodeScan - Unexpected MCU data, next marker is: " + fileMarker.Marker.ToString("X")); - // #endif - // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeBlockBaseline(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeBaseline(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetBlockBufferOffset(ref FrameComponent component, int row, int col) + private void DecodeMcuBaseline(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { - return 64 * (((component.BlocksPerLine + 1) * row) + col); + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeBaseline(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeMcu(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeBlockDCFirst(HuffmanTables dcHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCFirst(ref component, offset, dcHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuDCFirst(HuffmanTables dcHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = (mcu / mcusPerLine) | 0; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * component.VerticalFactor) + row; int blockCol = (mcuCol * component.HorizontalFactor) + col; - int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCFirst(ref component, offset, dcHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeBlock(HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, ref FrameComponent component, DecodeAction decode, int mcu, Stream stream) + private void DecodeBlockDCSuccessive(ref FrameComponent component, int mcu, Stream stream) { int blockRow = (mcu / component.BlocksPerLine) | 0; int blockCol = mcu % component.BlocksPerLine; - int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - decode(ref component, offset, dcHuffmanTables, acHuffmanTables, stream); + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCSuccessive(ref component, offset, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuDCSuccessive(ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + { + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeDCSuccessive(ref component, offset, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeBlockACFirst(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACFirst(ref component, offset, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuACFirst(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + { + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACFirst(ref component, offset, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeBlockACSuccessive(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcu, Stream stream) + { + int blockRow = (mcu / component.BlocksPerLine) | 0; + int blockCol = mcu % component.BlocksPerLine; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACSuccessive(ref component, offset, acHuffmanTables, stream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecodeMcuACSuccessive(HuffmanTables acHuffmanTables, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + { + int mcuRow = (mcu / mcusPerLine) | 0; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * component.VerticalFactor) + row; + int blockCol = (mcuCol * component.HorizontalFactor) + col; + int offset = GetBlockBufferOffset(component, blockRow, blockCol); + this.DecodeACSuccessive(ref component, offset, acHuffmanTables, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -257,10 +509,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.bitsData = stream.ReadByte(); - if (this.bitsData == JpegConstants.Markers.Prefix) + if (this.bitsData == 0xFF) { int nextByte = stream.ReadByte(); - if (nextByte > 0) + if (nextByte != 0) { throw new ImageFormatException($"Unexpected marker {(this.bitsData << 8) | nextByte}"); } @@ -354,7 +606,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; @@ -362,13 +614,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeDCSuccessive(ref FrameComponent component, int offset, Stream stream) { component.BlockData[offset] |= (short)(this.ReadBit(stream) << this.successiveState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeACFirst(ref FrameComponent component, int offset, HuffmanTables acHuffmanTables, Stream stream) { if (this.eobrun > 0) { @@ -404,7 +656,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACSuccessive(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) + private void DecodeACSuccessive(ref FrameComponent component, int offset, HuffmanTables acHuffmanTables, Stream stream) { int k = this.specStart; int e = this.specEnd; @@ -439,7 +691,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.successiveACNextValue = this.ReceiveAndExtend(s, stream); - this.successiveACState = r != 0 ? 2 : 3; + this.successiveACState = r > 0 ? 2 : 3; } continue; diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs new file mode 100644 index 000000000..02397e1d7 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs @@ -0,0 +1,128 @@ +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System.Runtime.CompilerServices; + using ImageSharp.PixelFormats; + + /// + /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace. + /// Methods to build the tables are based on libjpeg implementation. + /// + internal struct YCbCrToRgbTables + { + /// + /// The red red-chrominance table + /// + public static int[] CrRTable = new int[256]; + + /// + /// The blue blue-chrominance table + /// + public static int[] CbBTable = new int[256]; + + /// + /// The green red-chrominance table + /// + public static int[] CrGTable = new int[256]; + + /// + /// The green blue-chrominance table + /// + public static int[] CbGTable = new int[256]; + + // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places. + private const int ScaleBits = 16; + + private const int Half = 1 << (ScaleBits - 1); + + private const int MinSample = 0; + + private const int HalfSample = 128; + + private const int MaxSample = 255; + + /// + /// Initializes the YCbCr tables + /// + public static void Create() + { + for (int i = 0, x = -128; i <= 255; i++, x++) + { + // i is the actual input pixel value, in the range 0..255 + // The Cb or Cr value we are thinking of is x = i - 128 + // Cr=>R value is nearest int to 1.402 * x + CrRTable[i] = RightShift((Fix(1.402F) * x) + Half); + + // Cb=>B value is nearest int to 1.772 * x + CbBTable[i] = RightShift((Fix(1.772F) * x) + Half); + + // Cr=>G value is scaled-up -0.714136286 + CrGTable[i] = (-Fix(0.714136286F)) * x; + + // Cb => G value is scaled - up - 0.344136286 * x + // We also add in Half so that need not do it in inner loop + CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half; + } + } + + /// + /// Optimized method to pack bytes to the image from the YCbCr color space. + /// + /// The pixel format. + /// The packed pixel. + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PackYCbCr(ref TPixel packed, byte y, byte cb, byte cr) + where TPixel : struct, IPixel + { + byte r = (byte)(y + CrRTable[cr]).Clamp(0, 255); + + // The values for the G calculation are left scaled up, since we must add them together before rounding. + byte g = (byte)(y + RightShift(CbGTable[cb] + CrGTable[cr])).Clamp(0, 255); + + byte b = (byte)(y + CbBTable[cb]).Clamp(0, 255); + + packed.PackFromRgba32(new Rgba32(r, g, b, 255)); + } + + /// + /// Optimized method to pack bytes to the image from the YccK color space. + /// + /// The pixel format. + /// The packed pixel. + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + /// The keyline component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PackYccK(ref TPixel packed, byte y, byte cb, byte cr, byte k) + where TPixel : struct, IPixel + { + int c = (MaxSample - (y + CrRTable[cr])).Clamp(0, 255); + + // The values for the G calculation are left scaled up, since we must add them together before rounding. + int m = (MaxSample - (y + RightShift(CbGTable[cb] + CrGTable[cr]))).Clamp(0, 255); + + int cy = (MaxSample - (y + CbBTable[cb])).Clamp(0, 255); + + byte r = (byte)((c * k) / MaxSample); + byte g = (byte)((m * k) / MaxSample); + byte b = (byte)((cy * k) / MaxSample); + + packed.PackFromRgba32(new Rgba32(r, g, b, MaxSample)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Fix(float x) + { + return (int)((x * (1L << ScaleBits)) + 0.5F); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RightShift(int x) + { + return x >> ScaleBits; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index b608b4951..2c2cd57a5 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -49,11 +49,13 @@ namespace ImageSharp.Formats.Jpeg.Port private ComponentBlocks components; + private JpegPixelArea pixelArea; + private ushort resetInterval; - private int width; + private int imageWidth; - private int height; + private int imageHeight; private int numComponents; @@ -67,6 +69,14 @@ namespace ImageSharp.Formats.Jpeg.Port /// private Adobe adobe; + /// + /// Initializes static members of the class. + /// + static JpegDecoderCore() + { + YCbCrToRgbTables.Create(); + } + /// /// Initializes a new instance of the class. /// @@ -91,11 +101,12 @@ namespace ImageSharp.Formats.Jpeg.Port public static FileMarker FindNextFileMarkerNew(Stream stream) { byte[] marker = new byte[2]; + int value = stream.Read(marker, 0, 2); if (value == 0) { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); } if (marker[0] == JpegConstants.Markers.Prefix) @@ -107,14 +118,16 @@ namespace ImageSharp.Formats.Jpeg.Port int suffix = stream.ReadByte(); if (suffix == -1) { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length, true); + return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); } marker[1] = (byte)value; } + + return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); } - return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); + return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2), true); } /// @@ -182,7 +195,8 @@ namespace ImageSharp.Formats.Jpeg.Port this.InputStream = stream; this.ParseStream(); - var image = new Image(1, 1); + var image = new Image(this.imageWidth, this.imageHeight); + this.GetData(image); return image; } @@ -192,6 +206,7 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame?.Dispose(); this.components?.Dispose(); this.quantizationTables?.Dispose(); + this.pixelArea.Dispose(); // Set large fields to null. this.frame = null; @@ -205,6 +220,9 @@ namespace ImageSharp.Formats.Jpeg.Port return 64 * (((component.BlocksPerLine + 1) * row) + col); } + /// + /// Parses the input stream for file markers + /// private void ParseStream() { // Check for the Start Of Image marker. @@ -224,12 +242,11 @@ namespace ImageSharp.Formats.Jpeg.Port while (fileMarker.Marker != JpegConstants.Markers.EOI) { // Get the marker length - int remaining; + int remaining = this.ReadUint16() - 2; switch (fileMarker.Marker) { case JpegConstants.Markers.APP0: - remaining = this.ReadUint16() - 2; this.ProcessApplicationHeaderMarker(remaining); break; @@ -246,10 +263,10 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP12: case JpegConstants.Markers.APP13: + this.InputStream.Skip(remaining); break; case JpegConstants.Markers.APP14: - remaining = this.ReadUint16() - 2; this.ProcessApp14Marker(remaining); break; @@ -257,32 +274,28 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.COM: // TODO: Read data block + this.InputStream.Skip(remaining); break; case JpegConstants.Markers.DQT: - remaining = this.ReadUint16() - 2; - this.ProcessDqtMarker(remaining); + this.ProcessDefineQuantizationTablesMarker(remaining); break; case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: - remaining = this.ReadUint16() - 2; this.ProcessStartOfFrameMarker(remaining, fileMarker); break; case JpegConstants.Markers.DHT: - remaining = this.ReadUint16() - 2; this.ProcessDefineHuffmanTablesMarker(remaining); break; case JpegConstants.Markers.DRI: - remaining = this.ReadUint16() - 2; this.ProcessDefineRestartIntervalMarker(remaining); break; case JpegConstants.Markers.SOS: - this.InputStream.Skip(2); this.ProcessStartOfScanMarker(); break; @@ -295,30 +308,30 @@ namespace ImageSharp.Formats.Jpeg.Port break; - default: - - // TODO: Not convinced this is required - // Skip back as it could be incorrect encoding -- last 0xFF byte of the previous - // block was eaten by the encoder - this.InputStream.Position -= 3; - this.InputStream.Read(this.temp, 0, 2); - if (this.temp[0] == 0xFF && this.temp[1] >= 0xC0 && this.temp[1] <= 0xFE) - { - // Rewind that last bytes we read - this.InputStream.Position -= 2; - break; - } - - // throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); - break; + //default: + + // // TODO: Not convinced this is required + // // Skip back as it could be incorrect encoding -- last 0xFF byte of the previous + // // block was eaten by the encoder + // this.InputStream.Position -= 3; + // this.InputStream.Read(this.temp, 0, 2); + // if (this.temp[0] == 0xFF && this.temp[1] >= 0xC0 && this.temp[1] <= 0xFE) + // { + // // Rewind that last bytes we read + // this.InputStream.Position -= 2; + // break; + // } + + // // throw new ImageFormatException($"Unknown Marker {fileMarker.Marker} at {fileMarker.Position}"); + // break; } // Read on. TODO: Test this on damaged images. fileMarker = FindNextFileMarkerNew(this.InputStream); } - this.width = this.frame.SamplesPerLine; - this.height = this.frame.Scanlines; + this.imageWidth = this.frame.SamplesPerLine; + this.imageHeight = this.frame.Scanlines; this.components = new ComponentBlocks { Components = new Component[this.frame.ComponentCount] }; for (int i = 0; i < this.components.Components.Length; i++) @@ -339,6 +352,53 @@ namespace ImageSharp.Formats.Jpeg.Port this.numComponents = this.components.Components.Length; } + /// + /// Fills the given image with the color data + /// + /// The pixel format. + /// The image + private void GetData(Image image) + where TPixel : struct, IPixel + { + if (this.numComponents > 4) + { + throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.numComponents}"); + } + + this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.numComponents); + this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height); + + if (this.numComponents == 1) + { + this.FillGrayScaleImage(image); + return; + } + + if (this.numComponents == 3) + { + if (this.adobe.Equals(default(Adobe)) || this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYCbCr) + { + this.FillYCbCrImage(image); + } + else if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformUnknown) + { + this.FillRgbImage(image); + } + } + + if (this.numComponents == 4) + { + if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYcck) + { + this.FillYcckImage(image); + } + else + { + this.FillCmykImage(image); + } + } + } + /// /// Processes the application header containing the JFIF identifier plus extra data. /// @@ -398,10 +458,10 @@ namespace ImageSharp.Formats.Jpeg.Port remaining -= 12; bool isAdobe = this.temp[0] == JpegConstants.Markers.Adobe.A && - this.temp[1] == JpegConstants.Markers.Adobe.D && - this.temp[2] == JpegConstants.Markers.Adobe.O && - this.temp[3] == JpegConstants.Markers.Adobe.B && - this.temp[4] == JpegConstants.Markers.Adobe.E; + this.temp[1] == JpegConstants.Markers.Adobe.D && + this.temp[2] == JpegConstants.Markers.Adobe.O && + this.temp[3] == JpegConstants.Markers.Adobe.B && + this.temp[4] == JpegConstants.Markers.Adobe.E; if (isAdobe) { @@ -427,7 +487,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// /// Thrown if the tables do not match the header /// - private void ProcessDqtMarker(int remaining) + private void ProcessDefineQuantizationTablesMarker(int remaining) { while (remaining > 0) { @@ -437,7 +497,7 @@ namespace ImageSharp.Formats.Jpeg.Port if (quantizationTableSpec > 3) { - throw new ImageFormatException("Bad Tq index value"); + throw new ImageFormatException($"Bad Tq index value: {quantizationTableSpec}"); } switch (quantizationTableSpec >> 4) @@ -570,17 +630,17 @@ namespace ImageSharp.Formats.Jpeg.Port { if (remaining < 17) { - throw new ImageFormatException("DHT has wrong length"); + throw new ImageFormatException($"DHT has wrong length: {remaining}"); } - using (var huffmanData = new Buffer(16)) + using (var huffmanData = Buffer.CreateClean(16)) { for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); this.InputStream.Read(huffmanData.Array, 0, 16); - using (var codeLengths = new Buffer(16)) + using (var codeLengths = Buffer.CreateClean(16)) { int codeLengthSum = 0; @@ -589,7 +649,7 @@ namespace ImageSharp.Formats.Jpeg.Port codeLengthSum += codeLengths[j] = huffmanData[j]; } - using (var huffmanValues = new Buffer(codeLengthSum)) + using (var huffmanValues = Buffer.CreateClean(codeLengthSum)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); @@ -615,7 +675,7 @@ namespace ImageSharp.Formats.Jpeg.Port { if (remaining != 2) { - throw new ImageFormatException("DRI has wrong length"); + throw new ImageFormatException($"DRI has wrong length: {remaining}"); } this.resetInterval = this.ReadUint16(); @@ -660,31 +720,33 @@ namespace ImageSharp.Formats.Jpeg.Port int successiveApproximation = this.temp[2]; var scanDecoder = default(ScanDecoder); - scanDecoder.DecodeScan( - this.frame, - this.InputStream, - this.dcHuffmanTables, - this.acHuffmanTables, - this.frame.Components, - componentIndex, - selectorsCount, - this.resetInterval, - spectralStart, - spectralEnd, - successiveApproximation >> 4, - successiveApproximation & 15); + long position = scanDecoder.DecodeScan( + this.frame, + this.InputStream, + this.dcHuffmanTables, + this.acHuffmanTables, + this.frame.Components, + componentIndex, + selectorsCount, + this.resetInterval, + spectralStart, + spectralEnd, + successiveApproximation >> 4, + successiveApproximation & 15); + + this.InputStream.Position += position; Debug.WriteLine("spectralStart= " + spectralStart); Debug.WriteLine("spectralEnd= " + spectralEnd); Debug.WriteLine("successiveApproximation= " + successiveApproximation); Debug.WriteLine("Components after"); - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 10; j++) - { - Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]"); - } - } + //for (int i = 0; i < 3; i++) + //{ + // for (int j = 0; j < 10; j++) + // { + // Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]"); + // } + //} } /// @@ -694,7 +756,6 @@ namespace ImageSharp.Formats.Jpeg.Port /// The frame component private void BuildComponentData(ref Component component, ref FrameComponent frameComponent) { - // TODO: Write this int blocksPerLine = component.BlocksPerLine; int blocksPerColumn = component.BlocksPerColumn; using (var computationBuffer = Buffer.CreateClean(64)) @@ -776,21 +837,21 @@ namespace ImageSharp.Formats.Jpeg.Port /// private void PrepareComponents() { - int mcusPerLine = (int)Math.Ceiling(this.frame.SamplesPerLine / 8D / this.frame.MaxHorizontalFactor); - int mcusPerColumn = (int)Math.Ceiling(this.frame.Scanlines / 8D / this.frame.MaxVerticalFactor); + int mcusPerLine = (int)MathF.Ceiling(this.frame.SamplesPerLine / 8F / this.frame.MaxHorizontalFactor); + int mcusPerColumn = (int)MathF.Ceiling(this.frame.Scanlines / 8F / this.frame.MaxVerticalFactor); for (int i = 0; i < this.frame.ComponentCount; i++) { ref var component = ref this.frame.Components[i]; - int blocksPerLine = (int)Math.Ceiling(Math.Ceiling(this.frame.SamplesPerLine / 8D) * component.HorizontalFactor / this.frame.MaxHorizontalFactor); - int blocksPerColumn = (int)Math.Ceiling(Math.Ceiling(this.frame.Scanlines / 8D) * component.VerticalFactor / this.frame.MaxVerticalFactor); + int blocksPerLine = (int)MathF.Ceiling(MathF.Ceiling(this.frame.SamplesPerLine / 8F) * component.HorizontalFactor / this.frame.MaxHorizontalFactor); + int blocksPerColumn = (int)MathF.Ceiling(MathF.Ceiling(this.frame.Scanlines / 8F) * component.VerticalFactor / this.frame.MaxVerticalFactor); int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor; int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor; int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); - // Pooled. Disposed via frame siposal - component.BlockData = new Buffer(blocksBufferSize); + // Pooled. Disposed via frame disposal + component.BlockData = Buffer.CreateClean(blocksBufferSize); component.BlocksPerLine = blocksPerLine; component.BlocksPerColumn = blocksPerColumn; } @@ -799,6 +860,104 @@ namespace ImageSharp.Formats.Jpeg.Port this.frame.McusPerColumn = mcusPerColumn; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillGrayScaleImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + ref byte luminance = ref areaRowSpan[x]; + ref TPixel pixel = ref imageRowSpan[x]; + var rgba = new Rgba32(luminance, luminance, luminance); + pixel.PackFromRgba32(rgba); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillYCbCrImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + for (int x = 0, o = 0; x < image.Width; x++, o += 3) + { + ref byte yy = ref areaRowSpan[o]; + ref byte cb = ref areaRowSpan[o + 1]; + ref byte cr = ref areaRowSpan[o + 2]; + ref TPixel pixel = ref imageRowSpan[x]; + YCbCrToRgbTables.PackYCbCr(ref pixel, yy, cb, cr); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillYcckImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + for (int x = 0, o = 0; x < image.Width; x++, o += 4) + { + ref byte yy = ref areaRowSpan[o]; + ref byte cb = ref areaRowSpan[o + 1]; + ref byte cr = ref areaRowSpan[o + 2]; + ref byte k = ref areaRowSpan[o + 3]; + + ref TPixel pixel = ref imageRowSpan[x]; + YCbCrToRgbTables.PackYccK(ref pixel, yy, cb, cr, k); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillCmykImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + for (int x = 0, o = 0; x < image.Width; x++, o += 4) + { + ref byte c = ref areaRowSpan[o]; + ref byte m = ref areaRowSpan[o + 1]; + ref byte cy = ref areaRowSpan[o + 2]; + ref byte k = ref areaRowSpan[o + 3]; + + byte r = (byte)((c * k) / 255); + byte g = (byte)((m * k) / 255); + byte b = (byte)((cy * k) / 255); + + ref TPixel pixel = ref imageRowSpan[x]; + var rgba = new Rgba32(r, g, b); + pixel.PackFromRgba32(rgba); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FillRgbImage(Image image) + where TPixel : struct, IPixel + { + for (int y = 0; y < image.Height; y++) + { + Span imageRowSpan = image.GetRowSpan(y); + Span areaRowSpan = this.pixelArea.GetRowSpan(y); + + PixelOperations.Instance.PackFromRgb24Bytes(areaRowSpan, imageRowSpan, image.Width); + } + } + /// /// Reads a from the stream advancing it by two bytes /// diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index f1b78383c..d274d61a2 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -12,8 +12,6 @@ namespace ImageSharp.Tests using System.Linq; using System.Reflection; - using ImageSharp.PixelFormats; - /// /// A test image file. /// @@ -32,7 +30,7 @@ namespace ImageSharp.Tests /// /// The image. /// - private readonly Image image; + private Image image; /// /// The file. @@ -46,9 +44,7 @@ namespace ImageSharp.Tests private TestFile(string file) { this.file = file; - this.Bytes = File.ReadAllBytes(file); - this.image = Image.Load(this.Bytes); } /// @@ -129,7 +125,7 @@ namespace ImageSharp.Tests /// public Image CreateImage() { - return new Image(this.image); + return new Image(this.GetImage()); } /// @@ -144,6 +140,11 @@ namespace ImageSharp.Tests return Image.Load(this.Bytes, options); } + private Image GetImage() + { + return this.image ?? (this.image = Image.Load(this.Bytes)); + } + /// /// Gets the correct path to the formats directory. /// @@ -152,7 +153,7 @@ namespace ImageSharp.Tests /// private static string GetFormatsDirectory() { - List directories = new List< string > { + var directories = new List { "TestImages/Formats/", // Here for code coverage tests. "tests/ImageSharp.Tests/TestImages/Formats/", // from travis/build script "../../../../../ImageSharp.Tests/TestImages/Formats/", // from Sandbox46 @@ -167,9 +168,9 @@ namespace ImageSharp.Tests AddFormatsDirectoryFromTestAssebmlyPath(directories); - string directory = directories.FirstOrDefault(x => Directory.Exists(x)); + string directory = directories.FirstOrDefault(Directory.Exists); - if(directory != null) + if (directory != null) { return directory; } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg new file mode 100644 index 000000000..2fe8f0a61 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0 +size 611572