Browse Source

allocation optimizations for JpegDecoderCore

af/merge-core
Anton Firszov 9 years ago
parent
commit
fd11972bb8
  1. 79
      src/ImageSharp/Formats/Jpg/Components/Block.cs
  2. 10
      src/ImageSharp/Formats/Jpg/Components/Component.cs
  3. 2
      src/ImageSharp/Formats/Jpg/Components/FDCT.cs
  4. 65
      src/ImageSharp/Formats/Jpg/Components/Huffman.cs
  5. 5
      src/ImageSharp/Formats/Jpg/Components/IDCT.cs
  6. 6
      src/ImageSharp/Formats/Jpg/JpegDecoder.cs
  7. 691
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  8. 63
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  9. 2
      tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
  10. 85
      tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
  11. 2
      tests/ImageSharp.Tests/Formats/Png/PngTests.cs
  12. 9
      tests/ImageSharp.Tests/TestImages.cs
  13. 3
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/geneserath.jpg

79
src/ImageSharp/Formats/Jpg/Components/Block.cs

@ -3,13 +3,19 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
/// <summary> /// <summary>
/// Represents an 8x8 block of coefficients to transform and encode. /// Represents an 8x8 block of coefficients to transform and encode.
/// </summary> /// </summary>
internal class Block public struct Block : IDisposable
{ {
private static ArrayPool<int> IntArrayPool = ArrayPool<int>.Create(BlockSize, 50);
/// <summary> /// <summary>
/// Gets the size of the block. /// Gets the size of the block.
/// </summary> /// </summary>
@ -18,16 +24,41 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The array of block data. /// The array of block data.
/// </summary> /// </summary>
private readonly int[] data; public int[] Data;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Block"/> class. /// Initializes a new instance of the <see cref="Block"/> class.
/// </summary> /// </summary>
public Block() //public Block()
//{
// this.data = new int[BlockSize];
//}
public void Init()
{
//this.Data = new int[BlockSize];
this.Data = IntArrayPool.Rent(BlockSize);
}
public static Block Create()
{ {
this.data = new int[BlockSize]; var block = new Block();
block.Init();
return block;
} }
public static Block[] CreateArray(int size)
{
Block[] result = new Block[size];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
public bool IsInitialized => this.Data != null;
/// <summary> /// <summary>
/// Gets the pixel data at the given block index. /// Gets the pixel data at the given block index.
/// </summary> /// </summary>
@ -37,8 +68,44 @@ namespace ImageSharp.Formats
/// </returns> /// </returns>
public int this[int index] public int this[int index]
{ {
get { return this.data[index]; } [MethodImpl(MethodImplOptions.AggressiveInlining)]
set { this.data[index] = value; } get { return this.Data[index]; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set { this.Data[index] = value; }
}
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
public void Dispose()
{
if (Data != null)
{
IntArrayPool.Return(Data, true);
Data = null;
}
}
public static void DisposeAll(Block[] blocks)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
}
public void Clear()
{
for (int i = 0; i < Data.Length; i++)
{
Data[i] = 0;
}
}
public Block Clone()
{
Block clone = Create();
Array.Copy(Data, clone.Data, BlockSize);
return clone;
} }
} }
} }

10
src/ImageSharp/Formats/Jpg/Components/Component.cs

@ -8,26 +8,26 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Represents a single color component /// Represents a single color component
/// </summary> /// </summary>
internal class Component internal struct Component
{ {
/// <summary> /// <summary>
/// Gets or sets the horizontal sampling factor. /// Gets or sets the horizontal sampling factor.
/// </summary> /// </summary>
public int HorizontalFactor { get; set; } public int HorizontalFactor;
/// <summary> /// <summary>
/// Gets or sets the vertical sampling factor. /// Gets or sets the vertical sampling factor.
/// </summary> /// </summary>
public int VerticalFactor { get; set; } public int VerticalFactor;
/// <summary> /// <summary>
/// Gets or sets the identifier /// Gets or sets the identifier
/// </summary> /// </summary>
public byte Identifier { get; set; } public byte Identifier;
/// <summary> /// <summary>
/// Gets or sets the quantization table destination selector. /// Gets or sets the quantization table destination selector.
/// </summary> /// </summary>
public byte Selector { get; set; } public byte Selector;
} }
} }

2
src/ImageSharp/Formats/Jpg/Components/FDCT.cs

