Browse Source

Can now decode many images

af/merge-core
James Jackson-South 9 years ago
parent
commit
00712a277e
  1. 40
      src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs
  2. 6
      src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs
  3. 146
      src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs
  4. 496
      src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs
  5. 128
      src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs
  6. 301
      src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs
  7. 19
      tests/ImageSharp.Tests/TestFile.cs
  8. 3
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg

40
src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs

@ -6,10 +6,12 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System;
/// <summary>
/// Provides information about the Adobe marker segment
/// </summary>
internal struct Adobe
internal struct Adobe : IEquatable<Adobe>
{
/// <summary>
/// The DCT Encode Version
@ -34,5 +36,39 @@ namespace ImageSharp.Formats.Jpeg.Port.Components
/// 02 : YCCK
/// </summary>
public byte ColorTransform;
/// <inheritdoc/>
public bool Equals(Adobe other)
{
return this.DCTEncodeVersion == other.DCTEncodeVersion
&& this.APP14Flags0 == other.APP14Flags0
&& this.APP14Flags1 == other.APP14Flags1
&& this.ColorTransform == other.ColorTransform;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is Adobe && this.Equals((Adobe)obj);
}
/// <inheritdoc/>
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;
}
}
}
}
}

6
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
/// </summary>
public long Position { get; }
/// <inheritdoc/>
public override string ToString()
{
return this.Marker.ToString("X");
}
}
}

146
src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs

@ -0,0 +1,146 @@
// <copyright file="JpegPixelArea.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpeg.Port.Components
{
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using ImageSharp.Memory;
/// <summary>
/// Represents a section of the jpeg component data laid out in pixel order.
/// </summary>
internal struct JpegPixelArea : IDisposable
{
private readonly int imageWidth;
private readonly int imageHeight;
private Buffer<byte> componentData;
private int rowStride;
/// <summary>
/// Initializes a new instance of the <see cref="JpegPixelArea"/> struct.
/// </summary>
/// <param name="imageWidth">The image width</param>
/// <param name="imageHeight">The image height</param>
/// <param name="numberOfComponents">The number of components</param>
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;
}
/// <summary>
/// Gets the number of components
/// </summary>
public int NumberOfComponents { get; }
/// <summary>
/// Gets the width
/// </summary>
public int Width { get; private set; }
/// <summary>
/// Gets the height
/// </summary>
public int Height { get; private set; }
/// <summary>
/// Organsizes the decoded jpeg components into a linear array ordered by component.
/// This must be called before attempting to retrieve the data.
/// </summary>
/// <param name="components">The jpeg component blocks</param>
/// <param name="width">The pixel area width</param>
/// <param name="height">The pixel area height</param>
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<byte>(width * height * numberOfComponents);
const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs
using (var xScaleBlockOffset = new Buffer<int>(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<short> 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;
}
}
}
}
}
/// <summary>
/// Gets a <see cref="Span{Byte}"/> representing the row 'y' beginning from the the first byte on that row.
/// </summary>
/// <param name="y">The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the pixel area.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> GetRowSpan(int y)
{
this.CheckCoordinates(y);
return this.componentData.Slice(y * this.rowStride, this.rowStride);
}
/// <inheritdoc/>
public void Dispose()
{
this.componentData?.Dispose();
this.componentData = null;
}
/// <summary>
/// Checks the coordinates to ensure they are within bounds.
/// </summary>
/// <param name="y">The y-coordinate of the row. Must be greater than zero and less than the height of the area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the coordinates are not within the bounds of the image.
/// </exception>
[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.");
}
}
}
}

496
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;
/// <summary>
/// Encapsulates a decode method. TODO: This may well be a bottleneck
/// </summary>
/// <param name="component">The component</param>
/// <param name="offset">The offset</param>
/// <param name="dcHuffmanTables">The DC Huffman tables</param>
/// <param name="acHuffmanTables">The AC Huffman tables</param>
/// <param name="stream">The input stream</param>
internal delegate void DecodeAction(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream);
/// <summary>
/// Provides the means to decode a spectral scan
/// </summary>
@ -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
/// <param name="spectralEnd">The spectral selection end</param>
/// <param name="successivePrev">The successive approximation bit high end</param>
/// <param name="successive">The successive approximation bit low end</param>
public void DecodeScan(
/// <returns>The <see cref="long"/> representing the processed length in bytes</returns>
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;

128
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;
/// <summary>
/// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace.
/// Methods to build the tables are based on libjpeg implementation.
/// </summary>
internal struct YCbCrToRgbTables
{
/// <summary>
/// The red red-chrominance table
/// </summary>
public static int[] CrRTable = new int[256];
/// <summary>
/// The blue blue-chrominance table
/// </summary>
public static int[] CbBTable = new int[256];
/// <summary>
/// The green red-chrominance table
/// </summary>
public static int[] CrGTable = new int[256];
/// <summary>
/// The green blue-chrominance table
/// </summary>
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;
/// <summary>
/// Initializes the YCbCr tables
/// </summary>
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;
}
}
/// <summary>
/// Optimized method to pack bytes to the image from the YCbCr color space.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PackYCbCr<TPixel>(ref TPixel packed, byte y, byte cb, byte cr)
where TPixel : struct, IPixel<TPixel>
{
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));
}
/// <summary>
/// Optimized method to pack bytes to the image from the YccK color space.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
/// <param name="k">The keyline component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PackYccK<TPixel>(ref TPixel packed, byte y, byte cb, byte cr, byte k)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}

