Browse Source

finished re-applying existing encoder optimizations

pull/58/head
Anton Firszov 10 years ago
parent
commit
111ab7927e
  1. 1
      ImageSharp.sln.DotSettings
  2. 48
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
  3. 31
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  4. 375
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  5. 53
      src/ImageSharp/Formats/Jpg/JpegUtils.cs
  6. 38
      src/ImageSharp/Formats/Jpg/UnzigData.cs
  7. 2
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  8. 2
      tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs
  9. 2
      tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs
  10. 2
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  11. 3
      tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs

1
ImageSharp.sln.DotSettings

@ -340,6 +340,7 @@
&lt;/Patterns&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/QualifiedUsingAtNestedScope/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DC/@EntryIndexedValue">DC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FDCT/@EntryIndexedValue">FDCT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IDCT/@EntryIndexedValue">IDCT</s:String>

48
src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs

@ -319,5 +319,53 @@ namespace ImageSharp.Formats
src += 8;
}
}
/// <summary>
/// Unzig the elements of src into dest, while dividing them by elements of qt and rounding the values
/// </summary>
/// <param name="src">Source block</param>
/// <param name="dest">Destination block</param>
/// <param name="qt">Quantization table</param>
/// <param name="unzigPtr">Pointer to <see cref="UnzigData"/> elements</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void UnZigDivRound(Block8x8F* src, Block8x8F* dest, Block8x8F* qt, int* unzigPtr)
{
float* s = (float*)src;
float* d = (float*)dest;
float* q = (float*)qt;
for (int zig = 0; zig < ScalarCount; zig++)
{
float val = s[unzigPtr[zig]] / q[zig];
val = (int)val;
d[zig] = val;
}
}
/// <summary>
/// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block.
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="source">The source block.</param>
public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source)
{
float* d = (float*)destination;
for (int i = 0; i < 4; i++)
{
int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
float* iSource = (float*)(source + i);
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
int j = (16 * y) + (2 * x);
float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
d[(8 * y) + x + dstOff] = (sum + 2) / 4;
}
}
}
}
}
}

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