@ -45,7 +45,7 @@ namespace ImageSharp.Formats
/// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift.
/// </summary> /// </summary>
/// <param name="block">The block of coefficients.</param> /// <param name="block">The block of coefficients.</param>
public static void Transform(Block block) public static void Transform(ref Block block)
{ {
// Pass 1: process rows. // Pass 1: process rows.
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)

65
src/ImageSharp/Formats/Jpg/Components/Huffman.cs

@ -3,33 +3,47 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using System;
using System.Buffers;
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
/// <summary> /// <summary>
/// Represents a Huffman tree /// Represents a Huffman tree
/// </summary> /// </summary>
internal class Huffman internal struct Huffman : IDisposable
{ {
/// <summary> private static ArrayPool<ushort> UshortBuffer =
/// Initializes a new instance of the <see cref="Huffman"/> class. //ArrayPool<ushort>.Shared;
/// </summary> ArrayPool<ushort>.Create(1 << JpegDecoderCore.LutSize, 50);
/// <param name="lutSize">The log-2 size of the Huffman decoder's look-up table.</param>
/// <param name="maxNCodes">The maximum (inclusive) number of codes in a Huffman tree.</param> private static ArrayPool<byte> ByteBuffer =
/// <param name="maxCodeLength">The maximum (inclusive) number of bits in a Huffman code.</param> //ArrayPool<byte>.Shared;
public Huffman(int lutSize, int maxNCodes, int maxCodeLength) ArrayPool<byte>.Create(JpegDecoderCore.MaxNCodes, 50);
private static readonly ArrayPool<int> IntBuffer =
//ArrayPool<int>.Shared;
ArrayPool<int>.Create(JpegDecoderCore.MaxCodeLength, 50);
public void Init(int lutSize, int maxNCodes, int maxCodeLength)
{ {
this.Lut = new ushort[1 << lutSize]; //this.Lut = new ushort[1 << lutSize];
this.Values = new byte[maxNCodes]; //this.Values = new byte[maxNCodes];
this.MinCodes = new int[maxCodeLength]; //this.MinCodes = new int[maxCodeLength];
this.MaxCodes = new int[maxCodeLength]; //this.MaxCodes = new int[maxCodeLength];
this.Indices = new int[maxCodeLength]; //this.Indices = new int[maxCodeLength];
this.Length = 0;
this.Lut = UshortBuffer.Rent(1 << lutSize);
this.Values = ByteBuffer.Rent(maxNCodes);
this.MinCodes = IntBuffer.Rent(maxCodeLength);
this.MaxCodes = IntBuffer.Rent(maxCodeLength); ;
this.Indices = IntBuffer.Rent(maxCodeLength); ;
} }
/// <summary> /// <summary>
/// Gets or sets the number of codes in the tree. /// Gets or sets the number of codes in the tree.
/// </summary> /// </summary>
public int Length { get; set; } public int Length;
/// <summary> /// <summary>
/// Gets the look-up table for the next LutSize bits in the bit-stream. /// Gets the look-up table for the next LutSize bits in the bit-stream.
@ -37,28 +51,39 @@ namespace ImageSharp.Formats
/// are 1 plus the code length, or 0 if the value is too large to fit in /// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits. /// lutSize bits.
/// </summary> /// </summary>
public ushort[] Lut { get; } public ushort[] Lut;
/// <summary> /// <summary>
/// Gets the the decoded values, sorted by their encoding. /// Gets the the decoded values, sorted by their encoding.
/// </summary> /// </summary>
public byte[] Values { get; } public byte[] Values;
/// <summary> /// <summary>
/// Gets the array of minimum codes. /// Gets the array of minimum codes.
/// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
/// </summary> /// </summary>
public int[] MinCodes { get; } public int[] MinCodes;
/// <summary> /// <summary>
/// Gets the array of maximum codes. /// Gets the array of maximum codes.
/// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
/// </summary> /// </summary>
public int[] MaxCodes { get; } public int[] MaxCodes;
/// <summary> /// <summary>
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
/// </summary> /// </summary>
public int[] Indices { get; } public int[] Indices;
public void Dispose()
{
UshortBuffer.Return(Lut, true);
ByteBuffer.Return(Values, true);
IntBuffer.Return(MinCodes, true);
IntBuffer.Return(MaxCodes, true);
IntBuffer.Return(Indices, true);
}
} }
} }

5
src/ImageSharp/Formats/Jpg/Components/IDCT.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using System.Numerics;
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
/// <summary> /// <summary>
@ -39,7 +41,7 @@ namespace ImageSharp.Formats
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
/// </summary> /// </summary>
/// <param name="src">The source block of coefficients</param> /// <param name="src">The source block of coefficients</param>
public static void Transform(Block src) public static void Transform(ref Block src)
{ {
// Horizontal 1-D IDCT. // Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)
@ -165,5 +167,6 @@ namespace ImageSharp.Formats
src[56 + x] = (y7 - y1) >> 14; src[56 + x] = (y7 - y1) >> 14;
} }
} }
} }
} }

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