301
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
/// </summary>
private Adobe adobe;
/// <summary>
/// Initializes static members of the <see cref="JpegDecoderCore"/> class.
/// </summary>
static JpegDecoderCore()
{
YCbCrToRgbTables.Create();
}
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary>
@ -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);
}
/// <summary>
@ -182,7 +195,8 @@ namespace ImageSharp.Formats.Jpeg.Port
this.InputStream = stream;
this.ParseStream();
var image = new Image<TPixel>(1, 1);
var image = new Image<TPixel>(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);
}
/// <summary>
/// Parses the input stream for file markers
/// </summary>
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;
}
/// <summary>
/// Fills the given image with the color data
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image</param>
private void GetData<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
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);
}
}
}
/// <summary>
/// Processes the application header containing the JFIF identifier plus extra data.
/// </summary>
@ -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
/// <exception cref="ImageFormatException">
/// Thrown if the tables do not match the header
/// </exception>
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<byte>(16))
using (var huffmanData = Buffer<byte>.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<byte>(16))
using (var codeLengths = Buffer<byte>.CreateClean(16))
{
int codeLengthSum = 0;
@ -589,7 +649,7 @@ namespace ImageSharp.Formats.Jpeg.Port
codeLengthSum += codeLengths[j] = huffmanData[j];
}
using (var huffmanValues = new Buffer<byte>(codeLengthSum))
using (var huffmanValues = Buffer<byte>.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] + "]");
// }
//}
}
/// <summary>
@ -694,7 +756,6 @@ namespace ImageSharp.Formats.Jpeg.Port
/// <param name="frameComponent">The frame component</param>
private void BuildComponentData(ref Component component, ref FrameComponent frameComponent)
{
// TODO: Write this
int blocksPerLine = component.BlocksPerLine;
int blocksPerColumn = component.BlocksPerColumn;
using (var computationBuffer = Buffer<short>.CreateClean(64))
@ -776,21 +837,21 @@ namespace ImageSharp.Formats.Jpeg.Port
/// </summary>
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<short>(blocksBufferSize);
// Pooled. Disposed via frame disposal
component.BlockData = Buffer<short>.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<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> imageRowSpan = image.GetRowSpan(y);
Span<byte> 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<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> imageRowSpan = image.GetRowSpan(y);
Span<byte> 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<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> imageRowSpan = image.GetRowSpan(y);
Span<byte> 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<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> imageRowSpan = image.GetRowSpan(y);
Span<byte> 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<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> imageRowSpan = image.GetRowSpan(y);
Span<byte> areaRowSpan = this.pixelArea.GetRowSpan(y);
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(areaRowSpan, imageRowSpan, image.Width);
}
}
/// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
/// </summary>

19
tests/ImageSharp.Tests/TestFile.cs

@ -12,8 +12,6 @@ namespace ImageSharp.Tests
using System.Linq;
using System.Reflection;
using ImageSharp.PixelFormats;
/// <summary>
/// A test image file.
/// </summary>
@ -32,7 +30,7 @@ namespace ImageSharp.Tests
/// <summary>
/// The image.
/// </summary>
private readonly Image<Rgba32> image;
private Image<Rgba32> image;
/// <summary>
/// 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<Rgba32>(this.Bytes);
}
/// <summary>
@ -129,7 +125,7 @@ namespace ImageSharp.Tests
/// </returns>
public Image<Rgba32> CreateImage()
{
return new Image<Rgba32>(this.image);
return new Image<Rgba32>(this.GetImage());
}
/// <summary>
@ -144,6 +140,11 @@ namespace ImageSharp.Tests
return Image.Load<Rgba32>(this.Bytes, options);
}
private Image<Rgba32> GetImage()
{
return this.image ?? (this.image = Image.Load<Rgba32>(this.Bytes));
}
/// <summary>
/// Gets the correct path to the formats directory.
/// </summary>
@ -152,7 +153,7 @@ namespace ImageSharp.Tests
/// </returns>
private static string GetFormatsDirectory()
{
List<string> directories = new List< string > {
var directories = new List<string> {
"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;
}

3
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
Loading…
Cancel
Save