@ -62,20 +62,7 @@ namespace ImageSharp.Formats
/// The AC table index
/// </summary>
private const int AcTable = 1;
/// <summary>
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zigzag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
private static readonly int[] Unzig =
{
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33,
40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50,
43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
};
/// <summary>
/// The component array
/// </summary>
@ -1556,10 +1543,9 @@ namespace ImageSharp.Formats
Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F);
// Tricky way to copy contents of the Unzig static variable to the stack:
StackallocUnzigData unzigOnStack = default(StackallocUnzigData);
int* unzigPtr = unzigOnStack.Data;
Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
UnzigData unzig = UnzigData.Create();
int* unzigPtr = unzig.Data;
for (int my = 0; my < myy; my++)
{
@ -2013,7 +1999,7 @@ namespace ImageSharp.Formats
int blah = zig;
zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta);
zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta, unzigPtr);
if (zig > zigEnd)
{
throw new ImageFormatException($"Too many coefficients {zig} > {zigEnd}");
@ -2030,7 +2016,7 @@ namespace ImageSharp.Formats
if (this.eobRun > 0)
{
this.eobRun--;
this.RefineNonZeroes(b, zig, zigEnd, -1, delta);
this.RefineNonZeroes(b, zig, zigEnd, -1, delta, unzigPtr);
}
}
@ -2043,12 +2029,13 @@ namespace ImageSharp.Formats
/// <param name="zigEnd">The zig-zag end index</param>
/// <param name="nz">The non-zero entry</param>
/// <param name="delta">The low transform offset</param>
/// <param name="unzigPtr">Pointer to the Jpeg Unzig data (data part of <see cref="UnzigData"/>)</param>
/// <returns>The <see cref="int"/></returns>
private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta)
private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta, int* unzigPtr)
{
for (; zig <= zigEnd; zig++)
{
int u = Unzig[zig];
int u = unzigPtr[zig];
float bu = Block8x8F.GetScalarAt(b, u);
// TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary?

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

@ -7,7 +7,9 @@ namespace ImageSharp.Formats
{
using System;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Image encoder for writing an image to a stream as a jpeg.
/// </summary>
@ -18,20 +20,8 @@ namespace ImageSharp.Formats
/// </summary>
private const int QuantizationTableCount = 2;
/// <summary>
/// Maps from the zig-zag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zig-zag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
private static readonly int[] Unzig =
{
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26,
33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57,
50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31,
39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
};
#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines
/// <summary>
/// The Huffman encoding specifications.
/// This encoder uses the same Huffman encoding for all images.
@ -44,10 +34,7 @@ namespace ImageSharp.Formats
{
0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }),
new HuffmanSpec(
new byte[]
{
@ -122,7 +109,7 @@ namespace ImageSharp.Formats
/// <summary>
/// Counts the number of bits needed to hold an integer.
/// </summary>
private readonly byte[] bitCountLut =
private static readonly uint[] BitCountLut =
{
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
@ -143,7 +130,7 @@ namespace ImageSharp.Formats
/// The values are derived from section K.1 after converting from natural to
/// zig-zag order.
/// </summary>
private readonly byte[,] unscaledQuant =
private static readonly byte[,] UnscaledQuant =
{
{
// Luminance.
@ -180,7 +167,10 @@ namespace ImageSharp.Formats
/// <summary>
/// The scaled quantization tables, in zig-zag order.
/// </summary>
private readonly byte[][] quant = new byte[QuantizationTableCount][];
//private readonly float[][] quant = new float[QuantizationTableCount][];
//private readonly float[] quant = new float[QuantizationTableCount* Block8x8F.ScalarCount];
private Block8x8F luminanceQuantTable;
private Block8x8F chrominanceQuantTable;
/// <summary>
/// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
@ -251,20 +241,26 @@ namespace ImageSharp.Formats
/// </summary>
LuminanceDC = 0,
// ReSharper disable UnusedMember.Local
/// <summary>
/// The AC luminance huffman table index
/// </summary>
LuminanceAC = 1,
/// <summary>
/// The DC chrominance huffman table index
/// </summary>
ChrominanceDC = 2,
/// <summary>
/// The AC chrominance huffman table index
/// </summary>
ChrominanceAC = 3,
// ReSharper restore UnusedMember.Local
}
/// <summary>
@ -283,6 +279,26 @@ namespace ImageSharp.Formats
Chrominance = 1,
}
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
{
for (int j = 0; j < Block8x8F.ScalarCount; j++)
{
int x = UnscaledQuant[i, j];
x = ((x * scale) + 50) / 100;
if (x < 1)
{
x = 1;
}
if (x > 255)
{
x = 255;
}
quant[j] = x;
}
}
/// <summary>
/// Encode writes the image to the jpeg baseline format with the given options.
/// </summary>
@ -306,10 +322,10 @@ namespace ImageSharp.Formats
this.outputStream = stream;
this.subsample = sample;
for (int i = 0; i < QuantizationTableCount; i++)
{
this.quant[i] = new byte[Block.BlockSize];
}
//for (int i = 0; i < QuantizationTableCount; i++)
//{
// this.quant[i] = new float[];
//}
if (quality < 1)
{
@ -333,26 +349,10 @@ namespace ImageSharp.Formats
}
// Initialize the quantization tables.
for (int i = 0; i < QuantizationTableCount; i++)
{
for (int j = 0; j < Block.BlockSize; j++)
{
int x = this.unscaledQuant[i, j];
x = ((x * scale) + 50) / 100;
if (x < 1)
{
x = 1;
}
if (x > 255)
{
x = 255;
}
this.quant[i][j] = (byte)x;
}
}
InitQuantizationTable(0, scale, ref this.luminanceQuantTable);
InitQuantizationTable(1, scale, ref this.chrominanceQuantTable);
// Compute number of components based on input image type.
int componentCount = 3;
@ -382,23 +382,7 @@ namespace ImageSharp.Formats
stream.Write(this.buffer, 0, 2);
stream.Flush();
}
/// <summary>
/// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero.
/// </summary>
/// <param name="dividend">The value to divide.</param>
/// <param name="divisor">The value to divide by.</param>
/// <returns>The <see cref="int"/></returns>
private static int Round(int dividend, int divisor)
{
if (dividend >= 0)
{
return (dividend + (divisor >> 1)) / divisor;
}
return -((-dividend + (divisor >> 1)) / divisor);
}
/// <summary>
/// Emits the least significant count of bits of bits to the bit-stream.
/// The precondition is bits <example>&lt; 1&lt;&lt;nBits &amp;&amp; nBits &lt;= 16</example>.
@ -444,6 +428,7 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="value">The value to encode.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuff(HuffIndex index, int value)
{
uint x = TheHuffmanLut[(int)index].Values[value];
@ -456,6 +441,7 @@ namespace ImageSharp.Formats
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="runLength">The number of copies to encode.</param>
/// <param name="value">The value to encode.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuffRLE(HuffIndex index, int runLength, int value)
{
int a = value;
@ -469,11 +455,11 @@ namespace ImageSharp.Formats
uint bt;
if (a < 0x100)
{
bt = this.bitCountLut[a];
bt = BitCountLut[a];
}
else
{
bt = 8 + (uint)this.bitCountLut[a >> 8];
bt = 8 + BitCountLut[a >> 8];
}
this.EmitHuff(index, (int)((uint)(runLength << 4) | bt));
@ -488,25 +474,39 @@ namespace ImageSharp.Formats
/// returning the post-quantized DC value of the DCT-transformed block.
/// The block is in natural (not zig-zag) order.
/// </summary>
/// <param name="block">The block to write.</param>
/// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param>
/// <param name="src">Source block</param>
/// <param name="tempDest">Temporal block to be used as FDCT Destination</param>
/// <param name="temp2">Temporal block 2</param>
/// <param name="quant">Quantization table</param>
/// <param name="unzigPtr">The 8x8 Unzig block ptr</param>
/// <returns>The <see cref="int"/></returns>
private int WriteBlock(ref Block block, QuantIndex index, int prevDC)
private float WriteBlock(QuantIndex index, float prevDC, Block8x8F* src, Block8x8F* tempDest, Block8x8F* temp2, Block8x8F* quant, int* unzigPtr)
{
FDCT.Transform(ref block);
DCT.TransformFDCT(ref *src, ref *tempDest, ref *temp2);
Block8x8F.UnZigDivRound(tempDest, temp2, quant, unzigPtr);
//Block8x8F.RoundAll(tempDest);
float* d = (float*)temp2;
float* q = (float*)quant;
// Emit the DC delta.
int dc = Round(block[0], 8 * this.quant[(int)index][0]);
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC);
//float dc = Round(d[0], q[0]);
float dc = d[0];
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, (int)(dc - prevDC));
// Emit the AC components.
HuffIndex h = (HuffIndex)((2 * (int)index) + 1);
int runLength = 0;
for (int zig = 1; zig < Block.BlockSize; zig++)
{
int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]);
//float ac = Round(d[unzigPtr[zig]], q[zig]);
float ac = d[zig];
if (ac == 0)
{
@ -520,7 +520,7 @@ namespace ImageSharp.Formats
runLength -= 16;
}
this.EmitHuffRLE(h, runLength, ac);
this.EmitHuffRLE(h, runLength, (int)ac);
runLength = 0;
}
}
@ -543,58 +543,103 @@ namespace ImageSharp.Formats
/// <param name="yBlock">The luminance block.</param>
/// <param name="cbBlock">The red chroma block.</param>
/// <param name="crBlock">The blue chroma block.</param>
private void ToYCbCr<TColor>(PixelAccessor<TColor> pixels, int x, int y, ref Block yBlock, ref Block cbBlock, ref Block crBlock)
private static void ToYCbCr<TColor>(
PixelAccessor<TColor> pixels,
int x,
int y,
Block8x8F* yBlock,
Block8x8F* cbBlock,
Block8x8F* crBlock)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
float* yBlockRaw = (float*)yBlock;
float* cbBlockRaw = (float*)cbBlock;
float* crBlockRaw = (float*)crBlock;
PixelAccessor<Color> asStandardColorAccessor = pixels as PixelAccessor<Color>;
if (asStandardColorAccessor != null)
{
ColorRGBToYCbCr(asStandardColorAccessor, x, y, yBlockRaw, cbBlockRaw, crBlockRaw);
return;
}
Vector4 maxBytes = new Vector4(255f);
Vector4 half = new Vector4(0.5f);
int xmax = pixels.Width - 1;
int ymax = pixels.Height - 1;
byte[] color = new byte[3];
for (int j = 0; j < 8; j++)
{
int j8 = j * 8;
for (int i = 0; i < 8; i++)
{
pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToBytes(color, 0, ComponentOrder.XYZ);
Vector4 v = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToVector4();
v = v * maxBytes + half;
// Convert returned bytes into the YCbCr color space. Assume RGBA
float yy = ((0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z));
float cb = (128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z)));
float cr = (128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z)));
byte r = color[0];
byte g = color[1];
byte b = color[2];
int index = j8 + i;
// Convert returned bytes into the YCbCr color space. Assume RGBA
byte yy = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b));
byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)));
byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)));
int index = (8 * j) + i;
yBlock[index] = yy;
cbBlock[index] = cb;
crBlock[index] = cr;
yBlockRaw[index] = yy;
cbBlockRaw[index] = cb;
crBlockRaw[index] = cr;
}
}
}
/// <summary>
/// Scales the 16x16 region represented by the 4 source blocks to the 8x8
/// DST block.
/// </summary>
/// <param name="destination">The destination block array</param>
/// <param name="source">The source block array.</param>
private void Scale16X16To8X8(ref Block destination, Block[] source)
private static void ColorRGBToYCbCr(
PixelAccessor<Color> pixels,
int x,
int y,
float* yBlockRaw,
float* cbBlockRaw,
float* crBlockRaw)
{
for (int i = 0; i < 4; i++)
int colorSize = sizeof(Color);
int xmax = pixels.Width - 1;
int ymax = pixels.Height - 1;
byte* data = (byte*)pixels.DataPointer;
for (int j = 0; j < 8; j++)
{
int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
for (int y = 0; y < 4; y++)
int yPos = Math.Min(y + j, ymax);
int j8 = j * 8;
for (int i = 0; i < 8; i++)
{
for (int x = 0; x < 4; x++)
{
int j = (16 * y) + (2 * x);
int sum = source[i][j] + source[i][j + 1] + source[i][j + 8] + source[i][j + 9];
destination[(8 * y) + x + dstOff] = (sum + 2) / 4;
}
int xPos = Math.Min(x + i, xmax);
byte* dataPos = data + (((yPos * pixels.Width) + xPos) * colorSize);
Vector3 v = new Vector3(dataPos[0], dataPos[1], dataPos[2]);
// Convert returned bytes into the YCbCr color space. Assume RGBA
float yy = ((0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z));
float cb = (128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z)));
float cr = (128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z)));
int index = j8 + i;
yBlockRaw[index] = yy;
cbBlockRaw[index] = cb;
crBlockRaw[index] = cr;
}
}
//(((y * pixels.Width) + x) * colorSize);
}
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// </summary>
@ -641,7 +686,7 @@ namespace ImageSharp.Formats
/// <param name="image">The image.</param>
/// <typeparam name="TColor">The pixel format.</typeparam>
private void WriteProfiles<TColor>(Image<TColor> image)
where TColor : struct, IPackedPixel, IEquatable<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
this.WriteProfile(image.ExifProfile);
}
@ -691,19 +736,22 @@ namespace ImageSharp.Formats
// This allows us to reduce the number of writes to the stream.
byte[] dqt = new byte[(QuantizationTableCount * Block.BlockSize) + QuantizationTableCount];
int offset = 0;
for (int i = 0; i < QuantizationTableCount; i++)
{
dqt[offset++] = (byte)i;
int len = this.quant[i].Length;
for (int j = 0; j < len; j++)
{
dqt[offset++] = this.quant[i][j];
}
}
WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable);
WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable);
this.outputStream.Write(dqt, 0, dqt.Length);
}
private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q)
{
dqt[offset++] = (byte)i;
for (int j = 0; j < Block8x8F.ScalarCount; j++)
{
dqt[offset++] = (byte)q[j];
}
}
/// <summary>
/// Writes the Start Of Frame (Baseline) marker
/// </summary>
@ -842,27 +890,37 @@ namespace ImageSharp.Formats
private void Encode444<TColor>(PixelAccessor<TColor> pixels)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
Block b = Block.Create();
Block cb = Block.Create();
Block cr = Block.Create();
Block8x8F b = new Block8x8F();
Block8x8F cb = new Block8x8F();
Block8x8F cr = new Block8x8F();
Block8x8F temp1 = new Block8x8F();
Block8x8F temp2 = new Block8x8F();
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
UnzigData unzig = UnzigData.Create();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
float prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
for (int y = 0; y < pixels.Height; y += 8)
{
for (int x = 0; x < pixels.Width; x += 8)
{
this.ToYCbCr(pixels, x, y, ref b, ref cb, ref cr);
prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
prevDCCb = this.WriteBlock(ref cb, QuantIndex.Chrominance, prevDCCb);
prevDCCr = this.WriteBlock(ref cr, QuantIndex.Chrominance, prevDCCr);
ToYCbCr(pixels, x, y, &b, &cb, &cr);
prevDCY = this.WriteBlock(QuantIndex.Luminance, prevDCY, &b, &temp1, &temp2, &onStackLuminanceQuantTable, unzig.Data);
prevDCCb = this.WriteBlock(QuantIndex.Chrominance, prevDCCb, &cb, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data);
prevDCCr = this.WriteBlock(QuantIndex.Chrominance, prevDCCr, &cr, &temp1, &temp2, &onStackChrominanceQuantTable, unzig.Data);
}
}
}
b.Dispose();
cb.Dispose();
cr.Dispose();
struct BlockQuad
{
public fixed float Data[4*Block8x8F.ScalarCount];
}
/// <summary>
@ -874,12 +932,25 @@ namespace ImageSharp.Formats
private void Encode420<TColor>(PixelAccessor<TColor> pixels)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
Block b = Block.Create();
Block[] cb = Block.CreateArray(4);
Block[] cr = Block.CreateArray(4);
Block8x8F b = new Block8x8F();
BlockQuad cb = new BlockQuad();
BlockQuad cr = new BlockQuad();
Block8x8F* cbPtr = (Block8x8F*)cb.Data;
Block8x8F* crPtr = (Block8x8F*)cr.Data;
Block8x8F temp1 = new Block8x8F();
Block8x8F temp2 = new Block8x8F();
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
UnzigData unzig = UnzigData.Create();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
float prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
for (int y = 0; y < pixels.Height; y += 16)
{
@ -890,20 +961,38 @@ namespace ImageSharp.Formats
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]);
prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
ToYCbCr(pixels, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
&b,
&temp1,
&temp2,
&onStackLuminanceQuantTable,
unzig.Data);
}
this.Scale16X16To8X8(ref b, cb);
prevDCCb = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCb);
this.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCr);
Block8x8F.Scale16X16To8X8(&b, cbPtr);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
Block8x8F.Scale16X16To8X8(&b, crPtr);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
}
}
b.Dispose();
Block.DisposeAll(cb);
Block.DisposeAll(cr);
}
/// <summary>
@ -956,6 +1045,11 @@ namespace ImageSharp.Formats
/// </summary>
private class HuffmanLut
{
/// <summary>
/// The collection of huffman values.
/// </summary>
public uint[] Values { get; }
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanLut"/> class.
/// </summary>
@ -990,11 +1084,6 @@ namespace ImageSharp.Formats
code <<= 1;
}
}
/// <summary>
/// Gets the collection of huffman values.
/// </summary>
public uint[] Values { get; }
}
}
}