@ -83,8 +83,10 @@ namespace ImageSharp.Formats
Guard.NotNull(image, "image"); Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream"); Guard.NotNull(stream, "stream");
JpegDecoderCore decoder = new JpegDecoderCore(); using (JpegDecoderCore decoder = new JpegDecoderCore())
decoder.Decode(image, stream, false); {
decoder.Decode(image, stream, false);
}
} }
/// <summary> /// <summary>

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

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using System.Runtime.CompilerServices;
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
@ -12,22 +14,22 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Performs the jpeg decoding operation. /// Performs the jpeg decoding operation.
/// </summary> /// </summary>
internal class JpegDecoderCore internal class JpegDecoderCore : IDisposable
{ {
/// <summary> /// <summary>
/// The maximum (inclusive) number of bits in a Huffman code. /// The maximum (inclusive) number of bits in a Huffman code.
/// </summary> /// </summary>
private const int MaxCodeLength = 16; internal const int MaxCodeLength = 16;
/// <summary> /// <summary>
/// The maximum (inclusive) number of codes in a Huffman tree. /// The maximum (inclusive) number of codes in a Huffman tree.
/// </summary> /// </summary>
private const int MaxNCodes = 256; internal const int MaxNCodes = 256;
/// <summary> /// <summary>
/// The log-2 size of the Huffman decoder's look-up table. /// The log-2 size of the Huffman decoder's look-up table.
/// </summary> /// </summary>
private const int LutSize = 8; internal const int LutSize = 8;
/// <summary> /// <summary>
/// The maximum number of color components /// The maximum number of color components
@ -44,6 +46,8 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private const int MaxTh = 3; private const int MaxTh = 3;
private const int ThRowSize = MaxTh + 1;
/// <summary> /// <summary>
/// The maximum number of quantization tables /// The maximum number of quantization tables
/// </summary> /// </summary>
@ -85,7 +89,9 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The huffman trees /// The huffman trees
/// </summary> /// </summary>
private readonly Huffman[,] huffmanTrees; //private readonly Huffman[,] huffmanTrees;
private readonly Huffman[] huffmanTrees;
/// <summary> /// <summary>
/// Quantization tables, in zigzag order. /// Quantization tables, in zigzag order.
@ -187,13 +193,17 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private short verticalResolution; private short verticalResolution;
private int blockIndex;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore"/> class. /// Initializes a new instance of the <see cref="JpegDecoderCore"/> class.
/// </summary> /// </summary>
public JpegDecoderCore() public JpegDecoderCore()
{ {
this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1]; //this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
this.quantizationTables = new Block[MaxTq + 1]; this.huffmanTrees = new Huffman[(MaxTc + 1)*(MaxTh + 1)];
this.quantizationTables = Block.CreateArray(MaxTq + 1);
this.temp = new byte[2 * Block.BlockSize]; this.temp = new byte[2 * Block.BlockSize];
this.componentArray = new Component[MaxComponents]; this.componentArray = new Component[MaxComponents];
this.progCoeffs = new Block[MaxComponents][]; this.progCoeffs = new Block[MaxComponents][];
@ -201,25 +211,29 @@ namespace ImageSharp.Formats
this.bytes = new Bytes(); this.bytes = new Bytes();
// TODO: This looks like it could be static. // TODO: This looks like it could be static.
for (int i = 0; i < MaxTc + 1; i++) for (int i = 0; i < MaxTc + 1; i++)
{ {
for (int j = 0; j < MaxTh + 1; j++) for (int j = 0; j < MaxTh + 1; j++)
{ {
this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength); //this.huffmanTrees[i, j].Init(LutSize, MaxNCodes, MaxCodeLength);
this.huffmanTrees[i* ThRowSize + j].Init(LutSize, MaxNCodes, MaxCodeLength);
} }
} }
for (int i = 0; i < this.quantizationTables.Length; i++) //for (int i = 0; i < this.quantizationTables.Length; i++)
{ //{
this.quantizationTables[i] = new Block(); // //this.quantizationTables[i] = new Block();
} // this.quantizationTables[i].Init();
//}
for (int i = 0; i < this.componentArray.Length; i++) //for (int i = 0; i < this.componentArray.Length; i++)
{ //{
this.componentArray[i] = new Component(); // this.componentArray[i] = new Component();
} //}
} }
/// <summary> /// <summary>
/// Decodes the image from the specified this._stream and sets /// Decodes the image from the specified this._stream and sets
/// the data to image. /// the data to image.
@ -501,92 +515,96 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Bad Th value"); throw new ImageFormatException("Bad Th value");
} }
Huffman huffman = this.huffmanTrees[tc, th]; ProcessDefineHuffmanTablesMarkerLoop(ref this.huffmanTrees[tc* ThRowSize + th], ref remaining);
}
}
// Read nCodes and huffman.Valuess (and derive h.Length). private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining)
// nCodes[i] is the number of codes with code length i. {
// h.Length is the total number of codes.
huffman.Length = 0;
int[] ncodes = new int[MaxCodeLength]; // Read nCodes and huffman.Valuess (and derive h.Length).
for (int i = 0; i < ncodes.Length; i++) // nCodes[i] is the number of codes with code length i.
{ // h.Length is the total number of codes.
ncodes[i] = this.temp[i + 1]; huffman.Length = 0;
huffman.Length += ncodes[i];
}
if (huffman.Length == 0) int[] ncodes = new int[MaxCodeLength];
{ for (int i = 0; i < ncodes.Length; i++)
throw new ImageFormatException("Huffman table has zero length"); {
} ncodes[i] = this.temp[i + 1];
huffman.Length += ncodes[i];
}
if (huffman.Length > MaxNCodes) if (huffman.Length == 0)
{ {
throw new ImageFormatException("Huffman table has excessive length"); throw new ImageFormatException("Huffman table has zero length");
} }
remaining -= huffman.Length + 17; if (huffman.Length > MaxNCodes)
if (remaining < 0) {
{ throw new ImageFormatException("Huffman table has excessive length");
throw new ImageFormatException("DHT has wrong length"); }
}
remaining -= huffman.Length + 17;
if (remaining < 0)
{
throw new ImageFormatException("DHT has wrong length");
}
this.ReadFull(huffman.Values, 0, huffman.Length); this.ReadFull(huffman.Values, 0, huffman.Length);
// Derive the look-up table. // Derive the look-up table.
for (int i = 0; i < huffman.Lut.Length; i++) for (int i = 0; i < huffman.Lut.Length; i++)
{ {
huffman.Lut[i] = 0; huffman.Lut[i] = 0;
} }
uint x = 0, code = 0; uint x = 0, code = 0;
for (int i = 0; i < LutSize; i++)
{
code <<= 1;
for (int i = 0; i < LutSize; i++) for (int j = 0; j < ncodes[i]; j++)
{ {
code <<= 1; // The codeLength is 1+i, so shift code by 8-(1+i) to
// calculate the high bits for every 8-bit sequence
// whose codeLength's high bits matches code.
// The high 8 bits of lutValue are the encoded value.
// The low 8 bits are 1 plus the codeLength.
byte base2 = (byte) (code << (7 - i));
ushort lutValue = (ushort) ((huffman.Values[x] << 8) | (2 + i));
for (int j = 0; j < ncodes[i]; j++) for (int k = 0; k < 1 << (7 - i); k++)
{ {
// The codeLength is 1+i, so shift code by 8-(1+i) to huffman.Lut[base2 | k] = lutValue;
// calculate the high bits for every 8-bit sequence
// whose codeLength's high bits matches code.
// The high 8 bits of lutValue are the encoded value.
// The low 8 bits are 1 plus the codeLength.
byte base2 = (byte)(code << (7 - i));
ushort lutValue = (ushort)((huffman.Values[x] << 8) | (2 + i));
for (int k = 0; k < 1 << (7 - i); k++)
{
huffman.Lut[base2 | k] = lutValue;
}
code++;
x++;
} }
code++;
x++;
} }
}
// Derive minCodes, maxCodes, and indices. // Derive minCodes, maxCodes, and indices.
int c = 0, index = 0; int c = 0, index = 0;
for (int i = 0; i < ncodes.Length; i++) for (int i = 0; i < ncodes.Length; i++)
{
int nc = ncodes[i];
if (nc == 0)
{ {
int nc = ncodes[i]; huffman.MinCodes[i] = -1;
if (nc == 0) huffman.MaxCodes[i] = -1;
{ huffman.Indices[i] = -1;
huffman.MinCodes[i] = -1; }
huffman.MaxCodes[i] = -1; else
huffman.Indices[i] = -1; {
} huffman.MinCodes[i] = c;
else huffman.MaxCodes[i] = c + nc - 1;
{ huffman.Indices[i] = index;
huffman.MinCodes[i] = c; c += nc;
huffman.MaxCodes[i] = c + nc - 1; index += nc;
huffman.Indices[i] = index;
c += nc;
index += nc;
}
c <<= 1;
} }
c <<= 1;
} }
} }
@ -595,7 +613,7 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="huffman">The huffman value</param> /// <param name="huffman">The huffman value</param>
/// <returns>The <see cref="byte"/></returns> /// <returns>The <see cref="byte"/></returns>
private byte DecodeHuffman(Huffman huffman) private byte DecodeHuffman(ref Huffman huffman)
{ {
if (huffman.Length == 0) if (huffman.Length == 0)
{ {
@ -1385,7 +1403,7 @@ namespace ImageSharp.Formats
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
TColor packed = default(TColor); TColor packed = default(TColor);
this.PackYcbCr<TColor, TPacked>(ref packed, yy, cb, cr); PackYcbCr<TColor, TPacked>(ref packed, yy, cb, cr);
pixels[x, y] = packed; pixels[x, y] = packed;
} }
}); });
@ -1453,6 +1471,8 @@ namespace ImageSharp.Formats
} }
} }
private Block scanWorkerBlock = Block.Create();
/// <summary> /// <summary>
/// Processes the SOS (Start of scan marker). /// Processes the SOS (Start of scan marker).
/// </summary> /// </summary>
@ -1479,7 +1499,8 @@ namespace ImageSharp.Formats
this.ReadFull(this.temp, 0, remaining); this.ReadFull(this.temp, 0, remaining);
byte scanComponentCount = this.temp[0]; byte scanComponentCount = this.temp[0];
if (remaining != 4 + (2 * scanComponentCount)) int scanComponentCountX2 = 2 * scanComponentCount;
if (remaining != 4 + scanComponentCountX2)
{ {
throw new ImageFormatException("SOS length inconsistent with number of components"); throw new ImageFormatException("SOS length inconsistent with number of components");
} }
@ -1489,51 +1510,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < scanComponentCount; i++) for (int i = 0; i < scanComponentCount; i++)
{ {
// Component selector. ProcessScanImpl(i, ref scan[i], scan, ref totalHv);
int cs = this.temp[1 + (2 * i)];
int compIndex = -1;
for (int j = 0; j < this.componentCount; j++)
{
Component compv = this.componentArray[j];
if (cs == compv.Identifier)
{
compIndex = j;
}
}
if (compIndex < 0)
{
throw new ImageFormatException("Unknown component selector");
}
scan[i].Index = (byte)compIndex;
// Section B.2.3 states that "the value of Cs_j shall be different from
// the values of Cs_1 through Cs_(j-1)". Since we have previously
// verified that a frame's component identifiers (C_i values in section
// B.2.2) are unique, it suffices to check that the implicit indexes
// into comp are unique.
for (int j = 0; j < i; j++)
{
if (scan[i].Index == scan[j].Index)
{
throw new ImageFormatException("Repeated component selector");
}
}
totalHv += this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor;
scan[i].DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4);
if (scan[i].DcTableSelector > MaxTh)
{
throw new ImageFormatException("Bad DC table selector value");
}
scan[i].AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f);
if (scan[i].AcTableSelector > MaxTh)
{
throw new ImageFormatException("Bad AC table selector value");
}
} }
// Section B.2.3 states that if there is more than one component then the // Section B.2.3 states that if there is more than one component then the
@ -1564,10 +1541,10 @@ namespace ImageSharp.Formats
if (this.isProgressive) if (this.isProgressive)
{ {
zigStart = this.temp[1 + (2 * scanComponentCount)]; zigStart = this.temp[1 + scanComponentCountX2];
zigEnd = this.temp[2 + (2 * scanComponentCount)]; zigEnd = this.temp[2 + scanComponentCountX2];
ah = this.temp[3 + (2 * scanComponentCount)] >> 4; ah = this.temp[3 + scanComponentCountX2] >> 4;
al = this.temp[3 + (2 * scanComponentCount)] & 0x0f; al = this.temp[3 + scanComponentCountX2] & 0x0f;
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd) if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd)
{ {
@ -1603,11 +1580,11 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index; int compIndex = scan[i].Index;
if (this.progCoeffs[compIndex] == null) if (this.progCoeffs[compIndex] == null)
{ {
this.progCoeffs[compIndex] = new Block[mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor]; this.progCoeffs[compIndex] = Block.CreateArray(mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor);
for (int j = 0; j < this.progCoeffs[compIndex].Length; j++) for (int j = 0; j < this.progCoeffs[compIndex].Length; j++)
{ {
this.progCoeffs[compIndex][j] = new Block(); this.progCoeffs[compIndex][j].Init();
} }
} }
} }
@ -1619,7 +1596,7 @@ namespace ImageSharp.Formats
byte expectedRst = JpegConstants.Markers.RST0; byte expectedRst = JpegConstants.Markers.RST0;
// b is the decoded coefficients block, in natural (not zig-zag) order. // b is the decoded coefficients block, in natural (not zig-zag) order.
Block b; //Block b;
int[] dc = new int[MaxComponents]; int[] dc = new int[MaxComponents];
// bx and by are the location of the current block, in units of 8x8 // bx and by are the location of the current block, in units of 8x8
@ -1635,7 +1612,7 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index; int compIndex = scan[i].Index;
int hi = this.componentArray[compIndex].HorizontalFactor; int hi = this.componentArray[compIndex].HorizontalFactor;
int vi = this.componentArray[compIndex].VerticalFactor; int vi = this.componentArray[compIndex].VerticalFactor;
Block qt = this.quantizationTables[this.componentArray[compIndex].Selector];
for (int j = 0; j < hi * vi; j++) for (int j = 0; j < hi * vi; j++)
{ {
@ -1678,168 +1655,27 @@ namespace ImageSharp.Formats
} }
} }
// Load the previous partially decoded coefficients, if applicable. var qtIndex = this.componentArray[compIndex].Selector;
b = this.isProgressive ? this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] : new Block();
if (this.isProgressive) // Load the previous partially decoded coefficients, if applicable.
if (ah != 0)
{ {
this.Refine(b, this.huffmanTrees[AcTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al); blockIndex = ((@by * mxx) * hi) + bx;
ProcessBlockImpl(ah,
ref this.progCoeffs[compIndex][blockIndex],
scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi, bx,
ref this.quantizationTables[qtIndex]
);
} }
else else
{ {
int zig = zigStart; //var b = Block.Create();
if (zig == 0) scanWorkerBlock.Clear();
{
zig++; ProcessBlockImpl(ah, ref scanWorkerBlock, scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi,
bx, ref this.quantizationTables[qtIndex]
// Decode the DC coefficient, as specified in section F.2.2.1. );
byte value = this.DecodeHuffman(this.huffmanTrees[DcTable, scan[i].DcTableSelector]);
if (value > 16) //b.Dispose();
{
throw new ImageFormatException("Excessive DC component");
}
int deltaDC = this.ReceiveExtend(value);
dc[compIndex] += deltaDC;
b[0] = dc[compIndex] << al;
}
if (zig <= zigEnd && this.eobRun > 0)
{
this.eobRun--;
}
else
{
// Decode the AC coefficients, as specified in section F.2.2.2.
Huffman huffv = this.huffmanTrees[AcTable, scan[i].AcTableSelector];
for (; zig <= zigEnd; zig++)
{
byte value = this.DecodeHuffman(huffv);
byte val0 = (byte)(value >> 4);
byte val1 = (byte)(value & 0x0f);
if (val1 != 0)
{
zig += val0;
if (zig > zigEnd)
{
break;
}
int ac = this.ReceiveExtend(val1);
b[Unzig[zig]] = ac << al;
}
else
{
if (val0 != 0x0f)
{
this.eobRun = (ushort)(1 << val0);
if (val0 != 0)
{
this.eobRun |= (ushort)this.DecodeBits(val0);
}
this.eobRun--;
break;
}
zig += 0x0f;
}
}
}
}
if (this.isProgressive)
{
if (zigEnd != Block.BlockSize - 1 || al != 0)
{
// We haven't completely decoded this 8x8 block. Save the coefficients.
this.progCoeffs[compIndex][((by * mxx) * hi) + bx] = b;
// At this point, we could execute the rest of the loop body to dequantize and
// perform the inverse DCT, to save early stages of a progressive image to the
// *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
// the jpeg.Decode function does not return until the entire image is decoded,
// so we "continue" here to avoid wasted computation.
continue;
}
}
// Dequantize, perform the inverse DCT and store the block to the image.
for (int zig = 0; zig < Block.BlockSize; zig++)
{
b[Unzig[zig]] *= qt[zig];
}
IDCT.Transform(b);
byte[] dst;
int offset;
int stride;
if (this.componentCount == 1)
{
dst = this.grayImage.Pixels;
stride = this.grayImage.Stride;
offset = this.grayImage.Offset + (8 * ((by * this.grayImage.Stride) + bx));
}
else
{
switch (compIndex)
{
case 0:
dst = this.ycbcrImage.YChannel;
stride = this.ycbcrImage.YStride;
offset = this.ycbcrImage.YOffset + (8 * ((by * this.ycbcrImage.YStride) + bx));
break;
case 1:
dst = this.ycbcrImage.CbChannel;
stride = this.ycbcrImage.CStride;
offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx));
break;
case 2:
dst = this.ycbcrImage.CrChannel;
stride = this.ycbcrImage.CStride;
offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx));
break;
case 3:
dst = this.blackPixels;
stride = this.blackStride;
offset = 8 * ((by * this.blackStride) + bx);
break;
default:
throw new ImageFormatException("Too many components");
}
}
// Level shift by +128, clip to [0, 255], and write to dst.
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
int yStride = y * stride;
for (int x = 0; x < 8; x++)
{
int c = b[y8 + x];
if (c < -128)
{
c = 0;
}
else if (c > 127)
{
c = 255;
}
else
{
c += 128;
}
dst[yStride + x + offset] = (byte)c;
}
} }
} }
@ -1882,6 +1718,237 @@ namespace ImageSharp.Formats
// for my // for my
} }
private void ProcessBlockImpl(int ah, ref Block b, Scan[] scan, int i, int zigStart, int zigEnd, int al,
int[] dc, int compIndex, int @by, int mxx, int hi, int bx, ref Block qt)
{
if (ah != 0)
{
this.Refine(ref b, ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector], zigStart, zigEnd, 1 << al);
}
else
{
int zig = zigStart;
if (zig == 0)
{
zig++;
// Decode the DC coefficient, as specified in section F.2.2.1.
byte value = this.DecodeHuffman(ref this.huffmanTrees[DcTable * ThRowSize + scan[i].DcTableSelector]);
if (value > 16)
{
throw new ImageFormatException("Excessive DC component");
}
int deltaDC = this.ReceiveExtend(value);
dc[compIndex] += deltaDC;
b[0] = dc[compIndex] << al;
}
if (zig <= zigEnd && this.eobRun > 0)
{
this.eobRun--;
}
else
{
// Decode the AC coefficients, as specified in section F.2.2.2.
//Huffman huffv = ;
for (; zig <= zigEnd; zig++)
{
byte value = this.DecodeHuffman(ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector]);
byte val0 = (byte) (value >> 4);
byte val1 = (byte) (value & 0x0f);
if (val1 != 0)
{
zig += val0;
if (zig > zigEnd)
{
break;
}
int ac = this.ReceiveExtend(val1);
b[Unzig[zig]] = ac << al;
}
else
{
if (val0 != 0x0f)
{
this.eobRun = (ushort) (1 << val0);
if (val0 != 0)
{
this.eobRun |= (ushort) this.DecodeBits(val0);
}
this.eobRun--;
break;
}
zig += 0x0f;
}
}
}
}
if (this.isProgressive)
{
if (zigEnd != Block.BlockSize - 1 || al != 0)
{
// We haven't completely decoded this 8x8 block. Save the coefficients.
this.progCoeffs[compIndex][((@by*mxx)*hi) + bx] = b.Clone();
// At this point, we could execute the rest of the loop body to dequantize and
// perform the inverse DCT, to save early stages of a progressive image to the
// *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
// the jpeg.Decode function does not return until the entire image is decoded,
// so we "continue" here to avoid wasted computation.
return;
}
}
// Dequantize, perform the inverse DCT and store the block to the image.
for (int zig = 0; zig < Block.BlockSize; zig++)
{
b[Unzig[zig]] *= qt[zig];
}
IDCT.Transform(ref b);
// ******* Other experimental variants: *************
// FluxJpeg:
// https://github.com/antonfirsov/ImageSharp/blob/master/src/ImageSharp46/Formats/Jpg/Components/FloatIDCT.cs
// FloatIDCT.Transform(ref b);
// SIMD-based:
// https://github.com/antonfirsov/ImageSharp/blob/master/src/ImageSharp46/Formats/Jpg/Components/MagicDCT.cs
// MagicDCT.IDCT(ref b);
byte[] dst;
int offset;
int stride;
if (this.componentCount == 1)
{
dst = this.grayImage.Pixels;
stride = this.grayImage.Stride;
offset = this.grayImage.Offset + (8*((@by*this.grayImage.Stride) + bx));
}
else
{
switch (compIndex)
{
case 0:
dst = this.ycbcrImage.YChannel;
stride = this.ycbcrImage.YStride;
offset = this.ycbcrImage.YOffset + (8*((@by*this.ycbcrImage.YStride) + bx));
break;
case 1:
dst = this.ycbcrImage.CbChannel;
stride = this.ycbcrImage.CStride;
offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx));
break;
case 2:
dst = this.ycbcrImage.CrChannel;
stride = this.ycbcrImage.CStride;
offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx));
break;
case 3:
dst = this.blackPixels;
stride = this.blackStride;
offset = 8*((@by*this.blackStride) + bx);
break;
default:
throw new ImageFormatException("Too many components");
}
}
// Level shift by +128, clip to [0, 255], and write to dst.
for (int y = 0; y < 8; y++)
{
int y8 = y*8;
int yStride = y*stride;
for (int x = 0; x < 8; x++)
{
int c = b[y8 + x];
if (c < -128)
{
c = 0;
}
else if (c > 127)
{
c = 255;
}
else
{
c += 128;
}
dst[yStride + x + offset] = (byte) c;
}
}
}
private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv)
{
// Component selector.
int cs = this.temp[1 + (2 * i)];
int compIndex = -1;
for (int j = 0; j < this.componentCount; j++)
{
//Component compv = ;
if (cs == this.componentArray[j].Identifier)
{
compIndex = j;
}
}
if (compIndex < 0)
{
throw new ImageFormatException("Unknown component selector");
}
currentScan.Index = (byte)compIndex;
ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]);
}
private void ProcessComponentImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv, ref Component currentComponent)
{
// Section B.2.3 states that "the value of Cs_j shall be different from
// the values of Cs_1 through Cs_(j-1)". Since we have previously
// verified that a frame's component identifiers (C_i values in section
// B.2.2) are unique, it suffices to check that the implicit indexes
// into comp are unique.
for (int j = 0; j < i; j++)
{
if (currentScan.Index == scan[j].Index)
{
throw new ImageFormatException("Repeated component selector");
}
}
totalHv += currentComponent.HorizontalFactor*currentComponent.VerticalFactor;
currentScan.DcTableSelector = (byte) (this.temp[2 + (2*i)] >> 4);
if (currentScan.DcTableSelector > MaxTh)
{
throw new ImageFormatException("Bad DC table selector value");
}
currentScan.AcTableSelector = (byte) (this.temp[2 + (2*i)] & 0x0f);
if (currentScan.AcTableSelector > MaxTh)
{
throw new ImageFormatException("Bad AC table selector value");
}
}
/// <summary> /// <summary>
/// Decodes a successive approximation refinement block, as specified in section G.1.2. /// Decodes a successive approximation refinement block, as specified in section G.1.2.
/// </summary> /// </summary>
@ -1890,7 +1957,7 @@ namespace ImageSharp.Formats
/// <param name="zigStart">The zig-zag start index</param> /// <param name="zigStart">The zig-zag start index</param>
/// <param name="zigEnd">The zig-zag end index</param> /// <param name="zigEnd">The zig-zag end index</param>
/// <param name="delta">The low transform offset</param> /// <param name="delta">The low transform offset</param>
private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta) private void Refine(ref Block b, ref Huffman h, int zigStart, int zigEnd, int delta)
{ {
// Refining a DC component is trivial. // Refining a DC component is trivial.
if (zigStart == 0) if (zigStart == 0)
@ -1917,7 +1984,7 @@ namespace ImageSharp.Formats
{ {
bool done = false; bool done = false;
int z = 0; int z = 0;
byte val = this.DecodeHuffman(h); byte val = this.DecodeHuffman(ref h);
int val0 = val >> 4; int val0 = val >> 4;
int val1 = val & 0x0f; int val1 = val & 0x0f;
@ -2107,7 +2174,8 @@ namespace ImageSharp.Formats
/// <param name="y">The y luminance component.</param> /// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param> /// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param> /// <param name="cr">The cr chroma component.</param>
private void PackYcbCr<TColor, TPacked>(ref TColor packed, byte y, byte cb, byte cr) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PackYcbCr<TColor, TPacked>(ref TColor packed, byte y, byte cb, byte cr)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
@ -2199,5 +2267,24 @@ namespace ImageSharp.Formats
private class EOFException : Exception private class EOFException : Exception
{ {
} }
public void Dispose()
{
scanWorkerBlock.Dispose();
Block.DisposeAll(this.quantizationTables);
foreach (Block[] blocks in progCoeffs)
{
if (blocks != null)
{
Block.DisposeAll(blocks);
}
}
for (int i = 0; i < huffmanTrees.Length; i++)
{
huffmanTrees[i].Dispose();
}
}
} }
} }

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

