diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index 22f46979a3..fb607752a9 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -340,6 +340,7 @@ </Patterns> True True + AC DC FDCT IDCT diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 4aace549d4..c29591b6a5 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs @@ -319,5 +319,53 @@ namespace ImageSharp.Formats src += 8; } } + + /// + /// Unzig the elements of src into dest, while dividing them by elements of qt and rounding the values + /// + /// Source block + /// Destination block + /// Quantization table + /// Pointer to elements + [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; + } + } + + /// + /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block. + /// + /// The destination block. + /// The source block. + 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; + } + } + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index ca2090beed..c8f265ddff 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs @@ -62,20 +62,7 @@ namespace ImageSharp.Formats /// The AC table index /// private const int AcTable = 1; - - /// - /// 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). - /// - 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, - }; - + /// /// The component array /// @@ -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 /// The zig-zag end index /// The non-zero entry /// The low transform offset + /// Pointer to the Jpeg Unzig data (data part of ) /// The - 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? diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index c1c4e9b57a..548f237b75 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/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; + /// /// Image encoder for writing an image to a stream as a jpeg. /// @@ -18,20 +20,8 @@ namespace ImageSharp.Formats /// private const int QuantizationTableCount = 2; - /// - /// 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). - /// - 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 + /// /// 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 /// /// Counts the number of bits needed to hold an integer. /// - 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. /// - private readonly byte[,] unscaledQuant = + private static readonly byte[,] UnscaledQuant = { { // Luminance. @@ -180,7 +167,10 @@ namespace ImageSharp.Formats /// /// The scaled quantization tables, in zig-zag order. /// - 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; /// /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: @@ -251,20 +241,26 @@ namespace ImageSharp.Formats /// LuminanceDC = 0, + // ReSharper disable UnusedMember.Local + /// /// The AC luminance huffman table index /// + LuminanceAC = 1, + /// /// The DC chrominance huffman table index /// ChrominanceDC = 2, - + /// /// The AC chrominance huffman table index /// ChrominanceAC = 3, + + // ReSharper restore UnusedMember.Local } /// @@ -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; + } + } + /// /// Encode writes the image to the jpeg baseline format with the given options. /// @@ -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(); } - - /// - /// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero. - /// - /// The value to divide. - /// The value to divide by. - /// The - private static int Round(int dividend, int divisor) - { - if (dividend >= 0) - { - return (dividend + (divisor >> 1)) / divisor; - } - - return -((-dividend + (divisor >> 1)) / divisor); - } - + /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits < 1<<nBits && nBits <= 16. @@ -444,6 +428,7 @@ namespace ImageSharp.Formats /// /// The index of the Huffman encoder /// The value to encode. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EmitHuff(HuffIndex index, int value) { uint x = TheHuffmanLut[(int)index].Values[value]; @@ -456,6 +441,7 @@ namespace ImageSharp.Formats /// The index of the Huffman encoder /// The number of copies to encode. /// The value to encode. + [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. /// - /// The block to write. /// The quantization table index. /// The previous DC value. + /// Source block + /// Temporal block to be used as FDCT Destination + /// Temporal block 2 + /// Quantization table + /// The 8x8 Unzig block ptr /// The - 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 /// The luminance block. /// The red chroma block. /// The blue chroma block. - private void ToYCbCr(PixelAccessor pixels, int x, int y, ref Block yBlock, ref Block cbBlock, ref Block crBlock) + private static void ToYCbCr( + PixelAccessor pixels, + int x, + int y, + Block8x8F* yBlock, + Block8x8F* cbBlock, + Block8x8F* crBlock) where TColor : struct, IPackedPixel, IEquatable { + float* yBlockRaw = (float*)yBlock; + float* cbBlockRaw = (float*)cbBlock; + float* crBlockRaw = (float*)crBlock; + + PixelAccessor asStandardColorAccessor = pixels as PixelAccessor; + 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; } } } - /// - /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 - /// DST block. - /// - /// The destination block array - /// The source block array. - private void Scale16X16To8X8(ref Block destination, Block[] source) + + + private static void ColorRGBToYCbCr( + PixelAccessor 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); + + } + + /// /// Writes the application header containing the JFIF identifier plus extra data. /// @@ -641,7 +686,7 @@ namespace ImageSharp.Formats /// The image. /// The pixel format. private void WriteProfiles(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPackedPixel, IEquatable { 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]; + } + } + /// /// Writes the Start Of Frame (Baseline) marker /// @@ -842,27 +890,37 @@ namespace ImageSharp.Formats private void Encode444(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - 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]; } /// @@ -874,12 +932,25 @@ namespace ImageSharp.Formats private void Encode420(PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable { - 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); } /// @@ -956,6 +1045,11 @@ namespace ImageSharp.Formats /// private class HuffmanLut { + /// + /// The collection of huffman values. + /// + public uint[] Values { get; } + /// /// Initializes a new instance of the class. /// @@ -990,11 +1084,6 @@ namespace ImageSharp.Formats code <<= 1; } } - - /// - /// Gets the collection of huffman values. - /// - public uint[] Values { get; } } } } diff --git a/src/ImageSharp/Formats/Jpg/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/JpegUtils.cs new file mode 100644 index 0000000000..564ab8655f --- /dev/null +++ b/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(PixelArea area, int fromX, int fromY) + where TColor : struct, IPackedPixel, IEquatable + { + 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); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/UnzigData.cs b/src/ImageSharp/Formats/Jpg/UnzigData.cs new file mode 100644 index 0000000000..6dae6d9421 --- /dev/null +++ b/src/ImageSharp/Formats/Jpg/UnzigData.cs @@ -0,0 +1,38 @@ +namespace ImageSharp.Formats +{ + using System; + using System.Runtime.InteropServices; + + /// + /// 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). + /// + 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; + } + + /// + /// 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). + /// + 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, + }; + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 8939d2c207..7d66f6347a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/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; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index 006e959633..860c1b55ed 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/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; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs index 20a9d34b18..6191d221cc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs +++ b/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; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 8a8db3ee89..be5559e2a6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/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) diff --git a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs index 265d6b5f23..e30baddfc8 100644 --- a/tests/ImageSharp.Tests46/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/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;