53
src/ImageSharp/Formats/Jpg/JpegUtils.cs

@ -0,0 +1,53 @@
namespace ImageSharp.Formats
{
using System;
using System.Runtime.CompilerServices;
internal static unsafe class JpegUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void CopyRgb(byte* source, byte* dest)
{
*dest++ = *source++; // R
*dest++ = *source++; // G
*dest = *source; // B
}
internal static unsafe void RepeatPixelsBottomRight<TColor>(PixelArea<TColor> area, int fromX, int fromY)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
if (fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height)
{
throw new InvalidOperationException();
}
for (int y = 0; y < fromY; y++)
{
byte* ptrBase = (byte*)area.DataPointer + y * area.RowByteCount;
for (int x = fromX; x < area.Width; x++)
{
byte* prevPtr = ptrBase + (x - 1) * 3;
byte* currPtr = ptrBase + x * 3;
CopyRgb(prevPtr, currPtr);
}
}
for (int y = fromY; y < area.Height; y++)
{
byte* currBase = (byte*)area.DataPointer + y * area.RowByteCount;
byte* prevBase = (byte*)area.DataPointer + (y - 1) * area.RowByteCount;
for (int x = 0; x < area.Width; x++)
{
int x3 = 3 * x;
byte* currPtr = currBase + x3;
byte* prevPtr = prevBase + x3;
CopyRgb(prevPtr, currPtr);
}
}
}
}
}