@ -246,6 +246,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The AC luminance huffman table index /// The AC luminance huffman table index
/// </summary> /// </summary>
LuminanceAC = 1, LuminanceAC = 1,
// ReSharper restore UnusedMember.Local // ReSharper restore UnusedMember.Local
@ -487,9 +488,9 @@ namespace ImageSharp.Formats
/// <param name="index">The quantization table index.</param> /// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param> /// <param name="prevDC">The previous DC value.</param>
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
private int WriteBlock(Block block, QuantIndex index, int prevDC) private int WriteBlock(ref Block block, QuantIndex index, int prevDC)
{ {
FDCT.Transform(block); FDCT.Transform(ref block);
// Emit the DC delta. // Emit the DC delta.
int dc = Round(block[0], 8 * this.quant[(int)index][0]); int dc = Round(block[0], 8 * this.quant[(int)index][0]);
@ -540,7 +541,8 @@ namespace ImageSharp.Formats
/// <param name="cbBlock">The red chroma block.</param> /// <param name="cbBlock">The red chroma block.</param>
/// <param name="crBlock">The blue chroma block.</param> /// <param name="crBlock">The blue chroma block.</param>
// ReSharper disable StyleCop.SA1305 // ReSharper disable StyleCop.SA1305
private void ToYCbCr<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) private void ToYCbCr<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int x, int y,
ref Block yBlock, ref Block cbBlock, ref Block crBlock)
// ReSharper restore StyleCop.SA1305 // ReSharper restore StyleCop.SA1305
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
@ -577,7 +579,7 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="destination">The destination block array</param> /// <param name="destination">The destination block array</param>
/// <param name="source">The source block array.</param> /// <param name="source">The source block array.</param>
private void Scale16X16To8X8(Block destination, Block[] source) private void Scale16X16To8X8(ref Block destination, Block[] source)
{ {
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@ -847,10 +849,9 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
Block b = new Block(); Block b = Block.Create();
Block cb = new Block(); Block cb = Block.Create();
Block cr = new Block(); Block cr = Block.Create();
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@ -858,12 +859,15 @@ namespace ImageSharp.Formats
{ {
for (int x = 0; x < pixels.Width; x += 8) for (int x = 0; x < pixels.Width; x += 8)
{ {
this.ToYCbCr(pixels, x, y, b, cb, cr); this.ToYCbCr(pixels, x, y, ref b, ref cb, ref cr);
prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb); prevDCCb = this.WriteBlock(ref cb, QuantIndex.Chrominance, prevDCCb);
prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr); prevDCCr = this.WriteBlock(ref cr, QuantIndex.Chrominance, prevDCCr);
} }
} }
b.Dispose();
cb.Dispose();
cr.Dispose();
} }
/// <summary> /// <summary>
@ -877,23 +881,12 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
Block b = new Block(); Block b = Block.Create();
Block[] cb = new Block[4]; Block[] cb = Block.CreateArray(4);
Block[] cr = new Block[4]; Block[] cr = Block.CreateArray(4);
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
for (int i = 0; i < 4; i++)
{
cb[i] = new Block();
}
for (int i = 0; i < 4; i++)
{
cr[i] = new Block();
}
for (int y = 0; y < pixels.Height; y += 16) for (int y = 0; y < pixels.Height; y += 16)
{ {
for (int x = 0; x < pixels.Width; x += 16) for (int x = 0; x < pixels.Width; x += 16)
@ -903,16 +896,20 @@ namespace ImageSharp.Formats
int xOff = (i & 1) * 8; int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4; int yOff = (i & 2) * 4;
this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]);
prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
} }
this.Scale16X16To8X8(b, cb); this.Scale16X16To8X8(ref b, cb);
prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); prevDCCb = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCb);
this.Scale16X16To8X8(b, cr); this.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); prevDCCr = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCr);
} }
} }
b.Dispose();
Block.DisposeAll(cb);
Block.DisposeAll(cr);
} }
/// <summary> /// <summary>

