diff --git a/src/ImageSharp/Formats/Jpg/Components/Bits.cs b/src/ImageSharp/Formats/Jpg/Components/Bits.cs
index 68278e69bf..975c92aee0 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Bits.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Bits.cs
@@ -5,27 +5,92 @@
namespace ImageSharp.Formats
{
+ using System.Runtime.CompilerServices;
+
///
/// Holds the unprocessed bits that have been taken from the byte-stream.
/// The n least significant bits of a form the unread bits, to be read in MSB to
/// LSB order.
///
- internal class Bits
+ internal struct Bits
{
///
/// Gets or sets the accumulator.
///
- public uint Accumulator { get; set; }
+ public uint Accumulator;
///
/// Gets or sets the mask.
/// 0, with mask==0 when unreadbits==0.]]>
///
- public uint Mask { get; set; }
+ public uint Mask;
///
/// Gets or sets the number of unread bits in the accumulator.
///
- public int UnreadBits { get; set; }
+ public int UnreadBits;
+
+ ///
+ /// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
+ /// least n. For best performance (avoiding function calls inside hot loops),
+ /// the caller is the one responsible for first checking that bits.UnreadBits < n.
+ ///
+ /// The number of bits to ensure.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal JpegDecoderCore.ErrorCodes EnsureNBits(int n, JpegDecoderCore decoder)
+ {
+ while (true)
+ {
+ JpegDecoderCore.ErrorCodes errorCode;
+
+ byte c = decoder.bytes.ReadByteStuffedByte(decoder.inputStream, out errorCode);
+
+ if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
+ {
+ return errorCode;
+ }
+
+ this.Accumulator = (this.Accumulator << 8) | c;
+ this.UnreadBits += 8;
+ if (this.Mask == 0)
+ {
+ this.Mask = 1 << 7;
+ }
+ else
+ {
+ this.Mask <<= 8;
+ }
+
+ if (this.UnreadBits >= n)
+ {
+ return JpegDecoderCore.ErrorCodes.NoError;
+ }
+ }
+ }
+
+ internal int ReceiveExtend(byte t, JpegDecoderCore decoder)
+ {
+ if (this.UnreadBits < t)
+ {
+ var errorCode = this.EnsureNBits(t, decoder);
+ if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
+ {
+ throw new JpegDecoderCore.MissingFF00Exception();
+ }
+ }
+
+ this.UnreadBits -= t;
+ this.Mask >>= t;
+ int s = 1 << t;
+ int x = (int)((this.Accumulator >> this.UnreadBits) & (s - 1));
+
+ if (x < (s >> 1))
+ {
+ x += ((-1) << t) + 1;
+ }
+
+ return x;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Block.cs b/src/ImageSharp/Formats/Jpg/Components/Block.cs
index 8bc5a861ca..4d4ad4d81e 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Block.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Block.cs
@@ -2,14 +2,19 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageSharp.Formats
{
+ using System;
+ using System.Buffers;
+ using System.Runtime.CompilerServices;
+
///
/// Represents an 8x8 block of coefficients to transform and encode.
///
- internal class Block
+ internal struct Block : IDisposable
{
+ private static readonly ArrayPool ArrayPool = ArrayPool.Create(BlockSize, 50);
+
///
/// Gets the size of the block.
///
@@ -18,16 +23,137 @@ namespace ImageSharp.Formats
///
/// The array of block data.
///
- private readonly int[] data;
+ public int[] Data;
+
+ public void Init()
+ {
+ // this.Data = new int[BlockSize];
+ this.Data = ArrayPool.Rent(BlockSize);
+ }
+
+ public static Block Create()
+ {
+ 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;
///
- /// Initializes a new instance of the class.
+ /// Gets the pixel data at the given block index.
///
- public Block()
+ /// The index of the data to return.
+ ///
+ /// The .
+ ///
+ public int this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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 (this.Data != null)
+ {
+ ArrayPool.Return(this.Data, true);
+ this.Data = null;
+ }
+ }
+
+ public static void DisposeAll(Block[] blocks)
{
- this.data = new int[BlockSize];
+ for (int i = 0; i < blocks.Length; i++)
+ {
+ blocks[i].Dispose();
+ }
}
+ public void Clear()
+ {
+ for (int i = 0; i < this.Data.Length; i++)
+ {
+ this.Data[i] = 0;
+ }
+ }
+
+ public Block Clone()
+ {
+ Block clone = Create();
+ Array.Copy(this.Data, clone.Data, BlockSize);
+ return clone;
+ }
+ }
+
+ ///
+ /// TODO: Should be removed, when JpegEncoderCore is refactored to use Block8x8F
+ /// Temporal class to make refactoring easier.
+ /// 1. Refactor Block -> BlockF
+ /// 2. Test
+ /// 3. Refactor BlockF -> Block8x8F
+ ///
+ internal struct BlockF : IDisposable
+ {
+ private static readonly ArrayPool ArrayPool = ArrayPool.Create(BlockSize, 50);
+
+ ///
+ /// Size of the block.
+ ///
+ public const int BlockSize = 64;
+
+ ///
+ /// The array of block data.
+ ///
+ public float[] Data;
+
+ public void Init()
+ {
+ // this.Data = new int[BlockSize];
+ this.Data = ArrayPool.Rent(BlockSize);
+ }
+
+ public static BlockF Create()
+ {
+ var block = new BlockF();
+ block.Init();
+ return block;
+ }
+
+ public static BlockF[] CreateArray(int size)
+ {
+ BlockF[] result = new BlockF[size];
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i].Init();
+ }
+
+ return result;
+ }
+
+ public bool IsInitialized => this.Data != null;
+
///
/// Gets the pixel data at the given block index.
///
@@ -35,10 +161,52 @@ namespace ImageSharp.Formats
///
/// The .
///
- public int this[int index]
+ public float this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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 (this.Data != null)
+ {
+ ArrayPool.Return(this.Data, true);
+ this.Data = null;
+ }
+ }
+
+ public static void DisposeAll(BlockF[] blocks)
+ {
+ for (int i = 0; i < blocks.Length; i++)
+ {
+ blocks[i].Dispose();
+ }
+ }
+
+ public void Clear()
+ {
+ for (int i = 0; i < this.Data.Length; i++)
+ {
+ this.Data[i] = 0;
+ }
+ }
+
+ public BlockF Clone()
{
- get { return this.data[index]; }
- set { this.data[index] = value; }
+ BlockF clone = Create();
+ Array.Copy(this.Data, clone.Data, BlockSize);
+ return clone;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs
new file mode 100644
index 0000000000..3b48dc572d
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs
@@ -0,0 +1,52 @@
+//
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+
+namespace ImageSharp.Formats
+{
+ internal partial struct Block8x8F
+ {
+ private static readonly Vector4 CMin4 = new Vector4(-128f);
+ private static readonly Vector4 CMax4 = new Vector4(127f);
+ private static readonly Vector4 COff4 = new Vector4(128f);
+
+ ///
+ /// Transpose the block into d
+ ///
+ /// Destination
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void TransposeInto(ref Block8x8F d)
+ {
+ d.V0L.X = V0L.X; d.V1L.X = V0L.Y; d.V2L.X = V0L.Z; d.V3L.X = V0L.W; d.V4L.X = V0R.X; d.V5L.X = V0R.Y; d.V6L.X = V0R.Z; d.V7L.X = V0R.W;
+ d.V0L.Y = V1L.X; d.V1L.Y = V1L.Y; d.V2L.Y = V1L.Z; d.V3L.Y = V1L.W; d.V4L.Y = V1R.X; d.V5L.Y = V1R.Y; d.V6L.Y = V1R.Z; d.V7L.Y = V1R.W;
+ d.V0L.Z = V2L.X; d.V1L.Z = V2L.Y; d.V2L.Z = V2L.Z; d.V3L.Z = V2L.W; d.V4L.Z = V2R.X; d.V5L.Z = V2R.Y; d.V6L.Z = V2R.Z; d.V7L.Z = V2R.W;
+ d.V0L.W = V3L.X; d.V1L.W = V3L.Y; d.V2L.W = V3L.Z; d.V3L.W = V3L.W; d.V4L.W = V3R.X; d.V5L.W = V3R.Y; d.V6L.W = V3R.Z; d.V7L.W = V3R.W;
+ d.V0R.X = V4L.X; d.V1R.X = V4L.Y; d.V2R.X = V4L.Z; d.V3R.X = V4L.W; d.V4R.X = V4R.X; d.V5R.X = V4R.Y; d.V6R.X = V4R.Z; d.V7R.X = V4R.W;
+ d.V0R.Y = V5L.X; d.V1R.Y = V5L.Y; d.V2R.Y = V5L.Z; d.V3R.Y = V5L.W; d.V4R.Y = V5R.X; d.V5R.Y = V5R.Y; d.V6R.Y = V5R.Z; d.V7R.Y = V5R.W;
+ d.V0R.Z = V6L.X; d.V1R.Z = V6L.Y; d.V2R.Z = V6L.Z; d.V3R.Z = V6L.W; d.V4R.Z = V6R.X; d.V5R.Z = V6R.Y; d.V6R.Z = V6R.Z; d.V7R.Z = V6R.W;
+ d.V0R.W = V7L.X; d.V1R.W = V7L.Y; d.V2R.W = V7L.Z; d.V3R.W = V7L.W; d.V4R.W = V7R.X; d.V5R.W = V7R.Y; d.V6R.W = V7R.Z; d.V7R.W = V7R.W;
+ }
+
+ ///
+ /// Level shift by +128, clip to [0, 255]
+ ///
+ /// Destination
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
+ {
+ d.V0L = Vector4.Max(Vector4.Min(V0L, CMax4), CMin4) + COff4;d.V0R = Vector4.Max(Vector4.Min(V0R, CMax4), CMin4) + COff4;
+ d.V1L = Vector4.Max(Vector4.Min(V1L, CMax4), CMin4) + COff4;d.V1R = Vector4.Max(Vector4.Min(V1R, CMax4), CMin4) + COff4;
+ d.V2L = Vector4.Max(Vector4.Min(V2L, CMax4), CMin4) + COff4;d.V2R = Vector4.Max(Vector4.Min(V2R, CMax4), CMin4) + COff4;
+ d.V3L = Vector4.Max(Vector4.Min(V3L, CMax4), CMin4) + COff4;d.V3R = Vector4.Max(Vector4.Min(V3R, CMax4), CMin4) + COff4;
+ d.V4L = Vector4.Max(Vector4.Min(V4L, CMax4), CMin4) + COff4;d.V4R = Vector4.Max(Vector4.Min(V4R, CMax4), CMin4) + COff4;
+ d.V5L = Vector4.Max(Vector4.Min(V5L, CMax4), CMin4) + COff4;d.V5R = Vector4.Max(Vector4.Min(V5R, CMax4), CMin4) + COff4;
+ d.V6L = Vector4.Max(Vector4.Min(V6L, CMax4), CMin4) + COff4;d.V6R = Vector4.Max(Vector4.Min(V6R, CMax4), CMin4) + COff4;
+ d.V7L = Vector4.Max(Vector4.Min(V7L, CMax4), CMin4) + COff4;d.V7R = Vector4.Max(Vector4.Min(V7R, CMax4), CMin4) + COff4;
+ }
+
+
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt
new file mode 100644
index 0000000000..951d62d914
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt
@@ -0,0 +1,80 @@
+<#@ template debug="false" hostspecific="false" language="C#" #>
+<#@ assembly name="System.Core" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Text" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ output extension=".cs" #>
+//
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+<#
+char[] coordz = {'X', 'Y', 'Z', 'W'};
+#>
+
+namespace ImageSharp.Formats
+{
+ internal partial struct Block8x8F
+ {
+ private static readonly Vector4 CMin4 = new Vector4(-128f);
+ private static readonly Vector4 CMax4 = new Vector4(127f);
+ private static readonly Vector4 COff4 = new Vector4(128f);
+
+ ///
+ /// Transpose the block into d
+ ///
+ /// Destination
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void TransposeInto(ref Block8x8F d)
+ {
+ <#
+ PushIndent(" ");
+
+ for (int i = 0; i < 8; i++)
+ {
+ char destCoord = coordz[i % 4];
+ char destSide = (i / 4) % 2 == 0 ? 'L' : 'R';
+
+ for (int j = 0; j < 8; j++)
+ {
+ char srcCoord = coordz[j % 4];
+ char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R';
+
+ string expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord}; ";
+ Write(expression);
+ }
+ WriteLine("");
+ }
+ PopIndent();
+ #>
+ }
+
+ ///
+ /// Level shift by +128, clip to [0, 255]
+ ///
+ /// Destination
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
+ {
+ <#
+
+ PushIndent(" ");
+
+ for (int i = 0; i < 8; i++)
+ {
+ for (int j = 0; j < 2; j++)
+ {
+ char side = j == 0 ? 'L' : 'R';
+ Write($"d.V{i}{side} = Vector4.Max(Vector4.Min(V{i}{side}, CMax4), CMin4) + COff4;");
+ }
+ WriteLine("");
+ }
+ PopIndent();
+ #>
+ }
+
+
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
new file mode 100644
index 0000000000..6fefb86965
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
@@ -0,0 +1,405 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.Formats
+{
+ using System;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+
+ ///
+ /// DCT code Ported from https://github.com/norishigefukushima/dct_simd
+ ///
+ internal partial struct Block8x8F
+ {
+ public Vector4 V0L;
+ public Vector4 V0R;
+
+ public Vector4 V1L;
+ public Vector4 V1R;
+
+ public Vector4 V2L;
+ public Vector4 V2R;
+
+ public Vector4 V3L;
+ public Vector4 V3R;
+
+ public Vector4 V4L;
+ public Vector4 V4R;
+
+ public Vector4 V5L;
+ public Vector4 V5R;
+
+ public Vector4 V6L;
+ public Vector4 V6R;
+
+ public Vector4 V7L;
+ public Vector4 V7R;
+
+ public const int VectorCount = 16;
+ public const int ScalarCount = VectorCount*4;
+
+ ///
+ /// Load raw 32bit floating point data from source
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void LoadFrom(MutableSpan source)
+ {
+ fixed (void* ptr = &this.V0L)
+ {
+ Marshal.Copy(source.Data, source.Offset, (IntPtr)ptr, ScalarCount);
+ }
+ }
+
+ ///
+ /// Load raw 32bit floating point data from source
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void LoadFrom(Block8x8F* blockPtr, MutableSpan source)
+ {
+ Marshal.Copy(source.Data, source.Offset, (IntPtr)blockPtr, ScalarCount);
+ }
+
+ ///
+ /// Load raw 32bit floating point data from source
+ ///
+ internal unsafe void LoadFrom(MutableSpan source)
+ {
+ fixed (Vector4* ptr = &this.V0L)
+ {
+ float* fp = (float*)ptr;
+ for (int i = 0; i < ScalarCount; i++)
+ {
+ fp[i] = source[i];
+ }
+ }
+ }
+
+ ///
+ /// Copy raw 32bit floating point data to dest
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void CopyTo(MutableSpan dest)
+ {
+ fixed (void* ptr = &this.V0L)
+ {
+ Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount);
+ }
+ }
+
+ ///
+ /// Copy raw 32bit floating point data to dest
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void CopyTo(float[] dest)
+ {
+ fixed (void* ptr = &this.V0L)
+ {
+ Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount);
+ }
+ }
+
+ ///
+ /// Copy raw 32bit floating point data to dest
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest)
+ {
+ Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount);
+ }
+
+ ///
+ /// Copy raw 32bit floating point data to dest
+ ///
+ internal unsafe void CopyTo(MutableSpan dest)
+ {
+ fixed (Vector4* ptr = &this.V0L)
+ {
+ float* fp = (float*)ptr;
+ for (int i = 0; i < ScalarCount; i++)
+ {
+ dest[i] = (int)fp[i];
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void MultiplyAllInplace(Vector4 s)
+ {
+ this.V0L *= s;
+ this.V0R *= s;
+ this.V1L *= s;
+ this.V1R *= s;
+ this.V2L *= s;
+ this.V2R *= s;
+ this.V3L *= s;
+ this.V3R *= s;
+ this.V4L *= s;
+ this.V4R *= s;
+ this.V5L *= s;
+ this.V5R *= s;
+ this.V6L *= s;
+ this.V6R *= s;
+ this.V7L *= s;
+ this.V7R *= s;
+ }
+
+ ///
+ /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
+ ///
+ /// Destination
+ /// Temporary block provided by the caller
+ public void TransformIDCTInto(ref Block8x8F dest, ref Block8x8F temp)
+ {
+ this.TransposeInto(ref temp);
+ temp.IDCT8x4_LeftPart(ref dest);
+ temp.IDCT8x4_RightPart(ref dest);
+
+ dest.TransposeInto(ref temp);
+
+ temp.IDCT8x4_LeftPart(ref dest);
+ temp.IDCT8x4_RightPart(ref dest);
+
+ dest.MultiplyAllInplace(c_0_125);
+ }
+
+ private static readonly Vector4 c_1_175876 = new Vector4(1.175876f);
+ private static readonly Vector4 c_1_961571 = new Vector4(-1.961571f);
+ private static readonly Vector4 c_0_390181 = new Vector4(-0.390181f);
+ private static readonly Vector4 c_0_899976 = new Vector4(-0.899976f);
+ private static readonly Vector4 c_2_562915 = new Vector4(-2.562915f);
+ private static readonly Vector4 c_0_298631 = new Vector4(0.298631f);
+ private static readonly Vector4 c_2_053120 = new Vector4(2.053120f);
+ private static readonly Vector4 c_3_072711 = new Vector4(3.072711f);
+ private static readonly Vector4 c_1_501321 = new Vector4(1.501321f);
+ private static readonly Vector4 c_0_541196 = new Vector4(0.541196f);
+ private static readonly Vector4 c_1_847759 = new Vector4(-1.847759f);
+ private static readonly Vector4 c_0_765367 = new Vector4(0.765367f);
+
+ private static readonly Vector4 c_0_125 = new Vector4(0.1250f);
+
+ ///
+ /// Do IDCT internal operations on the left part of the block. Original source:
+ /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
+ ///
+ /// Destination block
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void IDCT8x4_LeftPart(ref Block8x8F d)
+ {
+ Vector4 my1 = this.V1L;
+ Vector4 my7 = this.V7L;
+ Vector4 mz0 = my1 + my7;
+
+ Vector4 my3 = this.V3L;
+ Vector4 mz2 = my3 + my7;
+ Vector4 my5 = this.V5L;
+ Vector4 mz1 = my3 + my5;
+ Vector4 mz3 = my1 + my5;
+
+ Vector4 mz4 = ((mz0 + mz1) * c_1_175876);
+
+ mz2 = (mz2 * c_1_961571) + mz4;
+ mz3 = (mz3 * c_0_390181) + mz4;
+ mz0 = mz0 * c_0_899976;
+ mz1 = mz1 * c_2_562915;
+
+ Vector4 mb3 = (my7 * c_0_298631) + mz0 + mz2;
+ Vector4 mb2 = (my5 * c_2_053120) + mz1 + mz3;
+ Vector4 mb1 = (my3 * c_3_072711) + mz1 + mz2;
+ Vector4 mb0 = (my1 * c_1_501321) + mz0 + mz3;
+
+ Vector4 my2 = this.V2L;
+ Vector4 my6 = this.V6L;
+ mz4 = (my2 + my6) * c_0_541196;
+ Vector4 my0 = this.V0L;
+ Vector4 my4 = this.V4L;
+ mz0 = my0 + my4;
+ mz1 = my0 - my4;
+
+ mz2 = mz4 + (my6 * c_1_847759);
+ mz3 = mz4 + (my2 * c_0_765367);
+
+ my0 = mz0 + mz3;
+ my3 = mz0 - mz3;
+ my1 = mz1 + mz2;
+ my2 = mz1 - mz2;
+
+ d.V0L = my0 + mb0;
+ d.V7L = my0 - mb0;
+ d.V1L = my1 + mb1;
+ d.V6L = my1 - mb1;
+ d.V2L = my2 + mb2;
+ d.V5L = my2 - mb2;
+ d.V3L = my3 + mb3;
+ d.V4L = my3 - mb3;
+ }
+
+ ///
+ /// Do IDCT internal operations on the right part of the block.
+ /// Original source:
+ /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void IDCT8x4_RightPart(ref Block8x8F d)
+ {
+ Vector4 my1 = this.V1R;
+ Vector4 my7 = this.V7R;
+ Vector4 mz0 = my1 + my7;
+
+ Vector4 my3 = this.V3R;
+ Vector4 mz2 = my3 + my7;
+ Vector4 my5 = this.V5R;
+ Vector4 mz1 = my3 + my5;
+ Vector4 mz3 = my1 + my5;
+
+ Vector4 mz4 = (mz0 + mz1) * c_1_175876;
+
+ mz2 = (mz2 * c_1_961571) + mz4;
+ mz3 = (mz3 * c_0_390181) + mz4;
+ mz0 = mz0 * c_0_899976;
+ mz1 = mz1 * c_2_562915;
+
+ Vector4 mb3 = (my7 * c_0_298631) + mz0 + mz2;
+ Vector4 mb2 = (my5 * c_2_053120) + mz1 + mz3;
+ Vector4 mb1 = (my3 * c_3_072711) + mz1 + mz2;
+ Vector4 mb0 = (my1 * c_1_501321) + mz0 + mz3;
+
+ Vector4 my2 = this.V2R;
+ Vector4 my6 = this.V6R;
+ mz4 = (my2 + my6) * c_0_541196;
+ Vector4 my0 = this.V0R;
+ Vector4 my4 = this.V4R;
+ mz0 = my0 + my4;
+ mz1 = my0 - my4;
+
+ mz2 = mz4 + (my6 * c_1_847759);
+ mz3 = mz4 + (my2 * c_0_765367);
+
+ my0 = mz0 + mz3;
+ my3 = mz0 - mz3;
+ my1 = mz1 + mz2;
+ my2 = mz1 - mz2;
+
+ d.V0R = my0 + mb0;
+ d.V7R = my0 - mb0;
+ d.V1R = my1 + mb1;
+ d.V6R = my1 - mb1;
+ d.V2R = my2 + mb2;
+ d.V5R = my2 - mb2;
+ d.V3R = my3 + mb3;
+ d.V4R = my3 - mb3;
+ }
+
+ public unsafe float this[int idx]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ fixed (Block8x8F* p = &this)
+ {
+ float* fp = (float*)p;
+ return fp[idx];
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set
+ {
+ fixed (Block8x8F* p = &this)
+ {
+ float* fp = (float*)p;
+ fp[idx] = value;
+ }
+ }
+ }
+
+ ///
+ /// Pointer-based "Indexer" (getter part)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
+ {
+ float* fp = (float*)blockPtr;
+ return fp[idx];
+ }
+
+ ///
+ /// Pointer-based "Indexer" (setter part)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
+ {
+ float* fp = (float*)blockPtr;
+ fp[idx] = value;
+ }
+
+ ///
+ /// Fill the block with defaults (zeroes)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void Clear()
+ {
+ // The cheapest way to do this in C#:
+ this = new Block8x8F();
+ }
+
+ ///
+ /// TODO: Should be removed when BlockF goes away
+ ///
+ ///
+ internal void LoadFrom(ref BlockF legacyBlock)
+ {
+ this.LoadFrom(legacyBlock.Data);
+ }
+
+ ///
+ /// TODO: Should be removed when BlockF goes away
+ ///
+ ///
+ internal void CopyTo(ref BlockF legacyBlock)
+ {
+ this.CopyTo(legacyBlock.Data);
+ }
+
+ ///
+ /// Level shift by +128, clip to [0, 255], and write to buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal unsafe void CopyColorsTo(MutableSpan buffer, int stride, Block8x8F* temp)
+ {
+ this.TransformByteConvetibleColorValuesInto(ref *temp);
+
+ float* src = (float*)temp;
+ for (int i = 0; i < 8; i++)
+ {
+ buffer[0] = (byte)src[0];
+ buffer[1] = (byte)src[1];
+ buffer[2] = (byte)src[2];
+ buffer[3] = (byte)src[3];
+ buffer[4] = (byte)src[4];
+ buffer[5] = (byte)src[5];
+ buffer[6] = (byte)src[6];
+ buffer[7] = (byte)src[7];
+ buffer.AddOffset(stride);
+ src += 8;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe void UnZig(Block8x8F* block, Block8x8F* qt, int* unzigPtr)
+ {
+ float* b = (float*)block;
+ float* qtp = (float*)qt;
+ for (int zig = 0; zig < BlockF.BlockSize; zig++)
+ {
+ float* unzigPos = b + unzigPtr[zig];
+ float val = *unzigPos;
+ val *= qtp[zig];
+ *unzigPos = val;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Bytes.cs b/src/ImageSharp/Formats/Jpg/Components/Bytes.cs
index d4159e9963..32a767d586 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Bytes.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Bytes.cs
@@ -2,25 +2,28 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageSharp.Formats
{
+ using System;
+ using System.Buffers;
+ using System.IO;
+ using System.Runtime.CompilerServices;
+
///
/// Bytes is a byte buffer, similar to a stream, except that it
/// has to be able to unread more than 1 byte, due to byte stuffing.
/// Byte stuffing is specified in section F.1.2.3.
///
- internal class Bytes
+ internal struct Bytes : IDisposable
{
+ private static readonly ArrayPool ArrayPool = ArrayPool.Create(4096, 50);
+
///
- /// Initializes a new instance of the class.
+ /// Creates a new instance of the , and initializes it's buffer.
///
- public Bytes()
+ public static Bytes Create()
{
- this.Buffer = new byte[4096];
- this.I = 0;
- this.J = 0;
- this.UnreadableBytes = 0;
+ return new Bytes { Buffer = ArrayPool.Rent(4096) };
}
///
@@ -28,16 +31,128 @@ namespace ImageSharp.Formats
/// buffer[i:j] are the buffered bytes read from the underlying
/// stream that haven't yet been passed further on.
///
- public byte[] Buffer { get; set; }
+ public byte[] Buffer;
- public int I { get; set; }
+ public int I;
- public int J { get; set; }
+ public int J;
///
/// Gets or sets the unreadable bytes. The number of bytes to back up i after
/// overshooting. It can be 0, 1 or 2.
///
- public int UnreadableBytes { get; set; }
+ public int UnreadableBytes;
+
+ public void Dispose()
+ {
+ if (this.Buffer != null) ArrayPool.Return(this.Buffer);
+ this.Buffer = null;
+ }
+
+ ///
+ /// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
+ ///
+ /// The
+ internal byte ReadByteStuffedByte(Stream inputStream, out JpegDecoderCore.ErrorCodes errorCode)
+ {
+ byte x;
+
+ errorCode = JpegDecoderCore.ErrorCodes.NoError;
+
+ // Take the fast path if bytes.buf contains at least two bytes.
+ if (this.I + 2 <= this.J)
+ {
+ x = this.Buffer[this.I];
+ this.I++;
+ this.UnreadableBytes = 1;
+ if (x != JpegConstants.Markers.XFF)
+ {
+ return x;
+ }
+
+ if (this.Buffer[this.I] != 0x00)
+ {
+ errorCode = JpegDecoderCore.ErrorCodes.MissingFF00;
+ return 0;
+
+ // throw new MissingFF00Exception();
+ }
+
+ this.I++;
+ this.UnreadableBytes = 2;
+ return JpegConstants.Markers.XFF;
+ }
+
+ this.UnreadableBytes = 0;
+
+ x = this.ReadByte(inputStream);
+ this.UnreadableBytes = 1;
+ if (x != JpegConstants.Markers.XFF)
+ {
+ return x;
+ }
+
+ x = this.ReadByte(inputStream);
+ this.UnreadableBytes = 2;
+ if (x != 0x00)
+ {
+ errorCode = JpegDecoderCore.ErrorCodes.MissingFF00;
+ return 0;
+
+ // throw new MissingFF00Exception();
+ }
+
+ return JpegConstants.Markers.XFF;
+ }
+
+ ///
+ /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
+ ///
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal byte ReadByte(Stream inputStream)
+ {
+ while (this.I == this.J)
+ {
+ this.Fill(inputStream);
+ }
+
+ byte x = this.Buffer[this.I];
+ this.I++;
+ this.UnreadableBytes = 0;
+ return x;
+ }
+
+ ///
+ /// Fills up the bytes buffer from the underlying stream.
+ /// It should only be called when there are no unread bytes in bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void Fill(Stream inputStream)
+ {
+ if (this.I != this.J)
+ {
+ throw new ImageFormatException("Fill called when unread bytes exist.");
+ }
+
+ // Move the last 2 bytes to the start of the buffer, in case we need
+ // to call UnreadByteStuffedByte.
+ if (this.J > 2)
+ {
+ this.Buffer[0] = this.Buffer[this.J - 2];
+ this.Buffer[1] = this.Buffer[this.J - 1];
+ this.I = 2;
+ this.J = 2;
+ }
+
+ // Fill in the rest of the buffer.
+ int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J);
+ if (n == 0)
+ {
+ throw new JpegDecoderCore.EOFException();
+ }
+
+ this.J += n;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Component.cs b/src/ImageSharp/Formats/Jpg/Components/Component.cs
index f56b6d513e..f70dbff3f4 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Component.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Component.cs
@@ -8,26 +8,26 @@ namespace ImageSharp.Formats
///
/// Represents a single color component
///
- internal class Component
+ internal struct Component
{
///
/// Gets or sets the horizontal sampling factor.
///
- public int HorizontalFactor { get; set; }
+ public int HorizontalFactor;
///
/// Gets or sets the vertical sampling factor.
///
- public int VerticalFactor { get; set; }
+ public int VerticalFactor;
///
/// Gets or sets the identifier
///
- public byte Identifier { get; set; }
+ public byte Identifier;
///
/// Gets or sets the quantization table destination selector.
///
- public byte Selector { get; set; }
+ public byte Selector;
}
}
diff --git a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs b/src/ImageSharp/Formats/Jpg/Components/FDCT.cs
index cd27b9e73c..13ce80f559 100644
--- a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs
+++ b/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.
///
/// The block of coefficients.
- public static void Transform(Block block)
+ public static void Transform(ref Block block)
{
// Pass 1: process rows.
for (int y = 0; y < 8; y++)
diff --git a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs b/src/ImageSharp/Formats/Jpg/Components/Huffman.cs
index 2c38cfd380..7a6f031e86 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Huffman.cs
@@ -2,34 +2,36 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageSharp.Formats
{
+ using System;
+ using System.Buffers;
+
///
/// Represents a Huffman tree
///
- internal class Huffman
+ internal struct Huffman : IDisposable
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The log-2 size of the Huffman decoder's look-up table.
- /// The maximum (inclusive) number of codes in a Huffman tree.
- /// The maximum (inclusive) number of bits in a Huffman code.
- public Huffman(int lutSize, int maxNCodes, int maxCodeLength)
+ private static ArrayPool UshortBuffer = ArrayPool.Create(1 << JpegDecoderCore.LutSize, 50);
+
+ private static ArrayPool ByteBuffer = ArrayPool.Create(JpegDecoderCore.MaxNCodes, 50);
+
+ private static readonly ArrayPool IntBuffer = ArrayPool.Create(JpegDecoderCore.MaxCodeLength, 50);
+
+ public void Init(int lutSize, int maxNCodes, int maxCodeLength)
{
- this.Lut = new ushort[1 << lutSize];
- this.Values = new byte[maxNCodes];
- this.MinCodes = new int[maxCodeLength];
- this.MaxCodes = 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);
+
}
///
/// Gets or sets the number of codes in the tree.
///
- public int Length { get; set; }
+ public int Length;
///
/// Gets the look-up table for the next LutSize bits in the bit-stream.
@@ -37,28 +39,37 @@ namespace ImageSharp.Formats
/// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits.
///
- public ushort[] Lut { get; }
+ public ushort[] Lut;
///
/// Gets the the decoded values, sorted by their encoding.
///
- public byte[] Values { get; }
+ public byte[] Values;
///
/// 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.
///
- public int[] MinCodes { get; }
+ public int[] MinCodes;
///
/// 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.
///
- public int[] MaxCodes { get; }
+ public int[] MaxCodes;
///
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
///
- public int[] Indices { get; }
+ public int[] Indices;
+
+ public void Dispose()
+ {
+ UshortBuffer.Return(this.Lut, true);
+ ByteBuffer.Return(this.Values, true);
+ IntBuffer.Return(this.MinCodes, true);
+ IntBuffer.Return(this.MaxCodes, true);
+ IntBuffer.Return(this.Indices, true);
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs b/src/ImageSharp/Formats/Jpg/Components/IDCT.cs
index bc145779a2..b3277e0278 100644
--- a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/IDCT.cs
@@ -39,7 +39,7 @@ namespace ImageSharp.Formats
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
///
/// The source block of coefficients
- public static void Transform(Block src)
+ public static void Transform(ref Block src)
{
// Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++)
@@ -165,5 +165,6 @@ namespace ImageSharp.Formats
src[56 + x] = (y7 - y1) >> 14;
}
}
+
}
}
diff --git a/src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs b/src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs
new file mode 100644
index 0000000000..492f28f5b4
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs
@@ -0,0 +1,108 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Like corefxlab Span, but with an AddOffset() method for efficiency.
+ /// TODO: When Span will be official, consider replacing this class!
+ ///
+ ///
+ /// https://github.com/dotnet/corefxlab/blob/master/src/System.Slices/System/Span.cs
+ ///
+ ///
+ internal struct MutableSpan
+ {
+ public T[] Data;
+
+ public int Offset;
+
+ public int TotalCount => this.Data.Length - this.Offset;
+
+ public MutableSpan(int size, int offset = 0)
+ {
+ this.Data = new T[size];
+ this.Offset = offset;
+ }
+
+ public MutableSpan(T[] data, int offset = 0)
+ {
+ this.Data = data;
+ this.Offset = offset;
+ }
+
+ public T this[int idx]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return this.Data[idx + this.Offset];
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set
+ {
+ this.Data[idx + this.Offset] = value;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public MutableSpan Slice(int offset)
+ {
+ return new MutableSpan(this.Data, this.Offset + offset);
+ }
+
+ public static implicit operator MutableSpan(T[] data) => new MutableSpan(data, 0);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddOffset(int offset)
+ {
+ this.Offset += offset;
+ }
+ }
+
+ internal static class MutableSpanExtensions
+ {
+ public static MutableSpan Slice(this T[] array, int offset) => new MutableSpan(array, offset);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SaveTo(this MutableSpan data, ref Vector4 v)
+ {
+ v.X = data[0];
+ v.Y = data[1];
+ v.Z = data[2];
+ v.W = data[3];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SaveTo(this MutableSpan data, ref Vector4 v)
+ {
+ v.X = data[0];
+ v.Y = data[1];
+ v.Z = data[2];
+ v.W = data[3];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void LoadFrom(this MutableSpan data, ref Vector4 v)
+ {
+ data[0] = v.X;
+ data[1] = v.Y;
+ data[2] = v.Z;
+ data[3] = v.W;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void LoadFrom(this MutableSpan data, ref Vector4 v)
+ {
+ data[0] = (int)v.X;
+ data[1] = (int)v.Y;
+ data[2] = (int)v.Z;
+ data[3] = (int)v.W;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpg/JpegDecoder.cs
index 666f1b35a0..d3c64a9c06 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoder.cs
@@ -83,8 +83,10 @@ namespace ImageSharp.Formats
Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream");
- JpegDecoderCore decoder = new JpegDecoderCore();
- decoder.Decode(image, stream, false);
+ using (JpegDecoderCore decoder = new JpegDecoderCore())
+ {
+ decoder.Decode(image, stream, false);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
index 332dcec488..446da29dbd 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
@@ -7,27 +7,29 @@ namespace ImageSharp.Formats
{
using System;
using System.IO;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
using System.Threading.Tasks;
///
/// Performs the jpeg decoding operation.
///
- internal class JpegDecoderCore
+ internal unsafe class JpegDecoderCore : IDisposable
{
///
/// The maximum (inclusive) number of bits in a Huffman code.
///
- private const int MaxCodeLength = 16;
+ internal const int MaxCodeLength = 16;
///
/// The maximum (inclusive) number of codes in a Huffman tree.
///
- private const int MaxNCodes = 256;
+ internal const int MaxNCodes = 256;
///
/// The log-2 size of the Huffman decoder's look-up table.
///
- private const int LutSize = 8;
+ internal const int LutSize = 8;
///
/// The maximum number of color components
@@ -44,6 +46,8 @@ namespace ImageSharp.Formats
///
private const int MaxTh = 3;
+ private const int ThRowSize = MaxTh + 1;
+
///
/// The maximum number of quantization tables
///
@@ -65,12 +69,12 @@ namespace ImageSharp.Formats
/// 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,
- };
+ {
+ 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
@@ -80,17 +84,18 @@ namespace ImageSharp.Formats
///
/// Saved state between progressive-mode scans.
///
- private readonly Block[][] progCoeffs;
+ private readonly Block8x8F[][] progCoeffs;
///
/// The huffman trees
///
- private readonly Huffman[,] huffmanTrees;
+ //private readonly Huffman[,] huffmanTrees;
+ private readonly Huffman[] huffmanTrees;
///
/// Quantization tables, in zigzag order.
///
- private readonly Block[] quantizationTables;
+ private readonly Block8x8F[] quantizationTables;
///
/// A temporary buffer for holding pixels
@@ -100,7 +105,7 @@ namespace ImageSharp.Formats
///
/// The byte buffer.
///
- private readonly Bytes bytes;
+ internal Bytes bytes;
///
/// The image width
@@ -130,12 +135,12 @@ namespace ImageSharp.Formats
///
/// The input stream.
///
- private Stream inputStream;
+ internal Stream inputStream;
///
/// Holds the unprocessed bits that have been taken from the byte-stream.
///
- private Bits bits;
+ internal Bits bits;
///
/// The array of keyline pixels in a CMYK image
@@ -187,37 +192,32 @@ namespace ImageSharp.Formats
///
private short verticalResolution;
+ private int blockIndex;
+
///
/// Initializes a new instance of the class.
///
public JpegDecoderCore()
{
- this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
- this.quantizationTables = new Block[MaxTq + 1];
- this.temp = new byte[2 * Block.BlockSize];
+ //this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
+ this.huffmanTrees = new Huffman[(MaxTc + 1) * (MaxTh + 1)];
+
+ this.quantizationTables = new Block8x8F[MaxTq + 1];
+ this.temp = new byte[2 * BlockF.BlockSize];
this.componentArray = new Component[MaxComponents];
- this.progCoeffs = new Block[MaxComponents][];
+ this.progCoeffs = new Block8x8F[MaxComponents][];
this.bits = new Bits();
- this.bytes = new Bytes();
+ this.bytes = Bytes.Create();
// TODO: This looks like it could be static.
+
for (int i = 0; i < MaxTc + 1; i++)
{
for (int j = 0; j < MaxTh + 1; j++)
{
- this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength);
+ this.huffmanTrees[i * ThRowSize + j].Init(LutSize, MaxNCodes, MaxCodeLength);
}
}
-
- for (int i = 0; i < this.quantizationTables.Length; i++)
- {
- this.quantizationTables[i] = new Block();
- }
-
- for (int i = 0; i < this.componentArray.Length; i++)
- {
- this.componentArray[i] = new Component();
- }
}
///
@@ -230,8 +230,7 @@ namespace ImageSharp.Formats
/// The stream, where the image should be.
/// Whether to decode metadata only.
public void Decode(Image image, Stream stream, bool configOnly)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
this.inputStream = stream;
@@ -369,7 +368,8 @@ namespace ImageSharp.Formats
this.ProcessApp14Marker(remaining);
break;
default:
- if ((JpegConstants.Markers.APP0 <= marker && marker <= JpegConstants.Markers.APP15) || marker == JpegConstants.Markers.COM)
+ if ((JpegConstants.Markers.APP0 <= marker && marker <= JpegConstants.Markers.APP15)
+ || marker == JpegConstants.Markers.COM)
{
this.Skip(remaining);
}
@@ -419,60 +419,6 @@ namespace ImageSharp.Formats
}
}
- ///
- /// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
- /// least n. For best performance (avoiding function calls inside hot loops),
- /// the caller is the one responsible for first checking that bits.UnreadBits < n.
- ///
- /// The number of bits to ensure.
- private void EnsureNBits(int n)
- {
- while (true)
- {
- byte c = this.ReadByteStuffedByte();
- this.bits.Accumulator = (this.bits.Accumulator << 8) | c;
- this.bits.UnreadBits += 8;
- if (this.bits.Mask == 0)
- {
- this.bits.Mask = 1 << 7;
- }
- else
- {
- this.bits.Mask <<= 8;
- }
-
- if (this.bits.UnreadBits >= n)
- {
- break;
- }
- }
- }
-
- ///
- /// The composition of RECEIVE and EXTEND, specified in section F.2.2.1.
- ///
- /// The byte
- /// The
- private int ReceiveExtend(byte t)
- {
- if (this.bits.UnreadBits < t)
- {
- this.EnsureNBits(t);
- }
-
- this.bits.UnreadBits -= t;
- this.bits.Mask >>= t;
- int s = 1 << t;
- int x = (int)((this.bits.Accumulator >> this.bits.UnreadBits) & (s - 1));
-
- if (x < (s >> 1))
- {
- x += ((-1) << t) + 1;
- }
-
- return x;
- }
-
///
/// Processes a Define Huffman Table marker, and initializes a huffman
/// struct from its contents. Specified in section B.2.4.2.
@@ -501,92 +447,95 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Bad Th value");
}
- Huffman huffman = this.huffmanTrees[tc, th];
+ this.ProcessDefineHuffmanTablesMarkerLoop(ref this.huffmanTrees[tc * ThRowSize + th], ref remaining);
+ }
+ }
- // Read nCodes and huffman.Valuess (and derive h.Length).
- // nCodes[i] is the number of codes with code length i.
- // h.Length is the total number of codes.
- huffman.Length = 0;
+ private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining)
+ {
+ // Read nCodes and huffman.Valuess (and derive h.Length).
+ // 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];
- for (int i = 0; i < ncodes.Length; i++)
- {
- ncodes[i] = this.temp[i + 1];
- huffman.Length += ncodes[i];
- }
+ int[] ncodes = new int[MaxCodeLength];
+ for (int i = 0; i < ncodes.Length; i++)
+ {
+ ncodes[i] = this.temp[i + 1];
+ huffman.Length += ncodes[i];
+ }
- if (huffman.Length == 0)
- {
- throw new ImageFormatException("Huffman table has zero length");
- }
+ if (huffman.Length == 0)
+ {
+ throw new ImageFormatException("Huffman table has zero length");
+ }
- if (huffman.Length > MaxNCodes)
- {
- throw new ImageFormatException("Huffman table has excessive length");
- }
+ if (huffman.Length > MaxNCodes)
+ {
+ throw new ImageFormatException("Huffman table has excessive length");
+ }
- remaining -= huffman.Length + 17;
- if (remaining < 0)
- {
- 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.
- for (int i = 0; i < huffman.Lut.Length; i++)
- {
- huffman.Lut[i] = 0;
- }
+ // Derive the look-up table.
+ for (int i = 0; i < huffman.Lut.Length; i++)
+ {
+ 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
- // 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++;
+ huffman.Lut[base2 | k] = lutValue;
}
+
+ code++;
+ x++;
}
+ }
- // Derive minCodes, maxCodes, and indices.
- int c = 0, index = 0;
- for (int i = 0; i < ncodes.Length; i++)
+ // Derive minCodes, maxCodes, and indices.
+ int c = 0, index = 0;
+ for (int i = 0; i < ncodes.Length; i++)
+ {
+ int nc = ncodes[i];
+ if (nc == 0)
{
- int nc = ncodes[i];
- if (nc == 0)
- {
- huffman.MinCodes[i] = -1;
- huffman.MaxCodes[i] = -1;
- huffman.Indices[i] = -1;
- }
- else
- {
- huffman.MinCodes[i] = c;
- huffman.MaxCodes[i] = c + nc - 1;
- huffman.Indices[i] = index;
- c += nc;
- index += nc;
- }
-
- c <<= 1;
+ huffman.MinCodes[i] = -1;
+ huffman.MaxCodes[i] = -1;
+ huffman.Indices[i] = -1;
+ }
+ else
+ {
+ huffman.MinCodes[i] = c;
+ huffman.MaxCodes[i] = c + nc - 1;
+ huffman.Indices[i] = index;
+ c += nc;
+ index += nc;
}
+
+ c <<= 1;
}
}
@@ -595,8 +544,10 @@ namespace ImageSharp.Formats
///
/// The huffman value
/// The
- private byte DecodeHuffman(Huffman huffman)
+ private byte DecodeHuffman(ref Huffman huffman)
{
+ // Copy stuff to the stack:
+
if (huffman.Length == 0)
{
throw new ImageFormatException("Uninitialized Huffman table");
@@ -604,10 +555,10 @@ namespace ImageSharp.Formats
if (this.bits.UnreadBits < 8)
{
- try
- {
- this.EnsureNBits(8);
+ var errorCode = this.bits.EnsureNBits(8, this);
+ if (errorCode == ErrorCodes.NoError)
+ {
ushort v = huffman.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff];
if (v != 0)
@@ -618,19 +569,9 @@ namespace ImageSharp.Formats
return (byte)(v >> 8);
}
}
- catch (MissingFF00Exception)
- {
- if (this.bytes.UnreadableBytes != 0)
- {
- this.UnreadByteStuffedByte();
- }
- }
- catch (ShortHuffmanDataException)
+ else
{
- if (this.bytes.UnreadableBytes != 0)
- {
- this.UnreadByteStuffedByte();
- }
+ this.UnreadByteStuffedByte();
}
}
@@ -639,7 +580,11 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits == 0)
{
- this.EnsureNBits(1);
+ var errorCode = this.bits.EnsureNBits(1, this);
+ if (errorCode != ErrorCodes.NoError)
+ {
+ throw new MissingFF00Exception();
+ }
}
if ((this.bits.Accumulator & this.bits.Mask) != 0)
@@ -669,7 +614,11 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits == 0)
{
- this.EnsureNBits(1);
+ var errorCode = this.bits.EnsureNBits(1, this);
+ if (errorCode != ErrorCodes.NoError)
+ {
+ throw new MissingFF00Exception();
+ }
}
bool ret = (this.bits.Accumulator & this.bits.Mask) != 0;
@@ -687,7 +636,11 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits < count)
{
- this.EnsureNBits(count);
+ var errorCode = this.bits.EnsureNBits(count, this);
+ if (errorCode != ErrorCodes.NoError)
+ {
+ throw new MissingFF00Exception();
+ }
}
uint ret = this.bits.Accumulator >> (this.bits.UnreadBits - count);
@@ -697,37 +650,6 @@ namespace ImageSharp.Formats
return ret;
}
- ///
- /// Fills up the bytes buffer from the underlying stream.
- /// It should only be called when there are no unread bytes in bytes.
- ///
- private void Fill()
- {
- if (this.bytes.I != this.bytes.J)
- {
- throw new ImageFormatException("Fill called when unread bytes exist.");
- }
-
- // Move the last 2 bytes to the start of the buffer, in case we need
- // to call UnreadByteStuffedByte.
- if (this.bytes.J > 2)
- {
- this.bytes.Buffer[0] = this.bytes.Buffer[this.bytes.J - 2];
- this.bytes.Buffer[1] = this.bytes.Buffer[this.bytes.J - 1];
- this.bytes.I = 2;
- this.bytes.J = 2;
- }
-
- // Fill in the rest of the buffer.
- int n = this.inputStream.Read(this.bytes.Buffer, this.bytes.J, this.bytes.Buffer.Length - this.bytes.J);
- if (n == 0)
- {
- throw new EOFException();
- }
-
- this.bytes.J += n;
- }
-
///
/// Undoes the most recent ReadByteStuffedByte call,
/// giving a byte of data back from bits to bytes. The Huffman look-up table
@@ -751,65 +673,10 @@ namespace ImageSharp.Formats
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
///
/// The
- private byte ReadByte()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal byte ReadByte()
{
- while (this.bytes.I == this.bytes.J)
- {
- this.Fill();
- }
-
- byte x = this.bytes.Buffer[this.bytes.I];
- this.bytes.I++;
- this.bytes.UnreadableBytes = 0;
- return x;
- }
-
- ///
- /// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
- ///
- /// The
- private byte ReadByteStuffedByte()
- {
- byte x;
-
- // Take the fast path if bytes.buf contains at least two bytes.
- if (this.bytes.I + 2 <= this.bytes.J)
- {
- x = this.bytes.Buffer[this.bytes.I];
- this.bytes.I++;
- this.bytes.UnreadableBytes = 1;
- if (x != JpegConstants.Markers.XFF)
- {
- return x;
- }
-
- if (this.bytes.Buffer[this.bytes.I] != 0x00)
- {
- throw new MissingFF00Exception();
- }
-
- this.bytes.I++;
- this.bytes.UnreadableBytes = 2;
- return JpegConstants.Markers.XFF;
- }
-
- this.bytes.UnreadableBytes = 0;
-
- x = this.ReadByte();
- this.bytes.UnreadableBytes = 1;
- if (x != JpegConstants.Markers.XFF)
- {
- return x;
- }
-
- x = this.ReadByte();
- this.bytes.UnreadableBytes = 2;
- if (x != 0x00)
- {
- throw new MissingFF00Exception();
- }
-
- return JpegConstants.Markers.XFF;
+ return this.bytes.ReadByte(this.inputStream);
}
///
@@ -846,7 +713,7 @@ namespace ImageSharp.Formats
length -= this.bytes.J - this.bytes.I;
this.bytes.I += this.bytes.J - this.bytes.I;
- this.Fill();
+ this.bytes.Fill(this.inputStream);
}
}
}
@@ -883,7 +750,7 @@ namespace ImageSharp.Formats
break;
}
- this.Fill();
+ this.bytes.Fill(this.inputStream);
}
}
@@ -1008,7 +875,8 @@ namespace ImageSharp.Formats
case 1:
{
// Cb.
- if (this.componentArray[0].HorizontalFactor % h != 0 || this.componentArray[0].VerticalFactor % v != 0)
+ if (this.componentArray[0].HorizontalFactor % h != 0
+ || this.componentArray[0].VerticalFactor % v != 0)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
@@ -1019,7 +887,8 @@ namespace ImageSharp.Formats
case 2:
{
// Cr.
- if (this.componentArray[1].HorizontalFactor != h || this.componentArray[1].VerticalFactor != v)
+ if (this.componentArray[1].HorizontalFactor != h
+ || this.componentArray[1].VerticalFactor != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
@@ -1059,7 +928,8 @@ namespace ImageSharp.Formats
break;
case 3:
- if (this.componentArray[0].HorizontalFactor != h || this.componentArray[0].VerticalFactor != v)
+ if (this.componentArray[0].HorizontalFactor != h
+ || this.componentArray[0].VerticalFactor != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
@@ -1099,32 +969,32 @@ namespace ImageSharp.Formats
switch (x >> 4)
{
case 0:
- if (remaining < Block.BlockSize)
+ if (remaining < BlockF.BlockSize)
{
done = true;
break;
}
- remaining -= Block.BlockSize;
- this.ReadFull(this.temp, 0, Block.BlockSize);
+ remaining -= BlockF.BlockSize;
+ this.ReadFull(this.temp, 0, BlockF.BlockSize);
- for (int i = 0; i < Block.BlockSize; i++)
+ for (int i = 0; i < BlockF.BlockSize; i++)
{
this.quantizationTables[tq][i] = this.temp[i];
}
break;
case 1:
- if (remaining < 2 * Block.BlockSize)
+ if (remaining < 2 * BlockF.BlockSize)
{
done = true;
break;
}
- remaining -= 2 * Block.BlockSize;
- this.ReadFull(this.temp, 0, 2 * Block.BlockSize);
+ remaining -= 2 * BlockF.BlockSize;
+ this.ReadFull(this.temp, 0, 2 * BlockF.BlockSize);
- for (int i = 0; i < Block.BlockSize; i++)
+ for (int i = 0; i < BlockF.BlockSize; i++)
{
this.quantizationTables[tq][i] = (this.temp[2 * i] << 8) | this.temp[(2 * i) + 1];
}
@@ -1177,11 +1047,8 @@ namespace ImageSharp.Formats
remaining -= 13;
// TODO: We should be using constants for this.
- this.isJfif = this.temp[0] == 'J' &&
- this.temp[1] == 'F' &&
- this.temp[2] == 'I' &&
- this.temp[3] == 'F' &&
- this.temp[4] == '\x00';
+ this.isJfif = this.temp[0] == 'J' && this.temp[1] == 'F' && this.temp[2] == 'I' && this.temp[3] == 'F'
+ && this.temp[4] == '\x00';
if (this.isJfif)
{
@@ -1203,8 +1070,7 @@ namespace ImageSharp.Formats
/// The remaining bytes in the segment block.
/// The image.
private void ProcessApp1Marker(int remaining, Image image)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
if (remaining < 6)
{
@@ -1215,12 +1081,8 @@ namespace ImageSharp.Formats
byte[] profile = new byte[remaining];
this.ReadFull(profile, 0, remaining);
- if (profile[0] == 'E' &&
- profile[1] == 'x' &&
- profile[2] == 'i' &&
- profile[3] == 'f' &&
- profile[4] == '\0' &&
- profile[5] == '\0')
+ if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
+ && profile[5] == '\0')
{
image.ExifProfile = new ExifProfile(profile);
}
@@ -1243,11 +1105,8 @@ namespace ImageSharp.Formats
this.ReadFull(this.temp, 0, 12);
remaining -= 12;
- if (this.temp[0] == 'A' &&
- this.temp[1] == 'd' &&
- this.temp[2] == 'o' &&
- this.temp[3] == 'b' &&
- this.temp[4] == 'e')
+ if (this.temp[0] == 'A' && this.temp[1] == 'd' && this.temp[2] == 'o' && this.temp[3] == 'b'
+ && this.temp[4] == 'e')
{
this.adobeTransformValid = true;
this.adobeTransform = this.temp[11];
@@ -1268,12 +1127,12 @@ namespace ImageSharp.Formats
/// The image height.
/// The image.
private void ConvertFromCmyk(int width, int height, Image image)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
if (!this.adobeTransformValid)
{
- throw new ImageFormatException("Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
+ throw new ImageFormatException(
+ "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
}
// If the 4-component JPEG image isn't explicitly marked as "Unknown (RGB
@@ -1294,21 +1153,21 @@ namespace ImageSharp.Formats
0,
height,
y =>
- {
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
-
- for (int x = 0; x < width; x++)
{
- byte yy = this.ycbcrImage.YChannel[yo + x];
- byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
- TColor packed = default(TColor);
- this.PackCmyk(ref packed, yy, cb, cr, x, y);
- pixels[x, y] = packed;
- }
- });
+ for (int x = 0; x < width; x++)
+ {
+ byte yy = this.ycbcrImage.YChannel[yo + x];
+ byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+
+ TColor packed = default(TColor);
+ this.PackCmyk(ref packed, yy, cb, cr, x, y);
+ pixels[x, y] = packed;
+ }
+ });
}
this.AssignResolution(image);
@@ -1324,8 +1183,7 @@ namespace ImageSharp.Formats
/// The image height.
/// The image.
private void ConvertFromGrayScale(int width, int height, Image image)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
image.InitPixels(width, height);
@@ -1336,17 +1194,17 @@ namespace ImageSharp.Formats
height,
Bootstrapper.Instance.ParallelOptions,
y =>
- {
- int yoff = this.grayImage.GetRowOffset(y);
- for (int x = 0; x < width; x++)
{
- byte rgb = this.grayImage.Pixels[yoff + x];
+ int yoff = this.grayImage.GetRowOffset(y);
+ for (int x = 0; x < width; x++)
+ {
+ byte rgb = this.grayImage.Pixels[yoff + x];
- TColor packed = default(TColor);
- packed.PackFromBytes(rgb, rgb, rgb, 255);
- pixels[x, y] = packed;
- }
- });
+ TColor packed = default(TColor);
+ packed.PackFromBytes(rgb, rgb, rgb, 255);
+ pixels[x, y] = packed;
+ }
+ });
}
this.AssignResolution(image);
@@ -1361,8 +1219,7 @@ namespace ImageSharp.Formats
/// The image height.
/// The image.
private void ConvertFromYCbCr(int width, int height, Image image)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
image.InitPixels(width, height);
@@ -1374,21 +1231,21 @@ namespace ImageSharp.Formats
height,
Bootstrapper.Instance.ParallelOptions,
y =>
- {
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
-
- for (int x = 0; x < width; x++)
{
- byte yy = this.ycbcrImage.YChannel[yo + x];
- byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
- TColor packed = default(TColor);
- this.PackYcbCr(ref packed, yy, cb, cr);
- pixels[x, y] = packed;
- }
- });
+ for (int x = 0; x < width; x++)
+ {
+ byte yy = this.ycbcrImage.YChannel[yo + x];
+ byte cb = this.ycbcrImage.CbChannel[co + (x / scale)];
+ byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
+
+ TColor packed = default(TColor);
+ PackYcbCr(ref packed, yy, cb, cr);
+ pixels[x, y] = packed;
+ }
+ });
}
this.AssignResolution(image);
@@ -1403,8 +1260,7 @@ namespace ImageSharp.Formats
/// The height.
/// The image.
private void ConvertFromRGB(int width, int height, Image image)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
int scale = this.componentArray[0].HorizontalFactor / this.componentArray[1].HorizontalFactor;
image.InitPixels(width, height);
@@ -1416,21 +1272,21 @@ namespace ImageSharp.Formats
height,
Bootstrapper.Instance.ParallelOptions,
y =>
- {
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
-
- for (int x = 0; x < width; x++)
{
- byte red = this.ycbcrImage.YChannel[yo + x];
- byte green = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte blue = this.ycbcrImage.CrChannel[co + (x / scale)];
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
- TColor packed = default(TColor);
- packed.PackFromBytes(red, green, blue, 255);
- pixels[x, y] = packed;
- }
- });
+ for (int x = 0; x < width; x++)
+ {
+ byte red = this.ycbcrImage.YChannel[yo + x];
+ byte green = this.ycbcrImage.CbChannel[co + (x / scale)];
+ byte blue = this.ycbcrImage.CrChannel[co + (x / scale)];
+
+ TColor packed = default(TColor);
+ packed.PackFromBytes(red, green, blue, 255);
+ pixels[x, y] = packed;
+ }
+ });
}
this.AssignResolution(image);
@@ -1443,8 +1299,7 @@ namespace ImageSharp.Formats
/// The packed format. uint, long, float.
/// The image to assign the resolution to.
private void AssignResolution(Image image)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0)
{
@@ -1453,6 +1308,11 @@ namespace ImageSharp.Formats
}
}
+ struct StackallocUnzigData
+ {
+ internal fixed int Data [64];
+ }
+
///
/// Processes the SOS (Start of scan marker).
///
@@ -1479,7 +1339,8 @@ namespace ImageSharp.Formats
this.ReadFull(this.temp, 0, remaining);
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");
}
@@ -1489,51 +1350,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < scanComponentCount; i++)
{
- // Component selector.
- 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");
- }
+ this.ProcessScanImpl(i, ref scan[i], scan, ref totalHv);
}
// Section B.2.3 states that if there is more than one component then the
@@ -1558,18 +1375,18 @@ namespace ImageSharp.Formats
// significant bit.
// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
int zigStart = 0;
- int zigEnd = Block.BlockSize - 1;
+ int zigEnd = BlockF.BlockSize - 1;
int ah = 0;
int al = 0;
if (this.isProgressive)
{
- zigStart = this.temp[1 + (2 * scanComponentCount)];
- zigEnd = this.temp[2 + (2 * scanComponentCount)];
- ah = this.temp[3 + (2 * scanComponentCount)] >> 4;
- al = this.temp[3 + (2 * scanComponentCount)] & 0x0f;
+ zigStart = this.temp[1 + scanComponentCountX2];
+ zigEnd = this.temp[2 + scanComponentCountX2];
+ ah = this.temp[3 + scanComponentCountX2] >> 4;
+ al = this.temp[3 + scanComponentCountX2] & 0x0f;
- if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd)
+ if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || BlockF.BlockSize <= zigEnd)
{
throw new ImageFormatException("Bad spectral selection bounds");
}
@@ -1603,12 +1420,10 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index;
if (this.progCoeffs[compIndex] == null)
{
- this.progCoeffs[compIndex] = new Block[mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor];
+ var size = mxx * myy * this.componentArray[compIndex].HorizontalFactor
+ * this.componentArray[compIndex].VerticalFactor;
- for (int j = 0; j < this.progCoeffs[compIndex].Length; j++)
- {
- this.progCoeffs[compIndex][j] = new Block();
- }
+ this.progCoeffs[compIndex] = new Block8x8F[size];
}
}
}
@@ -1619,13 +1434,22 @@ namespace ImageSharp.Formats
byte expectedRst = JpegConstants.Markers.RST0;
// b is the decoded coefficients block, in natural (not zig-zag) order.
- Block b;
+ //Block b;
int[] dc = new int[MaxComponents];
// bx and by are the location of the current block, in units of 8x8
// blocks: the third block in the first row has (bx, by) = (2, 0).
int bx, by, blockCount = 0;
+ Block8x8F b = new Block8x8F();
+ Block8x8F temp1 = new Block8x8F();
+ Block8x8F temp2 = new Block8x8F();
+
+ // Tricky way to copy contents of the Unzig static variable to the stack:
+ StackallocUnzigData unzigOnStack = new StackallocUnzigData();
+ int* unzigPtr = unzigOnStack.Data;
+ Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
+
for (int my = 0; my < myy; my++)
{
for (int mx = 0; mx < mxx; mx++)
@@ -1635,7 +1459,6 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index;
int hi = this.componentArray[compIndex].HorizontalFactor;
int vi = this.componentArray[compIndex].VerticalFactor;
- Block qt = this.quantizationTables[this.componentArray[compIndex].Selector];
for (int j = 0; j < hi * vi; j++)
{
@@ -1678,167 +1501,60 @@ namespace ImageSharp.Formats
}
}
- // Load the previous partially decoded coefficients, if applicable.
- b = this.isProgressive ? this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] : new Block();
+ var qtIndex = this.componentArray[compIndex].Selector;
- if (ah != 0)
- {
- this.Refine(b, this.huffmanTrees[AcTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al);
- }
- else
+ // TODO: Find a way to clean up this mess
+
+ fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex])
{
- int zig = zigStart;
- if (zig == 0)
+ if (this.isProgressive)
+ // Load the previous partially decoded coefficients, if applicable.
{
- zig++;
+ this.blockIndex = ((@by * mxx) * hi) + bx;
- // 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)
+ fixed (Block8x8F* bp = &this.progCoeffs[compIndex][this.blockIndex])
{
- throw new ImageFormatException("Excessive DC component");
+ this.ProcessBlockImpl(
+ ah,
+ bp,
+ &temp1,
+ &temp2,
+ unzigPtr,
+ scan,
+ i,
+ zigStart,
+ zigEnd,
+ al,
+ dc,
+ compIndex,
+ @by,
+ mxx,
+ hi,
+ bx,
+ qtp);
}
-
- 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;
+ b.Clear();
+ this.ProcessBlockImpl(
+ ah,
+ &b,
+ &temp1,
+ &temp2,
+ unzigPtr,
+ scan,
+ i,
+ zigStart,
+ zigEnd,
+ al,
+ dc,
+ compIndex,
+ @by,
+ mxx,
+ hi,
+ bx,
+ qtp);
}
}
}
@@ -1882,15 +1598,241 @@ namespace ImageSharp.Formats
// for my
}
+ private void ProcessBlockImpl(
+ int ah,
+ Block8x8F* b,
+ Block8x8F* temp1,
+ Block8x8F* temp2,
+ int* unzigPtr,
+ Scan[] scan,
+ int i,
+ int zigStart,
+ int zigEnd,
+ int al,
+ int[] dc,
+ int compIndex,
+ int @by,
+ int mxx,
+ int hi,
+ int bx,
+ Block8x8F* qt)
+ {
+ var huffmannIdx = AcTable * ThRowSize + scan[i].AcTableSelector;
+ if (ah != 0)
+ {
+ this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, 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.bits.ReceiveExtend(value, this);
+ dc[compIndex] += deltaDC;
+
+ // b[0] = dc[compIndex] << al;
+ Block8x8F.SetScalarAt(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[huffmannIdx]);
+ byte val0 = (byte)(value >> 4);
+ byte val1 = (byte)(value & 0x0f);
+ if (val1 != 0)
+ {
+ zig += val0;
+ if (zig > zigEnd)
+ {
+ break;
+ }
+
+ int ac = this.bits.ReceiveExtend(val1, this);
+
+ // b[Unzig[zig]] = ac << al;
+ Block8x8F.SetScalarAt(b, unzigPtr[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 != BlockF.BlockSize - 1 || al != 0)
+ {
+ // We haven't completely decoded this 8x8 block. Save the coefficients.
+
+ // TODO!!!
+ //throw new NotImplementedException();
+ //this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] = b.Clone();
+ 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.
+ return;
+ }
+ }
+
+ // Dequantize, perform the inverse DCT and store the block to the image.
+ Block8x8F.UnZig(b, qt, unzigPtr);
+
+ b->TransformIDCTInto(ref *temp1, ref *temp2);
+
+ 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.
+
+ temp1->CopyColorsTo(new MutableSpan(dst, offset), stride, temp2);
+ }
+
+ 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;
+
+ this.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");
+ }
+ }
+
///
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
///
/// The block of coefficients
/// The Huffman tree
+ ///
/// The zig-zag start index
/// The zig-zag end index
/// The low transform offset
- private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta)
+ private void Refine(Block8x8F* b, ref Huffman h, int* unzigPtr, int zigStart, int zigEnd, int delta)
{
// Refining a DC component is trivial.
if (zigStart == 0)
@@ -1903,7 +1845,12 @@ namespace ImageSharp.Formats
bool bit = this.DecodeBit();
if (bit)
{
- b[0] |= delta;
+ int stuff = (int)Block8x8F.GetScalarAt(b, 0);
+
+ // int stuff = (int)b[0];
+ stuff |= delta;
+ // b[0] = stuff;
+ Block8x8F.SetScalarAt(b, 0, stuff);
}
return;
@@ -1917,7 +1864,7 @@ namespace ImageSharp.Formats
{
bool done = false;
int z = 0;
- byte val = this.DecodeHuffman(h);
+ byte val = this.DecodeHuffman(ref h);
int val0 = val >> 4;
int val1 = val & 0x0f;
@@ -1954,6 +1901,8 @@ namespace ImageSharp.Formats
break;
}
+ int blah = zig;
+
zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta);
if (zig > zigEnd)
{
@@ -1962,7 +1911,8 @@ namespace ImageSharp.Formats
if (z != 0)
{
- b[Unzig[zig]] = z;
+ //b[Unzig[zig]] = z;
+ Block8x8F.SetScalarAt(b, unzigPtr[zig], z);
}
}
}
@@ -1984,12 +1934,15 @@ namespace ImageSharp.Formats
/// The non-zero entry
/// The low transform offset
/// The
- private int RefineNonZeroes(Block b, int zig, int zigEnd, int nz, int delta)
+ private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta)
{
for (; zig <= zigEnd; zig++)
{
int u = Unzig[zig];
- if (b[u] == 0)
+ float bu = Block8x8F.GetScalarAt(b, u);
+
+ // TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary?
+ if (bu == 0)
{
if (nz == 0)
{
@@ -2006,13 +1959,15 @@ namespace ImageSharp.Formats
continue;
}
- if (b[u] >= 0)
+ if (bu >= 0)
{
- b[u] += delta;
+ //b[u] += delta;
+ Block8x8F.SetScalarAt(b, u, bu + delta);
}
else
{
- b[u] -= delta;
+ //b[u] -= delta;
+ Block8x8F.SetScalarAt(b, u, bu - delta);
}
}
@@ -2094,7 +2049,8 @@ namespace ImageSharp.Formats
return true;
}
- return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G' && this.componentArray[2].Identifier == 'B';
+ return this.componentArray[0].Identifier == 'R' && this.componentArray[1].Identifier == 'G'
+ && this.componentArray[2].Identifier == 'B';
}
///
@@ -2107,9 +2063,9 @@ namespace ImageSharp.Formats
/// The y luminance component.
/// The cb chroma component.
/// The cr chroma component.
- private void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr)
+ where TColor : struct, IPackedPixel where TPacked : struct
{
int ccb = cb - 128;
int ccr = cr - 128;
@@ -2134,8 +2090,7 @@ namespace ImageSharp.Formats
/// The x-position within the image.
/// The y-position within the image.
private void PackCmyk(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
// TODO: We can speed this up further with Vector4
int ccb = cb - 128;
@@ -2178,10 +2133,21 @@ namespace ImageSharp.Formats
public byte AcTableSelector { get; set; }
}
+ ///
+ /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent)
+ /// It's better tho have an error code for this!
+ ///
+ internal enum ErrorCodes
+ {
+ NoError,
+ // ReSharper disable once InconsistentNaming
+ MissingFF00
+ }
+
///
/// The missing ff00 exception.
///
- private class MissingFF00Exception : Exception
+ internal class MissingFF00Exception : Exception
{
}
@@ -2196,8 +2162,17 @@ namespace ImageSharp.Formats
/// The EOF (End of File exception).
/// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker
///
- private class EOFException : Exception
+ internal class EOFException : Exception
+ {
+ }
+
+ public void Dispose()
{
+ for (int i = 0; i < this.huffmanTrees.Length; i++)
+ {
+ this.huffmanTrees[i].Dispose();
+ }
+ this.bytes.Dispose();
}
}
}
diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
index 82a99010f2..66d7cdc7b6 100644
--- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
@@ -246,6 +246,7 @@ namespace ImageSharp.Formats
///
/// The AC luminance huffman table index
///
+
LuminanceAC = 1,
// ReSharper restore UnusedMember.Local
@@ -487,9 +488,9 @@ namespace ImageSharp.Formats
/// The quantization table index.
/// The previous DC value.
/// The
- 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.
int dc = Round(block[0], 8 * this.quant[(int)index][0]);
@@ -540,7 +541,7 @@ namespace ImageSharp.Formats
/// The red chroma block.
/// The blue chroma block.
// ReSharper disable StyleCop.SA1305
- private void ToYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock)
+ private void ToYCbCr(PixelAccessor pixels, int x, int y, ref Block yBlock, ref Block cbBlock, ref Block crBlock)
// ReSharper restore StyleCop.SA1305
where TColor : struct, IPackedPixel
where TPacked : struct
@@ -577,7 +578,7 @@ namespace ImageSharp.Formats
///
/// The destination block array
/// The source block array.
- private void Scale16X16To8X8(Block destination, Block[] source)
+ private void Scale16X16To8X8(ref Block destination, Block[] source)
{
for (int i = 0; i < 4; i++)
{
@@ -847,10 +848,10 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct
{
- Block b = new Block();
- Block cb = new Block();
- Block cr = new Block();
-
+ Block b = Block.Create();
+ Block cb = Block.Create();
+ Block cr = Block.Create();
+
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@@ -858,12 +859,16 @@ namespace ImageSharp.Formats
{
for (int x = 0; x < pixels.Width; x += 8)
{
- this.ToYCbCr(pixels, x, y, b, cb, cr);
- prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY);
- prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb);
- prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr);
+ 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);
}
}
+
+ b.Dispose();
+ cb.Dispose();
+ cr.Dispose();
}
///
@@ -877,23 +882,13 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct
{
- Block b = new Block();
- Block[] cb = new Block[4];
- Block[] cr = new Block[4];
-
+ Block b = Block.Create();
+ Block[] cb = Block.CreateArray(4);
+ Block[] cr = Block.CreateArray(4);
+
// ReSharper disable once InconsistentNaming
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 x = 0; x < pixels.Width; x += 16)
@@ -903,16 +898,20 @@ namespace ImageSharp.Formats
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
- this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]);
- prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY);
+ this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]);
+ prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
}
- this.Scale16X16To8X8(b, cb);
- prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb);
- this.Scale16X16To8X8(b, cr);
- prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr);
+ 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);
}
}
+
+ b.Dispose();
+ Block.DisposeAll(cb);
+ Block.DisposeAll(cr);
}
///
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
index 549ac05ef4..c91b0ad1b1 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
@@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0.
//
+using ImageSharp.Formats;
+
namespace ImageSharp.Tests
{
using System.IO;
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
new file mode 100644
index 0000000000..524b84082e
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
@@ -0,0 +1,367 @@
+// Uncomment this to turn unit tests into benchmarks:
+//#define BENCHMARKING
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using ImageSharp.Formats;
+using Xunit;
+using Xunit.Abstractions;
+// ReSharper disable InconsistentNaming
+
+namespace ImageSharp.Tests.Formats.Jpg
+{
+ public class Block8x8FTests : UtilityTestClassBase
+ {
+#if BENCHMARKING
+ public const int Times = 1000000;
+#else
+ public const int Times = 1;
+#endif
+
+ public Block8x8FTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void Indexer()
+ {
+ float sum = 0;
+ this.Measure(Times, () =>
+ {
+ Block8x8F block = new Block8x8F();
+
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ block[i] = i;
+ }
+ sum = 0;
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ sum += block[i];
+ }
+ });
+ Assert.Equal(sum, 64f * 63f * 0.5f);
+ }
+
+ [Fact]
+ public unsafe void Indexer_GetScalarAt_SetScalarAt()
+ {
+ float sum = 0;
+ Measure(Times, () =>
+ {
+ Block8x8F block = new Block8x8F();
+
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ Block8x8F.SetScalarAt(&block, i, i);
+ }
+ sum = 0;
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ sum += Block8x8F.GetScalarAt(&block, i);
+ }
+ });
+ Assert.Equal(sum, 64f*63f*0.5f);
+ }
+
+ [Fact]
+ public void Indexer_ReferenceBenchmarkWithArray()
+ {
+ float sum = 0;
+
+
+ Measure(Times, () =>
+ {
+ //Block8x8F block = new Block8x8F();
+ float[] block = new float[64];
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ block[i] = i;
+ }
+ sum = 0;
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ sum += block[i];
+ }
+ });
+ Assert.Equal(sum, 64f*63f*0.5f);
+ }
+
+ [Fact]
+ public void Load_Store_FloatArray()
+ {
+ float[] data = new float[Block8x8F.ScalarCount];
+ float[] mirror = new float[Block8x8F.ScalarCount];
+
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ data[i] = i;
+ }
+ Measure(Times, () =>
+ {
+ Block8x8F b = new Block8x8F();
+ b.LoadFrom(data);
+ b.CopyTo(mirror);
+ });
+
+ Assert.Equal(data, mirror);
+ //PrintLinearData((MutableSpan)mirror);
+ }
+
+ [Fact]
+ public unsafe void Load_Store_FloatArray_Ptr()
+ {
+ float[] data = new float[Block8x8F.ScalarCount];
+ float[] mirror = new float[Block8x8F.ScalarCount];
+
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ data[i] = i;
+ }
+ Measure(Times, () =>
+ {
+ Block8x8F b = new Block8x8F();
+ Block8x8F.LoadFrom(&b, data);
+ Block8x8F.CopyTo(&b, mirror);
+ });
+
+ Assert.Equal(data, mirror);
+ //PrintLinearData((MutableSpan)mirror);
+ }
+
+ [Fact]
+ public void Load_Store_IntArray()
+ {
+ int[] data = new int[Block8x8F.ScalarCount];
+ int[] mirror = new int[Block8x8F.ScalarCount];
+
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ data[i] = i;
+ }
+ Measure(Times, () =>
+ {
+ Block8x8F v = new Block8x8F();
+ v.LoadFrom(data);
+ v.CopyTo(mirror);
+ });
+
+
+ Assert.Equal(data, mirror);
+ //PrintLinearData((MutableSpan)mirror);
+ }
+
+
+ [Fact]
+ public void TransposeInto()
+ {
+ float[] expected = Create8x8FloatData();
+ ReferenceImplementations.Transpose8x8(expected);
+
+ Block8x8F source = new Block8x8F();
+ source.LoadFrom(Create8x8FloatData());
+
+ Block8x8F dest = new Block8x8F();
+ source.TransposeInto(ref dest);
+
+ float[] actual = new float[64];
+ dest.CopyTo(actual);
+
+ Assert.Equal(expected, actual);
+ }
+
+
+
+ private class BufferHolder
+ {
+ public Block8x8F Buffer;
+ }
+
+ [Fact]
+ public void TranposeInto_Benchmark()
+ {
+ BufferHolder source = new BufferHolder();
+ source.Buffer.LoadFrom(Create8x8FloatData());
+ BufferHolder dest = new BufferHolder();
+
+ Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ...");
+ Stopwatch sw = Stopwatch.StartNew();
+
+ for (int i = 0; i < Times; i++)
+ {
+ source.Buffer.TransposeInto(ref dest.Buffer);
+ }
+
+ sw.Stop();
+ Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
+
+ }
+
+ [Fact]
+ public void iDCT2D8x4_LeftPart()
+ {
+ float[] sourceArray = Create8x8FloatData();
+ float[] expectedDestArray = new float[64];
+
+ ReferenceImplementations.iDCT2D8x4_32f(sourceArray, expectedDestArray);
+
+ Block8x8F source = new Block8x8F();
+ source.LoadFrom(sourceArray);
+
+ Block8x8F dest = new Block8x8F();
+
+ source.IDCT8x4_LeftPart(ref dest);
+
+ float[] actualDestArray = new float[64];
+ dest.CopyTo(actualDestArray);
+
+ Print8x8Data(expectedDestArray);
+ Output.WriteLine("**************");
+ Print8x8Data(actualDestArray);
+
+ Assert.Equal(expectedDestArray, actualDestArray);
+ }
+
+ [Fact]
+ public void iDCT2D8x4_RightPart()
+ {
+ MutableSpan sourceArray = Create8x8FloatData();
+ MutableSpan expectedDestArray = new float[64];
+
+ ReferenceImplementations.iDCT2D8x4_32f(sourceArray.Slice(4), expectedDestArray.Slice(4));
+
+ Block8x8F source = new Block8x8F();
+ source.LoadFrom(sourceArray);
+
+ Block8x8F dest = new Block8x8F();
+
+ source.IDCT8x4_RightPart(ref dest);
+
+ float[] actualDestArray = new float[64];
+ dest.CopyTo(actualDestArray);
+
+ Print8x8Data(expectedDestArray);
+ Output.WriteLine("**************");
+ Print8x8Data(actualDestArray);
+
+ Assert.Equal(expectedDestArray.Data, actualDestArray);
+ }
+
+ private struct ApproximateFloatComparer : IEqualityComparer
+ {
+ private const float Eps = 0.0001f;
+
+ public bool Equals(float x, float y)
+ {
+ float d = x - y;
+
+ return d > -Eps && d < Eps;
+ }
+
+ public int GetHashCode(float obj)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ [Fact]
+ public void IDCTInto()
+ {
+ float[] sourceArray = Create8x8FloatData();
+ float[] expectedDestArray = new float[64];
+ float[] tempArray = new float[64];
+
+ ReferenceImplementations.iDCT2D_llm(sourceArray, expectedDestArray, tempArray);
+
+ //ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray);
+
+ Block8x8F source = new Block8x8F();
+ source.LoadFrom(sourceArray);
+
+ Block8x8F dest = new Block8x8F();
+ Block8x8F tempBuffer = new Block8x8F();
+
+ source.TransformIDCTInto(ref dest, ref tempBuffer);
+
+ float[] actualDestArray = new float[64];
+ dest.CopyTo(actualDestArray);
+
+ Print8x8Data(expectedDestArray);
+ Output.WriteLine("**************");
+ Print8x8Data(actualDestArray);
+ Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer());
+ Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer());
+ }
+
+
+ [Fact]
+ public unsafe void CopyColorsTo()
+ {
+ var data = Create8x8FloatData();
+ Block8x8F block = new Block8x8F();
+ block.LoadFrom(data);
+ block.MultiplyAllInplace(new Vector4(5, 5, 5, 5));
+
+ int stride = 256;
+ int height = 42;
+ int offset = height*10 + 20;
+
+ byte[] colorsExpected = new byte[stride*height];
+ byte[] colorsActual = new byte[stride*height];
+
+ Block8x8F temp = new Block8x8F();
+
+ ReferenceImplementations.CopyColorsTo(ref block, new MutableSpan(colorsExpected, offset), stride);
+
+ block.CopyColorsTo(new MutableSpan(colorsActual, offset), stride, &temp);
+
+ //Output.WriteLine("******* EXPECTED: *********");
+ //PrintLinearData(colorsExpected);
+ //Output.WriteLine("******** ACTUAL: **********");
+
+ Assert.Equal(colorsExpected, colorsActual);
+ }
+
+ private static float[] Create8x8ColorCropTestData()
+ {
+ float[] result = new float[64];
+ for (int i = 0; i < 8; i++)
+ {
+ for (int j = 0; j < 8; j++)
+ {
+ result[i * 8 + j] = -300 + i * 100 + j * 10;
+ }
+ }
+ return result;
+ }
+
+ [Fact]
+ public void TransformByteConvetibleColorValuesInto()
+ {
+ Block8x8F block = new Block8x8F();
+ var input = Create8x8ColorCropTestData();
+ block.LoadFrom(input);
+ Output.WriteLine("Input:");
+ PrintLinearData(input);
+
+
+ Block8x8F dest = new Block8x8F();
+ block.TransformByteConvetibleColorValuesInto(ref dest);
+
+ float[] array = new float[64];
+ dest.CopyTo(array);
+ Output.WriteLine("Result:");
+ PrintLinearData(array);
+ foreach (float val in array)
+ {
+ Assert.InRange(val, 0, 255);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
new file mode 100644
index 0000000000..1177b57aba
--- /dev/null
+++ b/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