38
src/ImageSharp/Formats/Jpg/UnzigData.cs

@ -0,0 +1,38 @@
namespace ImageSharp.Formats
{
using System;
using System.Runtime.InteropServices;
/// <summary>
/// Holds the Jpeg UnZig array in a value/stack type.
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zigzag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
internal unsafe struct UnzigData
{
internal fixed int Data[64];
public static UnzigData Create()
{
UnzigData result = new UnzigData();
int* unzigPtr = result.Data;
Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
return result;
}
/// <summary>
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zigzag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
private static readonly int[] Unzig =
{
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33,
40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50,
43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
};
}
}

2
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -3,7 +3,7 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
namespace ImageSharp.Tests
{
using System.Diagnostics;
using System.Numerics;

2
tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs

@ -1,6 +1,6 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
namespace ImageSharp.Tests
{
using System;
using System.Numerics;

2
tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs

@ -3,7 +3,7 @@ using ImageSharp.Formats;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
namespace ImageSharp.Tests
{
using System;
using System.Collections.Generic;

2
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs

@ -80,7 +80,7 @@ namespace ImageSharp.Tests
this.Utility = new ImagingTestCaseUtility()
{
SourceFileOrDescription = this.SourceFileOrDescription,
PixelTypeName = typeof(TColor).Name
PixelTypeName = this.PixelType.ToString()
};
if (testMethod != null)

3
tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs

@ -1,8 +1,9 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Formats.Jpg
namespace ImageSharp.Tests
{
using System.Numerics;
using ImageSharp.Formats;
using Xunit;
using Xunit.Abstractions;

Loading…
Cancel
Save