diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index b058fad4e..1839bf2f8 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -342,6 +342,7 @@ True AC DC + DCT EOF FDCT IDCT diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index a25f2573d..740103533 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -77,6 +77,11 @@ namespace SixLabors.ImageSharp /// public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.imageFormats; + /// /// Gets the maximum header size of all the formats. /// @@ -97,11 +102,6 @@ namespace SixLabors.ImageSharp /// internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; - /// - /// Gets the currently registered s. - /// - internal IEnumerable ImageFormats => this.imageFormats; - #if !NETSTANDARD1_1 /// /// Gets or sets the filesystem helper for accessing the local file system. @@ -201,29 +201,12 @@ namespace SixLabors.ImageSharp this.SetMaxHeaderSize(); } - /// - /// Creates the default instance with the following s preregistered: - /// - /// - /// - /// - /// - /// The default configuration of - internal static Configuration CreateDefaultInstance() - { - return new Configuration( - new PngConfigurationModule(), - new JpegConfigurationModule(), - new GifConfigurationModule(), - new BmpConfigurationModule()); - } - /// /// For the specified mime type find the decoder. /// /// The format to discover /// The if found otherwise null - internal IImageDecoder FindDecoder(IImageFormat format) + public IImageDecoder FindDecoder(IImageFormat format) { Guard.NotNull(format, nameof(format)); if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder)) @@ -239,7 +222,7 @@ namespace SixLabors.ImageSharp /// /// The format to discover /// The if found otherwise null - internal IImageEncoder FindEncoder(IImageFormat format) + public IImageEncoder FindEncoder(IImageFormat format) { Guard.NotNull(format, nameof(format)); if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder)) @@ -250,6 +233,23 @@ namespace SixLabors.ImageSharp return null; } + /// + /// Creates the default instance with the following s preregistered: + /// + /// + /// + /// + /// + /// The default configuration of + internal static Configuration CreateDefaultInstance() + { + return new Configuration( + new PngConfigurationModule(), + new JpegConfigurationModule(), + new GifConfigurationModule(), + new BmpConfigurationModule()); + } + /// /// Sets the max header size. /// diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs new file mode 100644 index 000000000..3f4c69c3e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -0,0 +1,306 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + /// + /// Represents a Jpeg block with coefficiens. + /// + // ReSharper disable once InconsistentNaming + internal unsafe struct Block8x8 : IEquatable + { + /// + /// A number of scalar coefficients in a + /// + public const int Size = 64; + + /// + /// A fixed size buffer holding the values. + /// See: + /// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/unsafe-code-pointers/fixed-size-buffers + /// + /// + private fixed short data[Size]; + + /// + /// Initializes a new instance of the struct. + /// + /// A of coefficients + public Block8x8(Span coefficients) + { + ref byte selfRef = ref Unsafe.As(ref this); + ref byte sourceRef = ref coefficients.NonPortableCast().DangerousGetPinnableReference(); + Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); + } + + /// + /// Gets or sets a value at the given index + /// + /// The index + /// The value + public short this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + GuardBlockIndex(idx); + ref short selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, idx); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + GuardBlockIndex(idx); + ref short selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, idx) = value; + } + } + + /// + /// Gets or sets a value in a row+coulumn of the 8x8 block + /// + /// The x position index in the row + /// The column index + /// The value + public short this[int x, int y] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } + + public static bool operator ==(Block8x8 left, Block8x8 right) + { + return left.Equals(right); + } + + public static bool operator !=(Block8x8 left, Block8x8 right) + { + return !left.Equals(right); + } + + /// + /// Multiply all elements by a given + /// + public static Block8x8 operator *(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val *= value; + result[i] = (short)val; + } + + return result; + } + + /// + /// Divide all elements by a given + /// + public static Block8x8 operator /(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val /= value; + result[i] = (short)val; + } + + return result; + } + + /// + /// Add an to all elements + /// + public static Block8x8 operator +(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val += value; + result[i] = (short)val; + } + + return result; + } + + /// + /// Subtract an from all elements + /// + public static Block8x8 operator -(Block8x8 block, int value) + { + Block8x8 result = block; + for (int i = 0; i < Size; i++) + { + int val = result[i]; + val -= value; + result[i] = (short)val; + } + + return result; + } + + /// + /// Pointer-based "Indexer" (getter part) + /// + /// Block pointer + /// Index + /// The scaleVec value at the specified index + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short GetScalarAt(Block8x8* blockPtr, int idx) + { + GuardBlockIndex(idx); + + short* fp = blockPtr->data; + return fp[idx]; + } + + /// + /// Pointer-based "Indexer" (setter part) + /// + /// Block pointer + /// Index + /// Value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetScalarAt(Block8x8* blockPtr, int idx, short value) + { + GuardBlockIndex(idx); + + short* fp = blockPtr->data; + fp[idx] = value; + } + + /// + /// Convert into + /// + public Block8x8F AsFloatBlock() + { + // TODO: Optimize this + var result = default(Block8x8F); + for (int i = 0; i < Size; i++) + { + result[i] = this[i]; + } + + return result; + } + + /// + /// Copy all elements to an array of . + /// + public short[] ToArray() + { + short[] result = new short[Size]; + this.CopyTo(result); + return result; + } + + /// + /// Copy elements into 'destination' Span of values + /// + public void CopyTo(Span destination) + { + ref byte selfRef = ref Unsafe.As(ref this); + ref byte destRef = ref destination.NonPortableCast().DangerousGetPinnableReference(); + Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short)); + } + + /// + /// Copy elements into 'destination' Span of values + /// + public void CopyTo(Span destination) + { + for (int i = 0; i < Size; i++) + { + destination[i] = this[i]; + } + } + + /// + /// Cast and copy -s from the beginning of 'source' span. + /// + public void LoadFrom(Span source) + { + for (int i = 0; i < Size; i++) + { + this[i] = (short)source[i]; + } + } + + [Conditional("DEBUG")] + private static void GuardBlockIndex(int idx) + { + DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); + DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); + } + + /// + public override string ToString() + { + var bld = new StringBuilder(); + bld.Append('['); + for (int i = 0; i < Size; i++) + { + bld.Append(this[i]); + if (i < Size - 1) + { + bld.Append(','); + } + } + + bld.Append(']'); + return bld.ToString(); + } + + /// + public bool Equals(Block8x8 other) + { + for (int i = 0; i < Size; i++) + { + if (this[i] != other[i]) + { + return false; + } + } + + return true; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is Block8x8 && this.Equals((Block8x8)obj); + } + + /// + public override int GetHashCode() + { + return (this[0] * 31) + this[1]; + } + + /// + /// Calculate the total sum of absoulute differences of elements in 'a' and 'b'. + /// + public static long TotalDifference(ref Block8x8 a, ref Block8x8 b) + { + long result = 0; + for (int i = 0; i < Size; i++) + { + int d = a[i] - b[i]; + result += Math.Abs(d); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index 65f7abfe5..4c1b4f4d1 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) + internal void NormalizeColorsInto(ref Block8x8F d) { d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); @@ -117,5 +117,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); } - } + + + /// + /// Level shift by +128, clip to [0, 255] + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void NormalizeColorsInplace() + { + this.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); + this.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); + this.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4); + this.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4); + this.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4); + this.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4); + this.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4); + this.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4); + this.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4); + this.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4); + this.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4); + this.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4); + this.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4); + this.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4); + this.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); + this.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); + } + } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 1216c1841..4d0ec3393 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -2,16 +2,17 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; +using SixLabors.ImageSharp.Memory; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Common { /// - /// DCT code Ported from https://github.com/norishigefukushima/dct_simd + /// Represents a Jpeg block with coefficients. /// internal partial struct Block8x8F { @@ -26,9 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public const int VectorCount = 16; /// - /// Scalar count + /// A number of scalar coefficients in a /// - public const int ScalarCount = VectorCount * 4; + public const int Size = 64; #pragma warning disable SA1600 // ElementsMustBeDocumented public Vector4 V0L; @@ -64,29 +65,97 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// The index /// The float value at the specified index - public unsafe float this[int idx] + public float this[int idx] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - fixed (Block8x8F* p = &this) - { - float* fp = (float*)p; - return fp[idx]; - } + GuardBlockIndex(idx); + ref float selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, idx); } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - fixed (Block8x8F* p = &this) - { - float* fp = (float*)p; - fp[idx] = value; - } + GuardBlockIndex(idx); + ref float selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, idx) = value; } } + public float this[int x, int y] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } + + public static Block8x8F operator *(Block8x8F block, float value) + { + Block8x8F result = block; + for (int i = 0; i < Size; i++) + { + float val = result[i]; + val *= value; + result[i] = val; + } + + return result; + } + + public static Block8x8F operator /(Block8x8F block, float value) + { + Block8x8F result = block; + for (int i = 0; i < Size; i++) + { + float val = result[i]; + val /= value; + result[i] = (float)val; + } + + return result; + } + + public static Block8x8F operator +(Block8x8F block, float value) + { + Block8x8F result = block; + for (int i = 0; i < Size; i++) + { + float val = result[i]; + val += value; + result[i] = (float)val; + } + + return result; + } + + public static Block8x8F operator -(Block8x8F block, float value) + { + Block8x8F result = block; + for (int i = 0; i < Size; i++) + { + float val = result[i]; + val -= value; + result[i] = (float)val; + } + + return result; + } + + public static Block8x8F Load(Span data) + { + var result = default(Block8x8F); + result.LoadFrom(data); + return result; + } + + public static Block8x8F Load(Span data) + { + var result = default(Block8x8F); + result.LoadFrom(data); + return result; + } + /// /// Pointer-based "Indexer" (getter part) /// @@ -96,6 +165,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx) { + GuardBlockIndex(idx); + float* fp = (float*)blockPtr; return fp[idx]; } @@ -109,6 +180,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value) { + GuardBlockIndex(idx); + float* fp = (float*)blockPtr; fp[idx] = value; } @@ -128,12 +201,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Source [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void LoadFrom(MutableSpan source) + public void LoadFrom(Span source) { - fixed (void* ptr = &this.V0L) - { - Marshal.Copy(source.Data, source.Offset, (IntPtr)ptr, ScalarCount); - } + ref byte s = ref Unsafe.As(ref source.DangerousGetPinnableReference()); + ref byte d = ref Unsafe.As(ref this); + + Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } /// @@ -142,21 +215,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Block pointer /// Source [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void LoadFrom(Block8x8F* blockPtr, MutableSpan source) + public static unsafe void LoadFrom(Block8x8F* blockPtr, Span source) { - Marshal.Copy(source.Data, source.Offset, (IntPtr)blockPtr, ScalarCount); + blockPtr->LoadFrom(source); } /// /// Load raw 32bit floating point data from source /// /// Source - public unsafe void LoadFrom(MutableSpan source) + public unsafe void LoadFrom(Span source) { fixed (Vector4* ptr = &this.V0L) { float* fp = (float*)ptr; - for (int i = 0; i < ScalarCount; i++) + for (int i = 0; i < Size; i++) { fp[i] = source[i]; } @@ -168,12 +241,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Destination [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void CopyTo(MutableSpan dest) + public unsafe void CopyTo(Span dest) { - fixed (void* ptr = &this.V0L) - { - Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount); - } + ref byte d = ref Unsafe.As(ref dest.DangerousGetPinnableReference()); + ref byte s = ref Unsafe.As(ref this); + + Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } /// @@ -182,10 +255,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Pointer to block /// Destination [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest) + public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) { float* fPtr = (float*)blockPtr; - for (int i = 0; i < ScalarCount; i++) + for (int i = 0; i < Size; i++) { dest[i] = (byte)*fPtr; fPtr++; @@ -198,9 +271,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Block pointer /// Destination [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest) + public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) { - Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount); + blockPtr->CopyTo(dest); } /// @@ -212,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common { fixed (void* ptr = &this.V0L) { - Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount); + Marshal.Copy((IntPtr)ptr, dest, 0, Size); } } @@ -220,18 +293,79 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Copy raw 32bit floating point data to dest /// /// Destination - public unsafe void CopyTo(MutableSpan dest) + public unsafe void CopyTo(Span dest) { fixed (Vector4* ptr = &this.V0L) { float* fp = (float*)ptr; - for (int i = 0; i < ScalarCount; i++) + for (int i = 0; i < Size; i++) { dest[i] = (int)fp[i]; } } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) + { + ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); + ref byte d = ref Unsafe.Add(ref destBase, row * destStride); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } + + public void CopyTo(BufferArea area) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); + int destStride = area.Stride * sizeof(float); + + CopyRowImpl(ref selfBase, ref destBase, destStride, 0); + CopyRowImpl(ref selfBase, ref destBase, destStride, 1); + CopyRowImpl(ref selfBase, ref destBase, destStride, 2); + CopyRowImpl(ref selfBase, ref destBase, destStride, 3); + CopyRowImpl(ref selfBase, ref destBase, destStride, 4); + CopyRowImpl(ref selfBase, ref destBase, destStride, 5); + CopyRowImpl(ref selfBase, ref destBase, destStride, 6); + CopyRowImpl(ref selfBase, ref destBase, destStride, 7); + } + + public void CopyTo(BufferArea area, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + this.CopyTo(area); + return; + } + + // TODO: Optimize: implement all the cases with loopless special code! (T4?) + for (int y = 0; y < 8; y++) + { + int yy = y * verticalScale; + + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; + + float value = this[(y * 8) + x]; + + for (int i = 0; i < verticalScale; i++) + { + for (int j = 0; j < horizontalScale; j++) + { + area[xx + j, yy + i] = value; + } + } + } + } + } + + public float[] ToArray() + { + float[] result = new float[Size]; + this.CopyTo(result); + return result; + } + /// /// Multiply all elements of the block. /// @@ -283,17 +417,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } /// - /// Un-zig + /// Quantize the block. /// /// Block pointer /// Qt pointer /// Unzig pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void UnZigAndQuantize(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) + public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) { float* b = (float*)blockPtr; float* qtp = (float*)qtPtr; - for (int zig = 0; zig < ScalarCount; zig++) + for (int zig = 0; zig < Size; zig++) { float* unzigPos = b + unzigPtr[zig]; float val = *unzigPos; @@ -305,26 +439,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255], and write to buffer. /// - /// Color buffer + /// Color buffer /// Stride offset /// Temp Block pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void CopyColorsTo(MutableSpan buffer, int stride, Block8x8F* tempBlockPtr) + public unsafe void CopyColorsTo(Span destinationBuffer, int stride, Block8x8F* tempBlockPtr) { - this.TransformByteConvetibleColorValuesInto(ref *tempBlockPtr); - + this.NormalizeColorsInto(ref *tempBlockPtr); + ref byte d = ref destinationBuffer.DangerousGetPinnableReference(); float* src = (float*)tempBlockPtr; 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); + ref byte dRow = ref Unsafe.Add(ref d, i * stride); + Unsafe.Add(ref dRow, 0) = (byte)src[0]; + Unsafe.Add(ref dRow, 1) = (byte)src[1]; + Unsafe.Add(ref dRow, 2) = (byte)src[2]; + Unsafe.Add(ref dRow, 3) = (byte)src[3]; + Unsafe.Add(ref dRow, 4) = (byte)src[4]; + Unsafe.Add(ref dRow, 5) = (byte)src[5]; + Unsafe.Add(ref dRow, 6) = (byte)src[6]; + Unsafe.Add(ref dRow, 7) = (byte)src[7]; src += 8; } } @@ -346,7 +480,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common float* s = (float*)block; float* d = (float*)dest; - for (int zig = 0; zig < ScalarCount; zig++) + for (int zig = 0; zig < Size; zig++) { d[zig] = s[unzigPtr[zig]]; } @@ -401,6 +535,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common a.V7R = DivideRound(a.V7R, b.V7R); } + public void RoundInto(ref Block8x8 dest) + { + for (int i = 0; i < Size; i++) + { + float val = this[i]; + if (val < 0) + { + val -= 0.5f; + } + else + { + val += 0.5f; + } + + dest[i] = (short)val; + } + } + + public Block8x8 RoundAsInt16Block() + { + var result = default(Block8x8); + this.RoundInto(ref result); + return result; + } + + // TODO: Optimize this! + public void RoundInplace() + { + for (int i = 0; i < Size; i++) + { + this[i] = MathF.Round(this[i]); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { @@ -410,5 +578,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common // AlmostRound(dividend/divisor) = dividend/divisior + 0.5*sign(dividend) return (dividend / divisor) + (sign * Offset); } + + [Conditional("DEBUG")] + private static void GuardBlockIndex(int idx) + { + DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); + DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); + } + + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] + private struct Row + { + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs new file mode 100644 index 000000000..da97f9e2a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ComponentUtils.cs @@ -0,0 +1,16 @@ +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + /// + /// Various utilities for . + /// + internal static class ComponentUtils + { + /// + /// Gets a reference to the at the given row and column index from + /// + public static ref Block8x8 GetBlockReference(this IJpegComponent component, int bx, int by) + { + return ref component.SpectralBlocks[bx, by]; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IJpegComponent.cs new file mode 100644 index 000000000..4109fc10e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IJpegComponent.cs @@ -0,0 +1,46 @@ +using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + /// + /// Common interface to represent raw Jpeg components. + /// + internal interface IJpegComponent + { + /// + /// Gets the component's position in the components array. + /// + int Index { get; } + + /// + /// Gets the number of blocks in this component as + /// + Size SizeInBlocks { get; } + + /// + /// Gets the horizontal and the vertical sampling factor as + /// + Size SamplingFactors { get; } + + /// + /// Gets the divisors needed to apply when calculating colors. + /// + /// https://en.wikipedia.org/wiki/Chroma_subsampling + /// + /// In case of 4:2:0 subsampling the values are: Luma.SubSamplingDivisors = (1,1) Chroma.SubSamplingDivisors = (2,2) + /// + Size SubSamplingDivisors { get; } + + /// + /// Gets the index of the quantization table for this block. + /// + int QuantizationTableIndex { get; } + + /// + /// Gets the storing the "raw" frequency-domain decoded + unzigged blocks. + /// We need to apply IDCT and dequantiazition to transform them into color-space blocks. + /// + Buffer2D SpectralBlocks { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs new file mode 100644 index 000000000..3873656a4 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; + +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + /// + /// + /// Represents decompressed, unprocessed jpeg data with spectral space -s. + /// + internal interface IRawJpegData : IDisposable + { + /// + /// Gets the image size in pixels. + /// + Size ImageSizeInPixels { get; } + + /// + /// Gets the number of coponents. + /// + int ComponentCount { get; } + + /// + /// Gets the color space + /// + JpegColorSpace ColorSpace { get; } + + /// + /// Gets the components. + /// + IEnumerable Components { get; } + + /// + /// Gets the quantization tables, in zigzag order. + /// + Block8x8F[] QuantizationTables { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs similarity index 50% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs rename to src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index ba4a42127..4c4701753 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegBlockProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -2,17 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Memory; -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; +using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { /// /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. /// [StructLayout(LayoutKind.Sequential)] - internal unsafe struct JpegBlockProcessor + internal unsafe struct JpegBlockPostProcessor { /// /// The @@ -25,55 +24,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder private DataPointers pointers; /// - /// The component index. + /// Initialize the instance on the stack. /// - private int componentIndex; - - /// - /// Initialize the instance on the stack. - /// - /// The instance - /// The current component index - public static void Init(JpegBlockProcessor* processor, int componentIndex) + /// The instance + public static void Init(JpegBlockPostProcessor* postProcessor) { - processor->componentIndex = componentIndex; - processor->data = ComputationData.Create(); - processor->pointers = new DataPointers(&processor->data); + postProcessor->data = ComputationData.Create(); + postProcessor->pointers = new DataPointers(&postProcessor->data); } - /// - /// Dequantize, perform the inverse DCT and store the blocks to the into the corresponding instances. - /// - /// The instance - public void ProcessAllBlocks(OldJpegDecoderCore decoder) + public void QuantizeAndTransform(IRawJpegData decoder, IJpegComponent component, ref Block8x8 sourceBlock) { - Buffer blockArray = decoder.DecodedBlocks[this.componentIndex]; - for (int i = 0; i < blockArray.Length; i++) - { - this.ProcessBlockColors(decoder, ref blockArray[i]); - } + this.data.SourceBlock = sourceBlock.AsFloatBlock(); + int qtIndex = component.QuantizationTableIndex; + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + Block8x8F* b = this.pointers.SourceBlock; + + Block8x8F.DequantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + + FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2); } - /// - /// Dequantize, perform the inverse DCT and store decodedBlock.Block to the into the corresponding instance. - /// - /// The - /// The - private void ProcessBlockColors(OldJpegDecoderCore decoder, ref DecodedBlock decodedBlock) + public void ProcessBlockColorsInto( + IRawJpegData decoder, + IJpegComponent component, + ref Block8x8 sourceBlock, + BufferArea destArea) { - this.data.Block = decodedBlock.Block; - int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + this.QuantizeAndTransform(decoder, component, ref sourceBlock); - Block8x8F* b = this.pointers.Block; + this.data.WorkspaceBlock1.NormalizeColorsInplace(); - Block8x8F.UnZigAndQuantize(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + // To conform better to libjpeg we actually NEED TO loose precision here. + // This is because they store blocks as Int16 between all the operations. + // Unfortunately, we need to emulate this to be "more accurate" :( + this.data.WorkspaceBlock1.RoundInplace(); - DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); - - OldJpegPixelArea destChannel = decoder.GetDestinationChannel(this.componentIndex); - OldJpegPixelArea destArea = destChannel.GetOffsetedSubAreaForBlock(decodedBlock.Bx, decodedBlock.By); - destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); + Size divs = component.SubSamplingDivisors; + this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height); } /// @@ -83,19 +72,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public struct ComputationData { /// - /// Temporal block 1 to store intermediate and/or final computation results + /// Source block /// - public Block8x8F Block; + public Block8x8F SourceBlock; /// /// Temporal block 1 to store intermediate and/or final computation results /// - public Block8x8F Temp1; + public Block8x8F WorkspaceBlock1; /// /// Temporal block 2 to store intermediate and/or final computation results /// - public Block8x8F Temp2; + public Block8x8F WorkspaceBlock2; /// /// The quantization table as @@ -113,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The public static ComputationData Create() { - ComputationData data = default(ComputationData); + var data = default(ComputationData); data.Unzig = UnzigData.Create(); return data; } @@ -125,19 +114,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public struct DataPointers { /// - /// Pointer to + /// Pointer to /// - public Block8x8F* Block; + public Block8x8F* SourceBlock; /// - /// Pointer to + /// Pointer to /// - public Block8x8F* Temp1; + public Block8x8F* WorkspaceBlock1; /// - /// Pointer to + /// Pointer to /// - public Block8x8F* Temp2; + public Block8x8F* WorkspaceBlock2; /// /// Pointer to @@ -155,9 +144,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Pointer to internal DataPointers(ComputationData* dataPtr) { - this.Block = &dataPtr->Block; - this.Temp1 = &dataPtr->Temp1; - this.Temp2 = &dataPtr->Temp2; + this.SourceBlock = &dataPtr->SourceBlock; + this.WorkspaceBlock1 = &dataPtr->WorkspaceBlock1; + this.WorkspaceBlock2 = &dataPtr->WorkspaceBlock2; this.QuantiazationTable = &dataPtr->QuantiazationTable; this.Unzig = dataPtr->Unzig.Data; } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs new file mode 100644 index 000000000..524cc76df --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs @@ -0,0 +1,46 @@ +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + internal abstract partial class JpegColorConverter + { + private class FromCmyk : JpegColorConverter + { + public FromCmyk() + : base(JpegColorSpace.Cmyk) + { + } + + public override void ConvertToRGBA(ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan cVals = values.Component0; + ReadOnlySpan mVals = values.Component1; + ReadOnlySpan yVals = values.Component2; + ReadOnlySpan kVals = values.Component3; + + var v = new Vector4(0, 0, 0, 1F); + + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + for (int i = 0; i < result.Length; i++) + { + float c = cVals[i]; + float m = mVals[i]; + float y = yVals[i]; + float k = kVals[i] / 255F; + + v.X = c * k; + v.Y = m * k; + v.Z = y * k; + v.W = 1F; + + v *= scale; + + result[i] = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs new file mode 100644 index 000000000..9ff263dcf --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs @@ -0,0 +1,39 @@ +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + internal abstract partial class JpegColorConverter + { + private class FromGrayScale : JpegColorConverter + { + public FromGrayScale() + : base(JpegColorSpace.GrayScale) + { + } + + public override void ConvertToRGBA(ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan yVals = values.Component0; + + var v = new Vector4(0, 0, 0, 1); + + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + for (int i = 0; i < result.Length; i++) + { + float y = yVals[i]; + + v.X = y; + v.Y = y; + v.Z = y; + + v *= scale; + + result[i] = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs new file mode 100644 index 000000000..f4a702783 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs @@ -0,0 +1,43 @@ +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + internal abstract partial class JpegColorConverter + { + private class FromRgb : JpegColorConverter + { + public FromRgb() + : base(JpegColorSpace.RGB) + { + } + + public override void ConvertToRGBA(ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan rVals = values.Component0; + ReadOnlySpan gVals = values.Component1; + ReadOnlySpan bVals = values.Component2; + + var v = new Vector4(0, 0, 0, 1); + + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + for (int i = 0; i < result.Length; i++) + { + float r = rVals[i]; + float g = gVals[i]; + float b = bVals[i]; + + v.X = r; + v.Y = g; + v.Z = b; + + v *= scale; + + result[i] = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs new file mode 100644 index 000000000..24e8d753b --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs @@ -0,0 +1,43 @@ +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + internal abstract partial class JpegColorConverter + { + private class FromYCbCr : JpegColorConverter + { + public FromYCbCr() + : base(JpegColorSpace.YCbCr) + { + } + + public override void ConvertToRGBA(ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan yVals = values.Component0; + ReadOnlySpan cbVals = values.Component1; + ReadOnlySpan crVals = values.Component2; + + var v = new Vector4(0, 0, 0, 1); + + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + for (int i = 0; i < result.Length; i++) + { + float y = yVals[i]; + float cb = cbVals[i] - 128F; + float cr = crVals[i] - 128F; + + v.X = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); + v.Y = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); + v.Z = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + + v *= scale; + + result[i] = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs new file mode 100644 index 000000000..3449cc6b1 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs @@ -0,0 +1,46 @@ +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + internal abstract partial class JpegColorConverter + { + private class FromYccK : JpegColorConverter + { + public FromYccK() + : base(JpegColorSpace.Ycck) + { + } + + public override void ConvertToRGBA(ComponentValues values, Span result) + { + // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! + ReadOnlySpan yVals = values.Component0; + ReadOnlySpan cbVals = values.Component1; + ReadOnlySpan crVals = values.Component2; + ReadOnlySpan kVals = values.Component3; + + var v = new Vector4(0, 0, 0, 1F); + + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + for (int i = 0; i < result.Length; i++) + { + float y = yVals[i]; + float cb = cbVals[i] - 128F; + float cr = crVals[i] - 128F; + float k = kVals[i] / 255F; + + v.X = (255F - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (255F - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; + v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.W = 1F; + + v *= scale; + + result[i] = v; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs new file mode 100644 index 000000000..567713422 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + /// + /// Encapsulates the conversion of Jpeg channels to RGBA values packed in buffer. + /// + internal abstract partial class JpegColorConverter + { + /// + /// The avalilable converters + /// + private static readonly JpegColorConverter[] Converters = { new FromYCbCr(), new FromYccK(), new FromCmyk(), new FromGrayScale(), new FromRgb() }; + + /// + /// Initializes a new instance of the class. + /// + protected JpegColorConverter(JpegColorSpace colorSpace) + { + this.ColorSpace = colorSpace; + } + + /// + /// Gets the of this converter. + /// + public JpegColorSpace ColorSpace { get; } + + /// + /// Returns the corresponding to the given + /// + public static JpegColorConverter GetConverter(JpegColorSpace colorSpace) + { + JpegColorConverter converter = Converters.FirstOrDefault(c => c.ColorSpace == colorSpace); + if (converter == null) + { + throw new Exception($"Could not find any converter for JpegColorSpace {colorSpace}!"); + } + + return converter; + } + + /// + /// He implementation of the conversion. + /// + /// The input as a stack-only struct + /// The destination buffer of values + public abstract void ConvertToRGBA(ComponentValues values, Span result); + + /// + /// A stack-only struct to reference the input buffers using -s. + /// + public struct ComponentValues + { + /// + /// The component count + /// + public readonly int ComponentCount; + + /// + /// The component 0 (eg. Y) + /// + public readonly ReadOnlySpan Component0; + + /// + /// The component 1 (eg. Cb) + /// + public readonly ReadOnlySpan Component1; + + /// + /// The component 2 (eg. Cr) + /// + public readonly ReadOnlySpan Component2; + + /// + /// The component 4 + /// + public readonly ReadOnlySpan Component3; + + /// + /// Initializes a new instance of the struct. + /// + /// The 1-4 sized list of component buffers. + /// The row to convert + public ComponentValues(IReadOnlyList> componentBuffers, int row) + { + this.ComponentCount = componentBuffers.Count; + + this.Component0 = componentBuffers[0].GetRowSpan(row); + this.Component1 = Span.Empty; + this.Component2 = Span.Empty; + this.Component3 = Span.Empty; + + if (this.ComponentCount > 1) + { + this.Component1 = componentBuffers[1].GetRowSpan(row); + if (this.ComponentCount > 2) + { + this.Component2 = componentBuffers[2].GetRowSpan(row); + if (this.ComponentCount > 3) + { + this.Component3 = componentBuffers[3].GetRowSpan(row); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs new file mode 100644 index 000000000..da353d279 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs @@ -0,0 +1,20 @@ +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + /// + /// Identifies the colorspace of a Jpeg image + /// + internal enum JpegColorSpace + { + Undefined = 0, + + GrayScale, + + Ycck, + + Cmyk, + + RGB, + + YCbCr + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs new file mode 100644 index 000000000..feb5164d7 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs @@ -0,0 +1,105 @@ +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + /// + /// Encapsulates postprocessing data for one component for . + /// + internal class JpegComponentPostProcessor : IDisposable + { + /// + /// Points to the current row in . + /// + private int currentComponentRowInBlocks; + + /// + /// The size of the area in corrsponding to one 8x8 Jpeg block + /// + private readonly Size blockAreaSize; + + /// + /// Initializes a new instance of the class. + /// + public JpegComponentPostProcessor(JpegImagePostProcessor imagePostProcessor, IJpegComponent component) + { + this.Component = component; + this.ImagePostProcessor = imagePostProcessor; + this.ColorBuffer = new Buffer2D(imagePostProcessor.PostProcessorBufferSize); + + this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; + this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + } + + /// + /// Gets the + /// + public JpegImagePostProcessor ImagePostProcessor { get; } + + /// + /// Gets the + /// + public IJpegComponent Component { get; } + + /// + /// Gets the temporal working buffer of color values. + /// + public Buffer2D ColorBuffer { get; } + + /// + /// Gets + /// + public Size SizeInBlocks => this.Component.SizeInBlocks; + + /// + /// Gets the maximal number of block rows being processed in one step. + /// + public int BlockRowsPerStep { get; } + + /// + public void Dispose() + { + this.ColorBuffer.Dispose(); + } + + /// + /// Invoke for block rows, copy the result into . + /// + public unsafe void CopyBlocksToColorBuffer() + { + var blockPp = default(JpegBlockPostProcessor); + JpegBlockPostProcessor.Init(&blockPp); + + for (int y = 0; y < this.BlockRowsPerStep; y++) + { + int yBlock = this.currentComponentRowInBlocks + y; + + if (yBlock >= this.SizeInBlocks.Height) + { + break; + } + + int yBuffer = y * this.blockAreaSize.Height; + + for (int x = 0; x < this.SizeInBlocks.Width; x++) + { + int xBlock = x; + int xBuffer = x * this.blockAreaSize.Width; + + ref Block8x8 block = ref this.Component.GetBlockReference(xBlock, yBlock); + + BufferArea destArea = this.ColorBuffer.GetArea( + xBuffer, + yBuffer, + this.blockAreaSize.Width, + this.blockAreaSize.Height); + + blockPp.ProcessBlockColorsInto(this.ImagePostProcessor.RawJpeg, this.Component, ref block, destArea); + } + } + + this.currentComponentRowInBlocks += this.BlockRowsPerStep; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs new file mode 100644 index 000000000..2b583bdbb --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs @@ -0,0 +1,158 @@ +using System; +using System.Linq; +using System.Numerics; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder +{ + /// + /// Encapsulates the execution od post-processing algorithms to be applied on a to produce a valid :
+ /// (1) Dequantization
+ /// (2) IDCT
+ /// (3) Color conversion form one of the -s into a buffer of RGBA values
+ /// (4) Packing pixels from the buffer.
+ /// These operations are executed in steps. + /// image rows are converted in one step, + /// which means that size of the allocated memory is limited (does not depend on ). + ///
+ internal class JpegImagePostProcessor : IDisposable + { + /// + /// The number of block rows to be processed in one Step. + /// + public const int BlockRowsPerStep = 4; + + /// + /// The number of image pixel rows to be processed in one step. + /// + public const int PixelRowsPerStep = 4 * 8; + + /// + /// Temporal buffer to store a row of colors. + /// + private readonly Buffer rgbaBuffer; + + /// + /// The corresponding to the current determined by . + /// + private JpegColorConverter colorConverter; + + /// + /// Initializes a new instance of the class. + /// + /// The representing the uncompressed spectral Jpeg data + public JpegImagePostProcessor(IRawJpegData rawJpeg) + { + this.RawJpeg = rawJpeg; + IJpegComponent c0 = rawJpeg.Components.First(); + this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; + this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); + + this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray(); + this.rgbaBuffer = new Buffer(rawJpeg.ImageSizeInPixels.Width); + this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace); + } + + /// + /// Gets the instances. + /// + public JpegComponentPostProcessor[] ComponentProcessors { get; } + + /// + /// Gets the to be processed. + /// + public IRawJpegData RawJpeg { get; } + + /// + /// Gets the total number of post processor steps deduced from the height of the image and . + /// + public int NumberOfPostProcessorSteps { get; } + + /// + /// Gets the size of the temporal buffers we need to allocate into . + /// + public Size PostProcessorBufferSize { get; } + + /// + /// Gets the value of the counter that grows by each step by . + /// + public int PixelRowCounter { get; private set; } + + /// + public void Dispose() + { + foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) + { + cpp.Dispose(); + } + + this.rgbaBuffer.Dispose(); + } + + /// + /// Process all pixels into 'destination'. The image dimensions should match . + /// + /// The pixel type + /// The destination image + public void PostProcess(Image destination) + where TPixel : struct, IPixel + { + this.PixelRowCounter = 0; + + if (this.RawJpeg.ImageSizeInPixels != destination.Size()) + { + throw new ArgumentException("Input image is not of the size of the processed one!"); + } + + while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height) + { + this.DoPostProcessorStep(destination); + } + } + + /// + /// Execute one step rocessing pixel rows into 'destination'. + /// + /// The pixel type + /// The destination image. + public void DoPostProcessorStep(Image destination) + where TPixel : struct, IPixel + { + foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) + { + cpp.CopyBlocksToColorBuffer(); + } + + this.ConvertColorsInto(destination); + + this.PixelRowCounter += PixelRowsPerStep; + } + + /// + /// Convert and copy row of colors into 'destination' starting at row . + /// + /// The pixel type + /// The destination image + private void ConvertColorsInto(Image destination) + where TPixel : struct, IPixel + { + int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); + + Buffer2D[] buffers = this.ComponentProcessors.Select(cp => cp.ColorBuffer).ToArray(); + + for (int yy = this.PixelRowCounter; yy < maxY; yy++) + { + int y = yy - this.PixelRowCounter; + + var values = new JpegColorConverter.ComponentValues(buffers, y); + this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer); + + Span destRow = destination.GetRowSpan(yy); + + PixelOperations.Instance.PackFromVector4(this.rgbaBuffer, destRow, destination.Width); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/DCT.cs b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/DCT.cs rename to src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs index 881ef511d..8a4f56e3d 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/DCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs @@ -3,47 +3,47 @@ using System.Numerics; using System.Runtime.CompilerServices; -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Common { /// - /// Contains forward and inverse DCT implementations + /// Contains inaccurate, but fast forward and inverse DCT implementations. /// - internal static class DCT + internal static class FastFloatingPointDCT { #pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore - private static readonly float C_1_175876 = 1.175876f; + private const float C_1_175876 = 1.175875602f; - private static readonly float C_1_961571 = -1.961571f; + private const float C_1_961571 = -1.961570560f; - private static readonly float C_0_390181 = -0.390181f; + private const float C_0_390181 = -0.390180644f; - private static readonly float C_0_899976 = -0.899976f; + private const float C_0_899976 = -0.899976223f; - private static readonly float C_2_562915 = -2.562915f; + private const float C_2_562915 = -2.562915447f; - private static readonly float C_0_298631 = 0.298631f; + private const float C_0_298631 = 0.298631336f; - private static readonly float C_2_053120 = 2.053120f; + private const float C_2_053120 = 2.053119869f; - private static readonly float C_3_072711 = 3.072711f; + private const float C_3_072711 = 3.072711026f; - private static readonly float C_1_501321 = 1.501321f; + private const float C_1_501321 = 1.501321110f; - private static readonly float C_0_541196 = 0.541196f; + private const float C_0_541196 = 0.541196100f; - private static readonly float C_1_847759 = -1.847759f; + private const float C_1_847759 = -1.847759065f; - private static readonly float C_0_765367 = 0.765367f; + private const float C_0_765367 = 0.765366865f; - private static readonly float C_0_125 = 0.1250f; + private const float C_0_125 = 0.1250f; #pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); /// - /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization) + /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization). + /// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 /// /// Source /// Destination @@ -59,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components IDCT8x4_LeftPart(ref temp, ref dest); IDCT8x4_RightPart(ref temp, ref dest); + // TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? dest.MultiplyAllInplace(C_0_125); } diff --git a/src/ImageSharp/Formats/Jpeg/Common/SizeExtensions.cs b/src/ImageSharp/Formats/Jpeg/Common/SizeExtensions.cs new file mode 100644 index 000000000..b9bfe425a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/SizeExtensions.cs @@ -0,0 +1,48 @@ +using System.Numerics; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + /// + /// Extension methods for + /// + internal static class SizeExtensions + { + /// + /// Multiplies 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. + /// TODO: Shouldn't we expose this as operator in SixLabors.Core? + /// + public static Size MultiplyBy(this Size a, Size b) => new Size(a.Width * b.Width, a.Height * b.Height); + + /// + /// Divides 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. + /// TODO: Shouldn't we expose this as operator in SixLabors.Core? + /// + public static Size DivideBy(this Size a, Size b) => new Size(a.Width / b.Width, a.Height / b.Height); + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, int divX, int divY) + { + var sizeVect = (Vector2)(SizeF)originalSize; + sizeVect /= new Vector2(divX, divY); + sizeVect.X = MathF.Ceiling(sizeVect.X); + sizeVect.Y = MathF.Ceiling(sizeVect.Y); + + return new Size((int)sizeVect.X, (int)sizeVect.Y); + } + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, int divisor) => + DivideRoundUp(originalSize, divisor, divisor); + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, Size divisor) => + DivideRoundUp(originalSize, divisor.Width, divisor.Height); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs index aaefbb3af..e243938e3 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.Common { /// + /// TODO: This should be simply just a ! /// 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 diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs index 4f1b8783a..6b16ea824 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs @@ -14,6 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components /// /// The value-type buffer sized for 4 instances. /// - public fixed float Data[4 * Block8x8F.ScalarCount]; + public fixed float Data[4 * Block8x8F.Size]; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs index 4aa72bf83..acaa69e3a 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureNBits(int n, ref InputProcessor inputProcessor) { - OldDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor); + OrigDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor); errorCode.EnsureNoError(); } @@ -46,17 +46,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// 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. - /// This method does not throw. Returns instead. + /// This method does not throw. Returns instead. /// /// The number of bits to ensure. /// The /// Error code - public OldDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor) + public OrigDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor) { while (true) { - OldDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor); - if (errorCode != OldDecoderErrorCode.NoError || this.UnreadBits >= n) + OrigDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor); + if (errorCode != OrigDecoderErrorCode.NoError || this.UnreadBits >= n) { return errorCode; } @@ -67,8 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Unrolled version of for n==8 ///
/// The - /// A - public OldDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor) + /// A + public OrigDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor) { return this.EnsureBitsStepImpl(ref inputProcessor); } @@ -77,8 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Unrolled version of for n==1 ///
/// The - /// A - public OldDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor) + /// A + public OrigDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor) { return this.EnsureBitsStepImpl(ref inputProcessor); } @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public int ReceiveExtend(int t, ref InputProcessor inputProcessor) { int x; - OldDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out x); + OrigDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out x); errorCode.EnsureNoError(); return x; } @@ -104,13 +104,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Byte /// The /// Read bits value - /// The - public OldDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x) + /// The + public OrigDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x) { if (this.UnreadBits < t) { - OldDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor); - if (errorCode != OldDecoderErrorCode.NoError) + OrigDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor); + if (errorCode != OrigDecoderErrorCode.NoError) { x = int.MaxValue; return errorCode; @@ -127,15 +127,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder x += ((-1) << t) + 1; } - return OldDecoderErrorCode.NoError; + return OrigDecoderErrorCode.NoError; } - private OldDecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor) + private OrigDecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor) { int c; - OldDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out c); + OrigDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out c); - if (errorCode != OldDecoderErrorCode.NoError) + if (errorCode != OrigDecoderErrorCode.NoError) { return errorCode; } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs index a64f10fe5..b8c64fbe4 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs @@ -85,8 +85,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder ///
/// Input stream /// The result byte as - /// The - public OldDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x) + /// The + public OrigDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x) { // Take the fast path if bytes.buf contains at least two bytes. if (this.I + 2 <= this.J) @@ -94,50 +94,50 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder x = this.BufferAsInt[this.I]; this.I++; this.UnreadableBytes = 1; - if (x != OldJpegConstants.Markers.XFFInt) + if (x != OrigJpegConstants.Markers.XFFInt) { - return OldDecoderErrorCode.NoError; + return OrigDecoderErrorCode.NoError; } if (this.BufferAsInt[this.I] != 0x00) { - return OldDecoderErrorCode.MissingFF00; + return OrigDecoderErrorCode.MissingFF00; } this.I++; this.UnreadableBytes = 2; - x = OldJpegConstants.Markers.XFF; - return OldDecoderErrorCode.NoError; + x = OrigJpegConstants.Markers.XFF; + return OrigDecoderErrorCode.NoError; } this.UnreadableBytes = 0; - OldDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x); + OrigDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x); this.UnreadableBytes = 1; - if (errorCode != OldDecoderErrorCode.NoError) + if (errorCode != OrigDecoderErrorCode.NoError) { return errorCode; } - if (x != OldJpegConstants.Markers.XFF) + if (x != OrigJpegConstants.Markers.XFF) { - return OldDecoderErrorCode.NoError; + return OrigDecoderErrorCode.NoError; } errorCode = this.ReadByteAsIntUnsafe(inputStream, out x); this.UnreadableBytes = 2; - if (errorCode != OldDecoderErrorCode.NoError) + if (errorCode != OrigDecoderErrorCode.NoError) { return errorCode; } if (x != 0x00) { - return OldDecoderErrorCode.MissingFF00; + return OrigDecoderErrorCode.MissingFF00; } - x = OldJpegConstants.Markers.XFF; - return OldDecoderErrorCode.NoError; + x = OrigJpegConstants.Markers.XFF; + return OrigDecoderErrorCode.NoError; } /// @@ -149,25 +149,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public byte ReadByte(Stream inputStream) { byte result; - OldDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out result); + OrigDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out result); errorCode.EnsureNoError(); return result; } /// /// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing. - /// This method does not throw on format error, it returns a instead. + /// This method does not throw on format error, it returns a instead. /// /// Input stream /// The result as out parameter - /// The - public OldDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result) + /// The + public OrigDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result) { - OldDecoderErrorCode errorCode = OldDecoderErrorCode.NoError; + OrigDecoderErrorCode errorCode = OrigDecoderErrorCode.NoError; while (this.I == this.J) { errorCode = this.FillUnsafe(inputStream); - if (errorCode != OldDecoderErrorCode.NoError) + if (errorCode != OrigDecoderErrorCode.NoError) { result = 0; return errorCode; @@ -185,15 +185,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The input stream /// The result - /// A + /// A [MethodImpl(MethodImplOptions.AggressiveInlining)] - public OldDecoderErrorCode ReadByteAsIntUnsafe(Stream inputStream, out int result) + public OrigDecoderErrorCode ReadByteAsIntUnsafe(Stream inputStream, out int result) { - OldDecoderErrorCode errorCode = OldDecoderErrorCode.NoError; + OrigDecoderErrorCode errorCode = OrigDecoderErrorCode.NoError; while (this.I == this.J) { errorCode = this.FillUnsafe(inputStream); - if (errorCode != OldDecoderErrorCode.NoError) + if (errorCode != OrigDecoderErrorCode.NoError) { result = 0; return errorCode; @@ -215,18 +215,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Fill(Stream inputStream) { - OldDecoderErrorCode errorCode = this.FillUnsafe(inputStream); + OrigDecoderErrorCode errorCode = this.FillUnsafe(inputStream); errorCode.EnsureNoError(); } /// /// Fills up the bytes buffer from the underlying stream. /// It should only be called when there are no unread bytes in bytes. - /// This method does not throw , returns a instead! + /// This method does not throw , returns a instead! /// /// Input stream - /// The - public OldDecoderErrorCode FillUnsafe(Stream inputStream) + /// The + public OrigDecoderErrorCode FillUnsafe(Stream inputStream) { if (this.I != this.J) { @@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J); if (n == 0) { - return OldDecoderErrorCode.UnexpectedEndOfStream; + return OrigDecoderErrorCode.UnexpectedEndOfStream; } this.J += n; @@ -258,7 +258,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.BufferAsInt[i] = this.Buffer[i]; } - return OldDecoderErrorCode.NoError; + return OrigDecoderErrorCode.NoError; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecodedBlock.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecodedBlock.cs deleted file mode 100644 index 504c1174f..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecodedBlock.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - /// - /// A structure to store unprocessed instances and their coordinates while scanning the image. - /// The is present in a "raw" decoded frequency-domain form. - /// We need to apply IDCT and unzigging to transform them into color-space blocks. - /// - internal struct DecodedBlock - { - /// - /// A value indicating whether the instance is initialized. - /// - public bool Initialized; - - /// - /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) - /// - public int Bx; - - /// - /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) - /// - public int By; - - /// - /// The - /// - public Block8x8F Block; - - /// - /// Store the block data into a - /// - /// X coordinate of the block - /// Y coordinate of the block - /// The - public void SaveBlock(int bx, int by, ref Block8x8F block) - { - this.Bx = bx; - this.By = by; - this.Block = block; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs index 29d3c9703..d5a9340d7 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs @@ -12,19 +12,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder internal static class DecoderThrowHelper { /// - /// Throws an exception that belongs to the given + /// Throws an exception that belongs to the given /// - /// The + /// The [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowExceptionForErrorCode(this OldDecoderErrorCode errorCode) + public static void ThrowExceptionForErrorCode(this OrigDecoderErrorCode errorCode) { switch (errorCode) { - case OldDecoderErrorCode.NoError: + case OrigDecoderErrorCode.NoError: throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode)); - case OldDecoderErrorCode.MissingFF00: + case OrigDecoderErrorCode.MissingFF00: throw new MissingFF00Exception(); - case OldDecoderErrorCode.UnexpectedEndOfStream: + case OrigDecoderErrorCode.UnexpectedEndOfStream: throw new EOFException(); default: throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null); @@ -32,26 +32,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } /// - /// Throws an exception if the given defines an error. + /// Throws an exception if the given defines an error. /// - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EnsureNoError(this OldDecoderErrorCode errorCode) + public static void EnsureNoError(this OrigDecoderErrorCode errorCode) { - if (errorCode != OldDecoderErrorCode.NoError) + if (errorCode != OrigDecoderErrorCode.NoError) { ThrowExceptionForErrorCode(errorCode); } } /// - /// Throws an exception if the given is . + /// Throws an exception if the given is . /// - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EnsureNoEOF(this OldDecoderErrorCode errorCode) + public static void EnsureNoEOF(this OrigDecoderErrorCode errorCode) { - if (errorCode == OldDecoderErrorCode.UnexpectedEndOfStream) + if (errorCode == OrigDecoderErrorCode.UnexpectedEndOfStream) { errorCode.ThrowExceptionForErrorCode(); } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs index 8f39aa542..a7a5fcd98 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { /// - /// Encapsulates stream reading and processing data and operations for . + /// Encapsulates stream reading and processing data and operations for . /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s /// internal struct InputProcessor : IDisposable @@ -27,14 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Initializes a new instance of the struct. ///
/// The input - /// Temporal buffer, same as + /// Temporal buffer, same as public InputProcessor(Stream inputStream, byte[] temp) { this.Bits = default(Bits); this.Bytes = Bytes.Create(); this.InputStream = inputStream; this.Temp = temp; - this.UnexpectedEndOfStreamReached = false; + this.LastErrorCode = OrigDecoderErrorCode.NoError; } /// @@ -43,53 +43,53 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public Stream InputStream { get; } /// - /// Gets the temporal buffer, same instance as + /// Gets the temporal buffer, same instance as /// public byte[] Temp { get; } /// - /// Gets or sets a value indicating whether an unexpected EOF reached in . + /// Gets a value indicating whether an unexpected EOF reached in . /// - public bool UnexpectedEndOfStreamReached { get; set; } + public bool ReachedEOF => this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream; + + public bool HasError => this.LastErrorCode != OrigDecoderErrorCode.NoError; + + public OrigDecoderErrorCode LastErrorCode { get; private set; } + + public void ResetErrorState() => this.LastErrorCode = OrigDecoderErrorCode.NoError; /// - /// If errorCode indicates unexpected EOF, sets to true and returns false. + /// If errorCode indicates unexpected EOF, sets to true and returns false. /// Calls and returns true otherwise. /// - /// The - /// indicating whether everything is OK - public bool CheckEOFEnsureNoError(OldDecoderErrorCode errorCode) + /// A indicating whether EOF reached + public bool CheckEOFEnsureNoError() { - if (errorCode == OldDecoderErrorCode.UnexpectedEndOfStream) + if (this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream) { - this.UnexpectedEndOfStreamReached = true; return false; } - errorCode.EnsureNoError(); + this.LastErrorCode.EnsureNoError(); return true; } /// - /// If errorCode indicates unexpected EOF, sets to true and returns false. + /// If errorCode indicates unexpected EOF, sets to true and returns false. /// Returns true otherwise. /// - /// The - /// indicating whether everything is OK - public bool CheckEOF(OldDecoderErrorCode errorCode) + /// A indicating whether EOF reached + public bool CheckEOF() { - if (errorCode == OldDecoderErrorCode.UnexpectedEndOfStream) + if (this.LastErrorCode == OrigDecoderErrorCode.UnexpectedEndOfStream) { - this.UnexpectedEndOfStreamReached = true; return false; } return true; } - /// - /// Dispose - /// + /// public void Dispose() { this.Bytes.Dispose(); @@ -110,34 +110,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// TODO: This method (and also the usages) could be optimized by batching! /// /// The decoded bit as a - /// The - public OldDecoderErrorCode DecodeBitUnsafe(out bool result) + /// The + public OrigDecoderErrorCode DecodeBitUnsafe(out bool result) { if (this.Bits.UnreadBits == 0) { - OldDecoderErrorCode errorCode = this.Bits.Ensure1BitUnsafe(ref this); - if (errorCode != OldDecoderErrorCode.NoError) + this.LastErrorCode = this.Bits.Ensure1BitUnsafe(ref this); + if (this.LastErrorCode != OrigDecoderErrorCode.NoError) { result = false; - return errorCode; + return this.LastErrorCode; } } result = (this.Bits.Accumulator & this.Bits.Mask) != 0; this.Bits.UnreadBits--; this.Bits.Mask >>= 1; - return OldDecoderErrorCode.NoError; + return this.LastErrorCode = OrigDecoderErrorCode.NoError; } /// /// Reads exactly length bytes into data. It does not care about byte stuffing. - /// Does not throw on errors, returns instead! + /// Does not throw on errors, returns instead! /// /// The data to write to. /// The offset in the source buffer /// The number of bytes to read - /// The - public OldDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length) + /// The + public OrigDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length) { // Unread the overshot bytes, if any. if (this.Bytes.UnreadableBytes != 0) @@ -150,8 +150,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.Bytes.UnreadableBytes = 0; } - OldDecoderErrorCode errorCode = OldDecoderErrorCode.NoError; - while (length > 0) + this.LastErrorCode = OrigDecoderErrorCode.NoError; + while (length > 0 && this.LastErrorCode == OrigDecoderErrorCode.NoError) { if (this.Bytes.J - this.Bytes.I >= length) { @@ -166,11 +166,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder length -= this.Bytes.J - this.Bytes.I; this.Bytes.I += this.Bytes.J - this.Bytes.I; - errorCode = this.Bytes.FillUnsafe(this.InputStream); + this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream); } } - return errorCode; + return this.LastErrorCode; } /// @@ -178,19 +178,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The number of bits to decode. /// The result - /// The - public OldDecoderErrorCode DecodeBitsUnsafe(int count, out int result) + /// The + public OrigDecoderErrorCode DecodeBitsUnsafe(int count, out int result) { if (this.Bits.UnreadBits < count) { - this.Bits.EnsureNBits(count, ref this); + this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(count, ref this); + if (this.LastErrorCode != OrigDecoderErrorCode.NoError) + { + result = 0; + return this.LastErrorCode; + } } result = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); result = result & ((1 << count) - 1); this.Bits.UnreadBits -= count; this.Bits.Mask >>= count; - return OldDecoderErrorCode.NoError; + return this.LastErrorCode = OrigDecoderErrorCode.NoError; } /// @@ -198,8 +203,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The huffman value /// The decoded - /// The - public OldDecoderErrorCode DecodeHuffmanUnsafe(ref OldHuffmanTree huffmanTree, out int result) + /// The + public OrigDecoderErrorCode DecodeHuffmanUnsafe(ref OrigHuffmanTree huffmanTree, out int result) { result = 0; @@ -210,11 +215,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder if (this.Bits.UnreadBits < 8) { - OldDecoderErrorCode errorCode = this.Bits.Ensure8BitsUnsafe(ref this); + this.LastErrorCode = this.Bits.Ensure8BitsUnsafe(ref this); - if (errorCode == OldDecoderErrorCode.NoError) + if (this.LastErrorCode == OrigDecoderErrorCode.NoError) { - int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - OldHuffmanTree.LutSizeLog2)) & 0xFF; + int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - OrigHuffmanTree.LutSizeLog2)) & 0xFF; int v = huffmanTree.Lut[lutIndex]; if (v != 0) @@ -223,22 +228,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.Bits.UnreadBits -= n; this.Bits.Mask >>= n; result = v >> 8; - return errorCode; + return this.LastErrorCode; } } else { this.UnreadByteStuffedByte(); - return errorCode; + return this.LastErrorCode; } } int code = 0; - for (int i = 0; i < OldHuffmanTree.MaxCodeLength; i++) + for (int i = 0; i < OrigHuffmanTree.MaxCodeLength; i++) { if (this.Bits.UnreadBits == 0) { - this.Bits.EnsureNBits(1, ref this); + this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(1, ref this); + + if (this.HasError) + { + return this.LastErrorCode; + } } if ((this.Bits.Accumulator & this.Bits.Mask) != 0) @@ -252,7 +262,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder if (code <= huffmanTree.MaxCodes[i]) { result = huffmanTree.GetValue(code, i); - return OldDecoderErrorCode.NoError; + return this.LastErrorCode = OrigDecoderErrorCode.NoError; } code <<= 1; @@ -262,7 +272,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode(); // DUMMY RETURN! C# doesn't know we have thrown an exception! - return OldDecoderErrorCode.NoError; + return OrigDecoderErrorCode.NoError; } /// @@ -272,17 +282,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Skip(int count) { - OldDecoderErrorCode errorCode = this.SkipUnsafe(count); - errorCode.EnsureNoError(); + this.LastErrorCode = this.SkipUnsafe(count); + this.LastErrorCode.EnsureNoError(); } /// /// Skips the next n bytes. - /// Does not throw, returns instead! + /// Does not throw, returns instead! /// /// The number of bytes to ignore. - /// The - public OldDecoderErrorCode SkipUnsafe(int count) + /// The + public OrigDecoderErrorCode SkipUnsafe(int count) { // Unread the overshot bytes, if any. if (this.Bytes.UnreadableBytes != 0) @@ -310,14 +320,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder break; } - OldDecoderErrorCode errorCode = this.Bytes.FillUnsafe(this.InputStream); - if (errorCode != OldDecoderErrorCode.NoError) + this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream); + if (this.LastErrorCode != OrigDecoderErrorCode.NoError) { - return errorCode; + return this.LastErrorCode; } } - return OldDecoderErrorCode.NoError; + return this.LastErrorCode = OrigDecoderErrorCode.NoError; } /// @@ -329,8 +339,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadFull(byte[] data, int offset, int length) { - OldDecoderErrorCode errorCode = this.ReadFullUnsafe(data, offset, length); - errorCode.EnsureNoError(); + this.LastErrorCode = this.ReadFullUnsafe(data, offset, length); + this.LastErrorCode.EnsureNoError(); } /// @@ -357,10 +367,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Byte /// Read bits value - /// The - public OldDecoderErrorCode ReceiveExtendUnsafe(int t, out int x) + /// The + public OrigDecoderErrorCode ReceiveExtendUnsafe(int t, out int x) { - return this.Bits.ReceiveExtendUnsafe(t, ref this, out x); + this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x); + return this.LastErrorCode; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs deleted file mode 100644 index 3f1f70b20..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponent.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - /// - /// Represents a single color component - /// - internal struct OldComponent - { - /// - /// Gets or sets the horizontal sampling factor. - /// - public int HorizontalFactor; - - /// - /// Gets or sets the vertical sampling factor. - /// - public int VerticalFactor; - - /// - /// Gets or sets the identifier - /// - public byte Identifier; - - /// - /// Gets or sets the quantization table destination selector. - /// - public byte Selector; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegPixelArea.cs deleted file mode 100644 index 9f8f8d71a..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegPixelArea.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; -using SixLabors.ImageSharp.Memory; -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - /// - /// Represents an area of a Jpeg subimage (channel) - /// - internal struct OldJpegPixelArea - { - /// - /// Initializes a new instance of the struct from existing data. - /// - /// The pixel buffer - /// The stride - /// The offset - public OldJpegPixelArea(Buffer2D pixels, int stride, int offset) - { - this.Stride = stride; - this.Pixels = pixels; - this.Offset = offset; - } - - /// - /// Initializes a new instance of the struct from existing buffer. - /// will be set to of and will be set to 0. - /// - /// The pixel buffer - public OldJpegPixelArea(Buffer2D pixels) - : this(pixels, pixels.Width, 0) - { - } - - /// - /// Gets the pixels buffer. - /// - public Buffer2D Pixels { get; private set; } - - /// - /// Gets a value indicating whether the instance has been initalized. (Is not default(JpegPixelArea)) - /// - public bool IsInitialized => this.Pixels != null; - - /// - /// Gets the stride. - /// - public int Stride { get; } - - /// - /// Gets the offset. - /// - public int Offset { get; } - - /// - /// Gets a of bytes to the pixel area - /// - public MutableSpan Span => new MutableSpan(this.Pixels.Array, this.Offset); - - /// - /// Returns the pixel at (x, y) - /// - /// The x index - /// The y index - /// The pixel value - public byte this[int x, int y] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return this.Pixels[(y * this.Stride) + x]; - } - } - - /// - /// Gets the subarea that belongs to the Block8x8 defined by block indices - /// - /// The block X index - /// The block Y index - /// The subarea offseted by block indices - public OldJpegPixelArea GetOffsetedSubAreaForBlock(int bx, int by) - { - int offset = this.Offset + (8 * ((by * this.Stride) + bx)); - return new OldJpegPixelArea(this.Pixels, this.Stride, offset); - } - - /// - /// Gets the row offset at the given position - /// - /// The y-coordinate of the image. - /// The - public int GetRowOffset(int y) - { - return this.Offset + (y * this.Stride); - } - - /// - /// Load values to the pixel area from the given . - /// Level shift [-128.0, 128.0] floating point color values by +128, clip them to [0, 255], and convert them to - /// values - /// - /// The block holding the color values - /// Temporal block provided by the caller - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void LoadColorsFrom(Block8x8F* block, Block8x8F* temp) - { - // Level shift by +128, clip to [0, 255], and write to dst. - block->CopyColorsTo(new MutableSpan(this.Pixels.Array, this.Offset), this.Stride, temp); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs new file mode 100644 index 000000000..c87752b37 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs @@ -0,0 +1,244 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; +using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder +{ + /// + /// + /// Represents a single color component + /// + internal class OrigComponent : IDisposable, IJpegComponent + { + public OrigComponent(byte identifier, int index) + { + this.Identifier = identifier; + this.Index = index; + } + + /// + /// Gets the identifier + /// + public byte Identifier { get; } + + /// + public int Index { get; } + + public Size SizeInBlocks { get; private set; } + + public Size SamplingFactors { get; private set; } + + public Size SubSamplingDivisors { get; private set; } + + public int HorizontalSamplingFactor => this.SamplingFactors.Width; + + public int VerticalSamplingFactor => this.SamplingFactors.Height; + + /// + public int QuantizationTableIndex { get; private set; } + + /// + /// + /// Gets the storing the "raw" frequency-domain decoded blocks. + /// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks. + /// This is done by . + /// When us true, we are touching these blocks multiple times - each time we process a Scan. + /// + public Buffer2D SpectralBlocks { get; private set; } + + /// + /// Initializes + /// + /// The instance + public void InitializeDerivedData(OrigJpegDecoderCore decoder) + { + // For 4-component images (either CMYK or YCbCrK), we only support two + // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. + // Theoretically, 4-component JPEG images could mix and match hv values + // but in practice, those two combinations are the only ones in use, + // and it simplifies the applyBlack code below if we can assume that: + // - for CMYK, the C and K channels have full samples, and if the M + // and Y channels subsample, they subsample both horizontally and + // vertically. + // - for YCbCrK, the Y and K channels have full samples. + this.SizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(this.SamplingFactors); + + if (this.Index == 0 || this.Index == 3) + { + this.SubSamplingDivisors = new Size(1, 1); + } + else + { + OrigComponent c0 = decoder.Components[0]; + this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); + } + + this.SpectralBlocks = Buffer2D.CreateClean(this.SizeInBlocks); + } + + /// + /// Initializes all component data except . + /// + /// The instance + public void InitializeCoreData(OrigJpegDecoderCore decoder) + { + // Section B.2.2 states that "the value of C_i shall be different from + // the values of C_1 through C_(i-1)". + int i = this.Index; + + for (int j = 0; j < this.Index; j++) + { + if (this.Identifier == decoder.Components[j].Identifier) + { + throw new ImageFormatException("Repeated component identifier"); + } + } + + this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)]; + if (this.QuantizationTableIndex > OrigJpegDecoderCore.MaxTq) + { + throw new ImageFormatException("Bad Tq value"); + } + + byte hv = decoder.Temp[7 + (3 * i)]; + int h = hv >> 4; + int v = hv & 0x0f; + if (h < 1 || h > 4 || v < 1 || v > 4) + { + throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio"); + } + + if (h == 3 || v == 3) + { + throw new ImageFormatException("Lnsupported subsampling ratio"); + } + + switch (decoder.ComponentCount) + { + case 1: + + // If a JPEG image has only one component, section A.2 says "this data + // is non-interleaved by definition" and section A.2.2 says "[in this + // case...] the order of data units within a scan shall be left-to-right + // and top-to-bottom... regardless of the values of H_1 and V_1". Section + // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be + // one data unit". Similarly, section A.1.1 explains that it is the ratio + // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale + // images, H_1 is the maximum H_j for all components j, so that ratio is + // always 1. The component's (h, v) is effectively always (1, 1): even if + // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8 + // MCUs, not two 16x8 MCUs. + h = 1; + v = 1; + break; + + case 3: + + // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0, + // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the + // (h, v) values for the Y component are either (1, 1), (1, 2), + // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values + // must be a multiple of the Cb and Cr component's values. We also + // assume that the two chroma components have the same subsampling + // ratio. + switch (i) + { + case 0: + { + // Y. + // We have already verified, above, that h and v are both + // either 1, 2 or 4, so invalid (h, v) combinations are those + // with v == 4. + if (v == 4) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + + case 1: + { + // Cb. + Size s0 = decoder.Components[0].SamplingFactors; + + if (s0.Width % h != 0 || s0.Height % v != 0) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + + case 2: + { + // Cr. + Size s1 = decoder.Components[1].SamplingFactors; + + if (s1.Width != h || s1.Height != v) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + } + + break; + + case 4: + + // For 4-component images (either CMYK or YCbCrK), we only support two + // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. + // Theoretically, 4-component JPEG images could mix and match hv values + // but in practice, those two combinations are the only ones in use, + // and it simplifies the applyBlack code below if we can assume that: + // - for CMYK, the C and K channels have full samples, and if the M + // and Y channels subsample, they subsample both horizontally and + // vertically. + // - for YCbCrK, the Y and K channels have full samples. + switch (i) + { + case 0: + if (hv != 0x11 && hv != 0x22) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + case 1: + case 2: + if (hv != 0x11) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + case 3: + Size s0 = decoder.Components[0].SamplingFactors; + + if (s0.Width != h || s0.Height != v) + { + throw new ImageFormatException("Unsupported subsampling ratio"); + } + + break; + } + + break; + } + + this.SamplingFactors = new Size(h, v); + } + + public void Dispose() + { + this.SpectralBlocks.Dispose(); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponentScan.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponentScan.cs similarity index 94% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponentScan.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponentScan.cs index 5114ebd04..0d9804404 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldComponentScan.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponentScan.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Represents a component scan /// [StructLayout(LayoutKind.Sequential)] - internal struct OldComponentScan + internal struct OrigComponentScan { /// /// Gets or sets the component index. diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldDecoderErrorCode.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigDecoderErrorCode.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldDecoderErrorCode.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigDecoderErrorCode.cs index f4419fb62..02a8ea55e 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldDecoderErrorCode.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigDecoderErrorCode.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Represents "recoverable" decoder errors. /// - internal enum OldDecoderErrorCode + internal enum OrigDecoderErrorCode { /// /// NoError diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldHuffmanTree.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs similarity index 95% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldHuffmanTree.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs index 21210c95d..4c97d5741 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldHuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Represents a Huffman tree /// - internal struct OldHuffmanTree : IDisposable + internal struct OrigHuffmanTree : IDisposable { /// /// The index of the AC table row @@ -98,12 +98,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder private static readonly ArrayPool CodesPool16 = ArrayPool.Create(MaxCodeLength, 50); /// - /// Creates and initializes an array of instances of size + /// Creates and initializes an array of instances of size /// - /// An array of instances representing the Huffman tables - public static OldHuffmanTree[] CreateHuffmanTrees() + /// An array of instances representing the Huffman tables + public static OrigHuffmanTree[] CreateHuffmanTrees() { - OldHuffmanTree[] result = new OldHuffmanTree[NumberOfTrees]; + OrigHuffmanTree[] result = new OrigHuffmanTree[NumberOfTrees]; for (int i = 0; i < MaxTc + 1; i++) { for (int j = 0; j < MaxTh + 1; j++) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.ComputationData.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs similarity index 81% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.ComputationData.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs index 8f999bbef..6252d8209 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.ComputationData.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Conains the definition of /// - internal unsafe partial struct OldJpegScanDecoder + internal unsafe partial struct OrigJpegScanDecoder { /// /// Holds the "large" data blocks needed for computations. @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The main input/working block /// - public Block8x8F Block; + public Block8x8 Block; /// /// The jpeg unzig data @@ -29,14 +29,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public UnzigData Unzig; /// - /// The buffer storing the -s for each component + /// The buffer storing the -s for each component /// - public fixed byte ScanData[3 * OldJpegDecoderCore.MaxComponents]; + public fixed byte ScanData[3 * OrigJpegDecoderCore.MaxComponents]; /// /// The DC values for each component /// - public fixed int Dc[OldJpegDecoderCore.MaxComponents]; + public fixed int Dc[OrigJpegDecoderCore.MaxComponents]; /// /// Creates and initializes a new instance diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.DataPointers.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.DataPointers.cs similarity index 84% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.DataPointers.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.DataPointers.cs index 485884ca3..0098b4a4e 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.DataPointers.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.DataPointers.cs @@ -1,14 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; +using SixLabors.ImageSharp.Formats.Jpeg.Common; namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { /// /// Conains the definition of /// - internal unsafe partial struct OldJpegScanDecoder + internal unsafe partial struct OrigJpegScanDecoder { /// /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Pointer to /// - public Block8x8F* Block; + public Block8x8* Block; /// /// Pointer to as int* @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Pointer to as Scan* /// - public OldComponentScan* ComponentScan; + public OrigComponentScan* ComponentScan; /// /// Pointer to @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { this.Block = &basePtr->Block; this.Unzig = basePtr->Unzig.Data; - this.ComponentScan = (OldComponentScan*)basePtr->ScanData; + this.ComponentScan = (OrigComponentScan*)basePtr->ScanData; this.Dc = basePtr->Dc; } } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs similarity index 74% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs index 81e9a5034..b3a7bc4af 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OldJpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs @@ -3,8 +3,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; using SixLabors.ImageSharp.Memory; -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder @@ -28,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. /// [StructLayout(LayoutKind.Sequential)] - internal unsafe partial struct OldJpegScanDecoder + internal unsafe partial struct OrigJpegScanDecoder { // The JpegScanDecoder members should be ordered in a way that results in optimal memory layout. #pragma warning disable SA1202 // ElementsMustBeOrderedByAccess @@ -94,12 +95,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder private int eobRun; /// - /// Initializes a default-constructed instance for reading data from -s stream. + /// Initializes a default-constructed instance for reading data from -s stream. /// - /// Pointer to on the stack - /// The instance + /// Pointer to on the stack + /// The instance /// The remaining bytes in the segment block. - public static void InitStreamReading(OldJpegScanDecoder* p, OldJpegDecoderCore decoder, int remaining) + public static void InitStreamReading(OrigJpegScanDecoder* p, OrigJpegDecoderCore decoder, int remaining) { p->data = ComputationData.Create(); p->pointers = new DataPointers(&p->data); @@ -107,8 +108,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } /// - /// Read Huffman data from Jpeg scans in , - /// and decode it as into . + /// Read Huffman data from Jpeg scans in , + /// and decode it as into . /// /// The blocks are traversed one MCU at a time. For 4:2:0 chroma /// subsampling, there are four Y 8x8 blocks in every 16x16 MCU. @@ -133,12 +134,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// 0 1 2 /// 3 4 5 /// - /// The instance - public void DecodeBlocks(OldJpegDecoderCore decoder) + /// The instance + public void DecodeBlocks(OrigJpegDecoderCore decoder) { + decoder.InputProcessor.ResetErrorState(); + int blockCount = 0; int mcu = 0; - byte expectedRst = OldJpegConstants.Markers.RST0; + byte expectedRst = OrigJpegConstants.Markers.RST0; for (int my = 0; my < decoder.MCUCountY; my++) { @@ -147,8 +150,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) { this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; - this.hi = decoder.ComponentArray[this.ComponentIndex].HorizontalFactor; - int vi = decoder.ComponentArray[this.ComponentIndex].VerticalFactor; + OrigComponent component = decoder.Components[this.ComponentIndex]; + + this.hi = component.HorizontalSamplingFactor; + int vi = component.VerticalSamplingFactor; for (int j = 0; j < this.hi * vi; j++) { @@ -169,18 +174,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } } - // Take an existing block (required when progressive): - int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.ComponentIndex][blockIndex].Block; + // Find the block at (bx,by) in the component's buffer: + ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by); - if (!decoder.InputProcessor.UnexpectedEndOfStreamReached) + // Copy block to stack + this.data.Block = blockRefOnHeap; + + if (!decoder.InputProcessor.ReachedEOF) { this.DecodeBlock(decoder, scanIndex); } - // Store the decoded block - Buffer blocks = decoder.DecodedBlocks[this.ComponentIndex]; - blocks[blockIndex].SaveBlock(this.bx, this.by, ref this.data.Block); + // Store the result block: + blockRefOnHeap = this.data.Block; } // for j @@ -193,10 +199,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, // but this one assumes well-formed input, and hence the restart marker follows immediately. - if (!decoder.InputProcessor.UnexpectedEndOfStreamReached) + if (!decoder.InputProcessor.ReachedEOF) { - OldDecoderErrorCode errorCode = decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); - if (decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) + decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); + if (decoder.InputProcessor.CheckEOFEnsureNoError()) { if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) { @@ -204,9 +210,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } expectedRst++; - if (expectedRst == OldJpegConstants.Markers.RST7 + 1) + if (expectedRst == OrigJpegConstants.Markers.RST7 + 1) { - expectedRst = OldJpegConstants.Markers.RST0; + expectedRst = OrigJpegConstants.Markers.RST0; } } } @@ -228,15 +234,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder private void ResetDc() { - Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * OldJpegDecoderCore.MaxComponents); + Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * OrigJpegDecoderCore.MaxComponents); } /// /// The implementation part of as an instance method. /// - /// The + /// The /// The remaining bytes - private void InitStreamReadingImpl(OldJpegDecoderCore decoder, int remaining) + private void InitStreamReadingImpl(OrigJpegDecoderCore decoder, int remaining) { if (decoder.ComponentCount == 0) { @@ -271,7 +277,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder throw new ImageFormatException("Total sampling factors too large."); } - this.zigEnd = Block8x8F.ScalarCount - 1; + this.zigEnd = Block8x8F.Size - 1; if (decoder.IsProgressive) { @@ -281,7 +287,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.al = decoder.Temp[3 + scanComponentCountX2] & 0x0f; if ((this.zigStart == 0 && this.zigEnd != 0) || this.zigStart > this.zigEnd - || this.zigEnd >= Block8x8F.ScalarCount) + || this.zigEnd >= Block8x8F.Size) { throw new ImageFormatException("Bad spectral selection bounds"); } @@ -303,10 +309,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The decoder /// The index of the scan - private void DecodeBlock(OldJpegDecoderCore decoder, int scanIndex) + private void DecodeBlock(OrigJpegDecoderCore decoder, int scanIndex) { - Block8x8F* b = this.pointers.Block; - int huffmannIdx = (OldHuffmanTree.AcTableIndex * OldHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; + Block8x8* b = this.pointers.Block; + int huffmannIdx = (OrigHuffmanTree.AcTableIndex * OrigHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); @@ -314,18 +320,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder else { int zig = this.zigStart; - OldDecoderErrorCode errorCode; + if (zig == 0) { zig++; // Decode the DC coefficient, as specified in section F.2.2.1. int value; - int huffmanIndex = (OldHuffmanTree.DcTableIndex * OldHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; - errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe( + int huffmanIndex = (OrigHuffmanTree.DcTableIndex * OrigHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; + decoder.InputProcessor.DecodeHuffmanUnsafe( ref decoder.HuffmanTrees[huffmanIndex], out value); - if (!decoder.InputProcessor.CheckEOF(errorCode)) + if (!decoder.InputProcessor.CheckEOF()) { return; } @@ -336,8 +342,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } int deltaDC; - errorCode = decoder.InputProcessor.ReceiveExtendUnsafe(value, out deltaDC); - if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) + decoder.InputProcessor.ReceiveExtendUnsafe(value, out deltaDC); + if (!decoder.InputProcessor.CheckEOFEnsureNoError()) { return; } @@ -345,7 +351,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.pointers.Dc[this.ComponentIndex] += deltaDC; // b[0] = dc[compIndex] << al; - Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[this.ComponentIndex] << this.al); + value = this.pointers.Dc[this.ComponentIndex] << this.al; + Block8x8.SetScalarAt(b, 0, (short)value); } if (zig <= this.zigEnd && this.eobRun > 0) @@ -358,8 +365,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder for (; zig <= this.zigEnd; zig++) { int value; - errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value); - if (!decoder.InputProcessor.CheckEOF(errorCode)) + decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value); + if (decoder.InputProcessor.HasError) { return; } @@ -375,14 +382,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } int ac; - errorCode = decoder.InputProcessor.ReceiveExtendUnsafe(val1, out ac); - if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) + decoder.InputProcessor.ReceiveExtendUnsafe(val1, out ac); + if (decoder.InputProcessor.HasError) { return; } // b[Unzig[zig]] = ac << al; - Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al); + value = ac << this.al; + Block8x8.SetScalarAt(b, this.pointers.Unzig[zig], (short)value); } else { @@ -391,8 +399,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.eobRun = (ushort)(1 << val0); if (val0 != 0) { - errorCode = this.DecodeEobRun(val0, ref decoder.InputProcessor); - if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) + this.DecodeEobRun(val0, ref decoder.InputProcessor); + if (!decoder.InputProcessor.CheckEOFEnsureNoError()) { return; } @@ -409,30 +417,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } } - private OldDecoderErrorCode DecodeEobRun(int count, ref InputProcessor decoder) + private void DecodeEobRun(int count, ref InputProcessor processor) { int bitsResult; - OldDecoderErrorCode errorCode = decoder.DecodeBitsUnsafe(count, out bitsResult); - if (errorCode != OldDecoderErrorCode.NoError) + processor.DecodeBitsUnsafe(count, out bitsResult); + if (processor.LastErrorCode != OrigDecoderErrorCode.NoError) { - return errorCode; + return; } this.eobRun |= bitsResult; - return OldDecoderErrorCode.NoError; } /// - /// Gets the block index used to retieve blocks from in + /// Gets the block index used to retieve blocks from in /// - /// The instance + /// The instance /// The index - private int GetBlockIndex(OldJpegDecoderCore decoder) + private int GetBlockIndex(OrigJpegDecoderCore decoder) { return ((this.by * decoder.MCUCountX) * this.hi) + this.bx; } - private void InitComponentScan(OldJpegDecoderCore decoder, int i, ref OldComponentScan currentComponentScan, ref int totalHv) + private void InitComponentScan(OrigJpegDecoderCore decoder, int i, ref OrigComponentScan currentComponentScan, ref int totalHv) { // Component selector. int cs = decoder.Temp[1 + (2 * i)]; @@ -440,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder for (int j = 0; j < decoder.ComponentCount; j++) { // Component compv = ; - if (cs == decoder.ComponentArray[j].Identifier) + if (cs == decoder.Components[j].Identifier) { compIndex = j; } @@ -453,15 +460,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder currentComponentScan.ComponentIndex = (byte)compIndex; - this.ProcessComponentImpl(decoder, i, ref currentComponentScan, ref totalHv, ref decoder.ComponentArray[compIndex]); + this.ProcessComponentImpl(decoder, i, ref currentComponentScan, ref totalHv, decoder.Components[compIndex]); } private void ProcessComponentImpl( - OldJpegDecoderCore decoder, + OrigJpegDecoderCore decoder, int i, - ref OldComponentScan currentComponentScan, + ref OrigComponentScan currentComponentScan, ref int totalHv, - ref OldComponent currentComponent) + OrigComponent 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 @@ -476,16 +483,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } } - totalHv += currentComponent.HorizontalFactor * currentComponent.VerticalFactor; + totalHv += currentComponent.HorizontalSamplingFactor * currentComponent.VerticalSamplingFactor; currentComponentScan.DcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] >> 4); - if (currentComponentScan.DcTableSelector > OldHuffmanTree.MaxTh) + if (currentComponentScan.DcTableSelector > OrigHuffmanTree.MaxTh) { throw new ImageFormatException("Bad DC table selector value"); } currentComponentScan.AcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] & 0x0f); - if (currentComponentScan.AcTableSelector > OldHuffmanTree.MaxTh) + if (currentComponentScan.AcTableSelector > OrigHuffmanTree.MaxTh) { throw new ImageFormatException("Bad AC table selector value"); } @@ -497,9 +504,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The instance /// The Huffman tree /// The low transform offset - private void Refine(ref InputProcessor bp, ref OldHuffmanTree h, int delta) + private void Refine(ref InputProcessor bp, ref OrigHuffmanTree h, int delta) { - Block8x8F* b = this.pointers.Block; + Block8x8* b = this.pointers.Block; // Refining a DC component is trivial. if (this.zigStart == 0) @@ -510,21 +517,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } bool bit; - OldDecoderErrorCode errorCode = bp.DecodeBitUnsafe(out bit); - if (!bp.CheckEOFEnsureNoError(errorCode)) + bp.DecodeBitUnsafe(out bit); + if (!bp.CheckEOFEnsureNoError()) { return; } if (bit) { - int stuff = (int)Block8x8F.GetScalarAt(b, 0); + int stuff = (int)Block8x8.GetScalarAt(b, 0); // int stuff = (int)b[0]; stuff |= delta; // b[0] = stuff; - Block8x8F.SetScalarAt(b, 0, stuff); + Block8x8.SetScalarAt(b, 0, (short)stuff); } return; @@ -540,8 +547,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder int z = 0; int val; - OldDecoderErrorCode errorCode = bp.DecodeHuffmanUnsafe(ref h, out val); - if (!bp.CheckEOF(errorCode)) + bp.DecodeHuffmanUnsafe(ref h, out val); + if (!bp.CheckEOF()) { return; } @@ -557,8 +564,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.eobRun = 1 << val0; if (val0 != 0) { - errorCode = this.DecodeEobRun(val0, ref bp); - if (!bp.CheckEOFEnsureNoError(errorCode)) + this.DecodeEobRun(val0, ref bp); + if (!bp.CheckEOFEnsureNoError()) { return; } @@ -572,8 +579,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder z = delta; bool bit; - errorCode = bp.DecodeBitUnsafe(out bit); - if (!bp.CheckEOFEnsureNoError(errorCode)) + bp.DecodeBitUnsafe(out bit); + if (!bp.CheckEOFEnsureNoError()) { return; } @@ -594,20 +601,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } zig = this.RefineNonZeroes(ref bp, zig, val0, delta); - if (bp.UnexpectedEndOfStreamReached) + if (bp.ReachedEOF) { return; } - if (zig > this.zigEnd) - { - throw new ImageFormatException($"Too many coefficients {zig} > {this.zigEnd}"); - } - - if (z != 0) + if (z != 0 && zig <= this.zigEnd) { // b[Unzig[zig]] = z; - Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], z); + Block8x8.SetScalarAt(b, this.pointers.Unzig[zig], (short)z); } } } @@ -630,11 +632,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The private int RefineNonZeroes(ref InputProcessor bp, int zig, int nz, int delta) { - Block8x8F* b = this.pointers.Block; + Block8x8* b = this.pointers.Block; for (; zig <= this.zigEnd; zig++) { int u = this.pointers.Unzig[zig]; - float bu = Block8x8F.GetScalarAt(b, u); + int bu = Block8x8.GetScalarAt(b, u); // TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary? if (bu == 0) @@ -649,8 +651,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder } bool bit; - OldDecoderErrorCode errorCode = bp.DecodeBitUnsafe(out bit); - if (!bp.CheckEOFEnsureNoError(errorCode)) + bp.DecodeBitUnsafe(out bit); + if (bp.HasError) { return int.MinValue; } @@ -660,16 +662,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder continue; } - if (bu >= 0) - { - // b[u] += delta; - Block8x8F.SetScalarAt(b, u, bu + delta); - } - else - { - // b[u] -= delta; - Block8x8F.SetScalarAt(b, u, bu - delta); - } + int val = bu >= 0 ? bu + delta : bu - delta; + + Block8x8.SetScalarAt(b, u, (short)val); } return zig; diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs deleted file mode 100644 index 2fb5f3fa8..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrImage.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - /// - /// Represents an image made up of three color components (luminance, blue chroma, red chroma) - /// - internal class YCbCrImage : IDisposable - { - // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P -#pragma warning disable SA1401 // FieldsMustBePrivate - /// - /// Gets the luminance components channel as . - /// - public Buffer2D YChannel; - - /// - /// Gets the blue chroma components channel as . - /// - public Buffer2D CbChannel; - - /// - /// Gets an offseted to the Cr channel - /// - public Buffer2D CrChannel; -#pragma warning restore SA1401 - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The ratio. - public YCbCrImage(int width, int height, YCbCrSubsampleRatio ratio) - { - Size cSize = CalculateChrominanceSize(width, height, ratio); - - this.Ratio = ratio; - this.YStride = width; - this.CStride = cSize.Width; - - this.YChannel = Buffer2D.CreateClean(width, height); - this.CbChannel = Buffer2D.CreateClean(cSize.Width, cSize.Height); - this.CrChannel = Buffer2D.CreateClean(cSize.Width, cSize.Height); - } - - /// - /// Provides enumeration of the various available subsample ratios. - /// - public enum YCbCrSubsampleRatio - { - /// - /// YCbCrSubsampleRatio444 - /// - YCbCrSubsampleRatio444, - - /// - /// YCbCrSubsampleRatio422 - /// - YCbCrSubsampleRatio422, - - /// - /// YCbCrSubsampleRatio420 - /// - YCbCrSubsampleRatio420, - - /// - /// YCbCrSubsampleRatio440 - /// - YCbCrSubsampleRatio440, - - /// - /// YCbCrSubsampleRatio411 - /// - YCbCrSubsampleRatio411, - - /// - /// YCbCrSubsampleRatio410 - /// - YCbCrSubsampleRatio410, - } - - /// - /// Gets the Y slice index delta between vertically adjacent pixels. - /// - public int YStride { get; } - - /// - /// Gets the red and blue chroma slice index delta between vertically adjacent pixels - /// that map to separate chroma samples. - /// - public int CStride { get; } - - /// - /// Gets or sets the subsampling ratio. - /// - public YCbCrSubsampleRatio Ratio { get; set; } - - /// - /// Disposes the returning rented arrays to the pools. - /// - public void Dispose() - { - this.YChannel.Dispose(); - this.CbChannel.Dispose(); - this.CrChannel.Dispose(); - } - - /// - /// Returns the offset of the first chroma component at the given row - /// - /// The row number. - /// - /// The . - /// - public int GetRowCOffset(int y) - { - switch (this.Ratio) - { - case YCbCrSubsampleRatio.YCbCrSubsampleRatio422: - return y * this.CStride; - case YCbCrSubsampleRatio.YCbCrSubsampleRatio420: - return (y / 2) * this.CStride; - case YCbCrSubsampleRatio.YCbCrSubsampleRatio440: - return (y / 2) * this.CStride; - case YCbCrSubsampleRatio.YCbCrSubsampleRatio411: - return y * this.CStride; - case YCbCrSubsampleRatio.YCbCrSubsampleRatio410: - return (y / 2) * this.CStride; - default: - return y * this.CStride; - } - } - - /// - /// Returns the offset of the first luminance component at the given row - /// - /// The row number. - /// - /// The . - /// - public int GetRowYOffset(int y) - { - return y * this.YStride; - } - - /// - /// Returns the height and width of the chroma components - /// - /// The width. - /// The height. - /// The subsampling ratio. - /// The of the chrominance channel - internal static Size CalculateChrominanceSize( - int width, - int height, - YCbCrSubsampleRatio ratio) - { - switch (ratio) - { - case YCbCrSubsampleRatio.YCbCrSubsampleRatio422: - return new Size((width + 1) / 2, height); - case YCbCrSubsampleRatio.YCbCrSubsampleRatio420: - return new Size((width + 1) / 2, (height + 1) / 2); - case YCbCrSubsampleRatio.YCbCrSubsampleRatio440: - return new Size(width, (height + 1) / 2); - case YCbCrSubsampleRatio.YCbCrSubsampleRatio411: - return new Size((width + 3) / 4, height); - case YCbCrSubsampleRatio.YCbCrSubsampleRatio410: - return new Size((width + 3) / 4, (height + 1) / 2); - default: - // Default to 4:4:4 subsampling. - return new Size(width, height); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs deleted file mode 100644 index fe0cd6fc0..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/YCbCrToRgbTables.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder -{ - /// - /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace. - /// Methods to build the tables are based on libjpeg implementation. - /// - internal unsafe struct YCbCrToRgbTables - { - /// - /// The red red-chrominance table - /// - public fixed int CrRTable[256]; - - /// - /// The blue blue-chrominance table - /// - public fixed int CbBTable[256]; - - /// - /// The green red-chrominance table - /// - public fixed int CrGTable[256]; - - /// - /// The green blue-chrominance table - /// - public fixed int CbGTable[256]; - - // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places. - private const int ScaleBits = 16; - - private const int Half = 1 << (ScaleBits - 1); - - /// - /// Initializes the YCbCr tables - /// - /// The intialized - public static YCbCrToRgbTables Create() - { - YCbCrToRgbTables tables = default(YCbCrToRgbTables); - - for (int i = 0, x = -128; i <= 255; i++, x++) - { - // i is the actual input pixel value, in the range 0..255 - // The Cb or Cr value we are thinking of is x = i - 128 - // Cr=>R value is nearest int to 1.402 * x - tables.CrRTable[i] = RightShift((Fix(1.402F) * x) + Half); - - // Cb=>B value is nearest int to 1.772 * x - tables.CbBTable[i] = RightShift((Fix(1.772F) * x) + Half); - - // Cr=>G value is scaled-up -0.714136286 - tables.CrGTable[i] = (-Fix(0.714136286F)) * x; - - // Cb => G value is scaled - up - 0.344136286 * x - // We also add in Half so that need not do it in inner loop - tables.CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half; - } - - return tables; - } - - /// - /// Optimized method to pack bytes to the image from the YCbCr color space. - /// - /// The pixel format. - /// The packed pixel. - /// The reference to the tables instance. - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Pack(ref TPixel packed, YCbCrToRgbTables* tables, byte y, byte cb, byte cr) - where TPixel : struct, IPixel - { - // float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - byte r = (byte)(y + tables->CrRTable[cr]).Clamp(0, 255); - - // float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - // The values for the G calculation are left scaled up, since we must add them together before rounding. - byte g = (byte)(y + RightShift(tables->CbGTable[cb] + tables->CrGTable[cr])).Clamp(0, 255); - - // float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - byte b = (byte)(y + tables->CbBTable[cb]).Clamp(0, 255); - - packed.PackFromRgba32(new Rgba32(r, g, b, 255)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Fix(float x) - { - return (int)((x * (1L << ScaleBits)) + 0.5F); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RightShift(int x) - { - return x >> ScaleBits; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 229b8b7b5..984fb828c 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// private static readonly byte[] SosHeaderYCbCr = { - OldJpegConstants.Markers.XFF, OldJpegConstants.Markers.SOS, + OrigJpegConstants.Markers.XFF, OrigJpegConstants.Markers.SOS, // Marker 0x00, 0x0c, @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - ushort max = OldJpegConstants.MaxLength; + ushort max = OrigJpegConstants.MaxLength; if (image.Width >= max || image.Height >= max) { throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); @@ -246,8 +246,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } // Write the End Of Image marker. - this.buffer[0] = OldJpegConstants.Markers.XFF; - this.buffer[1] = OldJpegConstants.Markers.EOI; + this.buffer[0] = OrigJpegConstants.Markers.XFF; + this.buffer[1] = OrigJpegConstants.Markers.EOI; stream.Write(this.buffer, 0, 2); stream.Flush(); } @@ -262,7 +262,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant) { dqt[offset++] = (byte)i; - for (int j = 0; j < Block8x8F.ScalarCount; j++) + for (int j = 0; j < Block8x8F.Size; j++) { dqt[offset++] = (byte)quant[j]; } @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// The quantization table. private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) { - for (int j = 0; j < Block8x8F.ScalarCount; j++) + for (int j = 0; j < Block8x8F.Size; j++) { int x = UnscaledQuant[i, j]; x = ((x * scale) + 50) / 100; @@ -508,12 +508,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) { // Write the start of image marker. Markers are always prefixed with with 0xff. - this.buffer[0] = OldJpegConstants.Markers.XFF; - this.buffer[1] = OldJpegConstants.Markers.SOI; + this.buffer[0] = OrigJpegConstants.Markers.XFF; + this.buffer[1] = OrigJpegConstants.Markers.SOI; // Write the JFIF headers - this.buffer[2] = OldJpegConstants.Markers.XFF; - this.buffer[3] = OldJpegConstants.Markers.APP0; // Application Marker + this.buffer[2] = OrigJpegConstants.Markers.XFF; + this.buffer[3] = OrigJpegConstants.Markers.APP0; // Application Marker this.buffer[4] = 0x00; this.buffer[5] = 0x10; this.buffer[6] = 0x4a; // J @@ -562,7 +562,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort Block8x8F* quant, int* unzigPtr) { - DCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2); + FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2); Block8x8F.UnzigDivRound(tempDest1, tempDest2, quant, unzigPtr); float* unziggedDestPtr = (float*)tempDest2; @@ -576,7 +576,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort HuffIndex h = (HuffIndex)((2 * (int)index) + 1); int runLength = 0; - for (int zig = 1; zig < Block8x8F.ScalarCount; zig++) + for (int zig = 1; zig < Block8x8F.Size; zig++) { int ac = (int)unziggedDestPtr[zig]; @@ -627,7 +627,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort markerlen += 1 + 16 + s.Values.Length; } - this.WriteMarkerHeader(OldJpegConstants.Markers.DHT, markerlen); + this.WriteMarkerHeader(OrigJpegConstants.Markers.DHT, markerlen); for (int i = 0; i < specs.Length; i++) { HuffmanSpec spec = specs[i]; @@ -660,12 +660,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private void WriteDefineQuantizationTables() { // Marker + quantization table lengths - int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount)); - this.WriteMarkerHeader(OldJpegConstants.Markers.DQT, markerlen); + int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size)); + this.WriteMarkerHeader(OrigJpegConstants.Markers.DQT, markerlen); // Loop through and collect the tables as one array. // This allows us to reduce the number of writes to the stream. - int dqtCount = (QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount; + int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount; byte[] dqt = ArrayPool.Shared.Rent(dqtCount); int offset = 0; @@ -699,8 +699,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort int length = data.Length + 2; - this.buffer[0] = OldJpegConstants.Markers.XFF; - this.buffer[1] = OldJpegConstants.Markers.APP1; // Application Marker + this.buffer[0] = OrigJpegConstants.Markers.XFF; + this.buffer[1] = OrigJpegConstants.Markers.APP1; // Application Marker this.buffer[2] = (byte)((length >> 8) & 0xFF); this.buffer[3] = (byte)(length & 0xFF); @@ -758,8 +758,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort dataLength -= length; - this.buffer[0] = OldJpegConstants.Markers.XFF; - this.buffer[1] = OldJpegConstants.Markers.APP2; // Application Marker + this.buffer[0] = OrigJpegConstants.Markers.XFF; + this.buffer[1] = OrigJpegConstants.Markers.APP2; // Application Marker int markerLength = length + 16; this.buffer[2] = (byte)((markerLength >> 8) & 0xFF); this.buffer[3] = (byte)(markerLength & 0xFF); @@ -831,7 +831,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // Length (high byte, low byte), 8 + components * 3. int markerlen = 8 + (3 * componentCount); - this.WriteMarkerHeader(OldJpegConstants.Markers.SOF0, markerlen); + this.WriteMarkerHeader(OrigJpegConstants.Markers.SOF0, markerlen); this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported this.buffer[1] = (byte)(height >> 8); this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported @@ -974,7 +974,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private void WriteMarkerHeader(byte marker, int length) { // Markers are always prefixed with with 0xff. - this.buffer[0] = OldJpegConstants.Markers.XFF; + this.buffer[0] = OrigJpegConstants.Markers.XFF; this.buffer[1] = marker; this.buffer[2] = (byte)(length >> 8); this.buffer[3] = (byte)(length & 0xff); diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs deleted file mode 100644 index 765c5f39a..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoderCore.cs +++ /dev/null @@ -1,1374 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; -using SixLabors.ImageSharp.MetaData.Profiles.Icc; -using SixLabors.ImageSharp.PixelFormats; -using Block8x8F = SixLabors.ImageSharp.Formats.Jpeg.Common.Block8x8F; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort -{ - /// - /// Performs the jpeg decoding operation. - /// - internal sealed unsafe class OldJpegDecoderCore : IDisposable - { - /// - /// The maximum number of color components - /// - public const int MaxComponents = 4; - - /// - /// The maximum number of quantization tables - /// - public const int MaxTq = 3; - - // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P -#pragma warning disable SA1401 // FieldsMustBePrivate - - /// - /// Encapsulates stream reading and processing data and operations for . - /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s - /// - public InputProcessor InputProcessor; -#pragma warning restore SA401 - - /// - /// Lookup tables for converting YCbCr to Rgb - /// - private static YCbCrToRgbTables yCbCrToRgbTables = YCbCrToRgbTables.Create(); - - /// - /// The global configuration - /// - private readonly Configuration configuration; - - /// - /// The App14 marker color-space - /// - private byte adobeTransform; - - /// - /// Whether the image is in CMYK format with an App14 marker - /// - private bool adobeTransformValid; - - /// - /// The black image to decode to. - /// - private OldJpegPixelArea blackImage; - - /// - /// A grayscale image to decode to. - /// - private OldJpegPixelArea grayImage; - - /// - /// The horizontal resolution. Calculated if the image has a JFIF header. - /// - private short horizontalResolution; - - /// - /// Whether the image has a JFIF header - /// - private bool isJfif; - - /// - /// Whether the image has a EXIF header - /// - private bool isExif; - - /// - /// The vertical resolution. Calculated if the image has a JFIF header. - /// - private short verticalResolution; - - /// - /// The full color image to decode to. - /// - private YCbCrImage ycbcrImage; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The options. - public OldJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) - { - this.IgnoreMetadata = options.IgnoreMetadata; - this.configuration = configuration ?? Configuration.Default; - this.HuffmanTrees = OldHuffmanTree.CreateHuffmanTrees(); - this.QuantizationTables = new Block8x8F[MaxTq + 1]; - this.Temp = new byte[2 * Block8x8F.ScalarCount]; - this.ComponentArray = new OldComponent[MaxComponents]; - this.DecodedBlocks = new Buffer[MaxComponents]; - } - - /// - /// Gets the component array - /// - public OldComponent[] ComponentArray { get; } - - /// - /// Gets the huffman trees - /// - public OldHuffmanTree[] HuffmanTrees { get; } - - /// - /// Gets the array of -s storing the "raw" frequency-domain decoded blocks. - /// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks. - /// This is done by . - /// When ==true, we are touching these blocks multiple times - each time we process a Scan. - /// - public Buffer[] DecodedBlocks { get; } - - /// - /// Gets the quantization tables, in zigzag order. - /// - public Block8x8F[] QuantizationTables { get; } - - /// - /// Gets the temporary buffer used to store bytes read from the stream. - /// TODO: Should be stack allocated, fixed sized buffer! - /// - public byte[] Temp { get; } - - /// - /// Gets the number of color components within the image. - /// - public int ComponentCount { get; private set; } - - /// - /// Gets the image height - /// - public int ImageHeight { get; private set; } - - /// - /// Gets the image width - /// - public int ImageWidth { get; private set; } - - /// - /// Gets the input stream. - /// - public Stream InputStream { get; private set; } - - /// - /// Gets a value indicating whether the image is interlaced (progressive) - /// - public bool IsProgressive { get; private set; } - - /// - /// Gets the restart interval - /// - public int RestartInterval { get; private set; } - - /// - /// Gets the number of MCU-s (Minimum Coded Units) in the image along the X axis - /// - public int MCUCountX { get; private set; } - - /// - /// Gets the number of MCU-s (Minimum Coded Units) in the image along the Y axis - /// - public int MCUCountY { get; private set; } - - /// - /// Gets the the total number of MCU-s (Minimum Coded Units) in the image. - /// - public int TotalMCUCount => this.MCUCountX * this.MCUCountY; - - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; private set; } - - /// - /// Decodes the image from the specified and sets - /// the data to image. - /// - /// The pixel format. - /// The stream, where the image should be. - /// The decoded image. - public Image Decode(Stream stream) - where TPixel : struct, IPixel - { - ImageMetaData metadata = new ImageMetaData(); - this.ProcessStream(metadata, stream, false); - this.ProcessBlocksIntoJpegImageChannels(); - Image image = this.ConvertJpegPixelsToImagePixels(metadata); - - return image; - } - - /// - /// Dispose - /// - public void Dispose() - { - for (int i = 0; i < this.HuffmanTrees.Length; i++) - { - this.HuffmanTrees[i].Dispose(); - } - - foreach (Buffer blockArray in this.DecodedBlocks) - { - blockArray?.Dispose(); - } - - this.ycbcrImage?.Dispose(); - this.InputProcessor.Dispose(); - this.grayImage.Pixels?.Dispose(); - this.blackImage.Pixels?.Dispose(); - } - - /// - /// Gets the representing the channel at a given component index - /// - /// The component index - /// The of the channel - public OldJpegPixelArea GetDestinationChannel(int compIndex) - { - if (this.ComponentCount == 1) - { - return this.grayImage; - } - else - { - switch (compIndex) - { - case 0: - return new OldJpegPixelArea(this.ycbcrImage.YChannel); - case 1: - return new OldJpegPixelArea(this.ycbcrImage.CbChannel); - case 2: - return new OldJpegPixelArea(this.ycbcrImage.CrChannel); - case 3: - return this.blackImage; - default: - throw new ImageFormatException("Too many components"); - } - } - } - - /// - /// Read metadata from stream and read the blocks in the scans into . - /// - /// The metadata - /// The stream - /// Whether to decode metadata only. - private void ProcessStream(ImageMetaData metadata, Stream stream, bool metadataOnly) - { - this.InputStream = stream; - this.InputProcessor = new InputProcessor(stream, this.Temp); - - // Check for the Start Of Image marker. - this.InputProcessor.ReadFull(this.Temp, 0, 2); - if (this.Temp[0] != OldJpegConstants.Markers.XFF || this.Temp[1] != OldJpegConstants.Markers.SOI) - { - throw new ImageFormatException("Missing SOI marker."); - } - - // Process the remaining segments until the End Of Image marker. - bool processBytes = true; - - // we can't currently short circute progressive images so don't try. - while (processBytes) - { - this.InputProcessor.ReadFull(this.Temp, 0, 2); - while (this.Temp[0] != 0xff) - { - // Strictly speaking, this is a format error. However, libjpeg is - // liberal in what it accepts. As of version 9, next_marker in - // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and - // continues to decode the stream. Even before next_marker sees - // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many - // bytes as it can, possibly past the end of a scan's data. It - // effectively puts back any markers that it overscanned (e.g. an - // "\xff\xd9" EOI marker), but it does not put back non-marker data, - // and thus it can silently ignore a small number of extraneous - // non-marker bytes before next_marker has a chance to see them (and - // print a warning). - // We are therefore also liberal in what we accept. Extraneous data - // is silently ignore - // This is similar to, but not exactly the same as, the restart - // mechanism within a scan (the RST[0-7] markers). - // Note that extraneous 0xff bytes in e.g. SOS data are escaped as - // "\xff\x00", and so are detected a little further down below. - this.Temp[0] = this.Temp[1]; - this.Temp[1] = this.InputProcessor.ReadByte(); - } - - byte marker = this.Temp[1]; - if (marker == 0) - { - // Treat "\xff\x00" as extraneous data. - continue; - } - - while (marker == 0xff) - { - // Section B.1.1.2 says, "Any marker may optionally be preceded by any - // number of fill bytes, which are bytes assigned code X'FF'". - marker = this.InputProcessor.ReadByte(); - } - - // End Of Image. - if (marker == OldJpegConstants.Markers.EOI) - { - break; - } - - if (marker >= OldJpegConstants.Markers.RST0 && marker <= OldJpegConstants.Markers.RST7) - { - // Figures B.2 and B.16 of the specification suggest that restart markers should - // only occur between Entropy Coded Segments and not after the final ECS. - // However, some encoders may generate incorrect JPEGs with a final restart - // marker. That restart marker will be seen here instead of inside the ProcessSOS - // method, and is ignored as a harmless error. Restart markers have no extra data, - // so we check for this before we read the 16-bit length of the segment. - continue; - } - - // Read the 16-bit length of the segment. The value includes the 2 bytes for the - // length itself, so we subtract 2 to get the number of remaining bytes. - this.InputProcessor.ReadFull(this.Temp, 0, 2); - int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; - if (remaining < 0) - { - throw new ImageFormatException("Short segment length."); - } - - switch (marker) - { - case OldJpegConstants.Markers.SOF0: - case OldJpegConstants.Markers.SOF1: - case OldJpegConstants.Markers.SOF2: - this.IsProgressive = marker == OldJpegConstants.Markers.SOF2; - this.ProcessStartOfFrameMarker(remaining); - if (metadataOnly && this.isJfif) - { - return; - } - - break; - case OldJpegConstants.Markers.DHT: - if (metadataOnly) - { - this.InputProcessor.Skip(remaining); - } - else - { - this.ProcessDefineHuffmanTablesMarker(remaining); - } - - break; - case OldJpegConstants.Markers.DQT: - if (metadataOnly) - { - this.InputProcessor.Skip(remaining); - } - else - { - this.ProcessDqt(remaining); - } - - break; - case OldJpegConstants.Markers.SOS: - if (metadataOnly) - { - return; - } - - // when this is a progressive image this gets called a number of times - // need to know how many times this should be called in total. - this.ProcessStartOfScan(remaining); - if (this.InputProcessor.UnexpectedEndOfStreamReached || !this.IsProgressive) - { - // if unexpeced EOF reached or this is not a progressive image we can stop processing bytes as we now have the image data. - processBytes = false; - } - - break; - case OldJpegConstants.Markers.DRI: - if (metadataOnly) - { - this.InputProcessor.Skip(remaining); - } - else - { - this.ProcessDefineRestartIntervalMarker(remaining); - } - - break; - case OldJpegConstants.Markers.APP0: - this.ProcessApplicationHeader(remaining); - break; - case OldJpegConstants.Markers.APP1: - this.ProcessApp1Marker(remaining, metadata); - break; - case OldJpegConstants.Markers.APP2: - this.ProcessApp2Marker(remaining, metadata); - break; - case OldJpegConstants.Markers.APP14: - this.ProcessApp14Marker(remaining); - break; - default: - if ((marker >= OldJpegConstants.Markers.APP0 && marker <= OldJpegConstants.Markers.APP15) - || marker == OldJpegConstants.Markers.COM) - { - this.InputProcessor.Skip(remaining); - } - else if (marker < OldJpegConstants.Markers.SOF0) - { - // See Table B.1 "Marker code assignments". - throw new ImageFormatException("Unknown marker"); - } - else - { - throw new ImageFormatException("Unknown marker"); - } - - break; - } - } - } - - /// - /// Processes the SOS (Start of scan marker). - /// - /// The remaining bytes in the segment block. - /// - /// Missing SOF Marker - /// SOS has wrong length - /// - private void ProcessStartOfScan(int remaining) - { - OldJpegScanDecoder scan = default(OldJpegScanDecoder); - OldJpegScanDecoder.InitStreamReading(&scan, this, remaining); - this.InputProcessor.Bits = default(Bits); - this.MakeImage(); - scan.DecodeBlocks(this); - } - - /// - /// Process the blocks in into Jpeg image channels ( and ) - /// are in a "raw" frequency-domain form. We need to apply IDCT, dequantization and unzigging to transform them into color-space blocks. - /// We can copy these blocks into -s afterwards. - /// - /// The pixel type - private void ProcessBlocksIntoJpegImageChannels() - where TPixel : struct, IPixel - { - Parallel.For( - 0, - this.ComponentCount, - componentIndex => - { - JpegBlockProcessor processor = default(JpegBlockProcessor); - JpegBlockProcessor.Init(&processor, componentIndex); - processor.ProcessAllBlocks(this); - }); - } - - /// - /// Convert the pixel data in and/or into pixels of - /// - /// The pixel type - /// The metadata for the image. - /// The decoded image. - private Image ConvertJpegPixelsToImagePixels(ImageMetaData metadata) - where TPixel : struct, IPixel - { - Image image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, metadata); - - if (this.grayImage.IsInitialized) - { - this.ConvertFromGrayScale(image); - return image; - } - else if (this.ycbcrImage != null) - { - if (this.ComponentCount == 4) - { - if (!this.adobeTransformValid) - { - throw new ImageFormatException( - "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); - } - - // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html - // TODO: YCbCrA? - if (this.adobeTransform == OldJpegConstants.Adobe.ColorTransformYcck) - { - this.ConvertFromYcck(image); - } - else if (this.adobeTransform == OldJpegConstants.Adobe.ColorTransformUnknown) - { - // Assume CMYK - this.ConvertFromCmyk(image); - } - - return image; - } - - if (this.ComponentCount == 3) - { - if (this.IsRGB()) - { - this.ConvertFromRGB(image); - return image; - } - - this.ConvertFromYCbCr(image); - return image; - } - - throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); - } - else - { - throw new ImageFormatException("Missing SOS marker."); - } - } - - /// - /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. - /// - /// The pixel format. - /// The image to assign the resolution to. - private void AssignResolution(Image image) - where TPixel : struct, IPixel - { - if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) - { - image.MetaData.HorizontalResolution = this.horizontalResolution; - image.MetaData.VerticalResolution = this.verticalResolution; - } - else if (this.isExif) - { - ExifValue horizontal = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); - ExifValue vertical = image.MetaData.ExifProfile.GetValue(ExifTag.YResolution); - double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0; - double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0; - - if (horizontalValue > 0 && verticalValue > 0) - { - image.MetaData.HorizontalResolution = horizontalValue; - image.MetaData.VerticalResolution = verticalValue; - } - } - } - - /// - /// Converts the image from the original CMYK image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromCmyk(Image image) - where TPixel : struct, IPixel - { - int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - - using (PixelAccessor pixels = image.Lock()) - { - Parallel.For( - 0, - image.Height, - y => - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < image.Width; x++) - { - byte cyan = this.ycbcrImage.YChannel[yo + x]; - byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)]; - byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)]; - - TPixel packed = default(TPixel); - this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); - pixels[x, y] = packed; - } - }); - } - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original grayscale image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromGrayScale(Image image) - where TPixel : struct, IPixel - { - Parallel.For( - 0, - image.Height, - image.Configuration.ParallelOptions, - y => - { - ref TPixel pixelRowBaseRef = ref image.GetPixelReference(0, y); - - int yoff = this.grayImage.GetRowOffset(y); - - for (int x = 0; x < image.Width; x++) - { - byte rgb = this.grayImage.Pixels[yoff + x]; - ref TPixel pixel = ref Unsafe.Add(ref pixelRowBaseRef, x); - pixel.PackFromRgba32(new Rgba32(rgb, rgb, rgb, 255)); - } - }); - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original RBG image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromRGB(Image image) - where TPixel : struct, IPixel - { - int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - - Parallel.For( - 0, - image.Height, - image.Configuration.ParallelOptions, - y => - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - ref TPixel pixelRowBaseRef = ref image.GetPixelReference(0, y); - - Rgba32 rgba = new Rgba32(0, 0, 0, 255); - - for (int x = 0; x < image.Width; x++) - { - rgba.R = this.ycbcrImage.YChannel[yo + x]; - rgba.G = this.ycbcrImage.CbChannel[co + (x / scale)]; - rgba.B = this.ycbcrImage.CrChannel[co + (x / scale)]; - - ref TPixel pixel = ref Unsafe.Add(ref pixelRowBaseRef, x); - pixel.PackFromRgba32(rgba); - } - }); - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original YCbCr image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromYCbCr(Image image) - where TPixel : struct, IPixel - { - int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - using (PixelAccessor pixels = image.Lock()) - { - Parallel.For( - 0, - image.Height, - image.Configuration.ParallelOptions, - y => - { - // TODO. This Parallel loop doesn't give us the boost it should. - ref byte ycRef = ref this.ycbcrImage.YChannel[0]; - ref byte cbRef = ref this.ycbcrImage.CbChannel[0]; - ref byte crRef = ref this.ycbcrImage.CrChannel[0]; - fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables) - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - - for (int x = 0; x < image.Width; x++) - { - int cOff = co + (x / scale); - byte yy = Unsafe.Add(ref ycRef, yo + x); - byte cb = Unsafe.Add(ref cbRef, cOff); - byte cr = Unsafe.Add(ref crRef, cOff); - - TPixel packed = default(TPixel); - YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr); - pixels[x, y] = packed; - } - } - }); - } - - this.AssignResolution(image); - } - - /// - /// Converts the image from the original YCCK image pixels. - /// - /// The pixel format. - /// The image. - private void ConvertFromYcck(Image image) - where TPixel : struct, IPixel - { - int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - - Parallel.For( - 0, - image.Height, - y => - { - // TODO: Simplify + optimize + share duplicate code across converter methods - int yo = this.ycbcrImage.GetRowYOffset(y); - int co = this.ycbcrImage.GetRowCOffset(y); - ref TPixel pixelRowBaseRef = ref image.GetPixelReference(0, y); - - for (int x = 0; x < image.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)]; - - ref TPixel pixel = ref Unsafe.Add(ref pixelRowBaseRef, x); - this.PackYcck(ref pixel, yy, cb, cr, x, y); - } - }); - - this.AssignResolution(image); - } - - /// - /// Returns a value indicating whether the image in an RGB image. - /// - /// - /// The . - /// - private bool IsRGB() - { - if (this.isJfif) - { - return false; - } - - if (this.adobeTransformValid && this.adobeTransform == OldJpegConstants.Adobe.ColorTransformUnknown) - { - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr. - return true; - } - - return this.ComponentArray[0].Identifier == 'R' && this.ComponentArray[1].Identifier == 'G' - && this.ComponentArray[2].Identifier == 'B'; - } - - /// - /// Makes the image from the buffer. - /// - private void MakeImage() - { - if (this.grayImage.IsInitialized || this.ycbcrImage != null) - { - return; - } - - if (this.ComponentCount == 1) - { - Buffer2D buffer = Buffer2D.CreateClean(8 * this.MCUCountX, 8 * this.MCUCountY); - this.grayImage = new OldJpegPixelArea(buffer); - } - else - { - int h0 = this.ComponentArray[0].HorizontalFactor; - int v0 = this.ComponentArray[0].VerticalFactor; - int horizontalRatio = h0 / this.ComponentArray[1].HorizontalFactor; - int verticalRatio = v0 / this.ComponentArray[1].VerticalFactor; - - YCbCrImage.YCbCrSubsampleRatio ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; - switch ((horizontalRatio << 4) | verticalRatio) - { - case 0x11: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444; - break; - case 0x12: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440; - break; - case 0x21: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422; - break; - case 0x22: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420; - break; - case 0x41: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411; - break; - case 0x42: - ratio = YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410; - break; - } - - this.ycbcrImage = new YCbCrImage(8 * h0 * this.MCUCountX, 8 * v0 * this.MCUCountY, ratio); - - if (this.ComponentCount == 4) - { - int h3 = this.ComponentArray[3].HorizontalFactor; - int v3 = this.ComponentArray[3].VerticalFactor; - - Buffer2D buffer = Buffer2D.CreateClean(8 * h3 * this.MCUCountX, 8 * v3 * this.MCUCountY); - this.blackImage = new OldJpegPixelArea(buffer); - } - } - } - - /// - /// Optimized method to pack bytes to the image from the CMYK color space. - /// This is faster than implicit casting as it avoids double packing. - /// - /// The pixel format. - /// The packed pixel. - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The x-position within the image. - /// The y-position within the image. - private void PackCmyk(ref TPixel packed, byte c, byte m, byte y, int xx, int yy) - where TPixel : struct, IPixel - { - // Get keyline - float keyline = (255 - this.blackImage[xx, yy]) / 255F; - - // Convert back to RGB. CMY are not inverted - byte r = (byte)(((c / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - byte g = (byte)(((m / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - byte b = (byte)(((y / 255F) * (1F - keyline)).Clamp(0, 1) * 255); - - packed.PackFromRgba32(new Rgba32(r, g, b)); - } - - /// - /// Optimized method to pack bytes to the image from the YCCK color space. - /// This is faster than implicit casting as it avoids double packing. - /// - /// The pixel format. - /// The packed pixel. - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - /// The x-position within the image. - /// The y-position within the image. - private void PackYcck(ref TPixel packed, byte y, byte cb, byte cr, int xx, int yy) - where TPixel : struct, IPixel - { - // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get - // CMY, and patch in the original K. The RGB to CMY inversion cancels - // out the 'Adobe inversion' described in the applyBlack doc comment - // above, so in practice, only the fourth channel (black) is inverted. - int ccb = cb - 128; - int ccr = cr - 128; - - // Speed up the algorithm by removing floating point calculation - // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result - int r0 = 91881 * ccr; // (1.402F * 65536) + .5F - int g0 = 22554 * ccb; // (0.34414F * 65536) + .5F - int g1 = 46802 * ccr; // (0.71414F * 65536) + .5F - int b0 = 116130 * ccb; // (1.772F * 65536) + .5F - - // First convert from YCbCr to CMY - float cyan = (y + (r0 >> 16)).Clamp(0, 255) / 255F; - float magenta = (byte)(y - (g0 >> 16) - (g1 >> 16)).Clamp(0, 255) / 255F; - float yellow = (byte)(y + (b0 >> 16)).Clamp(0, 255) / 255F; - - // Get keyline - float keyline = (255 - this.blackImage[xx, yy]) / 255F; - - // Convert back to RGB - byte r = (byte)(((1 - cyan) * (1 - keyline)).Clamp(0, 1) * 255); - byte g = (byte)(((1 - magenta) * (1 - keyline)).Clamp(0, 1) * 255); - byte b = (byte)(((1 - yellow) * (1 - keyline)).Clamp(0, 1) * 255); - - packed.PackFromRgba32(new Rgba32(r, g, b)); - } - - /// - /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters. - /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not - /// deleted by default when deleting all metadata because it may affect the appearance of the image. - /// - /// The remaining number of bytes in the stream. - private void ProcessApp14Marker(int remaining) - { - if (remaining < 12) - { - this.InputProcessor.Skip(remaining); - return; - } - - this.InputProcessor.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') - { - this.adobeTransformValid = true; - this.adobeTransform = this.Temp[11]; - } - - if (remaining > 0) - { - this.InputProcessor.Skip(remaining); - } - } - - /// - /// Processes the App1 marker retrieving any stored metadata - /// - /// The remaining bytes in the segment block. - /// The image. - private void ProcessApp1Marker(int remaining, ImageMetaData metadata) - { - if (remaining < 6 || this.IgnoreMetadata) - { - this.InputProcessor.Skip(remaining); - return; - } - - byte[] profile = new byte[remaining]; - this.InputProcessor.ReadFull(profile, 0, remaining); - - if (profile[0] == 'E' && - profile[1] == 'x' && - profile[2] == 'i' && - profile[3] == 'f' && - profile[4] == '\0' && - profile[5] == '\0') - { - this.isExif = true; - metadata.ExifProfile = new ExifProfile(profile); - } - } - - /// - /// Processes the App2 marker retrieving any stored ICC profile information - /// - /// The remaining bytes in the segment block. - /// The image. - private void ProcessApp2Marker(int remaining, ImageMetaData metadata) - { - // Length is 14 though we only need to check 12. - const int Icclength = 14; - if (remaining < Icclength || this.IgnoreMetadata) - { - this.InputProcessor.Skip(remaining); - return; - } - - byte[] identifier = new byte[Icclength]; - this.InputProcessor.ReadFull(identifier, 0, Icclength); - remaining -= Icclength; // we have read it by this point - - if (identifier[0] == 'I' && - identifier[1] == 'C' && - identifier[2] == 'C' && - identifier[3] == '_' && - identifier[4] == 'P' && - identifier[5] == 'R' && - identifier[6] == 'O' && - identifier[7] == 'F' && - identifier[8] == 'I' && - identifier[9] == 'L' && - identifier[10] == 'E' && - identifier[11] == '\0') - { - byte[] profile = new byte[remaining]; - this.InputProcessor.ReadFull(profile, 0, remaining); - - if (metadata.IccProfile == null) - { - metadata.IccProfile = new IccProfile(profile); - } - else - { - metadata.IccProfile.Extend(profile); - } - } - else - { - // not an ICC profile we can handle read the remaining so we can carry on and ignore this. - this.InputProcessor.Skip(remaining); - } - } - - /// - /// Processes the application header containing the JFIF identifier plus extra data. - /// - /// The remaining bytes in the segment block. - private void ProcessApplicationHeader(int remaining) - { - if (remaining < 5) - { - this.InputProcessor.Skip(remaining); - return; - } - - this.InputProcessor.ReadFull(this.Temp, 0, 13); - 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'; - - if (this.isJfif) - { - this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[8] << 8)); - this.verticalResolution = (short)(this.Temp[11] + (this.Temp[10] << 8)); - } - - if (remaining > 0) - { - this.InputProcessor.Skip(remaining); - } - } - - /// - /// Processes a Define Huffman Table marker, and initializes a huffman - /// struct from its contents. Specified in section B.2.4.2. - /// - /// The remaining bytes in the segment block. - private void ProcessDefineHuffmanTablesMarker(int remaining) - { - while (remaining > 0) - { - if (remaining < 17) - { - throw new ImageFormatException("DHT has wrong length"); - } - - this.InputProcessor.ReadFull(this.Temp, 0, 17); - - int tc = this.Temp[0] >> 4; - if (tc > OldHuffmanTree.MaxTc) - { - throw new ImageFormatException("Bad Tc value"); - } - - int th = this.Temp[0] & 0x0f; - if (th > OldHuffmanTree.MaxTh || (!this.IsProgressive && (th > 1))) - { - throw new ImageFormatException("Bad Th value"); - } - - int huffTreeIndex = (tc * OldHuffmanTree.ThRowSize) + th; - this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop( - ref this.InputProcessor, - this.Temp, - ref remaining); - } - } - - /// - /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in - /// macroblocks - /// - /// The remaining bytes in the segment block. - private void ProcessDefineRestartIntervalMarker(int remaining) - { - if (remaining != 2) - { - throw new ImageFormatException("DRI has wrong length"); - } - - this.InputProcessor.ReadFull(this.Temp, 0, remaining); - this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1]; - } - - /// - /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. - /// - /// The remaining bytes in the segment block. - /// - /// Thrown if the tables do not match the header - /// - private void ProcessDqt(int remaining) - { - while (remaining > 0) - { - bool done = false; - - remaining--; - byte x = this.InputProcessor.ReadByte(); - int tq = x & 0x0F; - if (tq > MaxTq) - { - throw new ImageFormatException("Bad Tq value"); - } - - switch (x >> 4) - { - case 0: - if (remaining < Block8x8F.ScalarCount) - { - done = true; - break; - } - - remaining -= Block8x8F.ScalarCount; - this.InputProcessor.ReadFull(this.Temp, 0, Block8x8F.ScalarCount); - - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - this.QuantizationTables[tq][i] = this.Temp[i]; - } - - break; - case 1: - if (remaining < 2 * Block8x8F.ScalarCount) - { - done = true; - break; - } - - remaining -= 2 * Block8x8F.ScalarCount; - this.InputProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount); - - for (int i = 0; i < Block8x8F.ScalarCount; i++) - { - this.QuantizationTables[tq][i] = (this.Temp[2 * i] << 8) | this.Temp[(2 * i) + 1]; - } - - break; - default: - throw new ImageFormatException("Bad Pq value"); - } - - if (done) - { - break; - } - } - - if (remaining != 0) - { - throw new ImageFormatException("DQT has wrong length"); - } - } - - /// - /// Processes the Start of Frame marker. Specified in section B.2.2. - /// - /// The remaining bytes in the segment block. - private void ProcessStartOfFrameMarker(int remaining) - { - if (this.ComponentCount != 0) - { - throw new ImageFormatException("Multiple SOF markers"); - } - - switch (remaining) - { - case 6 + (3 * 1): // Grayscale image. - this.ComponentCount = 1; - break; - case 6 + (3 * 3): // YCbCr or RGB image. - this.ComponentCount = 3; - break; - case 6 + (3 * 4): // YCbCrK or CMYK image. - this.ComponentCount = 4; - break; - default: - throw new ImageFormatException("Incorrect number of components"); - } - - this.InputProcessor.ReadFull(this.Temp, 0, remaining); - - // We only support 8-bit precision. - if (this.Temp[0] != 8) - { - throw new ImageFormatException("Only 8-Bit precision supported."); - } - - this.ImageHeight = (this.Temp[1] << 8) + this.Temp[2]; - this.ImageWidth = (this.Temp[3] << 8) + this.Temp[4]; - if (this.Temp[5] != this.ComponentCount) - { - throw new ImageFormatException("SOF has wrong length"); - } - - for (int i = 0; i < this.ComponentCount; i++) - { - this.ComponentArray[i].Identifier = this.Temp[6 + (3 * i)]; - - // Section B.2.2 states that "the value of C_i shall be different from - // the values of C_1 through C_(i-1)". - for (int j = 0; j < i; j++) - { - if (this.ComponentArray[i].Identifier == this.ComponentArray[j].Identifier) - { - throw new ImageFormatException("Repeated component identifier"); - } - } - - this.ComponentArray[i].Selector = this.Temp[8 + (3 * i)]; - if (this.ComponentArray[i].Selector > MaxTq) - { - throw new ImageFormatException("Bad Tq value"); - } - - byte hv = this.Temp[7 + (3 * i)]; - int h = hv >> 4; - int v = hv & 0x0f; - if (h < 1 || h > 4 || v < 1 || v > 4) - { - throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio"); - } - - if (h == 3 || v == 3) - { - throw new ImageFormatException("Lnsupported subsampling ratio"); - } - - switch (this.ComponentCount) - { - case 1: - - // If a JPEG image has only one component, section A.2 says "this data - // is non-interleaved by definition" and section A.2.2 says "[in this - // case...] the order of data units within a scan shall be left-to-right - // and top-to-bottom... regardless of the values of H_1 and V_1". Section - // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be - // one data unit". Similarly, section A.1.1 explains that it is the ratio - // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale - // images, H_1 is the maximum H_j for all components j, so that ratio is - // always 1. The component's (h, v) is effectively always (1, 1): even if - // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8 - // MCUs, not two 16x8 MCUs. - h = 1; - v = 1; - break; - - case 3: - - // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0, - // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the - // (h, v) values for the Y component are either (1, 1), (1, 2), - // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values - // must be a multiple of the Cb and Cr component's values. We also - // assume that the two chroma components have the same subsampling - // ratio. - switch (i) - { - case 0: - { - // Y. - // We have already verified, above, that h and v are both - // either 1, 2 or 4, so invalid (h, v) combinations are those - // with v == 4. - if (v == 4) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } - - break; - } - - case 1: - { - // Cb. - if (this.ComponentArray[0].HorizontalFactor % h != 0 - || this.ComponentArray[0].VerticalFactor % v != 0) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } - - break; - } - - case 2: - { - // Cr. - if (this.ComponentArray[1].HorizontalFactor != h - || this.ComponentArray[1].VerticalFactor != v) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } - - break; - } - } - - break; - - case 4: - - // For 4-component images (either CMYK or YCbCrK), we only support two - // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. - // Theoretically, 4-component JPEG images could mix and match hv values - // but in practice, those two combinations are the only ones in use, - // and it simplifies the applyBlack code below if we can assume that: - // - for CMYK, the C and K channels have full samples, and if the M - // and Y channels subsample, they subsample both horizontally and - // vertically. - // - for YCbCrK, the Y and K channels have full samples. - switch (i) - { - case 0: - if (hv != 0x11 && hv != 0x22) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } - - break; - case 1: - case 2: - if (hv != 0x11) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } - - break; - case 3: - if (this.ComponentArray[0].HorizontalFactor != h - || this.ComponentArray[0].VerticalFactor != v) - { - throw new ImageFormatException("Unsupported subsampling ratio"); - } - - break; - } - - break; - } - - this.ComponentArray[i].HorizontalFactor = h; - this.ComponentArray[i].VerticalFactor = v; - } - - int h0 = this.ComponentArray[0].HorizontalFactor; - int v0 = this.ComponentArray[0].VerticalFactor; - this.MCUCountX = (this.ImageWidth + (8 * h0) - 1) / (8 * h0); - this.MCUCountY = (this.ImageHeight + (8 * v0) - 1) / (8 * v0); - - // As a preparation for parallelizing Scan decoder, we also allocate DecodedBlocks in the non-progressive case! - for (int i = 0; i < this.ComponentCount; i++) - { - int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor - * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = Buffer.CreateClean(count); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegConstants.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegConstants.cs similarity index 99% rename from src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegConstants.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegConstants.cs index c2326cc4f..f38c72820 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegConstants.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// Defines jpeg constants defined in the specification. /// - internal static class OldJpegConstants + internal static class OrigJpegConstants { /// /// The maximum allowable length in each dimension of a jpeg image. diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoder.cs similarity index 84% rename from src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoder.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoder.cs index 62898d399..13be70e30 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OldJpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoder.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// Image decoder for generating an image out of a jpg stream. /// - internal sealed class OldJpegDecoder : IImageDecoder, IJpegDecoderOptions + internal sealed class OrigJpegDecoder : IImageDecoder, IJpegDecoderOptions { /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { Guard.NotNull(stream, nameof(stream)); - using (var decoder = new OldJpegDecoderCore(configuration, this)) + using (var decoder = new OrigJpegDecoderCore(configuration, this)) { return decoder.Decode(stream); } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs new file mode 100644 index 000000000..33ebe72d0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -0,0 +1,823 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; + +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; +using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; +using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.MetaData.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort +{ + /// + /// + /// Performs the jpeg decoding operation. + /// + internal sealed unsafe class OrigJpegDecoderCore : IRawJpegData + { + /// + /// The maximum number of color components + /// + public const int MaxComponents = 4; + + /// + /// The maximum number of quantization tables + /// + public const int MaxTq = 3; + + // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P +#pragma warning disable SA1401 // FieldsMustBePrivate + + /// + /// Encapsulates stream reading and processing data and operations for . + /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s + /// + public InputProcessor InputProcessor; +#pragma warning restore SA401 + + /// + /// The global configuration + /// + private readonly Configuration configuration; + + /// + /// The App14 marker color-space + /// + private byte adobeTransform; + + /// + /// Whether the image is in CMYK format with an App14 marker + /// + private bool adobeTransformValid; + + /// + /// The horizontal resolution. Calculated if the image has a JFIF header. + /// + private short horizontalResolution; + + /// + /// Whether the image has a JFIF header + /// + private bool isJfif; + + /// + /// Whether the image has a EXIF header + /// + private bool isExif; + + /// + /// The vertical resolution. Calculated if the image has a JFIF header. + /// + private short verticalResolution; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The options. + public OrigJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) + { + this.IgnoreMetadata = options.IgnoreMetadata; + this.configuration = configuration ?? Configuration.Default; + this.HuffmanTrees = OrigHuffmanTree.CreateHuffmanTrees(); + this.QuantizationTables = new Block8x8F[MaxTq + 1]; + this.Temp = new byte[2 * Block8x8F.Size]; + } + + /// + public JpegColorSpace ColorSpace { get; private set; } + + /// + /// Gets the component array + /// + public OrigComponent[] Components { get; private set; } + + /// + /// Gets the huffman trees + /// + public OrigHuffmanTree[] HuffmanTrees { get; } + + /// + public Block8x8F[] QuantizationTables { get; } + + /// + /// Gets the temporary buffer used to store bytes read from the stream. + /// TODO: Should be stack allocated, fixed sized buffer! + /// + public byte[] Temp { get; } + + /// + public Size ImageSizeInPixels { get; private set; } + + /// + /// Gets the number of MCU blocks in the image as . + /// + public Size ImageSizeInMCU { get; private set; } + + /// + public int ComponentCount { get; private set; } + + IEnumerable IRawJpegData.Components => this.Components; + + /// + /// Gets the image height + /// + public int ImageHeight => this.ImageSizeInPixels.Height; + + /// + /// Gets the image width + /// + public int ImageWidth => this.ImageSizeInPixels.Width; + + /// + /// Gets the input stream. + /// + public Stream InputStream { get; private set; } + + /// + /// Gets a value indicating whether the image is interlaced (progressive) + /// + public bool IsProgressive { get; private set; } + + /// + /// Gets the restart interval + /// + public int RestartInterval { get; private set; } + + /// + /// Gets the number of MCU-s (Minimum Coded Units) in the image along the X axis + /// + public int MCUCountX => this.ImageSizeInMCU.Width; + + /// + /// Gets the number of MCU-s (Minimum Coded Units) in the image along the Y axis + /// + public int MCUCountY => this.ImageSizeInMCU.Height; + + /// + /// Gets the the total number of MCU-s (Minimum Coded Units) in the image. + /// + public int TotalMCUCount => this.MCUCountX * this.MCUCountY; + + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; } + + /// + /// Gets the decoded by this decoder instance. + /// + public ImageMetaData MetaData { get; private set; } + + /// + /// Decodes the image from the specified and sets + /// the data to image. + /// + /// The pixel format. + /// The stream, where the image should be. + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : struct, IPixel + { + this.ParseStream(stream); + + return this.PostProcessIntoImage(); + } + + /// + public void Dispose() + { + for (int i = 0; i < this.HuffmanTrees.Length; i++) + { + this.HuffmanTrees[i].Dispose(); + } + + if (this.Components != null) + { + foreach (OrigComponent component in this.Components) + { + component.Dispose(); + } + } + + this.InputProcessor.Dispose(); + } + + /// + /// Read metadata from stream and read the blocks in the scans into . + /// + /// The stream + /// Whether to decode metadata only. + public void ParseStream(Stream stream, bool metadataOnly = false) + { + this.MetaData = new ImageMetaData(); + this.InputStream = stream; + this.InputProcessor = new InputProcessor(stream, this.Temp); + + // Check for the Start Of Image marker. + this.InputProcessor.ReadFull(this.Temp, 0, 2); + if (this.Temp[0] != OrigJpegConstants.Markers.XFF || this.Temp[1] != OrigJpegConstants.Markers.SOI) + { + throw new ImageFormatException("Missing SOI marker."); + } + + // Process the remaining segments until the End Of Image marker. + bool processBytes = true; + + // we can't currently short circute progressive images so don't try. + while (processBytes) + { + this.InputProcessor.ReadFull(this.Temp, 0, 2); + while (this.Temp[0] != 0xff) + { + // Strictly speaking, this is a format error. However, libjpeg is + // liberal in what it accepts. As of version 9, next_marker in + // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and + // continues to decode the stream. Even before next_marker sees + // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many + // bytes as it can, possibly past the end of a scan's data. It + // effectively puts back any markers that it overscanned (e.g. an + // "\xff\xd9" EOI marker), but it does not put back non-marker data, + // and thus it can silently ignore a small number of extraneous + // non-marker bytes before next_marker has a chance to see them (and + // print a warning). + // We are therefore also liberal in what we accept. Extraneous data + // is silently ignore + // This is similar to, but not exactly the same as, the restart + // mechanism within a scan (the RST[0-7] markers). + // Note that extraneous 0xff bytes in e.g. SOS data are escaped as + // "\xff\x00", and so are detected a little further down below. + this.Temp[0] = this.Temp[1]; + this.Temp[1] = this.InputProcessor.ReadByte(); + } + + byte marker = this.Temp[1]; + if (marker == 0) + { + // Treat "\xff\x00" as extraneous data. + continue; + } + + while (marker == 0xff) + { + // Section B.1.1.2 says, "Any marker may optionally be preceded by any + // number of fill bytes, which are bytes assigned code X'FF'". + marker = this.InputProcessor.ReadByte(); + } + + // End Of Image. + if (marker == OrigJpegConstants.Markers.EOI) + { + break; + } + + if (marker >= OrigJpegConstants.Markers.RST0 && marker <= OrigJpegConstants.Markers.RST7) + { + // Figures B.2 and B.16 of the specification suggest that restart markers should + // only occur between Entropy Coded Segments and not after the final ECS. + // However, some encoders may generate incorrect JPEGs with a final restart + // marker. That restart marker will be seen here instead of inside the ProcessSOS + // method, and is ignored as a harmless error. Restart markers have no extra data, + // so we check for this before we read the 16-bit length of the segment. + continue; + } + + // Read the 16-bit length of the segment. The value includes the 2 bytes for the + // length itself, so we subtract 2 to get the number of remaining bytes. + this.InputProcessor.ReadFull(this.Temp, 0, 2); + int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; + if (remaining < 0) + { + throw new ImageFormatException("Short segment length."); + } + + switch (marker) + { + case OrigJpegConstants.Markers.SOF0: + case OrigJpegConstants.Markers.SOF1: + case OrigJpegConstants.Markers.SOF2: + this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2; + this.ProcessStartOfFrameMarker(remaining); + if (metadataOnly && this.isJfif) + { + return; + } + + break; + case OrigJpegConstants.Markers.DHT: + if (metadataOnly) + { + this.InputProcessor.Skip(remaining); + } + else + { + this.ProcessDefineHuffmanTablesMarker(remaining); + } + + break; + case OrigJpegConstants.Markers.DQT: + if (metadataOnly) + { + this.InputProcessor.Skip(remaining); + } + else + { + this.ProcessDqt(remaining); + } + + break; + case OrigJpegConstants.Markers.SOS: + if (metadataOnly) + { + return; + } + + // when this is a progressive image this gets called a number of times + // need to know how many times this should be called in total. + this.ProcessStartOfScan(remaining); + if (this.InputProcessor.ReachedEOF || !this.IsProgressive) + { + // if unexpeced EOF reached or this is not a progressive image we can stop processing bytes as we now have the image data. + processBytes = false; + } + + break; + case OrigJpegConstants.Markers.DRI: + if (metadataOnly) + { + this.InputProcessor.Skip(remaining); + } + else + { + this.ProcessDefineRestartIntervalMarker(remaining); + } + + break; + case OrigJpegConstants.Markers.APP0: + this.ProcessApplicationHeader(remaining); + break; + case OrigJpegConstants.Markers.APP1: + this.ProcessApp1Marker(remaining); + break; + case OrigJpegConstants.Markers.APP2: + this.ProcessApp2Marker(remaining); + break; + case OrigJpegConstants.Markers.APP14: + this.ProcessApp14Marker(remaining); + break; + default: + if ((marker >= OrigJpegConstants.Markers.APP0 && marker <= OrigJpegConstants.Markers.APP15) + || marker == OrigJpegConstants.Markers.COM) + { + this.InputProcessor.Skip(remaining); + } + else if (marker < OrigJpegConstants.Markers.SOF0) + { + // See Table B.1 "Marker code assignments". + throw new ImageFormatException("Unknown marker"); + } + else + { + throw new ImageFormatException("Unknown marker"); + } + + break; + } + } + + this.InitDerivedMetaDataProperties(); + } + + /// + /// Processes the SOS (Start of scan marker). + /// + /// The remaining bytes in the segment block. + /// + /// Missing SOF Marker + /// SOS has wrong length + /// + private void ProcessStartOfScan(int remaining) + { + var scan = default(OrigJpegScanDecoder); + OrigJpegScanDecoder.InitStreamReading(&scan, this, remaining); + this.InputProcessor.Bits = default(Bits); + scan.DecodeBlocks(this); + } + + /// + /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. + /// + private void InitDerivedMetaDataProperties() + { + if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) + { + this.MetaData.HorizontalResolution = this.horizontalResolution; + this.MetaData.VerticalResolution = this.verticalResolution; + } + else if (this.isExif) + { + ExifValue horizontal = this.MetaData.ExifProfile.GetValue(ExifTag.XResolution); + ExifValue vertical = this.MetaData.ExifProfile.GetValue(ExifTag.YResolution); + double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0; + double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0; + + if (horizontalValue > 0 && verticalValue > 0) + { + this.MetaData.HorizontalResolution = horizontalValue; + this.MetaData.VerticalResolution = verticalValue; + } + } + } + + /// + /// Returns a value indicating whether the image in an RGB image. + /// + /// + /// The . + /// + private bool IsRGB() + { + if (this.isJfif) + { + return false; + } + + if (this.adobeTransformValid && this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformUnknown) + { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr. + return true; + } + + return this.Components[0].Identifier == 'R' && this.Components[1].Identifier == 'G' + && this.Components[2].Identifier == 'B'; + } + + /// + /// Processes the "Adobe" APP14 segment stores image encoding information for DCT filters. + /// This segment may be copied or deleted as a block using the Extra "Adobe" tag, but note that it is not + /// deleted by default when deleting all metadata because it may affect the appearance of the image. + /// + /// The remaining number of bytes in the stream. + private void ProcessApp14Marker(int remaining) + { + if (remaining < 12) + { + this.InputProcessor.Skip(remaining); + return; + } + + this.InputProcessor.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') + { + this.adobeTransformValid = true; + this.adobeTransform = this.Temp[11]; + } + + if (remaining > 0) + { + this.InputProcessor.Skip(remaining); + } + } + + /// + /// Processes the App1 marker retrieving any stored metadata + /// + /// The remaining bytes in the segment block. + private void ProcessApp1Marker(int remaining) + { + if (remaining < 6 || this.IgnoreMetadata) + { + this.InputProcessor.Skip(remaining); + return; + } + + byte[] profile = new byte[remaining]; + this.InputProcessor.ReadFull(profile, 0, remaining); + + if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' + && profile[5] == '\0') + { + this.isExif = true; + this.MetaData.ExifProfile = new ExifProfile(profile); + } + } + + /// + /// Processes the App2 marker retrieving any stored ICC profile information + /// + /// The remaining bytes in the segment block. + private void ProcessApp2Marker(int remaining) + { + // Length is 14 though we only need to check 12. + const int Icclength = 14; + if (remaining < Icclength || this.IgnoreMetadata) + { + this.InputProcessor.Skip(remaining); + return; + } + + byte[] identifier = new byte[Icclength]; + this.InputProcessor.ReadFull(identifier, 0, Icclength); + remaining -= Icclength; // we have read it by this point + + if (identifier[0] == 'I' && identifier[1] == 'C' && identifier[2] == 'C' && identifier[3] == '_' + && identifier[4] == 'P' && identifier[5] == 'R' && identifier[6] == 'O' && identifier[7] == 'F' + && identifier[8] == 'I' && identifier[9] == 'L' && identifier[10] == 'E' && identifier[11] == '\0') + { + byte[] profile = new byte[remaining]; + this.InputProcessor.ReadFull(profile, 0, remaining); + + if (this.MetaData.IccProfile == null) + { + this.MetaData.IccProfile = new IccProfile(profile); + } + else + { + this.MetaData.IccProfile.Extend(profile); + } + } + else + { + // not an ICC profile we can handle read the remaining so we can carry on and ignore this. + this.InputProcessor.Skip(remaining); + } + } + + /// + /// Processes the application header containing the JFIF identifier plus extra data. + /// + /// The remaining bytes in the segment block. + private void ProcessApplicationHeader(int remaining) + { + if (remaining < 5) + { + this.InputProcessor.Skip(remaining); + return; + } + + this.InputProcessor.ReadFull(this.Temp, 0, 13); + 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'; + + if (this.isJfif) + { + this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[8] << 8)); + this.verticalResolution = (short)(this.Temp[11] + (this.Temp[10] << 8)); + } + + if (remaining > 0) + { + this.InputProcessor.Skip(remaining); + } + } + + /// + /// Processes a Define Huffman Table marker, and initializes a huffman + /// struct from its contents. Specified in section B.2.4.2. + /// + /// The remaining bytes in the segment block. + private void ProcessDefineHuffmanTablesMarker(int remaining) + { + while (remaining > 0) + { + if (remaining < 17) + { + throw new ImageFormatException("DHT has wrong length"); + } + + this.InputProcessor.ReadFull(this.Temp, 0, 17); + + int tc = this.Temp[0] >> 4; + if (tc > OrigHuffmanTree.MaxTc) + { + throw new ImageFormatException("Bad Tc value"); + } + + int th = this.Temp[0] & 0x0f; + if (th > OrigHuffmanTree.MaxTh || (!this.IsProgressive && (th > 1))) + { + throw new ImageFormatException("Bad Th value"); + } + + int huffTreeIndex = (tc * OrigHuffmanTree.ThRowSize) + th; + this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop( + ref this.InputProcessor, + this.Temp, + ref remaining); + } + } + + /// + /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in + /// macroblocks + /// + /// The remaining bytes in the segment block. + private void ProcessDefineRestartIntervalMarker(int remaining) + { + if (remaining != 2) + { + throw new ImageFormatException("DRI has wrong length"); + } + + this.InputProcessor.ReadFull(this.Temp, 0, remaining); + this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1]; + } + + /// + /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. + /// + /// The remaining bytes in the segment block. + /// + /// Thrown if the tables do not match the header + /// + private void ProcessDqt(int remaining) + { + while (remaining > 0) + { + bool done = false; + + remaining--; + byte x = this.InputProcessor.ReadByte(); + int tq = x & 0x0F; + if (tq > MaxTq) + { + throw new ImageFormatException("Bad Tq value"); + } + + switch (x >> 4) + { + case 0: + if (remaining < Block8x8F.Size) + { + done = true; + break; + } + + remaining -= Block8x8F.Size; + this.InputProcessor.ReadFull(this.Temp, 0, Block8x8F.Size); + + for (int i = 0; i < Block8x8F.Size; i++) + { + this.QuantizationTables[tq][i] = this.Temp[i]; + } + + break; + case 1: + if (remaining < 2 * Block8x8F.Size) + { + done = true; + break; + } + + remaining -= 2 * Block8x8F.Size; + this.InputProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.Size); + + for (int i = 0; i < Block8x8F.Size; i++) + { + this.QuantizationTables[tq][i] = (this.Temp[2 * i] << 8) | this.Temp[(2 * i) + 1]; + } + + break; + default: + throw new ImageFormatException("Bad Pq value"); + } + + if (done) + { + break; + } + } + + if (remaining != 0) + { + throw new ImageFormatException("DQT has wrong length"); + } + } + + /// + /// Processes the Start of Frame marker. Specified in section B.2.2. + /// + /// The remaining bytes in the segment block. + private void ProcessStartOfFrameMarker(int remaining) + { + if (this.ComponentCount != 0) + { + throw new ImageFormatException("Multiple SOF markers"); + } + + switch (remaining) + { + case 6 + (3 * 1): // Grayscale image. + this.ComponentCount = 1; + break; + case 6 + (3 * 3): // YCbCr or RGB image. + this.ComponentCount = 3; + break; + case 6 + (3 * 4): // YCbCrK or CMYK image. + this.ComponentCount = 4; + break; + default: + throw new ImageFormatException("Incorrect number of components"); + } + + this.InputProcessor.ReadFull(this.Temp, 0, remaining); + + // We only support 8-bit precision. + if (this.Temp[0] != 8) + { + throw new ImageFormatException("Only 8-Bit precision supported."); + } + + int height = (this.Temp[1] << 8) + this.Temp[2]; + int width = (this.Temp[3] << 8) + this.Temp[4]; + + this.ImageSizeInPixels = new Size(width, height); + + if (this.Temp[5] != this.ComponentCount) + { + throw new ImageFormatException("SOF has wrong length"); + } + + this.Components = new OrigComponent[this.ComponentCount]; + + for (int i = 0; i < this.ComponentCount; i++) + { + byte componentIdentifier = this.Temp[6 + (3 * i)]; + var component = new OrigComponent(componentIdentifier, i); + component.InitializeCoreData(this); + this.Components[i] = component; + } + + int h0 = this.Components[0].HorizontalSamplingFactor; + int v0 = this.Components[0].VerticalSamplingFactor; + + this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0); + + foreach (OrigComponent component in this.Components) + { + component.InitializeDerivedData(this); + } + + this.ColorSpace = this.DeduceJpegColorSpace(); + } + + private JpegColorSpace DeduceJpegColorSpace() + { + switch (this.ComponentCount) + { + case 1: return JpegColorSpace.GrayScale; + case 3: return this.IsRGB() ? JpegColorSpace.RGB : JpegColorSpace.YCbCr; + case 4: + + if (!this.adobeTransformValid) + { + throw new ImageFormatException( + "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); + } + + // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html + // TODO: YCbCrA? + if (this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformYcck) + { + return JpegColorSpace.Ycck; + } + else if (this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformUnknown) + { + // Assume CMYK + return JpegColorSpace.Cmyk; + } + + goto default; + + default: + throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); + } + } + + private Image PostProcessIntoImage() + where TPixel : struct, IPixel + { + using (var postProcessor = new JpegImagePostProcessor(this)) + { + var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); + postProcessor.PostProcess(image); + return image; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpan.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpan.cs deleted file mode 100644 index 0b8248f55..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpan.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils -{ - /// - /// 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 - /// - /// The type of the data in the span - internal struct MutableSpan - { - /// - /// Data - /// - public T[] Data; - - /// - /// Offset - /// - public int Offset; - - /// - /// Initializes a new instance of the struct. - /// - /// The size of the span - /// The offset (defaults to 0) - public MutableSpan(int size, int offset = 0) - { - this.Data = new T[size]; - this.Offset = offset; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The data - /// The offset (defaults to 0) - public MutableSpan(T[] data, int offset = 0) - { - this.Data = data; - this.Offset = offset; - } - - /// - /// Gets the total count of data - /// - public int TotalCount => this.Data.Length - this.Offset; - - /// - /// Index into the data - /// - /// The data - /// The value at the specified index - 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; - } - } - - public static implicit operator MutableSpan(T[] data) => new MutableSpan(data, 0); - - /// - /// Slice the data - /// - /// The offset - /// The new - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public MutableSpan Slice(int offset) - { - return new MutableSpan(this.Data, this.Offset + offset); - } - - /// - /// Add to the offset - /// - /// The additional offset - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddOffset(int offset) - { - this.Offset += offset; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpanExtensions.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpanExtensions.cs deleted file mode 100644 index 5119c8847..000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/MutableSpanExtensions.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils -{ - /// - /// MutableSpan Extensions - /// - internal static class MutableSpanExtensions - { - /// - /// Slice - /// - /// The type of the data in the span - /// The data array - /// The offset - /// The new - public static MutableSpan Slice(this T[] array, int offset) => new MutableSpan(array, offset); - - /// - /// Save to a Vector4 - /// - /// The data - /// The vector to save to - [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]; - } - - /// - /// Save to a Vector4 - /// - /// The data - /// The vector to save to - [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]; - } - - /// - /// Load from Vector4 - /// - /// The data - /// The vector to load from - [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; - } - - /// - /// Load from Vector4 - /// - /// The data - /// The vector to load from - [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; - } - - /// - /// Converts all int values of src to float - /// - /// Source - /// A new with float values - public static MutableSpan ConvertToFloat32MutableSpan(this MutableSpan src) - { - MutableSpan result = new MutableSpan(src.TotalCount); - for (int i = 0; i < src.TotalCount; i++) - { - result[i] = (float)src[i]; - } - - return result; - } - - /// - /// Converts all float values of src to int - /// - /// Source - /// A new with float values - public static MutableSpan ConvertToInt32MutableSpan(this MutableSpan src) - { - MutableSpan result = new MutableSpan(src.TotalCount); - for (int i = 0; i < src.TotalCount; i++) - { - result[i] = (int)src[i]; - } - - return result; - } - - /// - /// Add a scalar to all values of src - /// - /// The source - /// The scalar value to add - /// A new instance of - public static MutableSpan AddScalarToAllValues(this MutableSpan src, float scalar) - { - MutableSpan result = new MutableSpan(src.TotalCount); - for (int i = 0; i < src.TotalCount; i++) - { - result[i] = src[i] + scalar; - } - - return result; - } - - /// - /// Add a scalar to all values of src - /// - /// The source - /// The scalar value to add - /// A new instance of - public static MutableSpan AddScalarToAllValues(this MutableSpan src, int scalar) - { - MutableSpan result = new MutableSpan(src.TotalCount); - for (int i = 0; i < src.TotalCount; i++) - { - result[i] = src[i] + scalar; - } - - return result; - } - - /// - /// Copy all values in src to a new instance - /// - /// Element type - /// The source - /// A new instance of - public static MutableSpan Copy(this MutableSpan src) - { - MutableSpan result = new MutableSpan(src.TotalCount); - for (int i = 0; i < src.TotalCount; i++) - { - result[i] = src[i]; - } - - return result; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OldJpegUtils.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs similarity index 98% rename from src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OldJpegUtils.cs rename to src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs index a6e976258..01ed5063b 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OldJpegUtils.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils /// /// Jpeg specific utilities and extension methods /// - internal static unsafe class OldJpegUtils + internal static class OrigJpegUtils { /// /// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of the image. diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 38f1d7dbc..68f525305 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.IO; + +using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; @@ -23,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { Guard.NotNull(stream, nameof(stream)); - using (var decoder = new JpegDecoderCore(configuration, this)) + using (var decoder = new OrigJpegDecoderCore(configuration, this)) { return decoder.Decode(stream); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index 8dd59ac20..4f368dcde 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -18,9 +18,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public string DefaultMimeType => "image/jpeg"; /// - public IEnumerable MimeTypes => OldJpegConstants.MimeTypes; + public IEnumerable MimeTypes => OrigJpegConstants.MimeTypes; /// - public IEnumerable FileExtensions => OldJpegConstants.FileExtensions; + public IEnumerable FileExtensions => OrigJpegConstants.FileExtensions; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Adobe.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsAdobe.cs similarity index 91% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Adobe.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsAdobe.cs index fdc6ed2ca..9fba4ae9b 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Adobe.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsAdobe.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Provides information about the Adobe marker segment /// - internal struct Adobe : IEquatable + internal struct PdfJsAdobe : IEquatable { /// /// The DCT Encode Version @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components public byte ColorTransform; /// - public bool Equals(Adobe other) + public bool Equals(PdfJsAdobe other) { return this.DCTEncodeVersion == other.DCTEncodeVersion && this.APP14Flags0 == other.APP14Flags0 @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components return false; } - return obj is Adobe && this.Equals((Adobe)obj); + return obj is PdfJsAdobe && this.Equals((PdfJsAdobe)obj); } /// diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs similarity index 95% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Component.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs index 97c140120..3c35e311f 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Represents a component block /// - internal class Component : IDisposable + internal class PdfJsComponent : IDisposable { #pragma warning disable SA1401 /// diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ComponentBlocks.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponentBlocks.cs similarity index 85% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ComponentBlocks.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponentBlocks.cs index ffcd32109..86a0c6b31 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ComponentBlocks.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponentBlocks.cs @@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Contains all the decoded component blocks /// - internal sealed class ComponentBlocks : IDisposable + internal sealed class PdfJsComponentBlocks : IDisposable { /// /// Gets or sets the component blocks /// - public Component[] Components { get; set; } + public PdfJsComponent[] Components { get; set; } /// public void Dispose() diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FileMarker.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs similarity index 81% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FileMarker.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs index d0182b63a..d6ff1e9ed 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FileMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs @@ -6,14 +6,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Represents a jpeg file marker /// - internal struct FileMarker + internal struct PdfJsFileMarker { /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The marker /// The position within the stream - public FileMarker(ushort marker, long position) + public PdfJsFileMarker(ushort marker, long position) { this.Marker = marker; this.Position = position; @@ -21,12 +21,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The marker /// The position within the stream /// Whether the current marker is invalid - public FileMarker(ushort marker, long position, bool invalid) + public PdfJsFileMarker(ushort marker, long position, bool invalid) { this.Marker = marker; this.Position = position; diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Frame.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrame.cs similarity index 94% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Frame.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrame.cs index 2398b8c01..8ce981a09 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/Frame.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrame.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Represent a single jpeg frame /// - internal sealed class Frame : IDisposable + internal sealed class PdfJsFrame : IDisposable { /// /// Gets or sets a value indicating whether the frame uses the extended specification @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Gets or sets the frame component collection /// - public FrameComponent[] Components { get; set; } + public PdfJsFrameComponent[] Components { get; set; } /// /// Gets or sets the maximum horizontal sampling factor @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components for (int i = 0; i < this.ComponentCount; i++) { - FrameComponent component = this.Components[i]; + PdfJsFrameComponent component = this.Components[i]; component.Init(); } } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs similarity index 60% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index 8383f5454..f60097dc9 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -3,24 +3,28 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; using SixLabors.ImageSharp.Memory; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { /// /// Represents a single frame component /// - internal class FrameComponent : IDisposable + internal class PdfJsFrameComponent : IDisposable, IJpegComponent { #pragma warning disable SA1401 // Fields should be private - public FrameComponent(Frame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationIdentifier) + public PdfJsFrameComponent(PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) { this.Frame = frame; this.Id = id; - this.HorizontalFactor = horizontalFactor; - this.VerticalFactor = verticalFactor; - this.QuantizationIdentifier = quantizationIdentifier; + this.HorizontalSamplingFactor = horizontalFactor; + this.VerticalSamplingFactor = verticalFactor; + this.QuantizationTableIndex = quantizationTableIndex; + this.Index = index; } /// @@ -36,32 +40,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Gets the horizontal sampling factor. /// - public int HorizontalFactor { get; } + public int HorizontalSamplingFactor { get; } /// /// Gets the vertical sampling factor. /// - public int VerticalFactor { get; } + public int VerticalSamplingFactor { get; } - /// - /// Gets the identifier - /// - public byte QuantizationIdentifier { get; } + Buffer2D IJpegComponent.SpectralBlocks => throw new NotImplementedException(); + + // TODO: Should be derived from PdfJsComponent.Scale + public Size SubSamplingDivisors => throw new NotImplementedException(); + + /// + public int QuantizationTableIndex { get; } /// /// Gets the block data /// public Buffer BlockData { get; private set; } + /// + public int Index { get; } + + public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks); + + public Size SamplingFactors => new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); + /// /// Gets the number of blocks per line /// - public int BlocksPerLine { get; private set; } + public int WidthInBlocks { get; private set; } /// /// Gets the number of blocks per column /// - public int BlocksPerColumn { get; private set; } + public int HeightInBlocks { get; private set; } /// /// Gets or sets the index for the DC Huffman table @@ -77,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components internal int BlocksPerColumnForMcu { get; private set; } - public Frame Frame { get; } + public PdfJsFrame Frame { get; } /// public void Dispose() @@ -88,14 +102,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components public void Init() { - this.BlocksPerLine = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalFactor / this.Frame.MaxHorizontalFactor); + this.WidthInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor); - this.BlocksPerColumn = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalFactor / this.Frame.MaxVerticalFactor); + this.HeightInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor); - this.BlocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalFactor; - this.BlocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalFactor; + this.BlocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; + this.BlocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; int blocksBufferSize = 64 * this.BlocksPerColumnForMcu * (this.BlocksPerLineForMcu + 1); @@ -106,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetBlockBufferOffset(int row, int col) { - return 64 * (((this.BlocksPerLine + 1) * row) + col); + return 64 * (((this.WidthInBlocks + 1) * row) + col); } public Span GetBlockBuffer(int row, int col) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/HuffmanTable.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 1c8a8fc42..9dc831567 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Represents a Huffman Table /// - internal struct HuffmanTable : IDisposable + internal struct PdfJsHuffmanTable : IDisposable { private Buffer lookahead; private Buffer valOffset; @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components private Buffer huffval; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The code lengths /// The huffman values - public HuffmanTable(byte[] lengths, byte[] values) + public PdfJsHuffmanTable(byte[] lengths, byte[] values) { this.lookahead = Buffer.CreateClean(256); this.valOffset = Buffer.CreateClean(18); diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs similarity index 83% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/HuffmanTables.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs index 46487c025..5d59809cc 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs @@ -10,16 +10,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Defines a pair of huffman tables /// - internal sealed class HuffmanTables : IDisposable + internal sealed class PdfJsHuffmanTables : IDisposable { - private readonly HuffmanTable[] tables = new HuffmanTable[4]; + private readonly PdfJsHuffmanTable[] tables = new PdfJsHuffmanTable[4]; /// /// Gets or sets the table at the given index. /// /// The index /// The - public ref HuffmanTable this[int index] + public ref PdfJsHuffmanTable this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs similarity index 98% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/IDCT.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs index 6ea257492..49bdc2423 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/IDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Performs the inverse Descrete Cosine Transform on each frame component. /// - internal static class IDCT + internal static class PdfJsIDCT { /// /// Precomputed values scaled up by 14 bits @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components private static readonly byte[] Limit = new byte[5 * (MaxJSample + 1)]; - static IDCT() + static PdfJsIDCT() { // Main part of range limit table: limit[x] = x int i; @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// The block buffer offset /// The computational buffer for holding temp values /// The quantization table - public static void QuantizeAndInverse(FrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span quantizationTable) + public static void QuantizeAndInverse(PdfJsFrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span quantizationTable) { Span blockData = component.BlockData.Slice(blockBufferOffset); int v0, v1, v2, v3, v4, v5, v6, v7; @@ -307,7 +307,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// The block buffer offset /// The computational buffer for holding temp values /// The multiplier table - public static void QuantizeAndInverseFast(FrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span multiplierTable) + public static void QuantizeAndInverseFast(PdfJsFrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span multiplierTable) { Span blockData = component.BlockData.Slice(blockBufferOffset); int p0, p1, p2, p3, p4, p5, p6, p7; diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/JFif.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJFif.cs similarity index 92% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/JFif.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJFif.cs index 57f023c2b..52ba81bbc 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/JFif.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJFif.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// Provides information about the JFIF marker segment /// TODO: Thumbnail? /// - internal struct JFif : IEquatable + internal struct PdfJsJFif : IEquatable { /// /// The major version @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components public short YDensity; /// - public bool Equals(JFif other) + public bool Equals(PdfJsJFif other) { return this.MajorVersion == other.MajorVersion && this.MinorVersion == other.MinorVersion @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components return false; } - return obj is JFif && this.Equals((JFif)obj); + return obj is PdfJsJFif && this.Equals((PdfJsJFif)obj); } /// diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs similarity index 92% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/JpegPixelArea.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs index f04b7dadd..034986c2c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Represents a section of the jpeg component data laid out in pixel order. /// - internal struct JpegPixelArea : IDisposable + internal struct PdfJsJpegPixelArea : IDisposable { private readonly int imageWidth; @@ -23,12 +23,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components private int rowStride; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The image width /// The image height /// The number of components - public JpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) + public PdfJsJpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) { this.imageWidth = imageWidth; this.imageHeight = imageHeight; @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// The jpeg component blocks /// The pixel area width /// The pixel area height - public void LinearizeBlockData(ComponentBlocks components, int width, int height) + public void LinearizeBlockData(PdfJsComponentBlocks components, int width, int height) { this.Width = width; this.Height = height; @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components Span xScaleBlockOffsetSpan = xScaleBlockOffset; for (int i = 0; i < numberOfComponents; i++) { - ref Component component = ref components.Components[i]; + ref PdfJsComponent component = ref components.Components[i]; Vector2 componentScale = component.Scale * scale; int offset = i; Span output = component.Output; diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/QuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs similarity index 96% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/QuantizationTables.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs index 35dd0111a..1000ce82c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/QuantizationTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Contains the quantization tables. /// - internal sealed class QuantizationTables : IDisposable + internal sealed class PdfJsQuantizationTables : IDisposable { /// /// Gets the ZigZag scan table diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs similarity index 79% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs index d8152c7b9..e2e5d985e 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Provides the means to decode a spectral scan /// - internal struct ScanDecoder + internal struct PdfJsScanDecoder { private byte[] markerBuffer; @@ -59,11 +59,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// The successive approximation bit high end /// The successive approximation bit low end public void DecodeScan( - Frame frame, + PdfJsFrame frame, Stream stream, - HuffmanTables dcHuffmanTables, - HuffmanTables acHuffmanTables, - FrameComponent[] components, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + PdfJsFrameComponent[] components, int componentIndex, int componentsLength, ushort resetInterval, @@ -87,21 +87,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components int mcuExpected; if (componentsLength == 1) { - mcuExpected = components[this.compIndex].BlocksPerLine * components[this.compIndex].BlocksPerColumn; + mcuExpected = components[this.compIndex].WidthInBlocks * components[this.compIndex].HeightInBlocks; } else { mcuExpected = mcusPerLine * frame.McusPerColumn; } - FileMarker fileMarker; + PdfJsFileMarker fileMarker; while (mcu < mcuExpected) { // Reset interval stuff int mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; for (int i = 0; i < components.Length; i++) { - FrameComponent c = components[i]; + PdfJsFrameComponent c = components[i]; c.Pred = 0; } @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components this.bitsCount = 0; this.accumulator = 0; this.bitsUnRead = 0; - fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); + fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past // those to attempt to find a valid marker (fixes issue4090.pdf) in original code. @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components ushort marker = fileMarker.Marker; // RSTn - We've alread read the bytes and altered the position so no need to skip - if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) + if (marker >= PdfJsJpegConstants.Markers.RST0 && marker <= PdfJsJpegConstants.Markers.RST7) { continue; } @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } } - fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); + fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); // Some images include more Scan blocks than expected, skip past those and // attempt to find the next valid marker (fixes issue8182.pdf) in original code. @@ -189,9 +189,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeScanBaseline( - HuffmanTables dcHuffmanTables, - HuffmanTables acHuffmanTables, - FrameComponent[] components, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + PdfJsFrameComponent[] components, int componentsLength, int mcusPerLine, int mcuToRead, @@ -200,9 +200,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { if (componentsLength == 1) { - FrameComponent component = components[this.compIndex]; - ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + PdfJsFrameComponent component = components[this.compIndex]; + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; for (int n = 0; n < mcuToRead; n++) { @@ -221,11 +221,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { for (int i = 0; i < componentsLength; i++) { - FrameComponent component = components[i]; - ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - int h = component.HorizontalFactor; - int v = component.VerticalFactor; + PdfJsFrameComponent component = components[i]; + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; for (int j = 0; j < v; j++) { @@ -248,8 +248,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeScanDCFirst( - HuffmanTables dcHuffmanTables, - FrameComponent[] components, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsFrameComponent[] components, int componentsLength, int mcusPerLine, int mcuToRead, @@ -258,8 +258,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { if (componentsLength == 1) { - FrameComponent component = components[this.compIndex]; - ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + PdfJsFrameComponent component = components[this.compIndex]; + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; for (int n = 0; n < mcuToRead; n++) { @@ -278,10 +278,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { for (int i = 0; i < componentsLength; i++) { - FrameComponent component = components[i]; - ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - int h = component.HorizontalFactor; - int v = component.VerticalFactor; + PdfJsFrameComponent component = components[i]; + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; for (int j = 0; j < v; j++) { @@ -304,7 +304,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeScanDCSuccessive( - FrameComponent[] components, + PdfJsFrameComponent[] components, int componentsLength, int mcusPerLine, int mcuToRead, @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { if (componentsLength == 1) { - FrameComponent component = components[this.compIndex]; + PdfJsFrameComponent component = components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -331,9 +331,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { for (int i = 0; i < componentsLength; i++) { - FrameComponent component = components[i]; - int h = component.HorizontalFactor; - int v = component.VerticalFactor; + PdfJsFrameComponent component = components[i]; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; for (int j = 0; j < v; j++) { for (int k = 0; k < h; k++) @@ -355,8 +355,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeScanACFirst( - HuffmanTables acHuffmanTables, - FrameComponent[] components, + PdfJsHuffmanTables acHuffmanTables, + PdfJsFrameComponent[] components, int componentsLength, int mcusPerLine, int mcuToRead, @@ -365,8 +365,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { if (componentsLength == 1) { - FrameComponent component = components[this.compIndex]; - ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + PdfJsFrameComponent component = components[this.compIndex]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; for (int n = 0; n < mcuToRead; n++) { @@ -385,10 +385,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { for (int i = 0; i < componentsLength; i++) { - FrameComponent component = components[i]; - ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - int h = component.HorizontalFactor; - int v = component.VerticalFactor; + PdfJsFrameComponent component = components[i]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; for (int j = 0; j < v; j++) { @@ -411,8 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeScanACSuccessive( - HuffmanTables acHuffmanTables, - FrameComponent[] components, + PdfJsHuffmanTables acHuffmanTables, + PdfJsFrameComponent[] components, int componentsLength, int mcusPerLine, int mcuToRead, @@ -421,8 +421,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { if (componentsLength == 1) { - FrameComponent component = components[this.compIndex]; - ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + PdfJsFrameComponent component = components[this.compIndex]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; for (int n = 0; n < mcuToRead; n++) { @@ -441,10 +441,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { for (int i = 0; i < componentsLength; i++) { - FrameComponent component = components[i]; - ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - int h = component.HorizontalFactor; - int v = component.VerticalFactor; + PdfJsFrameComponent component = components[i]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; for (int j = 0; j < v; j++) { @@ -466,101 +466,101 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, FrameComponent component, int mcu, Stream stream) + private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBaseline(component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * component.VerticalFactor) + row; - int blockCol = (mcuCol * component.HorizontalFactor) + col; + int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; + int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBaseline(component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockDCFirst(ref HuffmanTable dcHuffmanTable, FrameComponent component, int mcu, Stream stream) + private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCFirst(component, offset, ref dcHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuDCFirst(ref HuffmanTable dcHuffmanTable, FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * component.VerticalFactor) + row; - int blockCol = (mcuCol * component.HorizontalFactor) + col; + int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; + int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCFirst(component, offset, ref dcHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockDCSuccessive(FrameComponent component, int mcu, Stream stream) + private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCSuccessive(component, offset, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuDCSuccessive(FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * component.VerticalFactor) + row; - int blockCol = (mcuCol * component.HorizontalFactor) + col; + int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; + int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeDCSuccessive(component, offset, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockACFirst(ref HuffmanTable acHuffmanTable, FrameComponent component, int mcu, Stream stream) + private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACFirst(component, offset, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuACFirst(ref HuffmanTable acHuffmanTable, FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * component.VerticalFactor) + row; - int blockCol = (mcuCol * component.HorizontalFactor) + col; + int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; + int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACFirst(component, offset, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockACSuccessive(ref HuffmanTable acHuffmanTable, FrameComponent component, int mcu, Stream stream) + private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, int mcu, Stream stream) { - int blockRow = mcu / component.BlocksPerLine; - int blockCol = mcu % component.BlocksPerLine; + int blockRow = mcu / component.WidthInBlocks; + int blockCol = mcu % component.WidthInBlocks; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACSuccessive(component, offset, ref acHuffmanTable, stream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuACSuccessive(ref HuffmanTable acHuffmanTable, FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) + private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) { int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * component.VerticalFactor) + row; - int blockCol = (mcuCol * component.HorizontalFactor) + col; + int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; + int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeACSuccessive(component, offset, ref acHuffmanTable, stream); } @@ -583,7 +583,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components this.endOfStreamReached = true; } - if (this.bitsData == JpegConstants.Markers.Prefix) + if (this.bitsData == PdfJsJpegConstants.Markers.Prefix) { int nextByte = stream.ReadByte(); if (nextByte != 0) @@ -606,7 +606,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private short DecodeHuffman(ref HuffmanTable tree, Stream stream) + private short DecodeHuffman(ref PdfJsHuffmanTable tree, Stream stream) { short code = -1; @@ -705,7 +705,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBaseline(FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, Stream stream) + private void DecodeBaseline(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, Stream stream) { int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -746,7 +746,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components break; } - byte z = QuantizationTables.DctZigZag[k]; + byte z = PdfJsQuantizationTables.DctZigZag[k]; short re = (short)this.ReceiveAndExtend(s, stream); component.BlockData[offset + z] = re; k++; @@ -754,7 +754,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCFirst(FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, Stream stream) + private void DecodeDCFirst(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable dcHuffmanTable, Stream stream) { int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -767,7 +767,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCSuccessive(FrameComponent component, int offset, Stream stream) + private void DecodeDCSuccessive(PdfJsFrameComponent component, int offset, Stream stream) { int bit = this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) @@ -779,7 +779,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACFirst(FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream) + private void DecodeACFirst(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream) { if (this.eobrun > 0) { @@ -814,14 +814,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } k += r; - byte z = QuantizationTables.DctZigZag[k]; + byte z = PdfJsQuantizationTables.DctZigZag[k]; componentBlockDataSpan[offset + z] = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState)); k++; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeACSuccessive(FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream) + private void DecodeACSuccessive(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable acHuffmanTable, Stream stream) { int k = this.specStart; int e = this.specEnd; @@ -829,7 +829,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components Span componentBlockDataSpan = component.BlockData.Span; while (k <= e) { - byte z = QuantizationTables.DctZigZag[k]; + byte z = PdfJsQuantizationTables.DctZigZag[k]; switch (this.successiveACState) { case 0: // Initial state diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsYCbCrToRgbTables.cs similarity index 99% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/YCbCrToRgbTables.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsYCbCrToRgbTables.cs index e4f99275b..ddc577270 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/YCbCrToRgbTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsYCbCrToRgbTables.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace. /// Methods to build the tables are based on libjpeg implementation. /// - internal struct YCbCrToRgbTables + internal struct PdfJsYCbCrToRgbTables { /// /// The red red-chrominance table diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs similarity index 99% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegConstants.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs index c23cb9e9b..08b42891d 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegConstants.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// /// Contains jpeg constant values /// - internal static class JpegConstants + internal static class PdfJsJpegConstants { /// /// Contains marker specific constants diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoder.cs new file mode 100644 index 000000000..37ce0151f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoder.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort +{ + /// + /// Image decoder for generating an image out of a jpg stream. + /// + internal sealed class PdfJsJpegDecoder : IImageDecoder, IJpegDecoderOptions + { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + using (var decoder = new PdfJsJpegDecoderCore(configuration, this)) + { + return decoder.Decode(stream); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs similarity index 78% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index f6976b1b3..f5159043a 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// Performs the jpeg decoding operation. /// Ported from with additional fixes to handle common encoding errors /// - internal sealed class JpegDecoderCore : IDisposable + internal sealed class PdfJsJpegDecoderCore : IDisposable { #pragma warning disable SA1401 // Fields should be private /// @@ -33,15 +33,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort private readonly byte[] markerBuffer = new byte[2]; - private QuantizationTables quantizationTables; + private PdfJsQuantizationTables quantizationTables; - private HuffmanTables dcHuffmanTables; + private PdfJsHuffmanTables dcHuffmanTables; - private HuffmanTables acHuffmanTables; + private PdfJsHuffmanTables acHuffmanTables; - private ComponentBlocks components; + private PdfJsComponentBlocks components; - private JpegPixelArea pixelArea; + private PdfJsJpegPixelArea pixelArea; private ushort resetInterval; @@ -53,27 +53,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// /// Contains information about the JFIF marker /// - private JFif jFif; + private PdfJsJFif jFif; /// /// Contains information about the Adobe marker /// - private Adobe adobe; + private PdfJsAdobe adobe; /// - /// Initializes static members of the class. + /// Initializes static members of the class. /// - static JpegDecoderCore() + static PdfJsJpegDecoderCore() { - YCbCrToRgbTables.Create(); + PdfJsYCbCrToRgbTables.Create(); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration. /// The options. - public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) + public PdfJsJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) { this.configuration = configuration ?? Configuration.Default; this.IgnoreMetadata = options.IgnoreMetadata; @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// /// Gets the frame /// - public Frame Frame { get; private set; } + public PdfJsFrame Frame { get; private set; } /// /// Gets the image width @@ -114,35 +114,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// /// The buffer to read file markers to /// The input stream - /// The - public static FileMarker FindNextFileMarker(byte[] marker, Stream stream) + /// The + public static PdfJsFileMarker FindNextFileMarker(byte[] marker, Stream stream) { int value = stream.Read(marker, 0, 2); if (value == 0) { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); + return new PdfJsFileMarker(PdfJsJpegConstants.Markers.EOI, (int)stream.Length - 2); } - if (marker[0] == JpegConstants.Markers.Prefix) + if (marker[0] == PdfJsJpegConstants.Markers.Prefix) { // According to Section B.1.1.2: // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF." - while (marker[1] == JpegConstants.Markers.Prefix) + while (marker[1] == PdfJsJpegConstants.Markers.Prefix) { int suffix = stream.ReadByte(); if (suffix == -1) { - return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); + return new PdfJsFileMarker(PdfJsJpegConstants.Markers.EOI, (int)stream.Length - 2); } marker[1] = (byte)value; } - return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); + return new PdfJsFileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); } - return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2), true); + return new PdfJsFileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2), true); } /// @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetBlockBufferOffset(ref Component component, int row, int col) + private static int GetBlockBufferOffset(ref PdfJsComponent component, int row, int col) { return 64 * (((component.BlocksPerLine + 1) * row) + col); } @@ -206,79 +206,79 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { // TODO: metadata only logic // Check for the Start Of Image marker. - var fileMarker = new FileMarker(this.ReadUint16(), 0); - if (fileMarker.Marker != JpegConstants.Markers.SOI) + var fileMarker = new PdfJsFileMarker(this.ReadUint16(), 0); + if (fileMarker.Marker != PdfJsJpegConstants.Markers.SOI) { throw new ImageFormatException("Missing SOI marker."); } ushort marker = this.ReadUint16(); - fileMarker = new FileMarker(marker, (int)this.InputStream.Position - 2); + fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2); - this.quantizationTables = new QuantizationTables(); - this.dcHuffmanTables = new HuffmanTables(); - this.acHuffmanTables = new HuffmanTables(); + this.quantizationTables = new PdfJsQuantizationTables(); + this.dcHuffmanTables = new PdfJsHuffmanTables(); + this.acHuffmanTables = new PdfJsHuffmanTables(); - while (fileMarker.Marker != JpegConstants.Markers.EOI) + while (fileMarker.Marker != PdfJsJpegConstants.Markers.EOI) { // Get the marker length int remaining = this.ReadUint16() - 2; switch (fileMarker.Marker) { - case JpegConstants.Markers.APP0: + case PdfJsJpegConstants.Markers.APP0: this.ProcessApplicationHeaderMarker(remaining); break; - case JpegConstants.Markers.APP1: + case PdfJsJpegConstants.Markers.APP1: this.ProcessApp1Marker(remaining, metaData); break; - case JpegConstants.Markers.APP2: + case PdfJsJpegConstants.Markers.APP2: this.ProcessApp2Marker(remaining, metaData); break; - case JpegConstants.Markers.APP3: - case JpegConstants.Markers.APP4: - case JpegConstants.Markers.APP5: - case JpegConstants.Markers.APP6: - case JpegConstants.Markers.APP7: - case JpegConstants.Markers.APP8: - case JpegConstants.Markers.APP9: - case JpegConstants.Markers.APP10: - case JpegConstants.Markers.APP11: - case JpegConstants.Markers.APP12: - case JpegConstants.Markers.APP13: + case PdfJsJpegConstants.Markers.APP3: + case PdfJsJpegConstants.Markers.APP4: + case PdfJsJpegConstants.Markers.APP5: + case PdfJsJpegConstants.Markers.APP6: + case PdfJsJpegConstants.Markers.APP7: + case PdfJsJpegConstants.Markers.APP8: + case PdfJsJpegConstants.Markers.APP9: + case PdfJsJpegConstants.Markers.APP10: + case PdfJsJpegConstants.Markers.APP11: + case PdfJsJpegConstants.Markers.APP12: + case PdfJsJpegConstants.Markers.APP13: this.InputStream.Skip(remaining); break; - case JpegConstants.Markers.APP14: + case PdfJsJpegConstants.Markers.APP14: this.ProcessApp14Marker(remaining); break; - case JpegConstants.Markers.APP15: - case JpegConstants.Markers.COM: + case PdfJsJpegConstants.Markers.APP15: + case PdfJsJpegConstants.Markers.COM: this.InputStream.Skip(remaining); break; - case JpegConstants.Markers.DQT: + case PdfJsJpegConstants.Markers.DQT: this.ProcessDefineQuantizationTablesMarker(remaining); break; - case JpegConstants.Markers.SOF0: - case JpegConstants.Markers.SOF1: - case JpegConstants.Markers.SOF2: + case PdfJsJpegConstants.Markers.SOF0: + case PdfJsJpegConstants.Markers.SOF1: + case PdfJsJpegConstants.Markers.SOF2: this.ProcessStartOfFrameMarker(remaining, fileMarker); break; - case JpegConstants.Markers.DHT: + case PdfJsJpegConstants.Markers.DHT: this.ProcessDefineHuffmanTablesMarker(remaining); break; - case JpegConstants.Markers.DRI: + case PdfJsJpegConstants.Markers.DRI: this.ProcessDefineRestartIntervalMarker(remaining); break; - case JpegConstants.Markers.SOS: + case PdfJsJpegConstants.Markers.SOS: this.ProcessStartOfScanMarker(); break; } @@ -289,18 +289,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.ImageWidth = this.Frame.SamplesPerLine; this.ImageHeight = this.Frame.Scanlines; - this.components = new ComponentBlocks { Components = new Component[this.Frame.ComponentCount] }; + this.components = new PdfJsComponentBlocks { Components = new PdfJsComponent[this.Frame.ComponentCount] }; for (int i = 0; i < this.components.Components.Length; i++) { - FrameComponent frameComponent = this.Frame.Components[i]; - var component = new Component + PdfJsFrameComponent frameComponent = this.Frame.Components[i]; + var component = new PdfJsComponent { Scale = new System.Numerics.Vector2( - frameComponent.HorizontalFactor / (float)this.Frame.MaxHorizontalFactor, - frameComponent.VerticalFactor / (float)this.Frame.MaxVerticalFactor), - BlocksPerLine = frameComponent.BlocksPerLine, - BlocksPerColumn = frameComponent.BlocksPerColumn + frameComponent.HorizontalSamplingFactor / (float)this.Frame.MaxHorizontalFactor, + frameComponent.VerticalSamplingFactor / (float)this.Frame.MaxVerticalFactor), + BlocksPerLine = frameComponent.WidthInBlocks, + BlocksPerColumn = frameComponent.HeightInBlocks }; // this.QuantizeAndInverseComponentData(ref component, frameComponent); @@ -314,8 +314,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { for (int i = 0; i < this.components.Components.Length; i++) { - FrameComponent frameComponent = this.Frame.Components[i]; - Component component = this.components.Components[i]; + PdfJsFrameComponent frameComponent = this.Frame.Components[i]; + PdfJsComponent component = this.components.Components[i]; this.QuantizeAndInverseComponentData(component, frameComponent); } @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.NumberOfComponents}"); } - this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.NumberOfComponents); + this.pixelArea = new PdfJsJpegPixelArea(image.Width, image.Height, this.NumberOfComponents); this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height); if (this.NumberOfComponents == 1) @@ -345,11 +345,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort if (this.NumberOfComponents == 3) { - if (this.adobe.Equals(default(Adobe)) || this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYCbCr) + if (this.adobe.Equals(default(PdfJsAdobe)) || this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYCbCr) { this.FillYCbCrImage(image); } - else if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformUnknown) + else if (this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformUnknown) { this.FillRgbImage(image); } @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort if (this.NumberOfComponents == 4) { - if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYcck) + if (this.adobe.ColorTransform == PdfJsJpegConstants.Markers.Adobe.ColorTransformYcck) { this.FillYcckImage(image); } @@ -412,15 +412,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.InputStream.Read(this.temp, 0, 13); remaining -= 13; - bool isJfif = this.temp[0] == JpegConstants.Markers.JFif.J && - this.temp[1] == JpegConstants.Markers.JFif.F && - this.temp[2] == JpegConstants.Markers.JFif.I && - this.temp[3] == JpegConstants.Markers.JFif.F && - this.temp[4] == JpegConstants.Markers.JFif.Null; + bool isJfif = this.temp[0] == PdfJsJpegConstants.Markers.JFif.J && + this.temp[1] == PdfJsJpegConstants.Markers.JFif.F && + this.temp[2] == PdfJsJpegConstants.Markers.JFif.I && + this.temp[3] == PdfJsJpegConstants.Markers.JFif.F && + this.temp[4] == PdfJsJpegConstants.Markers.JFif.Null; if (isJfif) { - this.jFif = new JFif + this.jFif = new PdfJsJFif { MajorVersion = this.temp[5], MinorVersion = this.temp[6], @@ -454,12 +454,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort byte[] profile = new byte[remaining]; this.InputStream.Read(profile, 0, remaining); - if (profile[0] == JpegConstants.Markers.Exif.E && - profile[1] == JpegConstants.Markers.Exif.X && - profile[2] == JpegConstants.Markers.Exif.I && - profile[3] == JpegConstants.Markers.Exif.F && - profile[4] == JpegConstants.Markers.Exif.Null && - profile[5] == JpegConstants.Markers.Exif.Null) + if (profile[0] == PdfJsJpegConstants.Markers.Exif.E && + profile[1] == PdfJsJpegConstants.Markers.Exif.X && + profile[2] == PdfJsJpegConstants.Markers.Exif.I && + profile[3] == PdfJsJpegConstants.Markers.Exif.F && + profile[4] == PdfJsJpegConstants.Markers.Exif.Null && + profile[5] == PdfJsJpegConstants.Markers.Exif.Null) { this.isExif = true; metadata.ExifProfile = new ExifProfile(profile); @@ -485,18 +485,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.InputStream.Read(identifier, 0, Icclength); remaining -= Icclength; // We have read it by this point - if (identifier[0] == JpegConstants.Markers.ICC.I && - identifier[1] == JpegConstants.Markers.ICC.C && - identifier[2] == JpegConstants.Markers.ICC.C && - identifier[3] == JpegConstants.Markers.ICC.UnderScore && - identifier[4] == JpegConstants.Markers.ICC.P && - identifier[5] == JpegConstants.Markers.ICC.R && - identifier[6] == JpegConstants.Markers.ICC.O && - identifier[7] == JpegConstants.Markers.ICC.F && - identifier[8] == JpegConstants.Markers.ICC.I && - identifier[9] == JpegConstants.Markers.ICC.L && - identifier[10] == JpegConstants.Markers.ICC.E && - identifier[11] == JpegConstants.Markers.ICC.Null) + if (identifier[0] == PdfJsJpegConstants.Markers.ICC.I && + identifier[1] == PdfJsJpegConstants.Markers.ICC.C && + identifier[2] == PdfJsJpegConstants.Markers.ICC.C && + identifier[3] == PdfJsJpegConstants.Markers.ICC.UnderScore && + identifier[4] == PdfJsJpegConstants.Markers.ICC.P && + identifier[5] == PdfJsJpegConstants.Markers.ICC.R && + identifier[6] == PdfJsJpegConstants.Markers.ICC.O && + identifier[7] == PdfJsJpegConstants.Markers.ICC.F && + identifier[8] == PdfJsJpegConstants.Markers.ICC.I && + identifier[9] == PdfJsJpegConstants.Markers.ICC.L && + identifier[10] == PdfJsJpegConstants.Markers.ICC.E && + identifier[11] == PdfJsJpegConstants.Markers.ICC.Null) { byte[] profile = new byte[remaining]; this.InputStream.Read(profile, 0, remaining); @@ -534,15 +534,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.InputStream.Read(this.temp, 0, 12); remaining -= 12; - bool isAdobe = this.temp[0] == JpegConstants.Markers.Adobe.A && - this.temp[1] == JpegConstants.Markers.Adobe.D && - this.temp[2] == JpegConstants.Markers.Adobe.O && - this.temp[3] == JpegConstants.Markers.Adobe.B && - this.temp[4] == JpegConstants.Markers.Adobe.E; + bool isAdobe = this.temp[0] == PdfJsJpegConstants.Markers.Adobe.A && + this.temp[1] == PdfJsJpegConstants.Markers.Adobe.D && + this.temp[2] == PdfJsJpegConstants.Markers.Adobe.O && + this.temp[3] == PdfJsJpegConstants.Markers.Adobe.B && + this.temp[4] == PdfJsJpegConstants.Markers.Adobe.E; if (isAdobe) { - this.adobe = new Adobe + this.adobe = new PdfJsAdobe { DCTEncodeVersion = (short)((this.temp[5] << 8) | this.temp[6]), APP14Flags0 = (short)((this.temp[7] << 8) | this.temp[8]), @@ -589,7 +589,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15); for (int j = 0; j < 64; j++) { - tableSpan[QuantizationTables.DctZigZag[j]] = this.temp[j]; + tableSpan[PdfJsQuantizationTables.DctZigZag[j]] = this.temp[j]; } } @@ -609,7 +609,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15); for (int j = 0; j < 64; j++) { - tableSpan[QuantizationTables.DctZigZag[j]] = (short)((this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]); + tableSpan[PdfJsQuantizationTables.DctZigZag[j]] = (short)((this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]); } } @@ -635,7 +635,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// /// The remaining bytes in the segment block. /// The current frame marker. - private void ProcessStartOfFrameMarker(int remaining, FileMarker frameMarker) + private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker) { if (this.Frame != null) { @@ -644,10 +644,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.InputStream.Read(this.temp, 0, remaining); - this.Frame = new Frame + this.Frame = new PdfJsFrame { - Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, - Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, + Extended = frameMarker.Marker == PdfJsJpegConstants.Markers.SOF1, + Progressive = frameMarker.Marker == PdfJsJpegConstants.Markers.SOF2, Precision = this.temp[0], Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), @@ -660,7 +660,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort // No need to pool this. They max out at 4 this.Frame.ComponentIds = new byte[this.Frame.ComponentCount]; - this.Frame.Components = new FrameComponent[this.Frame.ComponentCount]; + this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount]; for (int i = 0; i < this.Frame.Components.Length; i++) { @@ -677,7 +677,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort maxV = v; } - var component = new FrameComponent(this.Frame, this.temp[index], h, v, this.temp[index + 2]); + var component = new PdfJsFrameComponent(this.Frame, this.temp[index], h, v, this.temp[index + 2], i); this.Frame.Components[i] = component; this.Frame.ComponentIds[i] = component.Id; @@ -776,7 +776,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort throw new ImageFormatException("Unknown component selector"); } - ref FrameComponent component = ref this.Frame.Components[componentIndex]; + ref PdfJsFrameComponent component = ref this.Frame.Components[componentIndex]; int tableSpec = this.InputStream.ReadByte(); component.DCHuffmanTableId = tableSpec >> 4; component.ACHuffmanTableId = tableSpec & 15; @@ -787,7 +787,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort int spectralStart = this.temp[0]; int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; - var scanDecoder = default(ScanDecoder); + var scanDecoder = default(PdfJsScanDecoder); scanDecoder.DecodeScan( this.Frame, @@ -809,14 +809,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// /// The component /// The frame component - private void QuantizeAndInverseComponentData(Component component, FrameComponent frameComponent) + private void QuantizeAndInverseComponentData(PdfJsComponent component, PdfJsFrameComponent frameComponent) { int blocksPerLine = component.BlocksPerLine; int blocksPerColumn = component.BlocksPerColumn; using (var computationBuffer = Buffer.CreateClean(64)) using (var multiplicationBuffer = Buffer.CreateClean(64)) { - Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationIdentifier); + Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex); Span computationBufferSpan = computationBuffer; // For AA&N IDCT method, multiplier are equal to quantization @@ -835,7 +835,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) { int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); - IDCT.QuantizeAndInverse(frameComponent, offset, ref computationBufferSpan, ref quantizationTable); + PdfJsIDCT.QuantizeAndInverse(frameComponent, offset, ref computationBufferSpan, ref quantizationTable); } } } @@ -850,9 +850,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// The table index /// The codelengths /// The values - private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, byte[] values) + private void BuildHuffmanTable(PdfJsHuffmanTables tables, int index, byte[] codeLengths, byte[] values) { - tables[index] = new HuffmanTable(codeLengths, values); + tables[index] = new PdfJsHuffmanTable(codeLengths, values); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -888,7 +888,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ref byte cb = ref areaRowSpan[o + 1]; ref byte cr = ref areaRowSpan[o + 2]; ref TPixel pixel = ref imageRowSpan[x]; - YCbCrToRgbTables.PackYCbCr(ref pixel, yy, cb, cr); + PdfJsYCbCrToRgbTables.PackYCbCr(ref pixel, yy, cb, cr); } } } @@ -909,7 +909,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ref byte k = ref areaRowSpan[o + 3]; ref TPixel pixel = ref imageRowSpan[x]; - YCbCrToRgbTables.PackYccK(ref pixel, yy, cb, cr, k); + PdfJsYCbCrToRgbTables.PackYccK(ref pixel, yy, cb, cr, k); } } } diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index d9c6801a1..401003eed 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Memory { @@ -39,5 +40,55 @@ namespace SixLabors.ImageSharp.Memory { return buffer.Span.Slice(y * buffer.Width, buffer.Width); } + + /// + /// Returns the size of the buffer. + /// + /// The element type + /// The + /// The of the buffer + public static Size Size(this IBuffer2D buffer) + where T : struct + { + return new Size(buffer.Width, buffer.Height); + } + + /// + /// Returns a representing the full area of the buffer. + /// + /// The element type + /// The + /// The + public static Rectangle FullRectangle(this IBuffer2D buffer) + where T : struct + { + return new Rectangle(0, 0, buffer.Width, buffer.Height); + } + + /// + /// Return a to the subarea represented by 'rectangle' + /// + /// The element type + /// The + /// The rectangel subarea + /// The + public static BufferArea GetArea(this IBuffer2D buffer, Rectangle rectangle) + where T : struct => new BufferArea(buffer, rectangle); + + public static BufferArea GetArea(this IBuffer2D buffer, int x, int y, int width, int height) + where T : struct + { + var rectangle = new Rectangle(x, y, width, height); + return new BufferArea(buffer, rectangle); + } + + /// + /// Return a to the whole area of 'buffer' + /// + /// The element type + /// The + /// The + public static BufferArea GetArea(this IBuffer2D buffer) + where T : struct => new BufferArea(buffer); } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/Buffer2D.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs similarity index 80% rename from src/ImageSharp/Memory/Buffer2D.cs rename to src/ImageSharp/Memory/Buffer2D{T}.cs index d86eb5b26..99b10cae7 100644 --- a/src/ImageSharp/Memory/Buffer2D.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Memory { @@ -13,6 +14,11 @@ namespace SixLabors.ImageSharp.Memory internal class Buffer2D : Buffer, IBuffer2D where T : struct { + public Buffer2D(Size size) + : this(size.Width, size.Height) + { + } + /// /// Initializes a new instance of the class. /// @@ -55,6 +61,9 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] get { + DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + return ref this.Array[(this.Width * y) + x]; } } @@ -71,5 +80,12 @@ namespace SixLabors.ImageSharp.Memory buffer.Clear(); return buffer; } + + /// + /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// + /// The size of the buffer + /// The instance + public static Buffer2D CreateClean(Size size) => CreateClean(size.Width, size.Height); } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs new file mode 100644 index 000000000..92e78e9c0 --- /dev/null +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.CompilerServices; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents a rectangular area inside a 2D memory buffer (). + /// This type is kind-of 2D Span, but it can live on heap. + /// + /// The element type + internal struct BufferArea + where T : struct + { + /// + /// The rectangle specifying the boundaries of the area in . + /// + public readonly Rectangle Rectangle; + + public BufferArea(IBuffer2D destinationBuffer, Rectangle rectangle) + { + Guard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); + Guard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); + Guard.MustBeLessThanOrEqualTo(rectangle.Width, destinationBuffer.Width, nameof(rectangle)); + Guard.MustBeLessThanOrEqualTo(rectangle.Height, destinationBuffer.Height, nameof(rectangle)); + + this.DestinationBuffer = destinationBuffer; + this.Rectangle = rectangle; + } + + public BufferArea(IBuffer2D destinationBuffer) + : this(destinationBuffer, destinationBuffer.FullRectangle()) + { + } + + /// + /// Gets the being pointed by this instance. + /// + public IBuffer2D DestinationBuffer { get; } + + /// + /// Gets the size of the area. + /// + public Size Size => this.Rectangle.Size; + + /// + /// Gets the pixel stride which is equal to the width of . + /// + public int Stride => this.DestinationBuffer.Width; + + /// + /// Gets or sets a value at the given index. + /// + /// The position inside a row + /// The row index + /// The reference to the value + public ref T this[int x, int y] => ref this.DestinationBuffer.Span[this.GetIndexOf(x, y)]; + + /// + /// Gets a reference to the [0,0] element. + /// + /// The reference to the [0,0] element + public ref T GetReferenceToOrigo() => + ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; + + /// + /// Gets a span to row 'y' inside this area. + /// + /// The row index + /// The span + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + int yy = this.GetRowIndex(y); + int xx = this.Rectangle.X; + int width = this.Rectangle.Width; + + return this.DestinationBuffer.Span.Slice(yy + xx, width); + } + + /// + /// Returns a sub-area as . (Similar to .) + /// + /// The x index at the subarea origo + /// The y index at the subarea origo + /// The desired width of the subarea + /// The desired height of the subarea + /// The subarea + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferArea GetSubArea(int x, int y, int width, int height) + { + var rectangle = new Rectangle(x, y, width, height); + return this.GetSubArea(rectangle); + } + + /// + /// Returns a sub-area as . (Similar to .) + /// + /// The specifying the boundaries of the subarea + /// The subarea + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferArea GetSubArea(Rectangle rectangle) + { + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); + + int x = this.Rectangle.X + rectangle.X; + int y = this.Rectangle.Y + rectangle.Y; + rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); + return new BufferArea(this.DestinationBuffer, rectangle); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetIndexOf(int x, int y) + { + int yy = this.GetRowIndex(y); + int xx = this.Rectangle.X + x; + return yy + xx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetRowIndex(int y) + { + return (y + this.Rectangle.Y) * this.DestinationBuffer.Width; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Buffer.cs b/src/ImageSharp/Memory/Buffer{T}.cs similarity index 97% rename from src/ImageSharp/Memory/Buffer.cs rename to src/ImageSharp/Memory/Buffer{T}.cs index bbe37b859..f5c9ed00a 100644 --- a/src/ImageSharp/Memory/Buffer.cs +++ b/src/ImageSharp/Memory/Buffer{T}.cs @@ -7,12 +7,13 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory { + /// /// /// Manages a buffer of value type objects as a Disposable resource. /// The backing array is either pooled or comes from the outside. /// /// The value type. - internal class Buffer : IDisposable + internal class Buffer : IBuffer where T : struct { /// @@ -205,7 +206,8 @@ namespace SixLabors.ImageSharp.Memory { if (this.IsDisposedOrLostArrayOwnership) { - throw new InvalidOperationException("TakeArrayOwnership() is invalid: either Buffer is disposed or TakeArrayOwnership() has been called multiple times!"); + throw new InvalidOperationException( + "TakeArrayOwnership() is invalid: either Buffer is disposed or TakeArrayOwnership() has been called multiple times!"); } this.IsDisposedOrLostArrayOwnership = true; diff --git a/src/ImageSharp/Memory/IBuffer2D.cs b/src/ImageSharp/Memory/IBuffer2D{T}.cs similarity index 100% rename from src/ImageSharp/Memory/IBuffer2D.cs rename to src/ImageSharp/Memory/IBuffer2D{T}.cs diff --git a/src/ImageSharp/Memory/IBuffer{T}.cs b/src/ImageSharp/Memory/IBuffer{T}.cs new file mode 100644 index 000000000..a0f80063f --- /dev/null +++ b/src/ImageSharp/Memory/IBuffer{T}.cs @@ -0,0 +1,18 @@ +using System; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// + /// Represents a contigous memory buffer of value-type items "promising" a + /// + /// The value type + internal interface IBuffer : IDisposable + where T : struct + { + /// + /// Gets the span to the memory "promised" by this buffer + /// + Span Span { get; } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/RoundSinglePrecisionBlocks.cs b/tests/ImageSharp.Benchmarks/General/RoundSinglePrecisionBlocks.cs index 284e20859..044e973a9 100644 --- a/tests/ImageSharp.Benchmarks/General/RoundSinglePrecisionBlocks.cs +++ b/tests/ImageSharp.Benchmarks/General/RoundSinglePrecisionBlocks.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General [GlobalSetup] public void Setup() { - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { this.inputDividend[i] = i*44.8f; this.inputDivisior[i] = 100 - i; @@ -44,18 +44,18 @@ namespace SixLabors.ImageSharp.Benchmarks.General float* pDividend = (float*)&b1; float* pDivisor = (float*)&b2; - int* result = stackalloc int[Block8x8F.ScalarCount]; + int* result = stackalloc int[Block8x8F.Size]; for (int cnt = 0; cnt < ExecutionCount; cnt++) { sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { int a = (int) pDividend[i]; int b = (int) pDivisor; result[i] = RationalRound(a, b); } - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += result[i]; } @@ -77,12 +77,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General for (int cnt = 0; cnt < ExecutionCount; cnt++) { sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { double value = pDividend[i] / pDivisor[i]; pDividend[i] = (float) System.Math.Round(value); } - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += (int) pDividend[i]; } @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General { sum = 0; DivideRoundAll(ref bDividend, ref bDivisor); - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += (int)pDividend[i]; } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs index 102861a45..ce2762eb1 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { Guard.NotNull(stream, "stream"); - using (var decoder = new OldJpegDecoderCore(configuration, this)) + using (var decoder = new OrigJpegDecoderCore(configuration, this)) { return decoder.Decode(stream); } diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 9659ad0f5..b186ff4df 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -21,8 +21,6 @@ - - diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index f4f6ccabd..532bf9574 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Sandbox46 using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests.Colors; + using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats; using SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.Processing.Transforms; diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs index 0b0a47483..5ffd9f5f1 100644 --- a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs +++ b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByBezierLine() { - string path = this.CreateOutputDirectory("Drawing", "BezierLine"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "BezierLine"); using (Image image = new Image(500, 500)) { image.Mutate(x => x.BackgroundColor(Rgba32.Blue) @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedBezierLineWithOpacity() { - string path = this.CreateOutputDirectory("Drawing", "BezierLine"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "BezierLine"); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index 10d31a0d1..429acafb9 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPath() { - string path = this.CreateOutputDirectory("Drawing", "Path"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Path"); using (Image image = new Image(500, 500)) { LinearLineSegment linerSegemnt = new LinearLineSegment( @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPathWithOpacity() { - string path = this.CreateOutputDirectory("Drawing", "Path"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Path"); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void PathExtendingOffEdgeOfImageShouldNotBeCropped() { - string path = this.CreateOutputDirectory("Drawing", "Path"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Path"); using (var image = new Image(256, 256)) { image.Mutate(x => x.Fill(Rgba32.Black)); diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index 10988e9d1..d37058f5d 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing { private void Test(string name, Rgba32 background, IBrush brush, Rgba32[,] expectedPattern) { - string path = this.CreateOutputDirectory("Fill", "PatternBrush"); + string path = TestEnvironment.CreateOutputDirectory("Fill", "PatternBrush"); using (Image image = new Image(20, 20)) { image.Mutate(x => x diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index 9d64d6319..6eb139bac 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() { - string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); using (Image image = new Image(500, 500)) { image.Mutate(x => x @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeFloodFilledWithColor() { - string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); using (Image image = new Image(500, 500)) { image.Mutate(x => x @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeFloodFilledWithColorOpacity() { - string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush"); using (Image image = new Image(500, 500)) { Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 3fe67a5aa..6c0670a85 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutlineNoOverlapping() { - string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutlineOverlapping() { - string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), @@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutlineDashed() { - string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "LineComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), diff --git a/tests/ImageSharp.Tests/Drawing/LineTests.cs b/tests/ImageSharp.Tests/Drawing/LineTests.cs index c2d60d4ad..d8c5c41d8 100644 --- a/tests/ImageSharp.Tests/Drawing/LineTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineTests.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPath() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { image.Mutate(x => x @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPath_NoAntialias() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { image.Mutate(x => x @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPathDashed() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { image.Mutate(x => x @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPathDotted() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { image.Mutate(x => x @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPathDashDot() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); using (Image image = new Image(500, 500)) { image.Mutate(x => x @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPathDashDotDot() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); Image image = new Image(500, 500); image.Mutate(x => x @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPathWithOpacity() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPathOutline() { - string path = this.CreateOutputDirectory("Drawing", "Lines"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Lines"); Image image = new Image(500, 500); diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs index 996387d14..a43f14eb7 100644 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = this.CreateOutputDirectory("Drawing", "Polygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Polygons"); using (Image image = new Image(500, 500)) { @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = this.CreateOutputDirectory("Drawing", "Polygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Polygons"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByRectangleOutline() { - string path = this.CreateOutputDirectory("Drawing", "Polygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "Polygons"); using (Image image = new Image(500, 500)) { diff --git a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs index d2bbdbb05..52668cc56 100644 --- a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ImageShouldRecolorYellowToHotPink() { - string path = this.CreateOutputDirectory("Drawing", "RecolorImage"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "RecolorImage"); RecolorBrush brush = new RecolorBrush(Rgba32.Yellow, Rgba32.HotPink, 0.2f); @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ImageShouldRecolorYellowToHotPinkInARectangle() { - string path = this.CreateOutputDirectory("Drawing", "RecolorImage"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "RecolorImage"); RecolorBrush brush = new RecolorBrush(Rgba32.Yellow, Rgba32.HotPink, 0.2f); diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 0ddd99712..07e75acf4 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { - string path = this.CreateOutputDirectory("Drawing", "FilledBezier"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledBezier"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 400), new Vector2(30, 10), @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygonOpacity() { - string path = this.CreateOutputDirectory("Drawing", "FilledBezier"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledBezier"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 400), new Vector2(30, 10), diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index 8bad1a6b0..e1849b0d0 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "ComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOverlap() { - string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "ComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "ComplexPolygon"); Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 10625dedb..c210b66ed 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygonWithPattern() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygonNoAntialias() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygonImage() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygonOpacity() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(200, 150), @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledRectangle() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); using (Image image = new Image(500, 500)) { @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledTriangle() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); using (Image image = new Image(100, 100)) { @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledSeptagon() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); Configuration config = Configuration.CreateDefaultInstance(); config.ParallelOptions.MaxDegreeOfParallelism = 1; @@ -209,7 +209,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledEllipse() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); Configuration config = Configuration.CreateDefaultInstance(); config.ParallelOptions.MaxDegreeOfParallelism = 1; @@ -226,7 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedBySquareWithCornerClipped() { - string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledPolygons"); Configuration config = Configuration.CreateDefaultInstance(); config.ParallelOptions.MaxDegreeOfParallelism = 1; diff --git a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs index 07041956d..079510c33 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text { img.Mutate(x => x.Fill(Rgba32.DarkBlue) .DrawText("AB\nAB", new Font(this.Font, 50), Rgba32.Red, new Vector2(0, 0))); - img.Save($"{this.CreateOutputDirectory("Drawing", "Text")}/AB.png"); + img.Save($"{TestEnvironment.CreateOutputDirectory("Drawing", "Text")}/AB.png"); } } } diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 30e169c7d..bbb5c7bfa 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Tests /// /// The test base class for reading and writing to files. /// - public abstract class FileTestBase : TestBase + public abstract class FileTestBase { /// /// TODO: We really should not depend on this! Let's use well defined, test-case specific inputs everywhere! diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index ffadb8a9e..d96d3def5 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests [MemberData(nameof(BitsPerPixel))] public void BitmapCanEncodeDifferentBitRates(BmpBitsPerPixel bitsPerPixel) { - string path = this.CreateOutputDirectory("Bmp"); + string path = TestEnvironment.CreateOutputDirectory("Bmp"); foreach (TestFile file in Files) { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index d275decfb..473bc2b52 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ImageCanEncodeToString() { - string path = this.CreateOutputDirectory("ToString"); + string path = TestEnvironment.CreateOutputDirectory("ToString"); foreach (TestFile file in Files) { @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void DecodeThenEncodeImageFromStreamShouldSucceed() { - string path = this.CreateOutputDirectory("Encode"); + string path = TestEnvironment.CreateOutputDirectory("Encode"); foreach (TestFile file in Files) { @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void QuantizeImageShouldPreserveMaximumColorPrecision() { - string path = this.CreateOutputDirectory("Quantize"); + string path = TestEnvironment.CreateOutputDirectory("Quantize"); foreach (TestFile file in Files) { @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ImageCanConvertFormat() { - string path = this.CreateOutputDirectory("Format"); + string path = TestEnvironment.CreateOutputDirectory("Format"); foreach (TestFile file in Files) { @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ImageShouldPreservePixelByteOrderWhenSerialized() { - string path = this.CreateOutputDirectory("Serialized"); + string path = TestEnvironment.CreateOutputDirectory("Serialized"); foreach (TestFile file in Files) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs new file mode 100644 index 000000000..bcfe91708 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + + + +// Uncomment this to turn unit tests into benchmarks: +//#define BENCHMARKING + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Memory; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + using SixLabors.Primitives; + + using Xunit; + using Xunit.Abstractions; + + public partial class Block8x8FTests : JpegFixture + { + public class CopyToBufferArea : JpegFixture + { + public CopyToBufferArea(ITestOutputHelper output) + : base(output) + { + } + + private static void VerifyAllZeroOutsideSubArea(Buffer2D buffer, int subX, int subY, int horizontalFactor = 1, int verticalFactor = 1) + { + for (int y = 0; y < 20; y++) + { + for (int x = 0; x < 20; x++) + { + if (x < subX || x >= subX + 8 * horizontalFactor || y < subY || y >= subY + 8 * verticalFactor) + { + Assert.Equal(0, buffer[x, y]); + } + } + } + } + + // TODO: This test occasionally fails. Don't get the reason, BufferArea.CopyTo() is totally OK. + [Fact(Skip = "This test occasionally fails. Don't get the reason, BufferArea.CopyTo() is totally OK.")] + public void Unscaled() + { + Block8x8F block = CreateRandomFloatBlock(0, 100); + + using (var buffer = new Buffer2D(20, 20)) + { + BufferArea area = buffer.GetArea(5, 10, 8, 8); + block.CopyTo(area); + + Assert.Equal(block[0, 0], buffer[5, 10]); + Assert.Equal(block[1, 0], buffer[6, 10]); + Assert.Equal(block[0, 1], buffer[5, 11]); + Assert.Equal(block[0, 7], buffer[5, 17]); + Assert.Equal(block[63], buffer[12, 17]); + + VerifyAllZeroOutsideSubArea(buffer, 5, 10); + } + } + + // TODO: This test occasionally fails. Don't get the reason, BufferArea.CopyTo() is totally OK. + [Theory(Skip = "This test occasionally fails. Don't get the reason, BufferArea.CopyTo() is totally OK.")] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(2, 1)] + [InlineData(2, 2)] + [InlineData(4, 2)] + [InlineData(4, 4)] + public void Scaled(int horizontalFactor, int verticalFactor) + { + Block8x8F block = CreateRandomFloatBlock(0, 100); + + var start = new Point(50, 50); + + using (var buffer = new Buffer2D(100, 100)) + { + BufferArea area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); + block.CopyTo(area, horizontalFactor, verticalFactor); + + for (int y = 0; y < 8 * verticalFactor; y++) + { + for (int x = 0; x < 8 * horizontalFactor; x++) + { + int yy = y / verticalFactor; + int xx = x / horizontalFactor; + + float expected = block[xx, yy]; + float actual = area[x, y]; + + Assert.Equal(expected, actual); + } + } + + VerifyAllZeroOutsideSubArea(buffer, start.X, start.Y, horizontalFactor, verticalFactor); + } + } + } + } +} \ 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 3a5dd9c06..56921065c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -1,24 +1,25 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Diagnostics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg.Common; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; - -using Xunit; -using Xunit.Abstractions; // Uncomment this to turn unit tests into benchmarks: //#define BENCHMARKING // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public class Block8x8FTests : JpegUtilityTestFixture + using System; + using System.Diagnostics; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public partial class Block8x8FTests : JpegFixture { #if BENCHMARKING public const int Times = 1000000; @@ -41,13 +42,13 @@ namespace SixLabors.ImageSharp.Tests { Block8x8F block = new Block8x8F(); - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { block[i] = i; } sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += block[i]; } @@ -65,13 +66,13 @@ namespace SixLabors.ImageSharp.Tests { Block8x8F block = new Block8x8F(); - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { Block8x8F.SetScalarAt(&block, i, i); } sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += Block8x8F.GetScalarAt(&block, i); } @@ -90,13 +91,13 @@ namespace SixLabors.ImageSharp.Tests { // Block8x8F block = new Block8x8F(); float[] block = new float[64]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { block[i] = i; } sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += block[i]; } @@ -107,10 +108,10 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Load_Store_FloatArray() { - float[] data = new float[Block8x8F.ScalarCount]; - float[] mirror = new float[Block8x8F.ScalarCount]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { data[i] = i; } @@ -126,16 +127,16 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(data, mirror); - // PrintLinearData((MutableSpan)mirror); + // PrintLinearData((Span)mirror); } [Fact] public unsafe void Load_Store_FloatArray_Ptr() { - float[] data = new float[Block8x8F.ScalarCount]; - float[] mirror = new float[Block8x8F.ScalarCount]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { data[i] = i; } @@ -151,16 +152,16 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(data, mirror); - // PrintLinearData((MutableSpan)mirror); + // PrintLinearData((Span)mirror); } [Fact] public void Load_Store_IntArray() { - int[] data = new int[Block8x8F.ScalarCount]; - int[] mirror = new int[Block8x8F.ScalarCount]; + int[] data = new int[Block8x8F.Size]; + int[] mirror = new int[Block8x8F.Size]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { data[i] = i; } @@ -176,7 +177,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(data, mirror); - // PrintLinearData((MutableSpan)mirror); + // PrintLinearData((Span)mirror); } [Fact] @@ -220,88 +221,7 @@ namespace SixLabors.ImageSharp.Tests sw.Stop(); this.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(); - - DCT.IDCT8x4_LeftPart(ref source, ref dest); - - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); - - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.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(); - - DCT.IDCT8x4_RightPart(ref source, ref dest); - - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); - - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.Print8x8Data(actualDestArray); - - Assert.Equal(expectedDestArray.Data, actualDestArray); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void TransformIDCT(int seed) - { - MutableSpan sourceArray = Create8x8RandomFloatData(-200, 200, seed); - 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(); - - DCT.TransformIDCT(ref source, ref dest, ref tempBuffer); - - float[] actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); - - this.Print8x8Data(expectedDestArray); - this.Output.WriteLine("**************"); - this.Print8x8Data(actualDestArray); - Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); - Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f)); - } - + [Fact] public unsafe void CopyColorsTo() { @@ -319,9 +239,9 @@ namespace SixLabors.ImageSharp.Tests Block8x8F temp = new Block8x8F(); - ReferenceImplementations.CopyColorsTo(ref block, new MutableSpan(colorsExpected, offset), stride); + ReferenceImplementations.CopyColorsTo(ref block, new Span(colorsExpected, offset), stride); - block.CopyColorsTo(new MutableSpan(colorsActual, offset), stride, &temp); + block.CopyColorsTo(new Span(colorsActual, offset), stride, &temp); // Output.WriteLine("******* EXPECTED: *********"); // PrintLinearData(colorsExpected); @@ -343,17 +263,29 @@ namespace SixLabors.ImageSharp.Tests return result; } - [Fact] - public void TransformByteConvetibleColorValuesInto() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void NormalizeColors(bool inplace) { - Block8x8F block = new Block8x8F(); + var block = default(Block8x8F); float[] input = Create8x8ColorCropTestData(); block.LoadFrom(input); this.Output.WriteLine("Input:"); this.PrintLinearData(input); + + var dest = default(Block8x8F); - Block8x8F dest = new Block8x8F(); - block.TransformByteConvetibleColorValuesInto(ref dest); + if (inplace) + { + dest = block; + dest.NormalizeColorsInplace(); + } + else + { + block.NormalizeColorsInto(ref dest); + } + float[] array = new float[64]; dest.CopyTo(array); @@ -365,73 +297,6 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void FDCT8x4_LeftPart(int seed) - { - MutableSpan src = Create8x8RandomFloatData(-200, 200, seed); - Block8x8F srcBlock = new Block8x8F(); - srcBlock.LoadFrom(src); - - Block8x8F destBlock = new Block8x8F(); - - MutableSpan expectedDest = new MutableSpan(64); - - ReferenceImplementations.fDCT2D8x4_32f(src, expectedDest); - DCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); - - MutableSpan actualDest = new MutableSpan(64); - destBlock.CopyTo(actualDest); - - Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void FDCT8x4_RightPart(int seed) - { - MutableSpan src = Create8x8RandomFloatData(-200, 200, seed); - Block8x8F srcBlock = new Block8x8F(); - srcBlock.LoadFrom(src); - - Block8x8F destBlock = new Block8x8F(); - - MutableSpan expectedDest = new MutableSpan(64); - - ReferenceImplementations.fDCT2D8x4_32f(src.Slice(4), expectedDest.Slice(4)); - DCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); - - MutableSpan actualDest = new MutableSpan(64); - destBlock.CopyTo(actualDest); - - Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TransformFDCT(int seed) - { - MutableSpan src = Create8x8RandomFloatData(-200, 200, seed); - Block8x8F srcBlock = new Block8x8F(); - srcBlock.LoadFrom(src); - - Block8x8F destBlock = new Block8x8F(); - - MutableSpan expectedDest = new MutableSpan(64); - MutableSpan temp1 = new MutableSpan(64); - Block8x8F temp2 = new Block8x8F(); - - ReferenceImplementations.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); - DCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); - - MutableSpan actualDest = new MutableSpan(64); - destBlock.CopyTo(actualDest); - - Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); - } [Theory] [InlineData(1)] @@ -439,21 +304,21 @@ namespace SixLabors.ImageSharp.Tests public unsafe void UnzigDivRound(int seed) { Block8x8F block = new Block8x8F(); - block.LoadFrom(Create8x8RandomFloatData(-2000, 2000, seed)); + block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); Block8x8F qt = new Block8x8F(); - qt.LoadFrom(Create8x8RandomFloatData(-2000, 2000, seed)); + qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); UnzigData unzig = UnzigData.Create(); - int* expectedResults = stackalloc int[Block8x8F.ScalarCount]; + int* expectedResults = stackalloc int[Block8x8F.Size]; ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data); Block8x8F actualResults = default(Block8x8F); Block8x8F.UnzigDivRound(&block, &actualResults, &qt, unzig.Data); - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { int expected = expectedResults[i]; int actual = (int)actualResults[i]; @@ -461,5 +326,26 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected, actual); } } + + [Fact] + public void RoundInto() + { + float[] data = Create8x8RandomFloatData(-1000, 1000); + + var source = default(Block8x8F); + source.LoadFrom(data); + var dest = default(Block8x8); + + source.RoundInto(ref dest); + + for (int i = 0; i < Block8x8.Size; i++) + { + float expectedFloat = data[i]; + short expectedShort = (short) Math.Round(expectedFloat); + short actualShort = dest[i]; + + Assert.Equal(expectedShort, actualShort); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs new file mode 100644 index 000000000..c2fa8c8d4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -0,0 +1,143 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public class Block8x8Tests : JpegFixture + { + public Block8x8Tests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void Construct_And_Indexer_Get() + { + short[] data = Create8x8ShortData(); + + var block = new Block8x8(data); + + for (int i = 0; i < Block8x8.Size; i++) + { + Assert.Equal(data[i], block[i]); + } + } + + [Fact] + public void Indexer_Set() + { + var block = default(Block8x8); + + block[17] = 17; + block[42] = 42; + + Assert.Equal(0, block[0]); + Assert.Equal(17, block[17]); + Assert.Equal(42, block[42]); + } + + + [Fact] + public unsafe void Indexer_GetScalarAt_SetScalarAt() + { + int sum = 0; + var block = default(Block8x8); + + for (int i = 0; i < Block8x8.Size; i++) + { + Block8x8.SetScalarAt(&block, i, (short)i); + } + + sum = 0; + for (int i = 0; i < Block8x8.Size; i++) + { + sum += Block8x8.GetScalarAt(&block, i); + } + Assert.Equal(sum, 64 * 63 / 2); + } + + + [Fact] + public void AsFloatBlock() + { + short[] data = Create8x8ShortData(); + + var source = new Block8x8(data); + + Block8x8F dest = source.AsFloatBlock(); + + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal((float)data[i], dest[i]); + } + } + + [Fact] + public void ToArray() + { + short[] data = Create8x8ShortData(); + var block = new Block8x8(data); + + short[] result = block.ToArray(); + + Assert.Equal(data, result); + } + + [Fact] + public void Equality_WhenTrue() + { + short[] data = Create8x8ShortData(); + var block1 = new Block8x8(data); + var block2 = new Block8x8(data); + + block1[0] = 42; + block2[0] = 42; + + Assert.Equal(block1, block2); + Assert.Equal(block1.GetHashCode(), block2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + short[] data = Create8x8ShortData(); + var block1 = new Block8x8(data); + var block2 = new Block8x8(data); + + block1[0] = 42; + block2[0] = 666; + + Assert.NotEqual(block1, block2); + } + + [Fact] + public void IndexerXY() + { + var block = default(Block8x8); + block[8 * 3 + 5] = 42; + + short value = block[5, 3]; + + Assert.Equal(42, value); + } + + [Fact] + public void TotalDifference() + { + short[] data = Create8x8ShortData(); + var block1 = new Block8x8(data); + var block2 = new Block8x8(data); + + block2[10] += 7; + block2[63] += 8; + + long d = Block8x8.TotalDifference(ref block1, ref block2); + + Assert.Equal(15, d); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs new file mode 100644 index 000000000..ee6f5305f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -0,0 +1,182 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public static class DCTTests + { + public class FastFloatingPoint : JpegFixture + { + public FastFloatingPoint(ITestOutputHelper output) + : base(output) + { + } + + + [Fact] + public void iDCT2D8x4_LeftPart() + { + float[] sourceArray = JpegFixture.Create8x8FloatData(); + float[] expectedDestArray = new float[64]; + + ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray, expectedDestArray); + + Block8x8F source = new Block8x8F(); + source.LoadFrom(sourceArray); + + Block8x8F dest = new Block8x8F(); + + FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest); + + float[] actualDestArray = new float[64]; + dest.CopyTo(actualDestArray); + + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); + + Assert.Equal(expectedDestArray, actualDestArray); + } + + [Fact] + public void iDCT2D8x4_RightPart() + { + float[] sourceArray = JpegFixture.Create8x8FloatData(); + float[] expectedDestArray = new float[64]; + + ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray.AsSpan().Slice(4), expectedDestArray.AsSpan().Slice(4)); + + Block8x8F source = new Block8x8F(); + source.LoadFrom(sourceArray); + + Block8x8F dest = new Block8x8F(); + + FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest); + + float[] actualDestArray = new float[64]; + dest.CopyTo(actualDestArray); + + this.Print8x8Data(expectedDestArray); + this.Output.WriteLine("**************"); + this.Print8x8Data(actualDestArray); + + Assert.Equal(expectedDestArray, actualDestArray); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToNonOptimized(int seed) + { + float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-1000, 1000, seed); + + var source = Block8x8F.Load(sourceArray); + + Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); + + var temp = default(Block8x8F); + var actual = default(Block8x8F); + FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); + + this.CompareBlocks(expected, actual, 1f); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToAccurate(int seed) + { + float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-1000, 1000, seed); + + var source = Block8x8F.Load(sourceArray); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + + var temp = default(Block8x8F); + var actual = default(Block8x8F); + FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp); + + this.CompareBlocks(expected, actual, 1f); + } + + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_LeftPart(int seed) + { + Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + Block8x8F destBlock = new Block8x8F(); + + float[] expectedDest = new float[64]; + + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src, expectedDest); + FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); + + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void FDCT8x4_RightPart(int seed) + { + Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + Block8x8F destBlock = new Block8x8F(); + + float[] expectedDest = new float[64]; + + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan().Slice(4)); + FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); + + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformFDCT(int seed) + { + Span src = JpegFixture.Create8x8RoundedRandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); + srcBlock.LoadFrom(src); + + Block8x8F destBlock = new Block8x8F(); + + float[] expectedDest = new float[64]; + float[] temp1 = new float[64]; + Block8x8F temp2 = new Block8x8F(); + + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); + + float[] actualDest = new float[64]; + destBlock.CopyTo(actualDest); + + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } + + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs new file mode 100644 index 000000000..590cd322e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Numerics; + +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; +using SixLabors.ImageSharp.Memory; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class JpegColorConverterTests + { + private const float Precision = 0.1f; + + private const int InputBufferLength = 42; + + // The result buffer could be shorter + private const int ResultBufferLength = 40; + + private readonly Vector4[] result = new Vector4[ResultBufferLength]; + + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + + public JpegColorConverterTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + private static JpegColorConverter.ComponentValues CreateRandomValues(int componentCount, float maxVal = 255f) + { + var rnd = new Random(42); + Buffer2D[] buffers = new Buffer2D[componentCount]; + for (int i = 0; i < componentCount; i++) + { + float[] values = new float[InputBufferLength]; + + for (int j = 0; j < InputBufferLength; j++) + { + values[j] = (float)rnd.NextDouble() * maxVal; + } + + // no need to dispose when buffer is not array owner + buffers[i] = new Buffer2D(values, values.Length, 1); + } + return new JpegColorConverter.ComponentValues(buffers, 0); + } + + [Fact] + public void ConvertFromYCbCr() + { + var converter = JpegColorConverter.GetConverter(JpegColorSpace.YCbCr); + + JpegColorConverter.ComponentValues values = CreateRandomValues(3); + + converter.ConvertToRGBA(values, this.result); + + for (int i = 0; i < ResultBufferLength; i++) + { + float y = values.Component0[i]; + float cb = values.Component1[i]; + float cr = values.Component2[i]; + var ycbcr = new YCbCr(y, cb, cr); + + Vector4 rgba = this.result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = ColorSpaceConverter.ToRgb(ycbcr); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } + } + + [Fact] + public void ConvertFromCmyk() + { + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk); + + JpegColorConverter.ComponentValues values = CreateRandomValues(4); + + converter.ConvertToRGBA(values, this.result); + + var v = new Vector4(0, 0, 0, 1F); + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + for (int i = 0; i < ResultBufferLength; i++) + { + float c = values.Component0[i]; + float m = values.Component1[i]; + float y = values.Component2[i]; + float k = values.Component3[i] / 255F; + + v.X = c * k; + v.Y = m * k; + v.Z = y * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = this.result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } + } + + [Fact] + public void ConvertFromYcck() + { + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck); + + JpegColorConverter.ComponentValues values = CreateRandomValues(4); + + converter.ConvertToRGBA(values, this.result); + + var v = new Vector4(0, 0, 0, 1F); + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + for (int i = 0; i < ResultBufferLength; i++) + { + float y = values.Component0[i]; + float cb = values.Component1[i] - 128F; + float cr = values.Component2[i] - 128F; + float k = values.Component3[i] / 255F; + + v.X = (255F - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (255F - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; + v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = this.result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } + } + + [Fact] + public void ConvertFromGrayScale() + { + var converter = JpegColorConverter.GetConverter(JpegColorSpace.GrayScale); + + JpegColorConverter.ComponentValues values = CreateRandomValues(1); + + converter.ConvertToRGBA(values, this.result); + + for (int i = 0; i < ResultBufferLength; i++) + { + float y = values.Component0[i]; + Vector4 rgba = this.result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(y / 255F, y / 255F, y / 255F); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } + } + + [Fact] + public void ConvertFromRgb() + { + var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB); + + JpegColorConverter.ComponentValues values = CreateRandomValues(3); + + converter.ConvertToRGBA(values, this.result); + + for (int i = 0; i < ResultBufferLength; i++) + { + float r = values.Component0[i]; + float g = values.Component1[i]; + float b = values.Component2[i]; + Vector4 rgba = this.result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(r / 255F, g / 255F, b / 255F); + + Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(1, rgba.W); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index e3351d963..dd876a7a4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -1,25 +1,25 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.IO; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; + // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests + +using System; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - using System; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; @@ -28,9 +28,16 @@ namespace SixLabors.ImageSharp.Tests { public static string[] BaselineTestJpegs = { - TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, - TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Jpeg444, - TestImages.Jpeg.Baseline.Testimgorig, + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Jpeg.Baseline.Cmyk, + TestImages.Jpeg.Baseline.Ycck, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Testorig420, + + // BUG: The following image has a high difference compared to the expected output: + //TestImages.Jpeg.Baseline.Jpeg420Small, + + TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, TestImages.Jpeg.Baseline.Bad.ExifUndefType, }; @@ -38,7 +45,9 @@ namespace SixLabors.ImageSharp.Tests public static string[] ProgressiveTestJpegs = { TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Issues.BadCoeffsProgressive178, + TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, }; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; @@ -59,19 +68,35 @@ namespace SixLabors.ImageSharp.Tests private ITestOutputHelper Output { get; } - private static IImageDecoder OldJpegDecoder => new OldJpegDecoder(); + private static IImageDecoder OrigJpegDecoder => new OrigJpegDecoder(); - private static IImageDecoder PdfJsJpegDecoder => new JpegDecoder(); + private static IImageDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder(); + + [Fact] + public void ParseStream_BasicPropertiesAreCorrect1_PdfJs() + { + byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; + using (var ms = new MemoryStream(bytes)) + { + var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(ms); + + VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); + } + } + public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg(TestImageProvider provider) + public void DecodeBaselineJpeg_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(PdfJsJpegDecoder)) { image.DebugSave(provider); + provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); } } @@ -82,53 +107,65 @@ namespace SixLabors.ImageSharp.Tests public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider, bool useOldDecoder) where TPixel : struct, IPixel { - IImageDecoder decoder = useOldDecoder ? OldJpegDecoder : PdfJsJpegDecoder; + IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder; using (Image image = provider.GetImage(decoder)) { image.DebugSave(provider); - provider.Utility.TestName = nameof(this.DecodeBaselineJpeg); + provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); } } - + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg_Old(TestImageProvider provider) + public void DecodeBaselineJpeg_Orig(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(OldJpegDecoder)) + using (Image image = provider.GetImage(OrigJpegDecoder)) { image.DebugSave(provider); - provider.Utility.TestName = nameof(this.DecodeBaselineJpeg); + provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); } } - + + [Theory] + [WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)] + public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow_Orig(TestImageProvider provider) + where TPixel : struct, IPixel + { + // TODO: We need a public ImageDecoderException class in ImageSharp! + Assert.ThrowsAny(() => provider.GetImage(OrigJpegDecoder)); + } + + public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; + [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg(TestImageProvider provider) + public void DecodeProgressiveJpeg_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(PdfJsJpegDecoder)) { image.DebugSave(provider); + provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput(provider, PdfJsProgressiveComparer, appendPixelTypeToFileName: false); } } [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg_Old(TestImageProvider provider) + public void DecodeProgressiveJpeg_Orig(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(OldJpegDecoder)) + using (Image image = provider.GetImage(OrigJpegDecoder)) { image.DebugSave(provider); - provider.Utility.TestName = nameof(this.DecodeProgressiveJpeg); + provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); } } @@ -155,10 +192,15 @@ namespace SixLabors.ImageSharp.Tests private void CompareJpegDecodersImpl(TestImageProvider provider, string testName) where TPixel : struct, IPixel { + if (TestEnvironment.RunsOnCI) // Debug only test + { + return; + } + this.Output.WriteLine(provider.SourceFileOrDescription); provider.Utility.TestName = testName; - using (Image image = provider.GetImage(OldJpegDecoder)) + using (Image image = provider.GetImage(OrigJpegDecoder)) { double d = this.GetDifferenceInPercents(image, provider); this.Output.WriteLine($"Difference using ORIGINAL decoder: {d:0.0000}%"); @@ -176,7 +218,7 @@ namespace SixLabors.ImageSharp.Tests public void CompareJpegDecoders_Baseline(TestImageProvider provider) where TPixel : struct, IPixel { - this.CompareJpegDecodersImpl(provider, nameof(this.DecodeBaselineJpeg)); + this.CompareJpegDecodersImpl(provider, DecodeBaselineJpegOutputName); } [Theory] @@ -184,7 +226,7 @@ namespace SixLabors.ImageSharp.Tests public void CompareJpegDecoders_Progressive(TestImageProvider provider) where TPixel : struct, IPixel { - this.CompareJpegDecodersImpl(provider, nameof(this.DecodeProgressiveJpeg)); + this.CompareJpegDecodersImpl(provider, DecodeProgressiveJpegOutputName); } [Theory] @@ -193,7 +235,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 75)] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 100)] [WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 100)] - public void DecodeGenerated( + public void DecodeGenerated_Orig( TestImageProvider provider, JpegSubsample subsample, int quality) @@ -211,7 +253,7 @@ namespace SixLabors.ImageSharp.Tests } } - var mirror = Image.Load(data); + var mirror = Image.Load(data, OrigJpegDecoder); mirror.DebugSave(provider, $"_{subsample}_Q{quality}"); } @@ -228,7 +270,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Decoder_Reads_Correct_Resolution_From_Exif() { - using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Jpeg420).CreateImage()) + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Jpeg420Exif).CreateImage()) { Assert.Equal(72, image.MetaData.HorizontalResolution); Assert.Equal(72, image.MetaData.VerticalResolution); @@ -283,7 +325,7 @@ namespace SixLabors.ImageSharp.Tests byte[] sourceBytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; - provider.Utility.TestName = nameof(this.DecodeProgressiveJpeg); + provider.Utility.TestName = nameof(DecodeProgressiveJpegOutputName); var comparer = ImageComparer.Tolerant(0, 0); @@ -294,8 +336,8 @@ namespace SixLabors.ImageSharp.Tests ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult); ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult); - this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentage}"); - this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentage}"); + this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}"); + this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 60aaea846..3bd1ed265 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,22 +1,23 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; -using System.IO; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; - -using Xunit; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System.Collections.Generic; + using System.IO; + + using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Processing; + using SixLabors.Primitives; + + using Xunit; + using Xunit.Abstractions; + public class JpegEncoderTests : MeasureFixture { public static IEnumerable AllBmpFiles => TestImages.Bmp.All; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs new file mode 100644 index 000000000..871321df9 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -0,0 +1,107 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + using Xunit; + using Xunit.Abstractions; + + public class JpegImagePostProcessorTests + { + public static string[] BaselineTestJpegs = + { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Jpeg.Baseline.Cmyk, + TestImages.Jpeg.Baseline.Ycck, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, + TestImages.Jpeg.Baseline.Jpeg444, + TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.Bad.ExifUndefType, + }; + + public static string[] ProgressiveTestJpegs = + { + TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF + }; + + public JpegImagePostProcessorTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + private static void SaveBuffer(JpegComponentPostProcessor cp, TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) + { + image.DebugSave(provider, $"-C{cp.Component.Index}-"); + } + + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] + public void DoProcessorStep(TestImageProvider provider) + where TPixel : struct, IPixel + { + string imageFile = provider.SourceFileOrDescription; + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) + using (var pp = new JpegImagePostProcessor(decoder)) + using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) + { + pp.DoPostProcessorStep(image); + + JpegComponentPostProcessor[] cp = pp.ComponentProcessors; + + SaveBuffer(cp[0], provider); + SaveBuffer(cp[1], provider); + SaveBuffer(cp[2], provider); + } + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] + public void PostProcess(TestImageProvider provider) + where TPixel : struct, IPixel + { + string imageFile = provider.SourceFileOrDescription; + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) + using (var pp = new JpegImagePostProcessor(decoder)) + using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) + { + pp.PostProcess(image); + + image.DebugSave(provider); + + ImagingTestCaseUtility testUtil = provider.Utility; + testUtil.TestGroupName = nameof(JpegDecoderTests); + testUtil.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; + + using (Image referenceImage = + provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) + { + ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); + + this.Output.WriteLine($"*** {imageFile} ***"); + this.Output.WriteLine($"Difference: "+ report.DifferencePercentageString); + + // ReSharper disable once PossibleInvalidOperationException + Assert.True(report.TotalNormalizedDifference.Value < 0.005f); + } + } + + + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index c87fce6d8..792836cf8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -1,18 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.IO; -using System.Linq; -using System.Numerics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System; + using System.IO; + using System.Linq; + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg; + + using Xunit; + using Xunit.Abstractions; + public class JpegProfilingBenchmarks : MeasureFixture { public JpegProfilingBenchmarks(ITestOutputHelper output) @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests TestImages.Jpeg.Baseline.Ycck, TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Jpeg420, + TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Jpeg444, }; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs deleted file mode 100644 index feca19723..000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; -using System.Text; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests -{ - public class JpegUtilityTestFixture : MeasureFixture - { - public JpegUtilityTestFixture(ITestOutputHelper output) : base(output) - { - } - - // ReSharper disable once InconsistentNaming - public static float[] Create8x8FloatData() - { - float[] result = new float[64]; - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - result[i * 8 + j] = i * 10 + j; - } - } - return result; - } - - // ReSharper disable once InconsistentNaming - public static int[] Create8x8IntData() - { - int[] result = new int[64]; - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - result[i * 8 + j] = i * 10 + j; - } - } - return result; - } - - // ReSharper disable once InconsistentNaming - public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) - { - Random rnd = new Random(seed); - int[] result = new int[64]; - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - result[i * 8 + j] = rnd.Next(minValue, maxValue); - } - } - return result; - } - - internal static MutableSpan Create8x8RandomFloatData(int minValue, int maxValue, int seed = 42) - => new MutableSpan(Create8x8RandomIntData(minValue, maxValue, seed)).ConvertToFloat32MutableSpan(); - - internal void Print8x8Data(MutableSpan data) => this.Print8x8Data(data.Data); - - internal void Print8x8Data(T[] data) - { - StringBuilder bld = new StringBuilder(); - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 8; j++) - { - bld.Append($"{data[i * 8 + j],3} "); - } - bld.AppendLine(); - } - - this.Output.WriteLine(bld.ToString()); - } - - internal void PrintLinearData(T[] data) => this.PrintLinearData(new MutableSpan(data), data.Length); - - internal void PrintLinearData(MutableSpan data, int count = -1) - { - if (count < 0) count = data.TotalCount; - - StringBuilder bld = new StringBuilder(); - for (int i = 0; i < count; i++) - { - bld.Append($"{data[i],3} "); - } - this.Output.WriteLine(bld.ToString()); - } - - protected void Print(string msg) - { - Debug.WriteLine(msg); - this.Output.WriteLine(msg); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs index 60036c176..887e9d7e9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs @@ -1,17 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Numerics; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public class JpegUtilsTests : TestBase + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; + using SixLabors.ImageSharp.PixelFormats; + + using Xunit; + + public class JpegUtilsTests { public static Image CreateTestImage() where TPixel : struct, IPixel diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs deleted file mode 100644 index 04e755310..000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs +++ /dev/null @@ -1,449 +0,0 @@ -namespace SixLabors.ImageSharp.Tests -{ - using System; - using System.IO; - using System.Linq; - using System.Numerics; - using System.Reflection; - - using BitMiracle.LibJpeg.Classic; - - using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; - using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; - using SixLabors.ImageSharp.PixelFormats; - using SixLabors.Primitives; - - using Xunit; - - internal static class LibJpegTools - { - public unsafe struct Block : IEquatable - { - public Block(short[] data) - { - this.Data = data; - } - - public short[] Data { get; } - - public short this[int x, int y] - { - get => this.Data[y * 8 + x]; - set => this.Data[y * 8 + x] = value; - } - - public bool Equals(Block other) - { - for (int i = 0; i < 64; i++) - { - if (this.Data[i] != other.Data[i]) return false; - } - return true; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is Block && Equals((Block)obj); - } - - public override int GetHashCode() - { - return (this.Data != null ? this.Data.GetHashCode() : 0); - } - - public static bool operator ==(Block left, Block right) - { - return left.Equals(right); - } - - public static bool operator !=(Block left, Block right) - { - return !left.Equals(right); - } - } - - public class SpectralData : IEquatable - { - public int ComponentCount { get; private set; } - - public ComponentData[] Components { get; private set; } - - private SpectralData(Array wholeImage) - { - this.ComponentCount = 0; - - for (int i = 0; i < wholeImage.Length && wholeImage.GetValue(i) != null; i++) - { - this.ComponentCount++; - } - - this.Components = new ComponentData[this.ComponentCount]; - - for (int i = 0; i < this.ComponentCount; i++) - { - object jVirtArray = wholeImage.GetValue(i); - Array bloxSource = (Array)GetNonPublicMember(jVirtArray, "m_buffer"); - - this.Components[i] = ComponentData.Load(bloxSource, i); - } - } - - private SpectralData(ComponentData[] components) - { - this.ComponentCount = components.Length; - this.Components = components; - } - - public static SpectralData Load(jpeg_decompress_struct cinfo) - { - //short[][][] result = new short[cinfo.Image_height][][]; - //int blockPerMcu = (int)GetNonPublicMember(cinfo, "m_blocks_in_MCU"); - //int mcuPerRow = (int)GetNonPublicMember(cinfo, "m_MCUs_per_row"); - //int mcuRows = (int)GetNonPublicMember(cinfo, "m_MCU_rows_in_scan"); - - object coefController = GetNonPublicMember(cinfo, "m_coef"); - Array wholeImage = (Array)GetNonPublicMember(coefController, "m_whole_image"); - - var result = new SpectralData(wholeImage); - - return result; - } - - public static SpectralData Load(Stream fileStream) - { - jpeg_error_mgr err = new jpeg_error_mgr(); - jpeg_decompress_struct cinfo = new jpeg_decompress_struct(err); - - cinfo.jpeg_stdio_src(fileStream); - cinfo.jpeg_read_header(true); - cinfo.Buffered_image = true; - cinfo.Do_block_smoothing = false; - - cinfo.jpeg_start_decompress(); - - var output = CreateOutputArray(cinfo); - for (int scan = 0; scan < cinfo.Input_scan_number; scan++) - { - cinfo.jpeg_start_output(scan); - for (int i = 0; i < cinfo.Image_height; i++) - { - int numScanlines = cinfo.jpeg_read_scanlines(output, 1); - if (numScanlines != 1) throw new Exception("?"); - } - } - - var result = SpectralData.Load(cinfo); - return result; - } - - private static byte[][] CreateOutputArray(jpeg_decompress_struct cinfo) - { - byte[][] output = new byte[cinfo.Image_height][]; - for (int i = 0; i < cinfo.Image_height; i++) - { - output[i] = new byte[cinfo.Image_width * cinfo.Num_components]; - } - return output; - } - - public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder) - { - FrameComponent[] srcComponents = decoder.Frame.Components; - - ComponentData[] destComponents = srcComponents.Select(ComponentData.Load).ToArray(); - - return new SpectralData(destComponents); - } - - public Image TryCreateRGBSpectralImage() - { - if (this.ComponentCount != 3) return null; - - ComponentData c0 = this.Components[0]; - ComponentData c1 = this.Components[1]; - ComponentData c2 = this.Components[2]; - - if (c0.Size != c1.Size || c1.Size != c2.Size) - { - return null; - } - - Image result = new Image(c0.XCount * 8, c0.YCount * 8); - - for (int by = 0; by < c0.YCount; by++) - { - for (int bx = 0; bx < c0.XCount; bx++) - { - this.WriteToImage(bx, by, result); - } - } - return result; - } - - internal void WriteToImage(int bx, int by, Image image) - { - ComponentData c0 = this.Components[0]; - ComponentData c1 = this.Components[1]; - ComponentData c2 = this.Components[2]; - - Block block0 = c0.Blocks[by, bx]; - Block block1 = c1.Blocks[by, bx]; - Block block2 = c2.Blocks[by, bx]; - - float d0 = (c0.MaxVal - c0.MinVal); - float d1 = (c1.MaxVal - c1.MinVal); - float d2 = (c2.MaxVal - c2.MinVal); - - for (int y = 0; y < 8; y++) - { - for (int x = 0; x < 8; x++) - { - float val0 = c0.GetBlockValue(block0, x, y); - float val1 = c0.GetBlockValue(block1, x, y); - float val2 = c0.GetBlockValue(block2, x, y); - - Vector4 v = new Vector4(val0, val1, val2, 1); - Rgba32 color = default(Rgba32); - color.PackFromVector4(v); - - int yy = by * 8 + y; - int xx = bx * 8 + x; - image[xx, yy] = color; - } - } - } - - public bool Equals(SpectralData other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - if (this.ComponentCount != other.ComponentCount) - { - return false; - } - - for (int i = 0; i < this.ComponentCount; i++) - { - ComponentData a = this.Components[i]; - ComponentData b = other.Components[i]; - if (!a.Equals(b)) return false; - } - return true; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((SpectralData)obj); - } - - public override int GetHashCode() - { - unchecked - { - return (this.ComponentCount * 397) ^ (this.Components != null ? this.Components[0].GetHashCode() : 0); - } - } - - public static bool operator ==(SpectralData left, SpectralData right) - { - return Equals(left, right); - } - - public static bool operator !=(SpectralData left, SpectralData right) - { - return !Equals(left, right); - } - } - - public class ComponentData : IEquatable - { - public ComponentData(int yCount, int xCount, int index) - { - this.YCount = yCount; - this.XCount = xCount; - this.Index = index; - this.Blocks = new Block[this.YCount, this.XCount]; - } - - public Size Size => new Size(this.XCount, this.YCount); - - public int Index { get; } - - public int YCount { get; } - - public int XCount { get; } - - public Block[,] Blocks { get; private set; } - - public short MinVal { get; private set; } = short.MaxValue; - - public short MaxVal { get; private set; } = short.MinValue; - - public static ComponentData Load(Array bloxSource, int index) - { - int yCount = bloxSource.Length; - Array row0 = (Array)bloxSource.GetValue(0); - int xCount = row0.Length; - ComponentData result = new ComponentData(yCount, xCount, index); - result.Init(bloxSource); - return result; - } - - private void Init(Array bloxSource) - { - for (int y = 0; y < bloxSource.Length; y++) - { - Array row = (Array)bloxSource.GetValue(y); - for (int x = 0; x < row.Length; x++) - { - object jBlock = row.GetValue(x); - short[] data = (short[])GetNonPublicMember(jBlock, "data"); - this.MakeBlock(data, y, x); - } - } - } - - private void MakeBlock(short[] data, int y, int x) - { - this.MinVal = Math.Min(this.MinVal, data.Min()); - this.MaxVal = Math.Max(this.MaxVal, data.Max()); - this.Blocks[y, x] = new Block(data); - } - - public static ComponentData Load(FrameComponent sc, int index) - { - var result = new ComponentData( - sc.BlocksPerColumnForMcu, - sc.BlocksPerLineForMcu, - index - ); - result.Init(sc); - return result; - } - - private void Init(FrameComponent sc) - { - for (int y = 0; y < this.YCount; y++) - { - for (int x = 0; x < this.XCount; x++) - { - short[] data = sc.GetBlockBuffer(y, x).ToArray(); - this.MakeBlock(data, y, x); - } - } - } - - public Image CreateGrayScaleImage() - { - Image result = new Image(this.XCount * 8, this.YCount * 8); - - for (int by = 0; by < this.YCount; by++) - { - for (int bx = 0; bx < this.XCount; bx++) - { - this.WriteToImage(bx, by, result); - } - } - return result; - } - - internal void WriteToImage(int bx, int by, Image image) - { - Block block = this.Blocks[by, bx]; - - for (int y = 0; y < 8; y++) - { - for (int x = 0; x < 8; x++) - { - var val = this.GetBlockValue(block, x, y); - - Vector4 v = new Vector4(val, val, val, 1); - Rgba32 color = default(Rgba32); - color.PackFromVector4(v); - - int yy = by * 8 + y; - int xx = bx * 8 + x; - image[xx, yy] = color; - } - } - } - - internal float GetBlockValue(Block block, int x, int y) - { - float d = (this.MaxVal - this.MinVal); - float val = block[x, y]; - val -= this.MinVal; - val /= d; - return val; - } - - public bool Equals(ComponentData other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - bool ok = this.Index == other.Index && this.YCount == other.YCount && this.XCount == other.XCount - && this.MinVal == other.MinVal - && this.MaxVal == other.MaxVal; - if (!ok) return false; - - for (int i = 0; i < this.YCount; i++) - { - for (int j = 0; j < this.XCount; j++) - { - Block a = this.Blocks[i, j]; - Block b = other.Blocks[i, j]; - if (!a.Equals(b)) return false; - } - } - return true; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ComponentData)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = this.Index; - hashCode = (hashCode * 397) ^ this.YCount; - hashCode = (hashCode * 397) ^ this.XCount; - hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); - hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(ComponentData left, ComponentData right) - { - return Equals(left, right); - } - - public static bool operator !=(ComponentData left, ComponentData right) - { - return !Equals(left, right); - } - } - - internal static FieldInfo GetNonPublicField(object obj, string fieldName) - { - Type type = obj.GetType(); - return type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); - } - - internal static object GetNonPublicMember(object obj, string fieldName) - { - FieldInfo fi = GetNonPublicField(obj, fieldName); - return fi.GetValue(obj); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs new file mode 100644 index 000000000..773d7112b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs @@ -0,0 +1,52 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System.IO; + + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + + public class LibJpegToolsTests + { + [Fact] + public void RunDumpJpegCoeffsTool() + { + if (!TestEnvironment.IsWindows) return; + + string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); + string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); + string outputFile = Path.Combine(outputDir, "progress.dctdump"); + + LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile); + + Assert.True(File.Exists(outputFile)); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] + public void ExtractSpectralData(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!TestEnvironment.IsWindows) + { + return; + } + + string testImage = provider.SourceFileOrDescription; + LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage); + + Assert.True(data.ComponentCount == 3); + Assert.True(data.Components.Length == 3); + + VerifyJpeg.SaveSpectralImage(provider, data); + + // I knew this one well: + if (testImage == TestImages.Jpeg.Progressive.Progress) + { + VerifyJpeg.VerifyComponentSizes3(data.Components, 43, 61, 22, 31, 22, 31); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs new file mode 100644 index 000000000..e56e91207 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -0,0 +1,134 @@ +using System; + +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; +using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; +using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; +using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.Primitives; +using Xunit; +using Xunit.Abstractions; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System.Text; + + public class ParseStreamTests + { + private ITestOutputHelper Output { get; } + + public ParseStreamTests(ITestOutputHelper output) + { + this.Output = output; + } + + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Testorig420, JpegColorSpace.YCbCr)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.GrayScale)] + [InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)] + public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue) + { + var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue; + + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true)) + { + Assert.Equal(expecteColorSpace, decoder.ColorSpace); + } + } + + [Fact] + public void ComponentScalingIsCorrect_1ChannelJpeg() + { + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(TestImages.Jpeg.Baseline.Jpeg400, true)) + { + Assert.Equal(1, decoder.ComponentCount); + Assert.Equal(1, decoder.Components.Length); + + Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8); + + Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU); + + var uniform1 = new Size(1, 1); + OrigComponent c0 = decoder.Components[0]; + VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1); + } + } + + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Jpeg444)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small)] + [InlineData(TestImages.Jpeg.Baseline.Testorig420)] + [InlineData(TestImages.Jpeg.Baseline.Ycck)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk)] + public void PrintComponentData(string imageFile) + { + StringBuilder bld = new StringBuilder(); + + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true)) + { + bld.AppendLine(imageFile); + bld.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}"); + OrigComponent c0 = decoder.Components[0]; + OrigComponent c1 = decoder.Components[1]; + + bld.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); + bld.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); + } + this.Output.WriteLine(bld.ToString()); + } + + public static readonly TheoryData ComponentVerificationData = new TheoryData() + { + { TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) }, + // TODO: Find Ycck or Cmyk images with different subsampling + { TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) }, + { TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) }, + }; + + [Theory] + [MemberData(nameof(ComponentVerificationData))] + public void ComponentScalingIsCorrect_MultiChannelJpeg( + string imageFile, + int componentCount, + object expectedLumaFactors, + object expectedChromaFactors) + { + Size fLuma = (Size)expectedLumaFactors; + Size fChroma = (Size)expectedChromaFactors; + + using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true)) + { + Assert.Equal(componentCount, decoder.ComponentCount); + Assert.Equal(componentCount, decoder.Components.Length); + + OrigComponent c0 = decoder.Components[0]; + OrigComponent c1 = decoder.Components[1]; + OrigComponent c2 = decoder.Components[2]; + + var uniform1 = new Size(1, 1); + + Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma) ; + + Size divisor = fLuma.DivideBy(fChroma); + + Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor); + + VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1); + VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor); + VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor); + + if (componentCount == 4) + { + OrigComponent c3 = decoder.Components[2]; + VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs deleted file mode 100644 index 30a6719b3..000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ /dev/null @@ -1,918 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg.Common; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; - -// ReSharper disable InconsistentNaming - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests - /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd - /// - internal static class ReferenceImplementations - { - /// - /// Transpose 8x8 block stored linearly in a (inplace) - /// - /// - internal static void Transpose8x8(MutableSpan data) - { - for (int i = 1; i < 8; i++) - { - int i8 = i * 8; - for (int j = 0; j < i; j++) - { - float tmp = data[i8 + j]; - data[i8 + j] = data[j * 8 + i]; - data[j * 8 + i] = tmp; - } - } - } - - /// - /// Transpose 8x8 block stored linearly in a - /// - internal static void Transpose8x8(MutableSpan src, MutableSpan dest) - { - for (int i = 0; i < 8; i++) - { - int i8 = i * 8; - for (int j = 0; j < 8; j++) - { - dest[j * 8 + i] = src[i8 + j]; - } - } - } - - /// - /// The "original" libjpeg/golang based DCT implementation is used as reference implementation for tests. - /// - public static class IntegerReferenceDCT - { - private const int fix_0_298631336 = 2446; - private const int fix_0_390180644 = 3196; - private const int fix_0_541196100 = 4433; - private const int fix_0_765366865 = 6270; - private const int fix_0_899976223 = 7373; - private const int fix_1_175875602 = 9633; - private const int fix_1_501321110 = 12299; - private const int fix_1_847759065 = 15137; - private const int fix_1_961570560 = 16069; - private const int fix_2_053119869 = 16819; - private const int fix_2_562915447 = 20995; - private const int fix_3_072711026 = 25172; - - /// - /// The number of bits - /// - private const int Bits = 13; - - /// - /// The number of bits to shift by on the first pass. - /// - private const int Pass1Bits = 2; - - /// - /// The value to shift by - /// - private const int CenterJSample = 128; - - /// - /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. - /// Leave results scaled up by an overall factor of 8. - /// - /// The block of coefficients. - public static void TransformFDCTInplace(MutableSpan block) - { - // Pass 1: process rows. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - int x0 = block[y8]; - int x1 = block[y8 + 1]; - int x2 = block[y8 + 2]; - int x3 = block[y8 + 3]; - int x4 = block[y8 + 4]; - int x5 = block[y8 + 5]; - int x6 = block[y8 + 6]; - int x7 = block[y8 + 7]; - - int tmp0 = x0 + x7; - int tmp1 = x1 + x6; - int tmp2 = x2 + x5; - int tmp3 = x3 + x4; - - int tmp10 = tmp0 + tmp3; - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = x0 - x7; - tmp1 = x1 - x6; - tmp2 = x2 - x5; - tmp3 = x3 - x4; - - block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; - block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (Bits - Pass1Bits - 1); - block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); - block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (Bits - Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); - block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); - } - - // Pass 2: process columns. - // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. - for (int x = 0; x < 8; x++) - { - int tmp0 = block[x] + block[56 + x]; - int tmp1 = block[8 + x] + block[48 + x]; - int tmp2 = block[16 + x] + block[40 + x]; - int tmp3 = block[24 + x] + block[32 + x]; - - int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = block[x] - block[56 + x]; - tmp1 = block[8 + x] - block[48 + x]; - tmp2 = block[16 + x] - block[40 + x]; - tmp3 = block[24 + x] - block[32 + x]; - - block[x] = (tmp10 + tmp11) >> Pass1Bits; - block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - - int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (Bits + Pass1Bits - 1); - block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); - block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (Bits + Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); - block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); - block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); - block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); - } - - } - private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) - private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) - private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) - private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) - private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) - private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) - - private const int w1pw7 = w1 + w7; - private const int w1mw7 = w1 - w7; - private const int w2pw6 = w2 + w6; - private const int w2mw6 = w2 - w6; - private const int w3pw5 = w3 + w5; - private const int w3mw5 = w3 - w5; - - private const int r2 = 181; // 256/sqrt(2) - - /// - /// Performs a 2-D Inverse Discrete Cosine Transformation. - /// - /// The input coefficients should already have been multiplied by the - /// appropriate quantization table. We use fixed-point computation, with the - /// number of bits for the fractional component varying over the intermediate - /// stages. - /// - /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the - /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on - /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. - /// - /// The source block of coefficients - public static void TransformIDCTInplace(MutableSpan src) - { - // Horizontal 1-D IDCT. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - // If all the AC components are zero, then the IDCT is trivial. - if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && - src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) - { - int dc = src[y8 + 0] << 3; - src[y8 + 0] = dc; - src[y8 + 1] = dc; - src[y8 + 2] = dc; - src[y8 + 3] = dc; - src[y8 + 4] = dc; - src[y8 + 5] = dc; - src[y8 + 6] = dc; - src[y8 + 7] = dc; - continue; - } - - // Prescale. - int x0 = (src[y8 + 0] << 11) + 128; - int x1 = src[y8 + 4] << 11; - int x2 = src[y8 + 6]; - int x3 = src[y8 + 2]; - int x4 = src[y8 + 1]; - int x5 = src[y8 + 7]; - int x6 = src[y8 + 5]; - int x7 = src[y8 + 3]; - - // Stage 1. - int x8 = w7 * (x4 + x5); - x4 = x8 + (w1mw7 * x4); - x5 = x8 - (w1pw7 * x5); - x8 = w3 * (x6 + x7); - x6 = x8 - (w3mw5 * x6); - x7 = x8 - (w3pw5 * x7); - - // Stage 2. - x8 = x0 + x1; - x0 -= x1; - x1 = w6 * (x3 + x2); - x2 = x1 - (w2pw6 * x2); - x3 = x1 + (w2mw6 * x3); - x1 = x4 + x6; - x4 -= x6; - x6 = x5 + x7; - x5 -= x7; - - // Stage 3. - x7 = x8 + x3; - x8 -= x3; - x3 = x0 + x2; - x0 -= x2; - x2 = ((r2 * (x4 + x5)) + 128) >> 8; - x4 = ((r2 * (x4 - x5)) + 128) >> 8; - - // Stage 4. - src[y8 + 0] = (x7 + x1) >> 8; - src[y8 + 1] = (x3 + x2) >> 8; - src[y8 + 2] = (x0 + x4) >> 8; - src[y8 + 3] = (x8 + x6) >> 8; - src[y8 + 4] = (x8 - x6) >> 8; - src[y8 + 5] = (x0 - x4) >> 8; - src[y8 + 6] = (x3 - x2) >> 8; - src[y8 + 7] = (x7 - x1) >> 8; - } - - // Vertical 1-D IDCT. - for (int x = 0; x < 8; x++) - { - // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. - // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so - // we do not bother to check for the all-zero case. - - // Prescale. - int y0 = (src[x] << 8) + 8192; - int y1 = src[32 + x] << 8; - int y2 = src[48 + x]; - int y3 = src[16 + x]; - int y4 = src[8 + x]; - int y5 = src[56 + x]; - int y6 = src[40 + x]; - int y7 = src[24 + x]; - - // Stage 1. - int y8 = (w7 * (y4 + y5)) + 4; - y4 = (y8 + (w1mw7 * y4)) >> 3; - y5 = (y8 - (w1pw7 * y5)) >> 3; - y8 = (w3 * (y6 + y7)) + 4; - y6 = (y8 - (w3mw5 * y6)) >> 3; - y7 = (y8 - (w3pw5 * y7)) >> 3; - - // Stage 2. - y8 = y0 + y1; - y0 -= y1; - y1 = (w6 * (y3 + y2)) + 4; - y2 = (y1 - (w2pw6 * y2)) >> 3; - y3 = (y1 + (w2mw6 * y3)) >> 3; - y1 = y4 + y6; - y4 -= y6; - y6 = y5 + y7; - y5 -= y7; - - // Stage 3. - y7 = y8 + y3; - y8 -= y3; - y3 = y0 + y2; - y0 -= y2; - y2 = ((r2 * (y4 + y5)) + 128) >> 8; - y4 = ((r2 * (y4 - y5)) + 128) >> 8; - - // Stage 4. - src[x] = (y7 + y1) >> 14; - src[8 + x] = (y3 + y2) >> 14; - src[16 + x] = (y0 + y4) >> 14; - src[24 + x] = (y8 + y6) >> 14; - src[32 + x] = (y8 - y6) >> 14; - src[40 + x] = (y0 - y4) >> 14; - src[48 + x] = (y3 - y2) >> 14; - src[56 + x] = (y7 - y1) >> 14; - } - } - } - - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 - /// - /// - /// - private static void iDCT1Dllm_32f(MutableSpan y, MutableSpan x) - { - float a0, a1, a2, a3, b0, b1, b2, b3; - float z0, z1, z2, z3, z4; - - //float r0 = 1.414214f; - float r1 = 1.387040f; - float r2 = 1.306563f; - float r3 = 1.175876f; - //float r4 = 1.000000f; - float r5 = 0.785695f; - float r6 = 0.541196f; - float r7 = 0.275899f; - - z0 = y[1] + y[7]; - z1 = y[3] + y[5]; - z2 = y[3] + y[7]; - z3 = y[1] + y[5]; - z4 = (z0 + z1) * r3; - - z0 = z0 * (-r3 + r7); - z1 = z1 * (-r3 - r1); - z2 = z2 * (-r3 - r5) + z4; - z3 = z3 * (-r3 + r5) + z4; - - b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2; - b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3; - b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2; - b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3; - - z4 = (y[2] + y[6]) * r6; - z0 = y[0] + y[4]; - z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r2 + r6); - z3 = z4 + y[2] * (r2 - r6); - a0 = z0 + z3; - a3 = z0 - z3; - a1 = z1 + z2; - a2 = z1 - z2; - - x[0] = a0 + b0; - x[7] = a0 - b0; - x[1] = a1 + b1; - x[6] = a1 - b1; - x[2] = a2 + b2; - x[5] = a2 - b2; - x[3] = a3 + b3; - x[4] = a3 - b3; - } - - /// - /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 - /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporal block "temp" - /// - /// - /// - /// - internal static void iDCT2D_llm(MutableSpan s, MutableSpan d, MutableSpan temp) - { - int j; - - for (j = 0; j < 8; j++) - { - iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - for (j = 0; j < 8; j++) - { - iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - for (j = 0; j < 64; j++) - { - d[j] *= 0.125f; - } - } - - /// - /// Original: - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 - /// - /// - /// Source - /// Destination - public static void fDCT2D8x4_32f(MutableSpan s, MutableSpan d) - { - Vector4 c0 = _mm_load_ps(s, 0); - Vector4 c1 = _mm_load_ps(s, 56); - Vector4 t0 = (c0 + c1); - Vector4 t7 = (c0 - c1); - - c1 = _mm_load_ps(s, 48); - c0 = _mm_load_ps(s, 8); - Vector4 t1 = (c0 + c1); - Vector4 t6 = (c0 - c1); - - c1 = _mm_load_ps(s, 40); - c0 = _mm_load_ps(s, 16); - Vector4 t2 = (c0 + c1); - Vector4 t5 = (c0 - c1); - - c0 = _mm_load_ps(s, 24); - c1 = _mm_load_ps(s, 32); - Vector4 t3 = (c0 + c1); - Vector4 t4 = (c0 - c1); - - /* - c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; - c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; - c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; - c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; - */ - - c0 = (t0 + t3); - Vector4 c3 = (t0 - t3); - c1 = (t1 + t2); - Vector4 c2 = (t1 - t2); - - /* - c0 = t0 + t3; c3 = t0 - t3; - c1 = t1 + t2; c2 = t1 - t2; - */ - - _mm_store_ps(d, 0, (c0 + c1)); - - _mm_store_ps(d, 32, (c0 - c1)); - - /*y[0] = c0 + c1; - y[4] = c0 - c1;*/ - - Vector4 w0 = new Vector4(0.541196f); - Vector4 w1 = new Vector4(1.306563f); - - _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); - - _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); - /* - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - */ - - w0 = new Vector4(1.175876f); - w1 = new Vector4(0.785695f); - c3 = ((w0 * t4) + (w1 * t7)); - c0 = ((w0 * t7) - (w1 * t4)); - /* - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - */ - - w0 = new Vector4(1.387040f); - w1 = new Vector4(0.275899f); - c2 = ((w0 * t5) + (w1 * t6)); - c1 = ((w0 * t6) - (w1 * t5)); - /* - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; - */ - - _mm_store_ps(d, 24, (c0 - c2)); - - _mm_store_ps(d, 40, (c3 - c1)); - //y[5] = c3 - c1; y[3] = c0 - c2; - - Vector4 invsqrt2 = new Vector4(0.707107f); - c0 = ((c0 + c2) * invsqrt2); - c3 = ((c3 + c1) * invsqrt2); - //c0 = (c0 + c2) * invsqrt2; - //c3 = (c3 + c1) * invsqrt2; - - _mm_store_ps(d, 8, (c0 + c3)); - - _mm_store_ps(d, 56, (c0 - c3)); - //y[1] = c0 + c3; y[7] = c0 - c3; - - /*for(i = 0;i < 8;i++) - { - y[i] *= invsqrt2h; - }*/ - } - - public static void fDCT8x8_llm_sse(MutableSpan s, MutableSpan d, MutableSpan temp) - { - Transpose8x8(s, temp); - - fDCT2D8x4_32f(temp, d); - - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - - Transpose8x8(d, temp); - - fDCT2D8x4_32f(temp, d); - - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - - Vector4 c = new Vector4(0.1250f); - - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//0 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//1 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//2 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//3 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//4 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//5 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//6 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//7 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//8 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//9 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//10 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//11 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//12 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//13 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//14 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//15 - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 _mm_load_ps(MutableSpan src, int offset) - { - src = src.Slice(offset); - return new Vector4(src[0], src[1], src[2], src[3]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void _mm_store_ps(MutableSpan dest, int offset, Vector4 src) - { - dest = dest.Slice(offset); - dest[0] = src.X; - dest[1] = src.Y; - dest[2] = src.Z; - dest[3] = src.W; - } - - private static readonly Vector4 _1_175876 = new Vector4(1.175876f); - - private static readonly Vector4 _1_961571 = new Vector4(-1.961571f); - - private static readonly Vector4 _0_390181 = new Vector4(-0.390181f); - - private static readonly Vector4 _0_899976 = new Vector4(-0.899976f); - - private static readonly Vector4 _2_562915 = new Vector4(-2.562915f); - - private static readonly Vector4 _0_298631 = new Vector4(0.298631f); - - private static readonly Vector4 _2_053120 = new Vector4(2.053120f); - - private static readonly Vector4 _3_072711 = new Vector4(3.072711f); - - private static readonly Vector4 _1_501321 = new Vector4(1.501321f); - - private static readonly Vector4 _0_541196 = new Vector4(0.541196f); - - private static readonly Vector4 _1_847759 = new Vector4(-1.847759f); - - private static readonly Vector4 _0_765367 = new Vector4(0.765367f); - - /// - /// Original: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// Does a part of the IDCT job on the given parts of the blocks - /// - /// - /// - internal static void iDCT2D8x4_32f(MutableSpan y, MutableSpan x) - { - /* - float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; - for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } - */ - /* - 0: 1.414214 - 1: 1.387040 - 2: 1.306563 - 3: - 4: 1.000000 - 5: 0.785695 - 6: - 7: 0.275899 - */ - - Vector4 my1 = _mm_load_ps(y, 8); - Vector4 my7 = _mm_load_ps(y, 56); - Vector4 mz0 = my1 + my7; - - Vector4 my3 = _mm_load_ps(y, 24); - Vector4 mz2 = my3 + my7; - Vector4 my5 = _mm_load_ps(y, 40); - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = ((mz0 + mz1) * _1_175876); - //z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; - //z4 = (z0 + z1) * r[3]; - - mz2 = mz2 * _1_961571 + mz4; - mz3 = mz3 * _0_390181 + mz4; - mz0 = mz0 * _0_899976; - mz1 = mz1 * _2_562915; - - /* - -0.899976 - -2.562915 - -1.961571 - -0.390181 - z0 = z0 * (-r[3] + r[7]); - z1 = z1 * (-r[3] - r[1]); - z2 = z2 * (-r[3] - r[5]) + z4; - z3 = z3 * (-r[3] + r[5]) + z4;*/ - - Vector4 mb3 = my7 * _0_298631 + mz0 + mz2; - Vector4 mb2 = my5 * _2_053120 + mz1 + mz3; - Vector4 mb1 = my3 * _3_072711 + mz1 + mz2; - Vector4 mb0 = my1 * _1_501321 + mz0 + mz3; - - /* - 0.298631 - 2.053120 - 3.072711 - 1.501321 - b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; - b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; - b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; - b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; - */ - - Vector4 my2 = _mm_load_ps(y, 16); - Vector4 my6 = _mm_load_ps(y, 48); - mz4 = (my2 + my6) * _0_541196; - Vector4 my0 = _mm_load_ps(y, 0); - Vector4 my4 = _mm_load_ps(y, 32); - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + my6 * _1_847759; - mz3 = mz4 + my2 * _0_765367; - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - /* - 1.847759 - 0.765367 - z4 = (y[2] + y[6]) * r[6]; - z0 = y[0] + y[4]; z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r[2] + r[6]); - z3 = z4 + y[2] * (r[2] - r[6]); - a0 = z0 + z3; a3 = z0 - z3; - a1 = z1 + z2; a2 = z1 - z2; - */ - - _mm_store_ps(x, 0, my0 + mb0); - - _mm_store_ps(x, 56, my0 - mb0); - - _mm_store_ps(x, 8, my1 + mb1); - - _mm_store_ps(x, 48, my1 - mb1); - - _mm_store_ps(x, 16, my2 + mb2); - - _mm_store_ps(x, 40, my2 - mb2); - - _mm_store_ps(x, 24, my3 + mb3); - - _mm_store_ps(x, 32, my3 - mb3); - /* - x[0] = a0 + b0; x[7] = a0 - b0; - x[1] = a1 + b1; x[6] = a1 - b1; - x[2] = a2 + b2; x[5] = a2 - b2; - x[3] = a3 + b3; x[4] = a3 - b3; - for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } - */ - } - - /// - /// Copies color values from block to the destination image buffer. - /// - /// - /// - /// - internal static unsafe void CopyColorsTo(ref Block8x8F block, MutableSpan buffer, int stride) - { - fixed (Block8x8F* p = &block) - { - float* b = (float*)p; - - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - int yStride = y * stride; - - for (int x = 0; x < 8; x++) - { - float c = b[y8 + x]; - - if (c < -128) - { - c = 0; - } - else if (c > 127) - { - c = 255; - } - else - { - c += 128; - } - - buffer[yStride + x] = (byte)c; - } - } - } - } - - internal static void fDCT1Dllm_32f(MutableSpan x, MutableSpan y) - { - float t0, t1, t2, t3, t4, t5, t6, t7; - float c0, c1, c2, c3; - float[] r = new float[8]; - - //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } - r[0] = 1.414214f; - r[1] = 1.387040f; - r[2] = 1.306563f; - r[3] = 1.175876f; - r[4] = 1.000000f; - r[5] = 0.785695f; - r[6] = 0.541196f; - r[7] = 0.275899f; - - const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); - //const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; - - c1 = x[0]; - c2 = x[7]; - t0 = c1 + c2; - t7 = c1 - c2; - c1 = x[1]; - c2 = x[6]; - t1 = c1 + c2; - t6 = c1 - c2; - c1 = x[2]; - c2 = x[5]; - t2 = c1 + c2; - t5 = c1 - c2; - c1 = x[3]; - c2 = x[4]; - t3 = c1 + c2; - t4 = c1 - c2; - - c0 = t0 + t3; - c3 = t0 - t3; - c1 = t1 + t2; - c2 = t1 - t2; - - y[0] = c0 + c1; - y[4] = c0 - c1; - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; - - y[5] = c3 - c1; - y[3] = c0 - c2; - c0 = (c0 + c2) * invsqrt2; - c3 = (c3 + c1) * invsqrt2; - y[1] = c0 + c3; - y[7] = c0 - c3; - } - - internal static void fDCT2D_llm( - MutableSpan s, - MutableSpan d, - MutableSpan temp, - bool downscaleBy8 = false, - bool offsetSourceByNeg128 = false) - { - MutableSpan sWorker = offsetSourceByNeg128 ? s.AddScalarToAllValues(-128f) : s; - - for (int j = 0; j < 8; j++) - { - fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - for (int j = 0; j < 8; j++) - { - fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); - } - - Transpose8x8(temp, d); - - if (downscaleBy8) - { - for (int j = 0; j < 64; j++) - { - d[j] *= 0.125f; - } - } - } - - /// - /// Reference implementation to test . - /// Rounding is done used an integer-based algorithm defined in . - /// - /// The input block - /// The destination block of integers - /// The quantization table - /// Pointer to - public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) - { - float* s = (float*)src; - float* q = (float*)qt; - - for (int zig = 0; zig < Block8x8F.ScalarCount; zig++) - { - int a = (int)s[unzigPtr[zig]]; - int b = (int)q[zig]; - - int val = RationalRound(a, b); - dest[zig] = val; - } - } - - /// - /// Rounds a rational number defined as dividend/divisor into an integer - /// - /// The dividend - /// The divisior - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RationalRound(int dividend, int divisor) - { - if (dividend >= 0) - { - return (dividend + (divisor >> 1)) / divisor; - } - - return -((-dividend + (divisor >> 1)) / divisor); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs new file mode 100644 index 000000000..6b9e98d66 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs @@ -0,0 +1,36 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public partial class ReferenceImplementationsTests + { + public class AccurateDCT : JpegFixture + { + public AccurateDCT(ITestOutputHelper output) + : base(output) + { + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void ForwardThenInverse(int seed) + { + float[] data = JpegFixture.Create8x8RandomFloatData(-1000, 1000, seed); + + var b0 = default(Block8x8F); + b0.LoadFrom(data); + + Block8x8F b1 = ReferenceImplementations.AccurateDCT.TransformFDCT(ref b0); + Block8x8F b2 = ReferenceImplementations.AccurateDCT.TransformIDCT(ref b1); + + this.CompareBlocks(b0, b2, 1e-4f); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs new file mode 100644 index 000000000..1fc47726b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -0,0 +1,123 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public partial class ReferenceImplementationsTests + { + public class FastFloatingPointDCT : JpegFixture + { + public FastFloatingPointDCT(ITestOutputHelper output) + : base(output) + { + } + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void LLM_ForwardThenInverse(int seed, int startAt) + { + int[] data = JpegFixture.Create8x8RandomIntData(-1000, 1000, seed); + float[] original = data.ConvertAllToFloat(); + float[] src = data.ConvertAllToFloat(); + float[] dest = new float[64]; + float[] temp = new float[64]; + + ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, dest, temp, true); + ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D_llm(dest, src, temp); + + this.CompareBlocks(original, src, 0.1f); + } + + // [Fact] + public void LLM_CalcConstants() + { + ReferenceImplementations.LLM_FloatingPoint_DCT.PrintConstants(this.Output); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + float[] sourceArray = JpegFixture.Create8x8RoundedRandomFloatData(-range, range, seed); + + var source = Block8x8F.Load(sourceArray); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + + Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); + + this.CompareBlocks(expected, actual, 0.1f); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + public void LLM_IDCT_CompareToIntegerRoundedAccurateImplementation(int seed, int range) + { + Block8x8F fSource = CreateRoundedRandomFloatBlock(-range, range, seed); + Block8x8 iSource = fSource.RoundAsInt16Block(); + + Block8x8 iExpected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref iSource); + Block8x8F fExpected = iExpected.AsFloatBlock(); + + Block8x8F fActual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref fSource); + + this.CompareBlocks(fExpected, fActual, 2); + } + + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void LLM_FDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + float[] floatData = JpegFixture.Create8x8RandomFloatData(-1000, 1000); + + Block8x8F source = default(Block8x8F); + source.LoadFrom(floatData); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); + actual /= 8; + + this.CompareBlocks(expected, actual, 1f); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void GT_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + int[] intData = JpegFixture.Create8x8RandomIntData(-range, range, seed); + float[] floatSrc = intData.ConvertAllToFloat(); + + ReferenceImplementations.AccurateDCT.TransformIDCTInplace(intData); + + float[] dest = new float[64]; + + ReferenceImplementations.GT_FloatingPoint_DCT.iDCT8x8GT(floatSrc, dest); + + this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs new file mode 100644 index 000000000..f384a76c4 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -0,0 +1,90 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + using System; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + using Xunit; + using Xunit.Abstractions; + + public partial class ReferenceImplementationsTests + { + public class StandardIntegerDCT : JpegFixture + { + public StandardIntegerDCT(ITestOutputHelper output) + : base(output) + { + } + + [Theory] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + int[] data = Create8x8RandomIntData(-range, range, seed); + + Block8x8 source = default(Block8x8); + source.LoadFrom(data); + + Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformIDCT(ref source); + + this.CompareBlocks(expected, actual, 1); + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void FDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + int[] data = Create8x8RandomIntData(-1000, 1000, seed); + + Block8x8F source = default(Block8x8F); + source.LoadFrom(data); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + + source += 128; + Block8x8 temp = source.RoundAsInt16Block(); + Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp); + Block8x8F actual = actual8.AsFloatBlock(); + actual /= 8; + + this.CompareBlocks(expected, actual, 1f); + } + + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void ForwardThenInverse(int seed, int startAt) + { + Span original = JpegFixture.Create8x8RandomIntData(-200, 200, seed); + + Span block = original.AddScalarToAllValues(128); + + ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8_Inplace(block); + + for (int i = 0; i < 64; i++) + { + block[i] /= 8; + } + + ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(block); + + for (int i = startAt; i < 64; i++) + { + float expected = original[i]; + float actual = (float)block[i]; + + Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 21744cbfb..26ec454f9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -3,120 +3,18 @@ using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - public class ReferenceImplementationsTests : JpegUtilityTestFixture + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + + public partial class ReferenceImplementationsTests : JpegFixture { public ReferenceImplementationsTests(ITestOutputHelper output) : base(output) { } - - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); - MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); - - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData); - - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp); - - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void IntegerDCT_ForwardThenInverse(int seed, int startAt) - { - MutableSpan original = Create8x8RandomIntData(-200, 200, seed); - - MutableSpan block = original.AddScalarToAllValues(128); - - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block); - - for (int i = 0; i < 64; i++) - { - block[i] /= 8; - } - - ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block); - - for (int i = startAt; i < 64; i++) - { - float expected = original[i]; - float actual = (float)block[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); - } - - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt) - { - int[] data = Create8x8RandomIntData(-200, 200, seed); - MutableSpan src = new MutableSpan(data).ConvertToFloat32MutableSpan(); - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(src, dest, temp, true); - ReferenceImplementations.iDCT2D_llm(dest, src, temp); - - for (int i = startAt; i < 64; i++) - { - float expected = data[i]; - float actual = (float)src[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed) - { - MutableSpan intData = Create8x8RandomIntData(-200, 200, seed); - MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan(); - - ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData); - - MutableSpan dest = new MutableSpan(64); - MutableSpan temp = new MutableSpan(64); - - ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true); - - for (int i = 0; i < 64; i++) - { - float expected = intData[i]; - float actual = dest[i]; - - Assert.Equal(expected, actual, new ApproximateFloatComparer(1f)); - } - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 4a2d4939e..6e68c43f2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,15 +1,15 @@ // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - using System.Collections.Generic; + using System; using System.IO; using System.Linq; - using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; @@ -25,10 +25,9 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] BaselineTestJpegs = { - TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, - TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Jpeg444, - TestImages.Jpeg.Baseline.Testimgorig, - TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, TestImages.Jpeg.Baseline.Bad.ExifUndefType, }; @@ -39,48 +38,98 @@ namespace SixLabors.ImageSharp.Tests }; public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); - + [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void BuildLibJpegSpectralResult(TestImageProvider provider) + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void PdfJsDecoder_ParseStream_SaveSpectralResult(TestImageProvider provider) where TPixel : struct, IPixel { + PdfJsJpegDecoderCore decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder()); + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; using (var ms = new MemoryStream(sourceBytes)) { - LibJpegTools.SpectralData data = LibJpegTools.SpectralData.Load(ms); - Assert.True(data.ComponentCount > 0); - this.Output.WriteLine($"ComponentCount: {data.ComponentCount}"); + decoder.ParseStream(ms); - this.SaveSpectralImage(provider, data); - } + var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + VerifyJpeg.SaveSpectralImage(provider, data); + } } [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void JpegDecoderCore_ParseStream_SaveSpectralResult(TestImageProvider provider) + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void OriginalDecoder_ParseStream_SaveSpectralResult(TestImageProvider provider) where TPixel : struct, IPixel { - JpegDecoderCore decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + OrigJpegDecoderCore decoder = new OrigJpegDecoderCore(Configuration.Default, new JpegDecoder()); byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; using (var ms = new MemoryStream(sourceBytes)) { - decoder.ParseStream(ms); + decoder.ParseStream(ms, false); var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - this.SaveSpectralImage(provider, data); + VerifyJpeg.SaveSpectralImage(provider, data); } } - [Theory] + private void VerifySpectralCorrectness( + TestImageProvider provider, + LibJpegTools.SpectralData imageSharpData) + where TPixel : struct, IPixel + { + var libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); + + bool equality = libJpegData.Equals(imageSharpData); + this.Output.WriteLine("Spectral data equality: " + equality); + + int componentCount = imageSharpData.ComponentCount; + if (libJpegData.ComponentCount != componentCount) + { + throw new Exception("libJpegData.ComponentCount != componentCount"); + } + + double averageDifference = 0; + double totalDifference = 0; + double tolerance = 0; + + this.Output.WriteLine("*** Differences ***"); + for (int i = 0; i < componentCount; i++) + { + LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i]; + LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i]; + + (double total, double average) diff = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent); + + this.Output.WriteLine($"Component{i}: {diff}"); + averageDifference += diff.average; + totalDifference += diff.total; + tolerance += libJpegComponent.SpectralBlocks.Length; + } + averageDifference /= componentCount; + + tolerance /= 64; // fair enough? + + this.Output.WriteLine($"AVERAGE: {averageDifference}"); + this.Output.WriteLine($"TOTAL: {totalDifference}"); + this.Output.WriteLine($"TOLERANCE = totalNumOfBlocks / 64 = {tolerance}"); + + Assert.True(totalDifference < tolerance); + } + + [Theory(Skip = "Debug/Comparison only")] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] - public void CompareSpectralResults(TestImageProvider provider) + public void VerifySpectralCorrectness_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { - JpegDecoderCore decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + if (!TestEnvironment.IsWindows) + { + return; + } + + PdfJsJpegDecoderCore decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder()); byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; @@ -89,40 +138,31 @@ namespace SixLabors.ImageSharp.Tests decoder.ParseStream(ms); var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); - ms.Seek(0, SeekOrigin.Begin); - var libJpegData = LibJpegTools.SpectralData.Load(ms); - - bool equality = libJpegData.Equals(imageSharpData); - this.Output.WriteLine("Spectral data equality: " + equality); - - // Assert.Equal(libJpegData, imageSharpData); + this.VerifySpectralCorrectness(provider, imageSharpData); } } - - private void SaveSpectralImage(TestImageProvider provider, LibJpegTools.SpectralData data) + [Theory] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void VerifySpectralResults_OriginalDecoder(TestImageProvider provider) where TPixel : struct, IPixel { - foreach (LibJpegTools.ComponentData comp in data.Components) + if (!TestEnvironment.IsWindows) { - this.Output.WriteLine("Min: " + comp.MinVal); - this.Output.WriteLine("Max: " + comp.MaxVal); - - using (Image image = comp.CreateGrayScaleImage()) - { - string details = $"C{comp.Index}"; - image.DebugSave(provider, details, appendPixelTypeToFileName: false); - } + return; } - Image fullImage = data.TryCreateRGBSpectralImage(); + OrigJpegDecoderCore decoder = new OrigJpegDecoderCore(Configuration.Default, new JpegDecoder()); + + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - if (fullImage != null) + using (var ms = new MemoryStream(sourceBytes)) { - fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false); - fullImage.Dispose(); + decoder.ParseStream(ms); + var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); + + this.VerifySpectralCorrectness(provider, imageSharpData); } } - } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs new file mode 100644 index 000000000..07268ef21 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + + + +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Text; + + using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; + + using Xunit; + using Xunit.Abstractions; + + public class JpegFixture : MeasureFixture + { + public JpegFixture(ITestOutputHelper output) : base(output) + { + } + + // ReSharper disable once InconsistentNaming + public static float[] Create8x8FloatData() + { + float[] result = new float[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = i * 10 + j; + } + } + return result; + } + + // ReSharper disable once InconsistentNaming + public static int[] Create8x8IntData() + { + int[] result = new int[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = i * 10 + j; + } + } + return result; + } + + // ReSharper disable once InconsistentNaming + public static short[] Create8x8ShortData() + { + short[] result = new short[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = (short)(i * 10 + j); + } + } + return result; + } + + // ReSharper disable once InconsistentNaming + public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) + { + Random rnd = new Random(seed); + int[] result = new int[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = rnd.Next(minValue, maxValue); + } + } + return result; + } + + internal static float[] Create8x8RoundedRandomFloatData(int minValue, int maxValue, int seed = 42) + => Create8x8RandomIntData(minValue, maxValue, seed).ConvertAllToFloat(); + + public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42) + { + Random rnd = new Random(seed); + float[] result = new float[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + double val = rnd.NextDouble(); + val *= maxValue - minValue; + val += minValue; + + result[i * 8 + j] = (float)val; + } + } + return result; + } + + internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42) => + Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed)); + + internal static Block8x8F CreateRoundedRandomFloatBlock(int minValue, int maxValue, int seed = 42) => + Block8x8F.Load(Create8x8RoundedRandomFloatData(minValue, maxValue, seed)); + + internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data)); + + internal void Print8x8Data(Span data) + { + StringBuilder bld = new StringBuilder(); + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + bld.Append($"{data[i * 8 + j],3} "); + } + bld.AppendLine(); + } + + this.Output.WriteLine(bld.ToString()); + } + + internal void PrintLinearData(T[] data) => this.PrintLinearData(new Span(data), data.Length); + + internal void PrintLinearData(Span data, int count = -1) + { + if (count < 0) count = data.Length; + + StringBuilder bld = new StringBuilder(); + for (int i = 0; i < count; i++) + { + bld.Append($"{data[i],3} "); + } + this.Output.WriteLine(bld.ToString()); + } + + protected void Print(string msg) + { + Debug.WriteLine(msg); + this.Output.WriteLine(msg); + } + + internal void CompareBlocks(Block8x8 a, Block8x8 b, int tolerance) => + this.CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), (float)tolerance + 1e-5f); + + internal void CompareBlocks(Block8x8F a, Block8x8F b, float tolerance) + => this.CompareBlocks(a.ToArray(), b.ToArray(), tolerance); + + internal void CompareBlocks(Span a, Span b, float tolerance) + { + ApproximateFloatComparer comparer = new ApproximateFloatComparer(tolerance); + double totalDifference = 0.0; + + bool failed = false; + + for (int i = 0; i < 64; i++) + { + float expected = a[i]; + float actual = b[i]; + totalDifference += Math.Abs(expected - actual); + + if (!comparer.Equals(expected, actual)) + { + failed = true; + this.Output.WriteLine($"Difference too large at index {i}"); + } + } + + this.Output.WriteLine("TOTAL DIFF: "+totalDifference); + Assert.False(failed); + } + + internal static OrigJpegDecoderCore ParseStream(string testFileName, bool metaDataOnly = false) + { + byte[] bytes = TestFile.Create(testFileName).Bytes; + using (var ms = new MemoryStream(bytes)) + { + var decoder = new OrigJpegDecoderCore(Configuration.Default, new JpegDecoder()); + decoder.ParseStream(ms, metaDataOnly); + return decoder; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs new file mode 100644 index 000000000..40b41b9cb --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -0,0 +1,196 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Linq; + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; + using SixLabors.ImageSharp.Memory; + using SixLabors.Primitives; + + internal static partial class LibJpegTools + { + /// + /// Stores spectral blocks for jpeg components. + /// + public class ComponentData : IEquatable, IJpegComponent + { + public ComponentData(int widthInBlocks, int heightInBlocks, int index) + { + this.HeightInBlocks = heightInBlocks; + this.WidthInBlocks = widthInBlocks; + this.Index = index; + this.SpectralBlocks = new Buffer2D(this.WidthInBlocks, this.HeightInBlocks); + } + + public Size Size => new Size(this.WidthInBlocks, this.HeightInBlocks); + + public int Index { get; } + + public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks); + + public Size SamplingFactors => throw new NotSupportedException(); + + public Size SubSamplingDivisors => throw new NotSupportedException(); + + public int HeightInBlocks { get; } + + public int WidthInBlocks { get; } + + public int QuantizationTableIndex => throw new NotSupportedException(); + + public Buffer2D SpectralBlocks { get; private set; } + + public short MinVal { get; private set; } = short.MaxValue; + + public short MaxVal { get; private set; } = short.MinValue; + + internal void MakeBlock(short[] data, int y, int x) + { + this.MinVal = Math.Min((short)this.MinVal, data.Min()); + this.MaxVal = Math.Max((short)this.MaxVal, data.Max()); + this.SpectralBlocks[x, y] = new Block8x8(data); + } + + public static ComponentData Load(PdfJsFrameComponent c, int index) + { + var result = new ComponentData( + c.WidthInBlocks, + c.HeightInBlocks, + index + ); + + for (int y = 0; y < result.HeightInBlocks; y++) + { + for (int x = 0; x < result.WidthInBlocks; x++) + { + short[] data = c.GetBlockBuffer(y, x).ToArray(); + result.MakeBlock(data, y, x); + } + } + + return result; + } + + public static ComponentData Load(OrigComponent c) + { + var result = new ComponentData( + c.SizeInBlocks.Width, + c.SizeInBlocks.Height, + c.Index + ); + + for (int y = 0; y < result.HeightInBlocks; y++) + { + for (int x = 0; x < result.WidthInBlocks; x++) + { + short[] data = c.GetBlockReference(x, y).ToArray(); + result.MakeBlock(data, y, x); + } + } + + return result; + } + + public Image CreateGrayScaleImage() + { + Image result = new Image(this.WidthInBlocks * 8, this.HeightInBlocks * 8); + + for (int by = 0; by < this.HeightInBlocks; by++) + { + for (int bx = 0; bx < this.WidthInBlocks; bx++) + { + this.WriteToImage(bx, by, result); + } + } + return result; + } + + internal void WriteToImage(int bx, int by, Image image) + { + Block8x8 block = this.SpectralBlocks[bx, by]; + + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + var val = this.GetBlockValue(block, x, y); + + Vector4 v = new Vector4(val, val, val, 1); + Rgba32 color = default(Rgba32); + color.PackFromVector4(v); + + int yy = by * 8 + y; + int xx = bx * 8 + x; + image[xx, yy] = color; + } + } + } + + internal float GetBlockValue(Block8x8 block, int x, int y) + { + float d = (this.MaxVal - this.MinVal); + float val = block[y, x]; + val -= this.MinVal; + val /= d; + return val; + } + + public bool Equals(ComponentData other) + { + if (Object.ReferenceEquals(null, other)) return false; + if (Object.ReferenceEquals(this, other)) return true; + bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks + && this.WidthInBlocks == other.WidthInBlocks; + //&& this.MinVal == other.MinVal + //&& this.MaxVal == other.MaxVal; + if (!ok) return false; + + for (int y = 0; y < this.HeightInBlocks; y++) + { + for (int x = 0; x < this.WidthInBlocks; x++) + { + Block8x8 a = this.SpectralBlocks[x, y]; + Block8x8 b = other.SpectralBlocks[x, y]; + if (!a.Equals(b)) return false; + } + } + return true; + } + + public override bool Equals(object obj) + { + if (Object.ReferenceEquals(null, obj)) return false; + if (Object.ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return this.Equals((ComponentData)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = this.Index; + hashCode = (hashCode * 397) ^ this.HeightInBlocks; + hashCode = (hashCode * 397) ^ this.WidthInBlocks; + hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); + hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ComponentData left, ComponentData right) + { + return Object.Equals(left, right); + } + + public static bool operator !=(ComponentData left, ComponentData right) + { + return !Object.Equals(left, right); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs new file mode 100644 index 000000000..ae7a9c046 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -0,0 +1,149 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Linq; + using System.Numerics; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; + using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components; + + internal static partial class LibJpegTools + { + /// + /// Stores spectral jpeg compoent data in libjpeg-compatible style. + /// + public class SpectralData : IEquatable + { + public int ComponentCount { get; private set; } + + public LibJpegTools.ComponentData[] Components { get; private set; } + + internal SpectralData(LibJpegTools.ComponentData[] components) + { + this.ComponentCount = components.Length; + this.Components = components; + } + + public static SpectralData LoadFromImageSharpDecoder(PdfJsJpegDecoderCore decoder) + { + PdfJsFrameComponent[] srcComponents = decoder.Frame.Components; + LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); + + return new SpectralData(destComponents); + } + + public static SpectralData LoadFromImageSharpDecoder(OrigJpegDecoderCore decoder) + { + OrigComponent[] srcComponents = decoder.Components; + LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); + + return new SpectralData(destComponents); + } + + public Image TryCreateRGBSpectralImage() + { + if (this.ComponentCount != 3) return null; + + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; + + if (c0.Size != c1.Size || c1.Size != c2.Size) + { + return null; + } + + Image result = new Image(c0.WidthInBlocks * 8, c0.HeightInBlocks * 8); + + for (int by = 0; by < c0.HeightInBlocks; by++) + { + for (int bx = 0; bx < c0.WidthInBlocks; bx++) + { + this.WriteToImage(bx, by, result); + } + } + return result; + } + + internal void WriteToImage(int bx, int by, Image image) + { + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; + + Block8x8 block0 = c0.SpectralBlocks[bx, by]; + Block8x8 block1 = c1.SpectralBlocks[bx, by]; + Block8x8 block2 = c2.SpectralBlocks[bx, by]; + + float d0 = (c0.MaxVal - c0.MinVal); + float d1 = (c1.MaxVal - c1.MinVal); + float d2 = (c2.MaxVal - c2.MinVal); + + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) + { + float val0 = c0.GetBlockValue(block0, x, y); + float val1 = c0.GetBlockValue(block1, x, y); + float val2 = c0.GetBlockValue(block2, x, y); + + Vector4 v = new Vector4(val0, val1, val2, 1); + Rgba32 color = default(Rgba32); + color.PackFromVector4(v); + + int yy = by * 8 + y; + int xx = bx * 8 + x; + image[xx, yy] = color; + } + } + } + + public bool Equals(SpectralData other) + { + if (Object.ReferenceEquals(null, other)) return false; + if (Object.ReferenceEquals(this, other)) return true; + if (this.ComponentCount != other.ComponentCount) + { + return false; + } + + for (int i = 0; i < this.ComponentCount; i++) + { + LibJpegTools.ComponentData a = this.Components[i]; + LibJpegTools.ComponentData b = other.Components[i]; + if (!a.Equals(b)) return false; + } + return true; + } + + public override bool Equals(object obj) + { + if (Object.ReferenceEquals(null, obj)) return false; + if (Object.ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return this.Equals((SpectralData)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (this.ComponentCount * 397) ^ (this.Components != null ? this.Components[0].GetHashCode() : 0); + } + } + + public static bool operator ==(SpectralData left, SpectralData right) + { + return Object.Equals(left, right); + } + + public static bool operator !=(SpectralData left, SpectralData right) + { + return !Object.Equals(left, right); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs new file mode 100644 index 000000000..587511020 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -0,0 +1,133 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Numerics; + using System.Reflection; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + /// + /// Utilities to read raw libjpeg data for reference conversion. + /// + internal static partial class LibJpegTools + { + public static (double total, double average) CalculateDifference(ComponentData expected, ComponentData actual) + { + BigInteger totalDiff = 0; + if (actual.WidthInBlocks < expected.WidthInBlocks) + { + throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks"); + } + + if (actual.HeightInBlocks < expected.HeightInBlocks) + { + throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks"); + } + + int w = expected.WidthInBlocks; + int h = expected.HeightInBlocks; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + Block8x8 aa = expected.SpectralBlocks[x, y]; + Block8x8 bb = actual.SpectralBlocks[x, y]; + + long diff = Block8x8.TotalDifference(ref aa, ref bb); + totalDiff += diff; + } + } + + int count = w * h; + double total = (double)totalDiff; + double average = (double)totalDiff / (count * Block8x8.Size); + return (total, average); + } + + private static string DumpToolFullPath => Path.Combine( + TestEnvironment.ToolsDirectoryFullPath, + @"jpeg\dump-jpeg-coeffs.exe"); + + /// + /// Executes 'dump-jpeg-coeffs.exe' for the given jpeg image file, saving the libjpeg spectral data into 'destFile'. Windows only! + /// See: + /// + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/master/tools/jpeg/README.md + /// + /// + public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) + { + if (!TestEnvironment.IsWindows) + { + throw new InvalidOperationException("Can't run dump-jpeg-coeffs.exe in non-Windows environment. Skip this test on Linux/Unix!"); + } + + string args = $@"""{sourceFile}"" ""{destFile}"""; + var process = Process.Start(DumpToolFullPath, args); + process.WaitForExit(); + } + + /// + /// Extract libjpeg from the given jpg file with 'dump-jpeg-coeffs.exe'. Windows only! + /// See: + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/master/tools/jpeg/README.md + /// + public static SpectralData ExtractSpectralData(string inputFile) + { + TestFile testFile = TestFile.Create(inputFile); + + string outDir = TestEnvironment.CreateOutputDirectory(".Temp", $"JpegCoeffs"); + string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; + string coeffFileFullPath = Path.Combine(outDir, fn); + + try + { + RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); + + using (var dumpStream = new FileStream(coeffFileFullPath, FileMode.Open)) + using (var rdr = new BinaryReader(dumpStream)) + { + int componentCount = rdr.ReadInt16(); + ComponentData[] result = new ComponentData[componentCount]; + + for (int i = 0; i < componentCount; i++) + { + int widthInBlocks = rdr.ReadInt16(); + int heightInBlocks = rdr.ReadInt16(); + ComponentData resultComponent = new ComponentData(widthInBlocks, heightInBlocks, i); + result[i] = resultComponent; + } + + byte[] buffer = new byte[64*sizeof(short)]; + + for (int i = 0; i < result.Length; i++) + { + ComponentData c = result[i]; + + for (int y = 0; y < c.HeightInBlocks; y++) + { + for (int x = 0; x < c.WidthInBlocks; x++) + { + rdr.Read(buffer, 0, buffer.Length); + + short[] block = buffer.AsSpan().NonPortableCast().ToArray(); + c.MakeBlock(block, y, x); + } + } + } + + return new SpectralData(result); + } + } + finally + { + if (File.Exists(coeffFileFullPath)) + { + File.Delete(coeffFileFullPath); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs new file mode 100644 index 000000000..6a1e09a9b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -0,0 +1,122 @@ +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + internal static partial class ReferenceImplementations + { + /// + /// True accurate FDCT/IDCT implementations. We should test everything against them! + /// Based on: + /// https://github.com/keithw/mympeg2enc/blob/master/idct.c#L222 + /// Claiming: + /// /* reference idct taken from "ieeetest.c" + /// * Written by Tom Lane (tgl@cs.cmu.edu). + /// * Released to public domain 11/22/93. + /// */ + /// + internal static class AccurateDCT + { + private static double[,] CosLut = InitCosLut(); + + public static Block8x8 TransformIDCT(ref Block8x8 block) + { + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformIDCT(ref temp); + return res0.RoundAsInt16Block(); + } + + public static void TransformIDCTInplace(Span span) + { + var temp = new Block8x8(); + temp.LoadFrom(span); + Block8x8 result = TransformIDCT(ref temp); + result.CopyTo(span); + } + + public static Block8x8 TransformFDCT(ref Block8x8 block) + { + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformFDCT(ref temp); + return res0.RoundAsInt16Block(); + } + + public static void TransformFDCTInplace(Span span) + { + var temp = new Block8x8(); + temp.LoadFrom(span); + Block8x8 result = TransformFDCT(ref temp); + result.CopyTo(span); + } + + public static Block8x8F TransformIDCT(ref Block8x8F block) + { + int x, y, u, v; + double tmp, tmp2; + Block8x8F res = default(Block8x8F); + + for (y=0; y<8; y++) { + for (x=0; x<8; x++) { + tmp = 0.0; + for (v=0; v<8; v++) { + tmp2 = 0.0; + for (u=0; u<8; u++) { + tmp2 += (double) block[v * 8 + u] * CosLut[x, u]; + } + tmp += CosLut[y, v] * tmp2; + } + res[y * 8 + x] = (float)tmp; + } + } + return res; + } + + public static Block8x8F TransformFDCT(ref Block8x8F block) + { + int x, y, u, v; + double tmp, tmp2; + Block8x8F res = default(Block8x8F); + + for (v = 0; v < 8; v++) + { + for (u = 0; u < 8; u++) + { + tmp = 0.0; + for (y = 0; y < 8; y++) + { + tmp2 = 0.0; + for (x = 0; x < 8; x++) + { + tmp2 += (double)block[y * 8 + x] * CosLut[x,u]; + } + tmp += CosLut[y, v] * tmp2; + } + res[v * 8 + u] = (float) tmp; + } + } + + return res; + } + + private static double[,] InitCosLut() + { + double[,] coslu = new double[8, 8]; + int a, b; + double tmp; + + for (a = 0; a < 8; a++) + for (b = 0; b < 8; b++) + { + tmp = Math.Cos((double)((a + a + 1) * b) * (3.14159265358979323846 / 16.0)); + if (b == 0) + { + tmp /= Math.Sqrt(2.0); + } + coslu[a, b] = tmp * 0.5; + } + return coslu; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs new file mode 100644 index 000000000..2e2f12fbc --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs @@ -0,0 +1,69 @@ +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + + internal static partial class ReferenceImplementations + { + /// + /// Non-optimized method ported from: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L446 + /// + /// *** Paper *** + /// Plonka, Gerlind, and Manfred Tasche. "Fast and numerically stable algorithms for discrete cosine transforms." Linear algebra and its applications 394 (2005) : 309 - 345. + /// + internal static class GT_FloatingPoint_DCT + { + public static void idct81d_GT(Span src, Span dst) + { + for (int i = 0; i < 8; i++) + { + float mx00 = 1.4142135623731f * src[0]; + float mx01 = 1.38703984532215f * src[1] + 0.275899379282943f * src[7]; + float mx02 = 1.30656296487638f * src[2] + 0.541196100146197f * src[6]; + float mx03 = 1.17587560241936f * src[3] + 0.785694958387102f * src[5]; + float mx04 = 1.4142135623731f * src[4]; + float mx05 = -0.785694958387102f * src[3] + 1.17587560241936f * src[5]; + float mx06 = 0.541196100146197f * src[2] - 1.30656296487638f * src[6]; + float mx07 = -0.275899379282943f * src[1] + 1.38703984532215f * src[7]; + float mx09 = mx00 + mx04; + float mx0a = mx01 + mx03; + float mx0b = 1.4142135623731f * mx02; + float mx0c = mx00 - mx04; + float mx0d = mx01 - mx03; + float mx0e = 0.353553390593274f * (mx09 - mx0b); + float mx0f = 0.353553390593274f * (mx0c + mx0d); + float mx10 = 0.353553390593274f * (mx0c - mx0d); + float mx11 = 1.4142135623731f * mx06; + float mx12 = mx05 + mx07; + float mx13 = mx05 - mx07; + float mx14 = 0.353553390593274f * (mx11 + mx12); + float mx15 = 0.353553390593274f * (mx11 - mx12); + float mx16 = 0.5f * mx13; + dst[0] = 0.25f * (mx09 + mx0b) + 0.353553390593274f * mx0a; + dst[1] = 0.707106781186547f * (mx0f + mx15); + dst[2] = 0.707106781186547f * (mx0f - mx15); + dst[3] = 0.707106781186547f * (mx0e + mx16); + dst[4] = 0.707106781186547f * (mx0e - mx16); + dst[5] = 0.707106781186547f * (mx10 - mx14); + dst[6] = 0.707106781186547f * (mx10 + mx14); + dst[7] = 0.25f * (mx09 + mx0b) - 0.353553390593274f * mx0a; + dst = dst.Slice(8); + src = src.Slice(8); + } + } + + public static void iDCT8x8GT(Span s, Span d) + { + idct81d_GT(s, d); + + Transpose8x8(d); + + idct81d_GT(d, d); + + Transpose8x8(d); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs new file mode 100644 index 000000000..ef9a73d12 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs @@ -0,0 +1,558 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; + + using Xunit.Abstractions; + + internal static partial class ReferenceImplementations + { + /// + /// Contains port of non-optimized methods in: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp + /// + /// *** Paper *** + /// paper LLM89 + /// C. Loeffler, A. Ligtenberg, and G. S. Moschytz, + /// "Practical fast 1-D DCT algorithms with 11 multiplications," + /// Proc. Int'l. Conf. on Acoustics, Speech, and Signal Processing (ICASSP89), pp. 988-991, 1989. + /// + /// The main purpose of this code is testing and documentation, it is intented to be similar to it's original counterpart. + /// DO NOT clean it! + /// DO NOT StyleCop it! + /// + internal static class LLM_FloatingPoint_DCT + { + public static Block8x8F TransformIDCT(ref Block8x8F source) + { + float[] s = new float[64]; + source.CopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + iDCT2D_llm(s, d, temp); + Block8x8F result = default(Block8x8F); + result.LoadFrom(d); + return result; + } + + public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) + { + float[] s = new float[64]; + source.CopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + fDCT2D_llm(s, d, temp); + Block8x8F result = default(Block8x8F); + result.LoadFrom(d); + return result; + } + + private static double cos(double x) => Math.Cos(x); + + private const double M_PI = Math.PI; + + private static readonly double M_SQRT2 = Math.Sqrt(2); + + public static float[] PrintConstants(ITestOutputHelper output) + { + float[] r = new float[8]; + for (int i = 0; i < 8; i++) + { + r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); + output?.WriteLine($"float r{i} = {r[i]:R}f;"); + } + return r; + } + +#pragma warning disable 219 + + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 + /// + /// + /// + private static void iDCT1Dllm_32f(Span y, Span x) + { + float a0, a1, a2, a3, b0, b1, b2, b3; + float z0, z1, z2, z3, z4; + + // see: PrintConstants() + + float r0 = 1.41421354f; + float r1 = 1.3870399f; + float r2 = 1.306563f; + float r3 = 1.17587554f; + float r4 = 1f; + float r5 = 0.785694957f; + float r6 = 0.5411961f; + float r7 = 0.27589938f; + + z0 = y[1] + y[7]; + z1 = y[3] + y[5]; + z2 = y[3] + y[7]; + z3 = y[1] + y[5]; + z4 = (z0 + z1) * r3; + + z0 = z0 * (-r3 + r7); + z1 = z1 * (-r3 - r1); + z2 = z2 * (-r3 - r5) + z4; + z3 = z3 * (-r3 + r5) + z4; + + b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2; + b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3; + b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2; + b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3; + + z4 = (y[2] + y[6]) * r6; + z0 = y[0] + y[4]; + z1 = y[0] - y[4]; + z2 = z4 - y[6] * (r2 + r6); + z3 = z4 + y[2] * (r2 - r6); + a0 = z0 + z3; + a3 = z0 - z3; + a1 = z1 + z2; + a2 = z1 - z2; + + x[0] = a0 + b0; + x[7] = a0 - b0; + x[1] = a1 + b1; + x[6] = a1 - b1; + x[2] = a2 + b2; + x[5] = a2 - b2; + x[3] = a3 + b3; + x[4] = a3 - b3; + } + + /// + /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 + /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporal block "temp" + /// + /// + /// + /// + internal static void iDCT2D_llm(Span s, Span d, Span temp) + { + int j; + + for (j = 0; j < 8; j++) + { + iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + } + + Transpose8x8(temp, d); + + for (j = 0; j < 8; j++) + { + iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } + + Transpose8x8(temp, d); + + for (j = 0; j < 64; j++) + { + d[j] *= 0.125f; + } + } + + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + public static void fDCT2D8x4_32f(Span s, Span d) + { + Vector4 c0 = _mm_load_ps(s, 0); + Vector4 c1 = _mm_load_ps(s, 56); + Vector4 t0 = (c0 + c1); + Vector4 t7 = (c0 - c1); + + c1 = _mm_load_ps(s, 48); + c0 = _mm_load_ps(s, 8); + Vector4 t1 = (c0 + c1); + Vector4 t6 = (c0 - c1); + + c1 = _mm_load_ps(s, 40); + c0 = _mm_load_ps(s, 16); + Vector4 t2 = (c0 + c1); + Vector4 t5 = (c0 - c1); + + c0 = _mm_load_ps(s, 24); + c1 = _mm_load_ps(s, 32); + Vector4 t3 = (c0 + c1); + Vector4 t4 = (c0 - c1); + + /* + c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; + c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; + c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; + c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; + */ + + c0 = (t0 + t3); + Vector4 c3 = (t0 - t3); + c1 = (t1 + t2); + Vector4 c2 = (t1 - t2); + + /* + c0 = t0 + t3; c3 = t0 - t3; + c1 = t1 + t2; c2 = t1 - t2; + */ + + _mm_store_ps(d, 0, (c0 + c1)); + + _mm_store_ps(d, 32, (c0 - c1)); + + /*y[0] = c0 + c1; + y[4] = c0 - c1;*/ + + Vector4 w0 = new Vector4(0.541196f); + Vector4 w1 = new Vector4(1.306563f); + + _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); + + _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); + /* + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + */ + + w0 = new Vector4(1.175876f); + w1 = new Vector4(0.785695f); + c3 = ((w0 * t4) + (w1 * t7)); + c0 = ((w0 * t7) - (w1 * t4)); + /* + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + */ + + w0 = new Vector4(1.387040f); + w1 = new Vector4(0.275899f); + c2 = ((w0 * t5) + (w1 * t6)); + c1 = ((w0 * t6) - (w1 * t5)); + /* + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + */ + + _mm_store_ps(d, 24, (c0 - c2)); + + _mm_store_ps(d, 40, (c3 - c1)); + //y[5] = c3 - c1; y[3] = c0 - c2; + + Vector4 invsqrt2 = new Vector4(0.707107f); + c0 = ((c0 + c2) * invsqrt2); + c3 = ((c3 + c1) * invsqrt2); + //c0 = (c0 + c2) * invsqrt2; + //c3 = (c3 + c1) * invsqrt2; + + _mm_store_ps(d, 8, (c0 + c3)); + + _mm_store_ps(d, 56, (c0 - c3)); + //y[1] = c0 + c3; y[7] = c0 - c3; + + /*for(i = 0;i < 8;i++) + { + y[i] *= invsqrt2h; + }*/ + } + + public static void fDCT8x8_llm_sse(Span s, Span d, Span temp) + { + ReferenceImplementations.Transpose8x8(s, temp); + + fDCT2D8x4_32f(temp, d); + + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + + ReferenceImplementations.Transpose8x8(d, temp); + + fDCT2D8x4_32f(temp, d); + + fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + + Vector4 c = new Vector4(0.1250f); + + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//0 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//1 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//2 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//3 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//4 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//5 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//6 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//7 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//8 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//9 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//10 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//11 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//12 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//13 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//14 + _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//15 + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 _mm_load_ps(Span src, int offset) + { + src = src.Slice(offset); + return new Vector4(src[0], src[1], src[2], src[3]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _mm_store_ps(Span dest, int offset, Vector4 src) + { + dest = dest.Slice(offset); + dest[0] = src.X; + dest[1] = src.Y; + dest[2] = src.Z; + dest[3] = src.W; + } + + // Accurate variants of constants from: + // https://github.com/mozilla/mozjpeg/blob/master/simd/jfdctint-altivec.c + + private static readonly Vector4 _1_175876 = new Vector4(1.175875602f); + + private static readonly Vector4 _1_961571 = new Vector4(-1.961570560f); + + private static readonly Vector4 _0_390181 = new Vector4(-0.390180644f); + + private static readonly Vector4 _0_899976 = new Vector4(-0.899976223f); + + private static readonly Vector4 _2_562915 = new Vector4(-2.562915447f); + + private static readonly Vector4 _0_298631 = new Vector4(0.298631336f); + + private static readonly Vector4 _2_053120 = new Vector4(2.053119869f); + + private static readonly Vector4 _3_072711 = new Vector4(3.072711026f); + + private static readonly Vector4 _1_501321 = new Vector4(1.501321110f); + + private static readonly Vector4 _0_541196 = new Vector4(0.541196100f); + + private static readonly Vector4 _1_847759 = new Vector4(-1.847759065f); + + private static readonly Vector4 _0_765367 = new Vector4(0.765366865f); + + /// + /// Original: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 + /// Does a part of the IDCT job on the given parts of the blocks + /// + /// + /// + internal static void iDCT2D8x4_32f(Span y, Span x) + { + /* + float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; + for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + */ + /* + 0: 1.414214 + 1: 1.387040 + 2: 1.306563 + 3: + 4: 1.000000 + 5: 0.785695 + 6: + 7: 0.275899 + */ + + Vector4 my1 = _mm_load_ps(y, 8); + Vector4 my7 = _mm_load_ps(y, 56); + Vector4 mz0 = my1 + my7; + + Vector4 my3 = _mm_load_ps(y, 24); + Vector4 mz2 = my3 + my7; + Vector4 my5 = _mm_load_ps(y, 40); + Vector4 mz1 = my3 + my5; + Vector4 mz3 = my1 + my5; + + Vector4 mz4 = ((mz0 + mz1) * _1_175876); + //z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; + //z4 = (z0 + z1) * r[3]; + + mz2 = mz2 * _1_961571 + mz4; + mz3 = mz3 * _0_390181 + mz4; + mz0 = mz0 * _0_899976; + mz1 = mz1 * _2_562915; + + /* + -0.899976 + -2.562915 + -1.961571 + -0.390181 + z0 = z0 * (-r[3] + r[7]); + z1 = z1 * (-r[3] - r[1]); + z2 = z2 * (-r[3] - r[5]) + z4; + z3 = z3 * (-r[3] + r[5]) + z4;*/ + + Vector4 mb3 = my7 * _0_298631 + mz0 + mz2; + Vector4 mb2 = my5 * _2_053120 + mz1 + mz3; + Vector4 mb1 = my3 * _3_072711 + mz1 + mz2; + Vector4 mb0 = my1 * _1_501321 + mz0 + mz3; + + /* + 0.298631 + 2.053120 + 3.072711 + 1.501321 + b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; + b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; + b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; + b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; + */ + + Vector4 my2 = _mm_load_ps(y, 16); + Vector4 my6 = _mm_load_ps(y, 48); + mz4 = (my2 + my6) * _0_541196; + Vector4 my0 = _mm_load_ps(y, 0); + Vector4 my4 = _mm_load_ps(y, 32); + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + my6 * _1_847759; + mz3 = mz4 + my2 * _0_765367; + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + /* + 1.847759 + 0.765367 + z4 = (y[2] + y[6]) * r[6]; + z0 = y[0] + y[4]; z1 = y[0] - y[4]; + z2 = z4 - y[6] * (r[2] + r[6]); + z3 = z4 + y[2] * (r[2] - r[6]); + a0 = z0 + z3; a3 = z0 - z3; + a1 = z1 + z2; a2 = z1 - z2; + */ + + _mm_store_ps(x, 0, my0 + mb0); + + _mm_store_ps(x, 56, my0 - mb0); + + _mm_store_ps(x, 8, my1 + mb1); + + _mm_store_ps(x, 48, my1 - mb1); + + _mm_store_ps(x, 16, my2 + mb2); + + _mm_store_ps(x, 40, my2 - mb2); + + _mm_store_ps(x, 24, my3 + mb3); + + _mm_store_ps(x, 32, my3 - mb3); + /* + x[0] = a0 + b0; x[7] = a0 - b0; + x[1] = a1 + b1; x[6] = a1 - b1; + x[2] = a2 + b2; x[5] = a2 - b2; + x[3] = a3 + b3; x[4] = a3 - b3; + for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } + */ + } + + internal static void fDCT1Dllm_32f(Span x, Span y) + { + float t0, t1, t2, t3, t4, t5, t6, t7; + float c0, c1, c2, c3; + float[] r = new float[8]; + + //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + r[0] = 1.414214f; + r[1] = 1.387040f; + r[2] = 1.306563f; + r[3] = 1.175876f; + r[4] = 1.000000f; + r[5] = 0.785695f; + r[6] = 0.541196f; + r[7] = 0.275899f; + + const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); + //const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; + + c1 = x[0]; + c2 = x[7]; + t0 = c1 + c2; + t7 = c1 - c2; + c1 = x[1]; + c2 = x[6]; + t1 = c1 + c2; + t6 = c1 - c2; + c1 = x[2]; + c2 = x[5]; + t2 = c1 + c2; + t5 = c1 - c2; + c1 = x[3]; + c2 = x[4]; + t3 = c1 + c2; + t4 = c1 - c2; + + c0 = t0 + t3; + c3 = t0 - t3; + c1 = t1 + t2; + c2 = t1 - t2; + + y[0] = c0 + c1; + y[4] = c0 - c1; + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + c2 = t5 * r[1] + t6 * r[7]; + c1 = t6 * r[1] - t5 * r[7]; + + y[5] = c3 - c1; + y[3] = c0 - c2; + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; + y[1] = c0 + c3; + y[7] = c0 - c3; + } + + internal static void fDCT2D_llm( + Span s, + Span d, + Span temp, + bool downscaleBy8 = false, + bool subtract128FromSource = false) + { + Span sWorker = subtract128FromSource ? s.AddScalarToAllValues(-128f) : s; + + for (int j = 0; j < 8; j++) + { + fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + for (int j = 0; j < 8; j++) + { + fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } + + ReferenceImplementations.Transpose8x8(temp, d); + + if (downscaleBy8) + { + for (int j = 0; j < 64; j++) + { + d[j] *= 0.125f; + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs new file mode 100644 index 000000000..9afc4b0b3 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -0,0 +1,367 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + internal static partial class ReferenceImplementations + { + /// + /// TODO: produces really bad results for bigger values! + /// + /// Contains the "original" golang based DCT/IDCT implementations as reference implementations. + /// 1. ===== Forward DCT ===== + /// **** The original golang source claims: + /// It is based on the code in jfdctint.c from the Independent JPEG Group, + /// found at http://www.ijg.org/files/jpegsrc.v8c.tar.gz. + /// + /// **** Could be found here as well: + /// https://github.com/mozilla/mozjpeg/blob/master/jfdctint.c + /// + /// 2. ===== Inverse DCT ===== + /// + /// The golang source claims: + /// http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz + /// The referenced MPEG2 code claims: + /// /**********************************************************/ + /// /* inverse two dimensional DCT, Chen-Wang algorithm */ + /// /* (cf. IEEE ASSP-32, pp. 803-816, Aug. 1984) */ + /// /* 32-bit integer arithmetic (8 bit coefficients) */ + /// /* 11 mults, 29 adds per DCT */ + /// /* sE, 18.8.91 */ + /// /**********************************************************/ + /// /* coefficients extended to 12 bit for IEEE1180-1990 */ + /// /* compliance sE, 2.1.94 */ + /// /**********************************************************/ + /// + /// **** The code looks pretty similar to the standard libjpeg IDCT, but without quantization: + /// https://github.com/mozilla/mozjpeg/blob/master/jidctint.c + /// + public static class StandardIntegerDCT + { + private const int fix_0_298631336 = 2446; + private const int fix_0_390180644 = 3196; + private const int fix_0_541196100 = 4433; + private const int fix_0_765366865 = 6270; + private const int fix_0_899976223 = 7373; + private const int fix_1_175875602 = 9633; + private const int fix_1_501321110 = 12299; + private const int fix_1_847759065 = 15137; + private const int fix_1_961570560 = 16069; + private const int fix_2_053119869 = 16819; + private const int fix_2_562915447 = 20995; + private const int fix_3_072711026 = 25172; + + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + public static Block8x8 Subtract128_TransformFDCT_Upscale8(ref Block8x8 block) + { + int[] temp = new int[Block8x8.Size]; + block.CopyTo(temp); + Subtract128_TransformFDCT_Upscale8_Inplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; + } + + // [Obsolete("Looks like this method produces really bad results for bigger values!")] + public static Block8x8 TransformIDCT(ref Block8x8 block) + { + int[] temp = new int[Block8x8.Size]; + block.CopyTo(temp); + TransformIDCTInplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; + } + + /// + /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. + /// Leave results scaled up by an overall factor of 8. + /// + /// The block of coefficients. + public static void Subtract128_TransformFDCT_Upscale8_Inplace(Span block) + { + // Pass 1: process rows. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; + + int tmp0 = x0 + x7; + int tmp1 = x1 + x6; + int tmp2 = x2 + x5; + int tmp3 = x3 + x4; + + int tmp10 = tmp0 + tmp3; + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = x0 - x7; + tmp1 = x1 - x6; + tmp2 = x2 - x5; + tmp3 = x3 - x4; + + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits - Pass1Bits - 1); + block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits - Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); + } + + // Pass 2: process columns. + // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. + for (int x = 0; x < 8; x++) + { + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; + + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; + + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; + + int z1 = (tmp12 + tmp13) * fix_0_541196100; + z1 += 1 << (Bits + Pass1Bits - 1); + block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 += 1 << (Bits + Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); + block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); + } + + } + private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int w1pw7 = w1 + w7; + private const int w1mw7 = w1 - w7; + private const int w2pw6 = w2 + w6; + private const int w2mw6 = w2 - w6; + private const int w3pw5 = w3 + w5; + private const int w3mw5 = w3 - w5; + + private const int r2 = 181; // 256/sqrt(2) + + /// + /// Performs a 2-D Inverse Discrete Cosine Transformation. + /// + /// The input coefficients should already have been multiplied by the + /// appropriate quantization table. We use fixed-point computation, with the + /// number of bits for the fractional component varying over the intermediate + /// stages. + /// + /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the + /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on + /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. + /// + /// The source block of coefficients + // [Obsolete("Looks like this method produces really bad results for bigger values!")] + public static void TransformIDCTInplace(Span src) + { + // Horizontal 1-D IDCT. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + // If all the AC components are zero, then the IDCT is trivial. + if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && + src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) + { + int dc = src[y8 + 0] << 3; + src[y8 + 0] = dc; + src[y8 + 1] = dc; + src[y8 + 2] = dc; + src[y8 + 3] = dc; + src[y8 + 4] = dc; + src[y8 + 5] = dc; + src[y8 + 6] = dc; + src[y8 + 7] = dc; + continue; + } + + // Prescale. + int x0 = (src[y8 + 0] << 11) + 128; + int x1 = src[y8 + 4] << 11; + int x2 = src[y8 + 6]; + int x3 = src[y8 + 2]; + int x4 = src[y8 + 1]; + int x5 = src[y8 + 7]; + int x6 = src[y8 + 5]; + int x7 = src[y8 + 3]; + + // Stage 1. + int x8 = w7 * (x4 + x5); + x4 = x8 + (w1mw7 * x4); + x5 = x8 - (w1pw7 * x5); + x8 = w3 * (x6 + x7); + x6 = x8 - (w3mw5 * x6); + x7 = x8 - (w3pw5 * x7); + + // Stage 2. + x8 = x0 + x1; + x0 -= x1; + x1 = w6 * (x3 + x2); + x2 = x1 - (w2pw6 * x2); + x3 = x1 + (w2mw6 * x3); + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + // Stage 3. + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = ((r2 * (x4 + x5)) + 128) >> 8; + x4 = ((r2 * (x4 - x5)) + 128) >> 8; + + // Stage 4. + src[y8 + 0] = (x7 + x1) >> 8; + src[y8 + 1] = (x3 + x2) >> 8; + src[y8 + 2] = (x0 + x4) >> 8; + src[y8 + 3] = (x8 + x6) >> 8; + src[y8 + 4] = (x8 - x6) >> 8; + src[y8 + 5] = (x0 - x4) >> 8; + src[y8 + 6] = (x3 - x2) >> 8; + src[y8 + 7] = (x7 - x1) >> 8; + } + + // Vertical 1-D IDCT. + for (int x = 0; x < 8; x++) + { + // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. + // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so + // we do not bother to check for the all-zero case. + + // Prescale. + int y0 = (src[x] << 8) + 8192; + int y1 = src[32 + x] << 8; + int y2 = src[48 + x]; + int y3 = src[16 + x]; + int y4 = src[8 + x]; + int y5 = src[56 + x]; + int y6 = src[40 + x]; + int y7 = src[24 + x]; + + // Stage 1. + int y8 = (w7 * (y4 + y5)) + 4; + y4 = (y8 + (w1mw7 * y4)) >> 3; + y5 = (y8 - (w1pw7 * y5)) >> 3; + y8 = (w3 * (y6 + y7)) + 4; + y6 = (y8 - (w3mw5 * y6)) >> 3; + y7 = (y8 - (w3pw5 * y7)) >> 3; + + // Stage 2. + y8 = y0 + y1; + y0 -= y1; + y1 = (w6 * (y3 + y2)) + 4; + y2 = (y1 - (w2pw6 * y2)) >> 3; + y3 = (y1 + (w2mw6 * y3)) >> 3; + y1 = y4 + y6; + y4 -= y6; + y6 = y5 + y7; + y5 -= y7; + + // Stage 3. + y7 = y8 + y3; + y8 -= y3; + y3 = y0 + y2; + y0 -= y2; + y2 = ((r2 * (y4 + y5)) + 128) >> 8; + y4 = ((r2 * (y4 - y5)) + 128) >> 8; + + // Stage 4. + src[x] = (y7 + y1) >> 14; + src[8 + x] = (y3 + y2) >> 14; + src[16 + x] = (y0 + y4) >> 14; + src[24 + x] = (y8 + y6) >> 14; + src[32 + x] = (y8 - y6) >> 14; + src[40 + x] = (y0 - y4) >> 14; + src[48 + x] = (y3 - y2) >> 14; + src[56 + x] = (y7 - y1) >> 14; + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs new file mode 100644 index 000000000..c8240bf08 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -0,0 +1,135 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + + + +// ReSharper disable InconsistentNaming + + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + /// + /// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests + /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd + /// + internal static partial class ReferenceImplementations + { + /// + /// Transpose 8x8 block stored linearly in a (inplace) + /// + /// + internal static void Transpose8x8(Span data) + { + for (int i = 1; i < 8; i++) + { + int i8 = i * 8; + for (int j = 0; j < i; j++) + { + float tmp = data[i8 + j]; + data[i8 + j] = data[j * 8 + i]; + data[j * 8 + i] = tmp; + } + } + } + + /// + /// Transpose 8x8 block stored linearly in a + /// + internal static void Transpose8x8(Span src, Span dest) + { + for (int i = 0; i < 8; i++) + { + int i8 = i * 8; + for (int j = 0; j < 8; j++) + { + dest[j * 8 + i] = src[i8 + j]; + } + } + } + + /// + /// Copies color values from block to the destination image buffer. + /// + /// + /// + /// + internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) + { + fixed (Block8x8F* p = &block) + { + float* b = (float*)p; + + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + int yStride = y * stride; + + for (int x = 0; x < 8; x++) + { + float c = b[y8 + x]; + + if (c < -128) + { + c = 0; + } + else if (c > 127) + { + c = 255; + } + else + { + c += 128; + } + + buffer[yStride + x] = (byte)c; + } + } + } + } + + /// + /// Reference implementation to test . + /// Rounding is done used an integer-based algorithm defined in . + /// + /// The input block + /// The destination block of integers + /// The quantization table + /// Pointer to + public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) + { + float* s = (float*)src; + float* q = (float*)qt; + + for (int zig = 0; zig < Block8x8F.Size; zig++) + { + int a = (int)s[unzigPtr[zig]]; + int b = (int)q[zig]; + + int val = RationalRound(a, b); + dest[zig] = val; + } + } + + /// + /// Rounds a rational number defined as dividend/divisor into an integer + /// + /// The dividend + /// The divisior + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RationalRound(int dividend, int divisor) + { + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; + } + + return -((-dividend + (divisor >> 1)) / divisor); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs new file mode 100644 index 000000000..988b9e478 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs @@ -0,0 +1,121 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Span Extensions + /// + internal static class SpanExtensions + { + /// + /// Save to a Vector4 + /// + /// The data + /// The vector to save to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SaveTo(this Span data, ref Vector4 v) + { + v.X = data[0]; + v.Y = data[1]; + v.Z = data[2]; + v.W = data[3]; + } + + /// + /// Save to a Vector4 + /// + /// The data + /// The vector to save to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SaveTo(this Span data, ref Vector4 v) + { + v.X = data[0]; + v.Y = data[1]; + v.Z = data[2]; + v.W = data[3]; + } + + /// + /// Load from Vector4 + /// + /// The data + /// The vector to load from + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LoadFrom(this Span data, ref Vector4 v) + { + data[0] = v.X; + data[1] = v.Y; + data[2] = v.Z; + data[3] = v.W; + } + + /// + /// Load from Vector4 + /// + /// The data + /// The vector to load from + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LoadFrom(this Span data, ref Vector4 v) + { + data[0] = (int)v.X; + data[1] = (int)v.Y; + data[2] = (int)v.Z; + data[3] = (int)v.W; + } + + /// + /// Converts all int values of src to float + /// + /// Source + /// A new with float values + public static float[] ConvertAllToFloat(this int[] src) + { + float[] result = new float[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = (float)src[i]; + } + + return result; + } + + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of + public static Span AddScalarToAllValues(this Span src, float scalar) + { + float[] result = new float[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = src[i] + scalar; + } + + return result; + } + + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of + public static Span AddScalarToAllValues(this Span src, int scalar) + { + int[] result = new int[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = src[i] + scalar; + } + + return result; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs new file mode 100644 index 000000000..d0f7df12c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; + +using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +{ + internal static class VerifyJpeg + { + internal static void VerifySize(IJpegComponent component, int expectedBlocksX, int expectedBlocksY) + { + Assert.Equal(new Size(expectedBlocksX, expectedBlocksY), component.SizeInBlocks); + } + + internal static void VerifyComponent( + IJpegComponent component, + Size expectedSizeInBlocks, + Size expectedSamplingFactors, + Size expectedSubsamplingDivisors) + { + Assert.Equal(expectedSizeInBlocks, component.SizeInBlocks); + Assert.Equal(expectedSamplingFactors, component.SamplingFactors); + Assert.Equal(expectedSubsamplingDivisors, component.SubSamplingDivisors); + } + + internal static void VerifyComponentSizes3( + IEnumerable components, + int xBc0, + int yBc0, + int xBc1, + int yBc1, + int xBc2, + int yBc2) + { + IJpegComponent[] c = components.ToArray(); + Assert.Equal(3, components.Count()); + + VerifySize(c[0], xBc0, yBc0); + VerifySize(c[1], xBc1, yBc1); + VerifySize(c[2], xBc2, yBc2); + } + + internal static void SaveSpectralImage( + TestImageProvider provider, + LibJpegTools.SpectralData data, + ITestOutputHelper output = null) + where TPixel : struct, IPixel + { + foreach (LibJpegTools.ComponentData comp in data.Components) + { + output?.WriteLine("Min: " + comp.MinVal); + output?.WriteLine("Max: " + comp.MaxVal); + + using (Image image = comp.CreateGrayScaleImage()) + { + string details = $"C{comp.Index}"; + image.DebugSave(provider, details, appendPixelTypeToFileName: false); + } + } + + Image fullImage = data.TryCreateRGBSpectralImage(); + + if (fullImage != null) + { + fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false); + fullImage.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs deleted file mode 100644 index 9d0bcb2d4..000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; -using SixLabors.Primitives; -using Xunit; -using Xunit.Abstractions; - -namespace SixLabors.ImageSharp.Tests -{ - public class YCbCrImageTests - { - public YCbCrImageTests(ITestOutputHelper output) - { - this.Output = output; - } - - private ITestOutputHelper Output { get; } - - [Theory] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4, 2)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4, 1)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2, 2)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2, 1)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1, 2)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1, 1)] - internal void CalculateChrominanceSize( - YCbCrImage.YCbCrSubsampleRatio ratioValue, - int expectedDivX, - int expectedDivY) - { - YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue; - - //this.Output.WriteLine($"RATIO: {ratio}"); - Size size = YCbCrImage.CalculateChrominanceSize(400, 400, ratio); - //this.Output.WriteLine($"Ch Size: {size}"); - - Assert.Equal(new Size(400 / expectedDivX, 400 / expectedDivY), size); - } - - [Theory] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio410, 4)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio411, 4)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio420, 2)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio422, 2)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio440, 1)] - [InlineData(YCbCrImage.YCbCrSubsampleRatio.YCbCrSubsampleRatio444, 1)] - internal void Create(YCbCrImage.YCbCrSubsampleRatio ratioValue, int expectedCStrideDiv) - { - YCbCrImage.YCbCrSubsampleRatio ratio = (YCbCrImage.YCbCrSubsampleRatio)ratioValue; - - this.Output.WriteLine($"RATIO: {ratio}"); - - YCbCrImage img = new YCbCrImage(400, 400, ratio); - - //this.PrintChannel("Y", img.YChannel); - //this.PrintChannel("Cb", img.CbChannel); - //this.PrintChannel("Cr", img.CrChannel); - - Assert.Equal(400, img.YChannel.Width); - Assert.Equal(img.CbChannel.Width, 400 / expectedCStrideDiv); - Assert.Equal(img.CrChannel.Width, 400 / expectedCStrideDiv); - } - - private void PrintChannel(string name, OldJpegPixelArea channel) - { - this.Output.WriteLine($"{name}: Stride={channel.Stride}"); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs index bc9c28898..0e7dc5917 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests private static (Size original, Size rotated) Rotate(int angle) { var file = TestFile.Create(TestImages.Bmp.Car); - using (var image = Image.Load(file.FilePath)) + using (var image = Image.Load(file.FullPath)) { Size original = image.Bounds().Size; image.Mutate(x => x.Rotate(angle)); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 323eaf65c..a9952f5c4 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests public void ConstructorFileSystem() { TestFile file = TestFile.Create(TestImages.Bmp.Car); - using (Image image = Image.Load(file.FilePath)) + using (Image image = Image.Load(file.FullPath)) { Assert.Equal(600, image.Width); Assert.Equal(450, image.Height); @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Save_DetecedEncoding() { - string dir = this.CreateOutputDirectory(nameof(ImageTests)); + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string file = System.IO.Path.Combine(dir, "Save_DetecedEncoding.png"); using (Image image = new Image(10, 10)) @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Save_WhenExtensionIsUnknown_Throws() { - string dir = this.CreateOutputDirectory(nameof(ImageTests)); + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string file = System.IO.Path.Combine(dir, "Save_UnknownExtensionsEncoding_Throws.tmp"); NotSupportedException ex = Assert.Throws( @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Save_SetEncoding() { - string dir = this.CreateOutputDirectory(nameof(ImageTests)); + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string file = System.IO.Path.Combine(dir, "Save_SetEncoding.dat"); using (Image image = new Image(10, 10)) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index a89168606..e8a6e8c59 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -16,7 +16,6 @@ - @@ -25,10 +24,6 @@ - - - - diff --git a/tests/ImageSharp.Tests/Common/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs similarity index 72% rename from tests/ImageSharp.Tests/Common/Buffer2DTests.cs rename to tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 2f275b754..d662a1b3e 100644 --- a/tests/ImageSharp.Tests/Common/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -1,15 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using Xunit; -using static SixLabors.ImageSharp.Tests.Common.TestStructs; - -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Memory { - public unsafe class Buffer2DTests + using System; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Memory; + using SixLabors.ImageSharp.Tests.Common; + + using Xunit; + + public class Buffer2DTests { // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert @@ -29,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1025, 17)] public void Construct(int width, int height) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); @@ -42,8 +44,8 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1025, 17)] public void Construct_FromExternalArray(int width, int height) { - Foo[] array = new Foo[width * height + 10]; - using (Buffer2D buffer = new Buffer2D(array, width, height)) + TestStructs.Foo[] array = new TestStructs.Foo[width * height + 10]; + using (Buffer2D buffer = new Buffer2D(array, width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); @@ -74,9 +76,9 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(17, 42, 41)] public void GetRowSpanY(int width, int height, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { - Span span = buffer.GetRowSpan(y); + Span span = buffer.GetRowSpan(y); // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); @@ -90,9 +92,9 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(17, 42, 0, 41)] public void GetRowSpanXY(int width, int height, int x, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { - Span span = buffer.GetRowSpan(x, y); + Span span = buffer.GetRowSpan(x, y); // Assert.Equal(width * y + x, span.Start); Assert.Equal(width - x, span.Length); @@ -106,13 +108,13 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(99, 88, 98, 87)] public void Indexer(int width, int height, int x, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { - Foo[] array = buffer.Array; + TestStructs.Foo[] array = buffer.Array; - ref Foo actual = ref buffer[x, y]; + ref TestStructs.Foo actual = ref buffer[x, y]; - ref Foo expected = ref array[y * width + x]; + ref TestStructs.Foo expected = ref array[y * width + x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs new file mode 100644 index 000000000..58051c894 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -0,0 +1,135 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Memory +{ + using System; + + using SixLabors.ImageSharp.Memory; + using SixLabors.Primitives; + + using Xunit; + + public class BufferAreaTests + { + [Fact] + public void Construct() + { + using (var buffer = new Buffer2D(10, 20)) + { + var rectangle = new Rectangle(3,2, 5, 6); + var area = new BufferArea(buffer, rectangle); + + Assert.Equal(buffer, area.DestinationBuffer); + Assert.Equal(rectangle, area.Rectangle); + } + } + + private static Buffer2D CreateTestBuffer(int w, int h) + { + var buffer = new Buffer2D(w, h); + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + buffer[x, y] = y * 100 + x; + } + } + return buffer; + } + + [Theory] + [InlineData(-1, 1, 0, 0)] + [InlineData(1, -1, 0, 0)] + [InlineData(0, 0, 1, 0)] + [InlineData(0, 0, 0, 42)] + public void Construct_WhenRectangleIsOutsideOfBufferBoundaries_Throws(int dx, int dy, int dWidth, int dHeight) + { + using (var buffer = new Buffer2D(10, 20)) + { + Rectangle r = buffer.FullRectangle(); + + r = new Rectangle(r.X+dx, r.Y+dy, r.Width + dWidth, r.Height + dHeight ); + + Assert.ThrowsAny( + () => + { + var area = new BufferArea(buffer, r); + }); + } + } + + [Theory] + [InlineData(2, 3, 2, 2)] + [InlineData(5, 4, 3, 2)] + public void Indexer(int rx, int ry, int x, int y) + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + Rectangle r = new Rectangle(rx, ry, 5, 6); + + BufferArea area = buffer.GetArea(r); + + int value = area[x, y]; + int expected = (ry + y) * 100 + rx + x; + Assert.Equal(expected, value); + } + } + + [Theory] + [InlineData(2, 3, 2, 5, 6)] + [InlineData(5, 4, 3, 6, 5)] + public void GetRowSpan(int rx, int ry, int y, int w, int h) + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + Rectangle r = new Rectangle(rx, ry, w, h); + + BufferArea area = buffer.GetArea(r); + + Span span = area.GetRowSpan(y); + + Assert.Equal(w, span.Length); + + for (int i = 0; i < w; i++) + { + int expected = (ry + y) * 100 + rx + i; + int value = span[i]; + + Assert.Equal(expected, value); + } + } + } + + [Fact] + public void GetSubArea() + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + + BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); + + var expectedRect = new Rectangle(10, 12, 5, 5); + + Assert.Equal(buffer, area1.DestinationBuffer); + Assert.Equal(expectedRect, area1.Rectangle); + + int value00 = 12 * 100 + 10; + Assert.Equal(value00, area1[0, 0]); + } + } + + [Fact] + public void DangerousGetPinnableReference() + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + + ref int r = ref area0.GetReferenceToOrigo(); + + int expected = buffer[6, 8]; + Assert.Equal(expected, r); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/BufferTests.cs b/tests/ImageSharp.Tests/Memory/BufferTests.cs similarity index 69% rename from tests/ImageSharp.Tests/Common/BufferTests.cs rename to tests/ImageSharp.Tests/Memory/BufferTests.cs index e1883ec7f..e1efeb24e 100644 --- a/tests/ImageSharp.Tests/Common/BufferTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferTests.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Memory; -using Xunit; -using static SixLabors.ImageSharp.Tests.Common.TestStructs; - -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Memory { + using System; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Memory; + + using Xunit; + public unsafe class BufferTests { // ReSharper disable once ClassNeverInstantiated.Local @@ -36,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1111)] public void ConstructWithOwnArray(int count) { - using (Buffer buffer = new Buffer(count)) + using (Buffer buffer = new Buffer(count)) { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.NotNull(buffer.Array); @@ -50,8 +49,8 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1111)] public void ConstructWithExistingArray(int count) { - Foo[] array = new Foo[count]; - using (Buffer buffer = new Buffer(array)) + TestStructs.Foo[] array = new TestStructs.Foo[count]; + using (Buffer buffer = new Buffer(array)) { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.Equal(array, buffer.Array); @@ -62,13 +61,13 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void Clear() { - Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; - using (Buffer buffer = new Buffer(a)) + TestStructs.Foo[] a = { new TestStructs.Foo() { A = 1, B = 2 }, new TestStructs.Foo() { A = 3, B = 4 } }; + using (Buffer buffer = new Buffer(a)) { buffer.Clear(); - Assert.Equal(default(Foo), a[0]); - Assert.Equal(default(Foo), a[1]); + Assert.Equal(default(TestStructs.Foo), a[0]); + Assert.Equal(default(TestStructs.Foo), a[1]); } } @@ -102,11 +101,11 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(IndexerData))] public void Read(int length, int index) { - Foo[] a = Foo.CreateArray(length); + TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - using (Buffer buffer = new Buffer(a)) + using (Buffer buffer = new Buffer(a)) { - Foo element = buffer[index]; + TestStructs.Foo element = buffer[index]; Assert.Equal(a[index], element); } @@ -116,13 +115,13 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(IndexerData))] public void Write(int length, int index) { - Foo[] a = Foo.CreateArray(length); + TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - using (Buffer buffer = new Buffer(a)) + using (Buffer buffer = new Buffer(a)) { - buffer[index] = new Foo(666, 666); + buffer[index] = new TestStructs.Foo(666, 666); - Assert.Equal(new Foo(666, 666), a[index]); + Assert.Equal(new TestStructs.Foo(666, 666), a[index]); } } } @@ -130,7 +129,7 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void Dispose() { - Buffer buffer = new Buffer(42); + Buffer buffer = new Buffer(42); buffer.Dispose(); Assert.True(buffer.IsDisposedOrLostArrayOwnership); @@ -141,9 +140,9 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(123)] public void CastToSpan(int bufferLength) { - using (Buffer buffer = new Buffer(bufferLength)) + using (Buffer buffer = new Buffer(bufferLength)) { - Span span = buffer; + Span span = buffer; //Assert.Equal(buffer.Array, span.ToArray()); //Assert.Equal(0, span.Start); @@ -155,9 +154,9 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void Span() { - using (Buffer buffer = new Buffer(42)) + using (Buffer buffer = new Buffer(42)) { - Span span = buffer.Span; + Span span = buffer.Span; // Assert.Equal(buffer.Array, span.ToArray()); // Assert.Equal(0, span.Start); @@ -174,9 +173,9 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(123, 17)] public void WithStartOnly(int bufferLength, int start) { - using (Buffer buffer = new Buffer(bufferLength)) + using (Buffer buffer = new Buffer(bufferLength)) { - Span span = buffer.Slice(start); + Span span = buffer.Slice(start); Assert.SpanPointsTo(span, buffer, start); Assert.Equal(span.Length, bufferLength - start); @@ -188,9 +187,9 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(123, 17, 42)] public void WithStartAndLength(int bufferLength, int start, int spanLength) { - using (Buffer buffer = new Buffer(bufferLength)) + using (Buffer buffer = new Buffer(bufferLength)) { - Span span = buffer.Slice(start, spanLength); + Span span = buffer.Slice(start, spanLength); Assert.SpanPointsTo(span, buffer, start); Assert.Equal(span.Length, spanLength); @@ -201,8 +200,8 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void UnPinAndTakeArrayOwnership() { - Foo[] data = null; - using (Buffer buffer = new Buffer(42)) + TestStructs.Foo[] data = null; + using (Buffer buffer = new Buffer(42)) { data = buffer.TakeArrayOwnership(); Assert.True(buffer.IsDisposedOrLostArrayOwnership); @@ -217,10 +216,10 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void ReturnsPinnedPointerToTheBeginningOfArray() { - using (Buffer buffer = new Buffer(42)) + using (Buffer buffer = new Buffer(42)) { - Foo* actual = (Foo*)buffer.Pin(); - fixed (Foo* expected = buffer.Array) + TestStructs.Foo* actual = (TestStructs.Foo*)buffer.Pin(); + fixed (TestStructs.Foo* expected = buffer.Array) { Assert.Equal(expected, actual); } @@ -230,7 +229,7 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void SecondCallReturnsTheSamePointer() { - using (Buffer buffer = new Buffer(42)) + using (Buffer buffer = new Buffer(42)) { IntPtr ptr1 = buffer.Pin(); IntPtr ptr2 = buffer.Pin(); @@ -242,7 +241,7 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void WhenCalledOnDisposedBuffer_ThrowsInvalidOperationException() { - Buffer buffer = new Buffer(42); + Buffer buffer = new Buffer(42); buffer.Dispose(); Assert.Throws(() => buffer.Pin()); diff --git a/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs similarity index 95% rename from tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs rename to tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs index 88d8a73e8..5cdbe638a 100644 --- a/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs +++ b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using SixLabors.ImageSharp.Memory; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Memory { + using System; + + using SixLabors.ImageSharp.Memory; + + using Xunit; + public class Fast2DArrayTests { private static readonly float[,] FloydSteinbergMatrix = diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs similarity index 89% rename from tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs rename to tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs index 7b3d337ba..fdfd4c4b7 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs @@ -1,14 +1,15 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Linq; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; + // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Memory { + using SixLabors.ImageSharp.Memory; + + using Xunit; + /// /// Tests the class. /// diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs similarity index 63% rename from tests/ImageSharp.Tests/Common/BufferSpanTests.cs rename to tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs index fb51880f3..395c32546 100644 --- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs +++ b/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs @@ -1,17 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; -using static SixLabors.ImageSharp.Tests.Common.TestStructs; - -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Memory { - public unsafe class SpanTests + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + using SixLabors.ImageSharp.Memory; + using SixLabors.ImageSharp.Tests.Common; + + using Xunit; + + public unsafe class SpanUtilityTests { // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert @@ -20,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Common { ref T1 bb = ref Unsafe.As(ref b); - True(Unsafe.AreSame(ref a, ref bb), "References are not same!"); + Assert.True(Unsafe.AreSame(ref a, ref bb), "References are not same!"); } } @@ -42,15 +43,15 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void AsBytes() { - Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; + TestStructs.Foo[] fooz = { new TestStructs.Foo(1, 2), new TestStructs.Foo(3, 4), new TestStructs.Foo(5, 6) }; - using (Buffer colorBuf = new Buffer(fooz)) + using (Buffer colorBuf = new Buffer(fooz)) { - Span orig = colorBuf.Slice(1); + Span orig = colorBuf.Slice(1); Span asBytes = orig.AsBytes(); // Assert.Equal(asBytes.Start, sizeof(Foo)); - Assert.Equal(orig.Length * Unsafe.SizeOf(), asBytes.Length); + Assert.Equal(orig.Length * Unsafe.SizeOf(), asBytes.Length); Assert.SameRefs(ref orig.DangerousGetPinnableReference(), ref asBytes.DangerousGetPinnableReference()); } } @@ -60,10 +61,10 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void Basic() { - Foo[] array = Foo.CreateArray(3); + TestStructs.Foo[] array = TestStructs.Foo.CreateArray(3); // Act: - Span span = new Span(array); + Span span = new Span(array); // Assert: Assert.Equal(array, span.ToArray()); @@ -74,11 +75,11 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void WithStart() { - Foo[] array = Foo.CreateArray(4); + TestStructs.Foo[] array = TestStructs.Foo.CreateArray(4); int start = 2; // Act: - Span span = new Span(array, start); + Span span = new Span(array, start); // Assert: Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); @@ -88,11 +89,11 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void WithStartAndLength() { - Foo[] array = Foo.CreateArray(10); + TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10); int start = 2; int length = 3; // Act: - Span span = new Span(array, start, length); + Span span = new Span(array, start, length); // Assert: Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); @@ -105,12 +106,12 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void StartOnly() { - Foo[] array = Foo.CreateArray(5); + TestStructs.Foo[] array = TestStructs.Foo.CreateArray(5); int start0 = 2; int start1 = 2; int totalOffset = start0 + start1; - Span span = new Span(array, start0); + Span span = new Span(array, start0); // Act: span = span.Slice(start1); @@ -123,13 +124,13 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void StartAndLength() { - Foo[] array = Foo.CreateArray(10); + TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10); int start0 = 2; int start1 = 2; int totalOffset = start0 + start1; int sliceLength = 3; - Span span = new Span(array, start0); + Span span = new Span(array, start0); // Act: span = span.Slice(start1, sliceLength); @@ -176,10 +177,10 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(IndexerData))] public void Read(int length, int start, int index) { - Foo[] a = Foo.CreateArray(length); - Span span = new Span(a, start); + TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); + Span span = new Span(a, start); - Foo element = span[index]; + TestStructs.Foo element = span[index]; Assert.Equal(a[start + index], element); } @@ -188,12 +189,12 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(IndexerData))] public void Write(int length, int start, int index) { - Foo[] a = Foo.CreateArray(length); - Span span = new Span(a, start); + TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); + Span span = new Span(a, start); - span[index] = new Foo(666, 666); + span[index] = new TestStructs.Foo(666, 666); - Assert.Equal(new Foo(666, 666), a[start + index]); + Assert.Equal(new TestStructs.Foo(666, 666), a[start + index]); } [Theory] @@ -203,15 +204,15 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(10, 1, 1, 7)] public void AsBytes_Read(int length, int start, int index, int byteOffset) { - Foo[] a = Foo.CreateArray(length); - Span span = new Span(a, start); + TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); + Span span = new Span(a, start); Span bytes = span.AsBytes(); - byte actual = bytes[index * Unsafe.SizeOf() + byteOffset]; + byte actual = bytes[index * Unsafe.SizeOf() + byteOffset]; - ref byte baseRef = ref Unsafe.As(ref a[0]); - byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf() + byteOffset); + ref byte baseRef = ref Unsafe.As(ref a[0]); + byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf() + byteOffset); Assert.Equal(expected, actual); } @@ -223,9 +224,9 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(3, 4)] public void DangerousGetPinnableReference(int start, int length) { - Foo[] a = Foo.CreateArray(length); - Span span = new Span(a, start); - ref Foo r = ref span.DangerousGetPinnableReference(); + TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); + Span span = new Span(a, start); + ref TestStructs.Foo r = ref span.DangerousGetPinnableReference(); Assert.True(Unsafe.AreSame(ref a[start], ref r)); } @@ -263,11 +264,11 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1500)] public void GenericToOwnType(int count) { - Foo[] source = Foo.CreateArray(count + 2); - Foo[] dest = new Foo[count + 5]; + TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); + TestStructs.Foo[] dest = new TestStructs.Foo[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + Span apSource = new Span(source, 1); + Span apDest = new Span(dest, 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -286,11 +287,11 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1500)] public void GenericToOwnType_Aligned(int count) { - AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); - AlignedFoo[] dest = new AlignedFoo[count + 5]; + TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); + TestStructs.AlignedFoo[] dest = new TestStructs.AlignedFoo[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + Span apSource = new Span(source, 1); + Span apDest = new Span(dest, 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -332,22 +333,22 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1500)] public void GenericToBytes(int count) { - int destCount = count * sizeof(Foo); - Foo[] source = Foo.CreateArray(count + 2); - byte[] dest = new byte[destCount + sizeof(Foo) * 2]; + int destCount = count * sizeof(TestStructs.Foo); + TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); + byte[] dest = new byte[destCount + sizeof(TestStructs.Foo) * 2]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, sizeof(Foo)); + Span apSource = new Span(source, 1); + Span apDest = new Span(dest, sizeof(TestStructs.Foo)); - SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(Foo)); + SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.Foo)); AssertNotDefault(source, 1); - Assert.False(ElementsAreEqual(source, dest, 0)); - Assert.True(ElementsAreEqual(source, dest, 1)); - Assert.True(ElementsAreEqual(source, dest, 2)); - Assert.True(ElementsAreEqual(source, dest, count - 1)); - Assert.False(ElementsAreEqual(source, dest, count)); + Assert.False((bool)ElementsAreEqual(source, dest, 0)); + Assert.True((bool)ElementsAreEqual(source, dest, 1)); + Assert.True((bool)ElementsAreEqual(source, dest, 2)); + Assert.True((bool)ElementsAreEqual(source, dest, count - 1)); + Assert.False((bool)ElementsAreEqual(source, dest, count)); } [Theory] @@ -355,22 +356,22 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1500)] public void GenericToBytes_Aligned(int count) { - int destCount = count * sizeof(Foo); - AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); - byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2]; + int destCount = count * sizeof(TestStructs.Foo); + TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); + byte[] dest = new byte[destCount + sizeof(TestStructs.AlignedFoo) * 2]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, sizeof(AlignedFoo)); + Span apSource = new Span(source, 1); + Span apDest = new Span(dest, sizeof(TestStructs.AlignedFoo)); - SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(AlignedFoo)); + SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.AlignedFoo)); AssertNotDefault(source, 1); - Assert.False(ElementsAreEqual(source, dest, 0)); - Assert.True(ElementsAreEqual(source, dest, 1)); - Assert.True(ElementsAreEqual(source, dest, 2)); - Assert.True(ElementsAreEqual(source, dest, count - 1)); - Assert.False(ElementsAreEqual(source, dest, count)); + Assert.False((bool)ElementsAreEqual(source, dest, 0)); + Assert.True((bool)ElementsAreEqual(source, dest, 1)); + Assert.True((bool)ElementsAreEqual(source, dest, 2)); + Assert.True((bool)ElementsAreEqual(source, dest, count - 1)); + Assert.False((bool)ElementsAreEqual(source, dest, count)); } [Theory] @@ -389,9 +390,9 @@ namespace SixLabors.ImageSharp.Tests.Common AssertNotDefault(source, 1); - Assert.True(ElementsAreEqual(source, dest, 0)); - Assert.True(ElementsAreEqual(source, dest, count - 1)); - Assert.False(ElementsAreEqual(source, dest, count)); + Assert.True((bool)ElementsAreEqual(source, dest, 0)); + Assert.True((bool)ElementsAreEqual(source, dest, count - 1)); + Assert.False((bool)ElementsAreEqual(source, dest, count)); } [Theory] @@ -399,22 +400,22 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(1500)] public void BytesToGeneric(int count) { - int srcCount = count * sizeof(Foo); + int srcCount = count * sizeof(TestStructs.Foo); byte[] source = CreateTestBytes(srcCount); - Foo[] dest = new Foo[count + 2]; + TestStructs.Foo[] dest = new TestStructs.Foo[count + 2]; Span apSource = new Span(source); - Span apDest = new Span(dest); + Span apDest = new Span(dest); - SpanHelper.Copy(apSource, apDest.AsBytes(), count * sizeof(Foo)); + SpanHelper.Copy(apSource, apDest.AsBytes(), count * sizeof(TestStructs.Foo)); - AssertNotDefault(source, sizeof(Foo) + 1); + AssertNotDefault(source, sizeof(TestStructs.Foo) + 1); AssertNotDefault(dest, 1); - Assert.True(ElementsAreEqual(dest, source, 0)); - Assert.True(ElementsAreEqual(dest, source, 1)); - Assert.True(ElementsAreEqual(dest, source, count - 1)); - Assert.False(ElementsAreEqual(dest, source, count)); + Assert.True((bool)ElementsAreEqual(dest, source, 0)); + Assert.True((bool)ElementsAreEqual(dest, source, 1)); + Assert.True((bool)ElementsAreEqual(dest, source, count - 1)); + Assert.False((bool)ElementsAreEqual(dest, source, count)); } [Fact] @@ -436,29 +437,29 @@ namespace SixLabors.ImageSharp.Tests.Common } } - internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) + internal static bool ElementsAreEqual(TestStructs.Foo[] array, byte[] rawArray, int index) { - fixed (Foo* pArray = array) + fixed (TestStructs.Foo* pArray = array) fixed (byte* pRaw = rawArray) { - Foo* pCasted = (Foo*)pRaw; + TestStructs.Foo* pCasted = (TestStructs.Foo*)pRaw; - Foo val1 = pArray[index]; - Foo val2 = pCasted[index]; + TestStructs.Foo val1 = pArray[index]; + TestStructs.Foo val2 = pCasted[index]; return val1.Equals(val2); } } - internal static bool ElementsAreEqual(AlignedFoo[] array, byte[] rawArray, int index) + internal static bool ElementsAreEqual(TestStructs.AlignedFoo[] array, byte[] rawArray, int index) { - fixed (AlignedFoo* pArray = array) + fixed (TestStructs.AlignedFoo* pArray = array) fixed (byte* pRaw = rawArray) { - AlignedFoo* pCasted = (AlignedFoo*)pRaw; + TestStructs.AlignedFoo* pCasted = (TestStructs.AlignedFoo*)pRaw; - AlignedFoo val1 = pArray[index]; - AlignedFoo val2 = pCasted[index]; + TestStructs.AlignedFoo val1 = pArray[index]; + TestStructs.AlignedFoo val2 = pCasted[index]; return val1.Equals(val2); } diff --git a/tests/ImageSharp.Tests/Common/TestStructs.cs b/tests/ImageSharp.Tests/Memory/TestStructs.cs similarity index 96% rename from tests/ImageSharp.Tests/Common/TestStructs.cs rename to tests/ImageSharp.Tests/Memory/TestStructs.cs index 87cf9a632..608e3c6cb 100644 --- a/tests/ImageSharp.Tests/Common/TestStructs.cs +++ b/tests/ImageSharp.Tests/Memory/TestStructs.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Memory { + using Xunit; + public static class TestStructs { public struct Foo diff --git a/tests/ImageSharp.Tests/TestBase.cs b/tests/ImageSharp.Tests/TestBase.cs deleted file mode 100644 index f83c428fc..000000000 --- a/tests/ImageSharp.Tests/TestBase.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Reflection; -using SixLabors.ImageSharp.Formats; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// The test base class. Inherit from this class for any image manipulation tests. - /// - public abstract class TestBase - { - /// - /// Creates the image output directory. - /// - /// The path. - /// The path parts. - /// - /// The . - /// - protected string CreateOutputDirectory(string path, params string[] pathParts) - { - path = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, path); - - if (pathParts != null && pathParts.Length > 0) - { - path = Path.Combine(path, Path.Combine(pathParts)); - } - - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - return path; - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index d3c40f86a..f56802e54 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -38,39 +38,34 @@ namespace SixLabors.ImageSharp.Tests /// private byte[] bytes; - /// - /// The file. - /// - private readonly string file; - /// /// Initializes a new instance of the class. /// /// The file. private TestFile(string file) { - this.file = file; + this.FullPath = file; } /// /// Gets the image bytes. /// - public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.file)); + public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath)); /// - /// The file name. + /// The full path to file. /// - public string FilePath => this.file; + public string FullPath { get; } /// /// The file name. /// - public string FileName => Path.GetFileName(this.file); + public string FileName => Path.GetFileName(this.FullPath); /// /// The file name without extension. /// - public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.file); + public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); /// /// Gets the image with lazy initialization. @@ -116,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests /// public string GetFileName(object value) { - return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.file)}"; + return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; } /// diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 4c96b1c50..dbcacb4f3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Linq; +// ReSharper disable InconsistentNaming // ReSharper disable MemberHidesStaticFromOuterClass namespace SixLabors.ImageSharp.Tests @@ -102,18 +103,26 @@ namespace SixLabors.ImageSharp.Tests public const string Snake = "Jpg/baseline/Snake.jpg"; public const string Lake = "Jpg/baseline/Lake.jpg"; public const string Jpeg400 = "Jpg/baseline/jpeg400jfif.jpg"; - public const string Jpeg420 = "Jpg/baseline/jpeg420exif.jpg"; + public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg"; public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; - public const string Testimgorig = "Jpg/baseline/testorig.jpg"; + public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; + public const string Testorig420 = "Jpg/baseline/testorig.jpg"; public static readonly string[] All = { Cmyk, Ycck, Exif, Floorplan, Calliphora, Turtle, GammaDalaiLamaGray, - Hiyamugi, Jpeg400, Jpeg420, Jpeg444, + Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, }; } + public class Issues + { + public const string CriticalEOF214 = "Jpg/issues/Issue214-CriticalEOF.jpg"; + public const string MissingFF00ProgressiveGirl159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg"; + public const string BadCoeffsProgressive178 = "Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg"; + } + public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagePixelsAreDifferentException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs similarity index 74% rename from tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagePixelsAreDifferentException.cs rename to tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index 56bf6e90b..e5f031b50 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagePixelsAreDifferentException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -5,12 +5,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison using System.Linq; using System.Text; - public class ImagePixelsAreDifferentException : ImagesSimilarityException + public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException { public ImageSimilarityReport[] Reports { get; } - public ImagePixelsAreDifferentException(IEnumerable reports) - : base("Images are not similar enough!" + StringifyReports(reports)) + public ImageDifferenceIsOverThresholdException(IEnumerable reports) + : base("Image difference is over threshold!" + StringifyReports(reports)) { this.Reports = reports.ToArray(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index 74f46a869..4fabd6076 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison IEnumerable reports = comparer.CompareImages(expected, actual); if (reports.Any()) { - throw new ImagePixelsAreDifferentException(reports); + throw new ImageDifferenceIsOverThresholdException(reports); } } @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison if (cleanedReports.Any()) { - throw new ImagePixelsAreDifferentException(cleanedReports); + throw new ImageDifferenceIsOverThresholdException(cleanedReports); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 22a8d2cff..8a992b17d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -20,11 +20,12 @@ } public static ImageSimilarityReport Empty => - new ImageSimilarityReport(null, null, Enumerable.Empty(), null); + new ImageSimilarityReport(null, null, Enumerable.Empty(), 0f); + // TODO: This should not be a nullable value! public float? TotalNormalizedDifference { get; } - public string DifferencePercentage => this.TotalNormalizedDifference.HasValue + public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue ? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%" : "?"; @@ -46,7 +47,7 @@ var sb = new StringBuilder(); if (this.TotalNormalizedDifference.HasValue) { - sb.AppendLine($"Total difference: {this.DifferencePercentage}"); + sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); } int max = Math.Min(5, this.Differences.Length); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 88c69a979..1b3ebf016 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests /// Utility class to provide information about the test image & the test case for the test code, /// and help managing IO. /// - public class ImagingTestCaseUtility : TestBase + public class ImagingTestCaseUtility { /// /// Name of the TPixel in the owner @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests internal string GetTestOutputDir() { string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); - return this.CreateOutputDirectory(testGroupName); + return TestEnvironment.CreateOutputDirectory(testGroupName); } public static void ModifyPixel(ImageBase img, int x, int y, byte perChannelChange) diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 50974cef8..7725994c4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Utility class to measure the execution of an operation. It can be used either by inheritance or by composition. /// - public class MeasureFixture : TestBase + public class MeasureFixture { /// /// Value indicating whether priniting is enabled. diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index 1bd0f77d2..dd8a08fcd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -27,13 +27,15 @@ namespace SixLabors.ImageSharp.Tests private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\External\ReferenceOutput"; + private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; + private static Lazy solutionDirectoryFullPath = new Lazy(GetSolutionDirectoryFullPathImpl); private static Lazy runsOnCi = new Lazy( () => { bool isCi; - return bool.TryParse(Environment.GetEnvironmentVariable("CI"), out isCi) && isCi; + return Boolean.TryParse(Environment.GetEnvironmentVariable("CI"), out isCi) && isCi; }); private static Lazy configuration = new Lazy(CreateDefaultConfiguration); @@ -120,25 +122,26 @@ namespace SixLabors.ImageSharp.Tests return directory.FullName; } + private static string GetFullPath(string relativePath) => + Path.Combine(SolutionDirectoryFullPath, relativePath) + .Replace('\\', Path.DirectorySeparatorChar); + /// /// Gets the correct full path to the Input Images directory. /// - internal static string InputImagesDirectoryFullPath => - Path.Combine(SolutionDirectoryFullPath, InputImagesRelativePath).Replace('\\', Path.DirectorySeparatorChar); - + internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); + /// /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) /// - internal static string ActualOutputDirectoryFullPath => Path.Combine( - SolutionDirectoryFullPath, - ActualOutputDirectoryRelativePath).Replace('\\', Path.DirectorySeparatorChar); + internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); /// /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) /// - internal static string ReferenceOutputDirectoryFullPath => Path.Combine( - SolutionDirectoryFullPath, - ReferenceOutputDirectoryRelativePath).Replace('\\', Path.DirectorySeparatorChar); + internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); + + internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); internal static string GetReferenceOutputFileName(string actualOutputFileName) => actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); @@ -163,7 +166,33 @@ namespace SixLabors.ImageSharp.Tests return format; } - internal static bool IsLinux => - System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + /// + /// Creates the image output directory. + /// + /// The path. + /// The path parts. + /// + /// The . + /// + internal static string CreateOutputDirectory(string path, params string[] pathParts) + { + path = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, path); + + if (pathParts != null && pathParts.Length > 0) + { + path = Path.Combine(path, Path.Combine(pathParts)); + } + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + return path; + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index cd2a22388..774fd4f7b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -13,6 +13,10 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests { + using System.Numerics; + + using SixLabors.ImageSharp.Memory; + public static class TestImageExtensions { /// @@ -187,5 +191,21 @@ namespace SixLabors.ImageSharp.Tests return image; } + + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) + { + var image = new Image(buffer.Width, buffer.Height); + + Span pixels = image.Pixels; + + for (int i = 0; i < buffer.Length; i++) + { + float value = buffer[i] * scale; + var v = new Vector4(value, value, value, 1f); + pixels[i].PackFromVector4(v); + } + + return image; + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index 29acabdc4..f131f51f2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests var comparer = ImageComparer.Tolerant(); - ImagePixelsAreDifferentException ex = Assert.ThrowsAny( + ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny( () => { comparer.VerifySimilarity(image, clone); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs index 34300c56e..45ac2d6cc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests { ImagingTestCaseUtility.ModifyPixel(image, 3, 1, 1); - Assert.ThrowsAny( + Assert.ThrowsAny( () => { image.CompareToOriginal(provider, ImageComparer.Exact); diff --git a/tests/Images/External b/tests/Images/External index 086c854f0..f99c2ea41 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 086c854f001e3bb1fa9085e76ba902171140dcc6 +Subproject commit f99c2ea41419cb3b3e80e5beeab611682252df78 diff --git a/tests/Images/Input/Jpg/baseline/jpeg420small.jpg b/tests/Images/Input/Jpg/baseline/jpeg420small.jpg new file mode 100644 index 000000000..8e5d1c7bd --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/jpeg420small.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac2cbbe4e6240cff33468e780eaf3de8a1800e5b8cdce5a9959268c17d566e92 +size 5276 diff --git a/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg b/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg new file mode 100644 index 000000000..34bc89b2f --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98c5a53bd06e1a2d6c40614c19fbc360f5ed8b16c83da0c3ee295a0a2e080a00 +size 60927 diff --git a/tests/Images/Input/Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg b/tests/Images/Input/Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg new file mode 100644 index 000000000..aee0182a6 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f8b4a062931424bedc6ce9a6b02fb8eec59cd8a386b09a1c7a790a041cbea89 +size 279270 diff --git a/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF .jpg b/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF .jpg new file mode 100644 index 000000000..165eb1669 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue214-CriticalEOF .jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab21c74db071f88a600984bf39c6c5d0448433c353b719584f60621d4c21dcb1 +size 35601