2
tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using ImageSharp.Formats;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using System.IO; using System.IO;

85
tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ImageSharp.Formats;
using Xunit;
using Xunit.Abstractions;
namespace ImageSharp.Tests.Formats.Jpg
{
public class JpegTests
{
public const string TestOutputDirectory = "TestOutput/Jpeg";
private ITestOutputHelper Output { get; }
public JpegTests(ITestOutputHelper output)
{
Output = output;
}
protected string CreateTestOutputFile(string fileName)
{
if (!Directory.Exists(TestOutputDirectory))
{
Directory.CreateDirectory(TestOutputDirectory);
}
//string id = Guid.NewGuid().ToString().Substring(0, 4);
string ext = Path.GetExtension(fileName);
fileName = Path.GetFileNameWithoutExtension(fileName);
return $"{TestOutputDirectory}/{fileName}{ext}";
}
protected Stream CreateOutputStream(string fileName)
{
fileName = CreateTestOutputFile(fileName);
Output?.WriteLine("Opened for write: "+fileName);
return File.OpenWrite(fileName);
}
public static IEnumerable<object[]> AllJpegFiles
=> TestImages.Jpeg.All.Select(fn => new object[] {fn});
[Theory]
[MemberData(nameof(AllJpegFiles))]
public void OpenJpeg_SaveBmp(string jpegPath)
{
string bmpFileName = Path.GetFileNameWithoutExtension(jpegPath) + ".bmp";
using (var inputStream = File.OpenRead(jpegPath))
{
var image = new Image(inputStream);
using (var outputStream = CreateOutputStream(bmpFileName))
{
image.Save(outputStream, new BmpFormat());
}
}
}
public static IEnumerable<object[]> AllBmpFiles
=> TestImages.Jpeg.All.Select(fn => new object[] {fn});
[Theory]
[MemberData(nameof(AllBmpFiles))]
public void OpenBmp_SaveJpeg(string bmpPath)
{
string jpegPath = Path.GetFileNameWithoutExtension(bmpPath) + ".jpeg";
using (var inputStream = File.OpenRead(bmpPath))
{
var image = new Image(inputStream);
using (var outputStream = CreateOutputStream(jpegPath))
{
image.Save(outputStream, new JpegFormat());
}
}
}
}
}

