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