2
tests/ImageSharp.Tests/Formats/Png/PngTests.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using ImageSharp.Formats;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using System.IO; using System.IO;

9
tests/ImageSharp.Tests/TestImages.cs

@ -25,13 +25,20 @@ namespace ImageSharp.Tests
{ {
private static readonly string folder = "TestImages/Formats/Jpg/"; private static readonly string folder = "TestImages/Formats/Jpg/";
public static string Cmyk => folder + "cmyk.jpg"; public static string Cmyk => folder + "cmyk.jpg";
public static string Exif => folder + "exif.jpeg"; public static string Exif => folder + "exif.jpg";
public static string Floorplan => folder + "Floorplan.jpeg"; public static string Floorplan => folder + "Floorplan.jpeg";
public static string Calliphora => folder + "Calliphora.jpg"; public static string Calliphora => folder + "Calliphora.jpg";
public static string Turtle => folder + "turtle.jpg"; public static string Turtle => folder + "turtle.jpg";
public static string Fb => folder + "fb.jpg"; public static string Fb => folder + "fb.jpg";
public static string Progress => folder + "progress.jpg"; public static string Progress => folder + "progress.jpg";
public static string GammaDalaiLamaGray => folder + "gamma_dalai_lama_gray.jpg"; public static string GammaDalaiLamaGray => folder + "gamma_dalai_lama_gray.jpg";
public static string Geneserath => folder + "geneserath.jpg";
public static readonly string[] All = new[]
{
Cmyk, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray, Geneserath
};
} }
public static class Bmp public static class Bmp

3
tests/ImageSharp.Tests/TestImages/Formats/Jpg/geneserath.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c71e5b1ba181a5b17ba9814c2025650de592efabf4062bd77baa3c8e774df007
size 223841
Loading…
Cancel
Save