diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 01b68f9fa5..ceeba0faae 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -21,6 +21,10 @@ public static class HwIntrinsics public static ReadOnlySpan PermuteMaskSwitchInnerDWords8x32 => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0 }; + private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; + + internal static ReadOnlySpan ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF }; + private static ReadOnlySpan ShuffleMaskPad4Nx16 => new byte[] { 0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80 }; private static ReadOnlySpan ShuffleMaskSlice4Nx16 => new byte[] { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80 }; @@ -962,6 +966,49 @@ internal static void PackFromRgbPlanesAvx2Reduce( blueChannel = blueChannel.Slice(slice); destination = destination.Slice(slice); } + + internal static void UnpackToRgbPlanesAvx2Reduce( + ref Span redChannel, + ref Span greenChannel, + ref Span blueChannel, + ref ReadOnlySpan source) + { + ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 destRRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); + ref Vector256 destGRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); + ref Vector256 destBRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); + + Vector256 extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); + Vector256 extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); + Vector256 rgb, rg, bx; + Vector256 r, g, b; + + const int bytesPerRgbStride = 24; + int count = (int)((uint)source.Length / 8); + for (int i = 0; i < count; i++) + { + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); + + rgb = Avx2.Shuffle(rgb, extractRgbMask); + + rg = Avx2.UnpackLow(rgb, Vector256.Zero); + bx = Avx2.UnpackHigh(rgb, Vector256.Zero); + + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, Vector256.Zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, Vector256.Zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, Vector256.Zero).AsInt32()); + + Unsafe.Add(ref destRRef, i) = r; + Unsafe.Add(ref destGRef, i) = g; + Unsafe.Add(ref destBRef, i) = b; + } + + int sliceCount = count * 8; + redChannel = redChannel.Slice(sliceCount); + greenChannel = greenChannel.Slice(sliceCount); + blueChannel = blueChannel.Slice(sliceCount); + source = source.Slice(sliceCount); + } } } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs index 3ee2086cfe..fb62cb77e5 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs @@ -65,6 +65,25 @@ internal static void PackFromRgbPlanes( PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); } + [MethodImpl(InliningOptions.ShortMethod)] + internal static void UnpackToRgbPlanes( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + { + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); + + if (Avx2.IsSupported) + { + HwIntrinsics.UnpackToRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref source); + } + + UnpackToRgbPlanesScalar(redChannel, greenChannel, blueChannel, source); + } + private static void PackFromRgbPlanesScalarBatchedReduce( ref ReadOnlySpan redChannel, ref ReadOnlySpan greenChannel, @@ -200,5 +219,29 @@ private static void PackFromRgbPlanesRemainder( d.A = 255; } } + + private static void UnpackToRgbPlanesScalar( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + { + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); + + ref float r = ref MemoryMarshal.GetReference(redChannel); + ref float g = ref MemoryMarshal.GetReference(greenChannel); + ref float b = ref MemoryMarshal.GetReference(blueChannel); + ref Rgb24 rgb = ref MemoryMarshal.GetReference(source); + + for (int i = 0; i < source.Length; i++) + { + ref Rgb24 src = ref Unsafe.Add(ref rgb, i); + Unsafe.Add(ref r, i) = src.R; + Unsafe.Add(ref g, i) = src.G; + Unsafe.Add(ref b, i) = src.B; + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index d83edbd5bc..040f7092f7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -122,6 +122,21 @@ public void CopyTo(Span destination) } } + public static Block8x8 Load(ReadOnlySpan data) + { + Unsafe.SkipInit(out Block8x8 result); + result.LoadFrom(data); + return result; + } + + public void LoadFrom(ReadOnlySpan source) + { + for (int i = 0; i < Size; i++) + { + this[i] = source[i]; + } + } + /// /// Load raw 16bit integers from source. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 10501bada8..4c40659261 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -8,7 +8,7 @@ // namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - internal partial struct Block8x8F + internal partial struct Block8x8F { /// /// Level shift by +maximum/2, clip to [0, maximum] diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs new file mode 100644 index 0000000000..e50175ffa0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -0,0 +1,159 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; + +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal partial struct Block8x8F + { + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyFrom(ref float areaOrigin, int areaStride) => + CopyFrom1x1Scale(ref Unsafe.As(ref areaOrigin), ref Unsafe.As(ref this), areaStride); + + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + CopyTo1x1Scale(ref Unsafe.As(ref this), ref Unsafe.As(ref areaOrigin), areaStride); + return; + } + + if (horizontalScale == 2 && verticalScale == 2) + { + this.CopyTo2x2Scale(ref areaOrigin, areaStride); + return; + } + + // TODO: Optimize: implement all cases with scale-specific, loopless code! + this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); + } + + private void CopyTo2x2Scale(ref float areaOrigin, int areaStride) + { + ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); + int destStride = (int)((uint)areaStride / 2); + + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, nint row, nint destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + nint offset = 2 * row * destStride; + ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); + ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); + + var xyLeft = new Vector4(sLeft.X); + xyLeft.Z = sLeft.Y; + xyLeft.W = sLeft.Y; + + var zwLeft = new Vector4(sLeft.Z); + zwLeft.Z = sLeft.W; + zwLeft.W = sLeft.W; + + var xyRight = new Vector4(sRight.X); + xyRight.Z = sRight.Y; + xyRight.W = sRight.Y; + + var zwRight = new Vector4(sRight.Z); + zwRight.Z = sRight.W; + zwRight.W = sRight.W; + + dTopLeft = xyLeft; + Unsafe.Add(ref dTopLeft, 1) = zwLeft; + Unsafe.Add(ref dTopLeft, 2) = xyRight; + Unsafe.Add(ref dTopLeft, 3) = zwRight; + + dBottomLeft = xyLeft; + Unsafe.Add(ref dBottomLeft, 1) = zwLeft; + Unsafe.Add(ref dBottomLeft, 2) = xyRight; + Unsafe.Add(ref dBottomLeft, 3) = zwRight; + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 8; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; + + float value = this[y8 + x]; + nint baseIdx = (yy * areaStride) + xx; + + for (nint i = 0; i < verticalScale; i++, baseIdx += areaStride) + { + for (nint j = 0; j < horizontalScale; j++) + { + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; + } + } + } + } + } + + private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride) + { + int destStride = areaStride * sizeof(float); + + CopyRowImpl(ref origin, ref dest, destStride, 0); + CopyRowImpl(ref origin, ref dest, destStride, 1); + CopyRowImpl(ref origin, ref dest, destStride, 2); + CopyRowImpl(ref origin, ref dest, destStride, 3); + CopyRowImpl(ref origin, ref dest, destStride, 4); + CopyRowImpl(ref origin, ref dest, destStride, 5); + CopyRowImpl(ref origin, ref dest, destStride, 6); + CopyRowImpl(ref origin, ref dest, destStride, 7); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row) + { + origin = ref Unsafe.Add(ref origin, row * 8 * sizeof(float)); + dest = ref Unsafe.Add(ref dest, row * destStride); + Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); + } + } + + private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride) + { + int destStride = areaStride * sizeof(float); + + CopyRowImpl(ref origin, ref dest, destStride, 0); + CopyRowImpl(ref origin, ref dest, destStride, 1); + CopyRowImpl(ref origin, ref dest, destStride, 2); + CopyRowImpl(ref origin, ref dest, destStride, 3); + CopyRowImpl(ref origin, ref dest, destStride, 4); + CopyRowImpl(ref origin, ref dest, destStride, 5); + CopyRowImpl(ref origin, ref dest, destStride, 6); + CopyRowImpl(ref origin, ref dest, destStride, 7); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row) + { + origin = ref Unsafe.Add(ref origin, row * sourceStride); + dest = ref Unsafe.Add(ref dest, row * 8 * sizeof(float)); + Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs deleted file mode 100644 index b9fb18b8be..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -// ReSharper disable UseObjectOrCollectionInitializer -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components -{ - internal partial struct Block8x8F - { - /// - /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(in Buffer2DRegion region, int horizontalScale, int verticalScale) - { - ref float areaOrigin = ref region.GetReferenceToOrigin(); - this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - this.Copy1x1Scale(ref areaOrigin, areaStride); - return; - } - - if (horizontalScale == 2 && verticalScale == 2) - { - this.Copy2x2Scale(ref areaOrigin, areaStride); - return; - } - - // TODO: Optimize: implement all cases with scale-specific, loopless code! - this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); - } - - public void Copy1x1Scale(ref float areaOrigin, int areaStride) - { - ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref areaOrigin); - int destStride = areaStride * 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); - } - - [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)); - } - - private void Copy2x2Scale(ref float areaOrigin, int areaStride) - { - ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); - int destStride = areaStride / 2; - - this.WidenCopyRowImpl2x2(ref destBase, 0, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 1, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 2, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 3, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 4, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 5, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 6, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WidenCopyRowImpl2x2(ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref this.V0L, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - int offset = 2 * row * destStride; - ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); - ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); - - var xyLeft = new Vector4(sLeft.X); - xyLeft.Z = sLeft.Y; - xyLeft.W = sLeft.Y; - - var zwLeft = new Vector4(sLeft.Z); - zwLeft.Z = sLeft.W; - zwLeft.W = sLeft.W; - - var xyRight = new Vector4(sRight.X); - xyRight.Z = sRight.Y; - xyRight.W = sRight.Y; - - var zwRight = new Vector4(sRight.Z); - zwRight.Z = sRight.W; - zwRight.W = sRight.W; - - dTopLeft = xyLeft; - Unsafe.Add(ref dTopLeft, 1) = zwLeft; - Unsafe.Add(ref dTopLeft, 2) = xyRight; - Unsafe.Add(ref dTopLeft, 3) = zwRight; - - dBottomLeft = xyLeft; - Unsafe.Add(ref dBottomLeft, 1) = zwLeft; - Unsafe.Add(ref dBottomLeft, 2) = xyRight; - Unsafe.Add(ref dBottomLeft, 3) = zwRight; - } - - [MethodImpl(InliningOptions.ColdPath)] - private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) - { - for (int y = 0; y < 8; y++) - { - int yy = y * verticalScale; - int y8 = y * 8; - - for (int x = 0; x < 8; x++) - { - int xx = x * horizontalScale; - - float value = this[y8 + x]; - - for (int i = 0; i < verticalScale; i++) - { - int baseIdx = ((yy + i) * areaStride) + xx; - - for (int j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 6faaadac7b..d2f3e9664b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -352,6 +352,19 @@ public void NormalizeColorsAndRoundInPlace(float maximum) } } + public void DE_NormalizeColors(float maximum) + { + if (SimdUtils.HasVector8) + { + this.NormalizeColorsAndRoundInPlaceVector8(maximum); + } + else + { + this.NormalizeColorsInPlace(maximum); + this.RoundInPlace(); + } + } + /// /// Rounds all values in the block. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs new file mode 100644 index 0000000000..8edaa2efe3 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#if SUPPORTS_RUNTIME_INTRINSICS +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class CmykAvx : JpegColorConverterAvx + { + public CmykAvx(int precision) + : base(JpegColorSpace.Cmyk, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 c3Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + // Used for the color conversion + var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c = ref Unsafe.Add(ref c0Base, i); + ref Vector256 m = ref Unsafe.Add(ref c1Base, i); + ref Vector256 y = ref Unsafe.Add(ref c2Base, i); + Vector256 k = Unsafe.Add(ref c3Base, i); + + k = Avx.Multiply(k, scale); + c = Avx.Multiply(c, k); + m = Avx.Multiply(m, k); + y = Avx.Multiply(y, k); + } + } + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); + + public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) + { + ref Vector256 destC = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destM = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 destK = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + ref Vector256 srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + var scale = Vector256.Create(maxValue); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + Vector256 ctmp = Avx.Subtract(scale, Unsafe.Add(ref srcR, i)); + Vector256 mtmp = Avx.Subtract(scale, Unsafe.Add(ref srcG, i)); + Vector256 ytmp = Avx.Subtract(scale, Unsafe.Add(ref srcB, i)); + Vector256 ktmp = Avx.Min(ctmp, Avx.Min(mtmp, ytmp)); + + Vector256 kMask = Avx.CompareNotEqual(ktmp, scale); + + ctmp = Avx.And(Avx.Divide(Avx.Subtract(ctmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + + Unsafe.Add(ref destC, i) = Avx.Subtract(scale, Avx.Multiply(ctmp, scale)); + Unsafe.Add(ref destM, i) = Avx.Subtract(scale, Avx.Multiply(mtmp, scale)); + Unsafe.Add(ref destY, i) = Avx.Subtract(scale, Avx.Multiply(ytmp, scale)); + Unsafe.Add(ref destK, i) = Avx.Subtract(scale, ktmp); + } + } + } + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs new file mode 100644 index 0000000000..70d47b9b79 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class CmykScalar : JpegColorConverterScalar + { + public CmykScalar(int precision) + : base(JpegColorSpace.Cmyk, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) => + ConvertToRgbInplace(values, this.MaximumValue); + + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.MaximumValue, r, g, b); + + public static void ConvertToRgbInplace(in ComponentValues values, float maxValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + float scale = 1 / (maxValue * maxValue); + for (int i = 0; i < c0.Length; i++) + { + float c = c0[i]; + float m = c1[i]; + float y = c2[i]; + float k = c3[i]; + + k *= scale; + c0[i] = c * k; + c1[i] = m * k; + c2[i] = y * k; + } + } + + public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span r, Span g, Span b) + { + Span c = values.Component0; + Span m = values.Component1; + Span y = values.Component2; + Span k = values.Component3; + + for (int i = 0; i < c.Length; i++) + { + float ctmp = 255f - r[i]; + float mtmp = 255f - g[i]; + float ytmp = 255f - b[i]; + float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); + + if (ktmp >= 255f) + { + ctmp = 0f; + mtmp = 0f; + ytmp = 0f; + } + else + { + ctmp = (ctmp - ktmp) / (255f - ktmp); + mtmp = (mtmp - ktmp) / (255f - ktmp); + ytmp = (ytmp - ktmp) / (255f - ktmp); + } + + c[i] = maxValue - (ctmp * maxValue); + m[i] = maxValue - (mtmp * maxValue); + y[i] = maxValue - (ytmp * maxValue); + k[i] = maxValue - ktmp; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs new file mode 100644 index 0000000000..6d7688bcd8 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs @@ -0,0 +1,106 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class CmykVector : JpegColorConverterVector + { + public CmykVector(int precision) + : base(JpegColorSpace.Cmyk, precision) + { + } + + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector mBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector yBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector kBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c = ref Unsafe.Add(ref cBase, i); + ref Vector m = ref Unsafe.Add(ref mBase, i); + ref Vector y = ref Unsafe.Add(ref yBase, i); + Vector k = Unsafe.Add(ref kBase, i); + + k *= scale; + c *= k; + m *= k; + y *= k; + } + } + + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue); + + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, r, g, b); + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceRemainder(values, this.MaximumValue, r, g, b); + + public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, float maxValue, Span r, Span g, Span b) + { + ref Vector destC = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destM = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector destK = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(r)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(g)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(b)); + + // Used for the color conversion + var scale = new Vector(maxValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector ctmp = scale - Unsafe.Add(ref srcR, i); + Vector mtmp = scale - Unsafe.Add(ref srcG, i); + Vector ytmp = scale - Unsafe.Add(ref srcB, i); + Vector ktmp = Vector.Min(ctmp, Vector.Min(mtmp, ytmp)); + + var kMask = Vector.Equals(ktmp, scale); + ctmp = Vector.AndNot((ctmp - ktmp) / (scale - ktmp), kMask.As()); + mtmp = Vector.AndNot((mtmp - ktmp) / (scale - ktmp), kMask.As()); + ytmp = Vector.AndNot((ytmp - ktmp) / (scale - ktmp), kMask.As()); + + Unsafe.Add(ref destC, i) = scale - (ctmp * scale); + Unsafe.Add(ref destM, i) = scale - (mtmp * scale); + Unsafe.Add(ref destY, i) = scale - (ytmp * scale); + Unsafe.Add(ref destK, i) = scale - ktmp; + } + } + + public static void ConvertFromRgbInplaceRemainder(in ComponentValues values, float maxValue, Span r, Span g, Span b) + => CmykScalar.ConvertFromRgb(values, maxValue, r, g, b); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs new file mode 100644 index 0000000000..26e8791853 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#if SUPPORTS_RUNTIME_INTRINSICS +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static SixLabors.ImageSharp.SimdUtils; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class GrayscaleAvx : JpegColorConverterAvx + { + public GrayscaleAvx(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + // Used for the color conversion + var scale = Vector256.Create(1 / this.MaximumValue); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + c0 = Avx.Multiply(c0, scale); + } + } + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector256 destLuminance = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + ref Vector256 srcRed = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcGreen = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcBlue = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + // Used for the color conversion + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 r = ref Unsafe.Add(ref srcRed, i); + ref Vector256 g = ref Unsafe.Add(ref srcGreen, i); + ref Vector256 b = ref Unsafe.Add(ref srcBlue, i); + + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + Unsafe.Add(ref destLuminance, i) = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + } + } + } + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs new file mode 100644 index 0000000000..2e5129f328 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class GrayscaleScalar : JpegColorConverterScalar + { + public GrayscaleScalar(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgbInplace(values.Component0, this.MaximumValue); + + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertCoreInplaceFromRgb(values, r, g, b); + + internal static void ConvertToRgbInplace(Span values, float maxValue) + { + ref float valuesRef = ref MemoryMarshal.GetReference(values); + float scale = 1 / maxValue; + + for (nint i = 0; i < values.Length; i++) + { + Unsafe.Add(ref valuesRef, i) *= scale; + } + } + + internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + Span c0 = values.Component0; + + for (int i = 0; i < c0.Length; i++) + { + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + float luma = (0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i]); + c0[i] = luma; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs new file mode 100644 index 0000000000..0f903c0519 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class GrayScaleVector : JpegColorConverterVector + { + public GrayScaleVector(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + var scale = new Vector(1 / this.MaximumValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref cBase, i); + c0 *= scale; + } + } + + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => GrayscaleScalar.ConvertToRgbInplace(values.Component0, this.MaximumValue); + + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector destLuma = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + var rMult = new Vector(0.299f); + var gMult = new Vector(0.587f); + var bMult = new Vector(0.114f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector r = Unsafe.Add(ref srcR, i); + Vector g = Unsafe.Add(ref srcR, i); + Vector b = Unsafe.Add(ref srcR, i); + + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + Unsafe.Add(ref destLuma, i) = (rMult * r) + (gMult * g) + (bMult * b); + } + } + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => GrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs similarity index 75% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs index 93dfe46880..5fd97e3684 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs @@ -2,22 +2,24 @@ // Licensed under the Six Labors Split License. #if SUPPORTS_RUNTIME_INTRINSICS +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbAvx : JpegColorConverterAvx + internal sealed class RgbAvx : JpegColorConverterAvx { - public FromRgbAvx(int precision) + public RgbAvx(int precision) : base(JpegColorSpace.RGB, precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 rBase = @@ -40,6 +42,14 @@ public override void ConvertToRgbInplace(in ComponentValues values) b = Avx.Multiply(b, scale); } } + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + rLane.CopyTo(values.Component0); + gLane.CopyTo(values.Component1); + bLane.CopyTo(values.Component2); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs new file mode 100644 index 0000000000..a3de4493fd --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class RgbScalar : JpegColorConverterScalar + { + public RgbScalar(int precision) + : base(JpegColorSpace.RGB, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgbInplace(values, this.MaximumValue); + + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, r, g, b); + + internal static void ConvertToRgbInplace(ComponentValues values, float maxValue) + { + GrayscaleScalar.ConvertToRgbInplace(values.Component0, maxValue); + GrayscaleScalar.ConvertToRgbInplace(values.Component1, maxValue); + GrayscaleScalar.ConvertToRgbInplace(values.Component2, maxValue); + } + + internal static void ConvertFromRgb(ComponentValues values, Span r, Span g, Span b) + { + r.CopyTo(values.Component0); + g.CopyTo(values.Component1); + b.CopyTo(values.Component2); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs similarity index 56% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs index 24d430f0a6..ba36733804 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs @@ -1,22 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbVector : JpegColorConverterVector + internal sealed class RgbVector : JpegColorConverterVector { - public FromRgbVector(int precision) + public RgbVector(int precision) : base(JpegColorSpace.RGB, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) { ref Vector rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -39,8 +41,21 @@ protected override void ConvertCoreVectorizedInplace(in ComponentValues values) } } - protected override void ConvertCoreInplace(in ComponentValues values) => - FromRgbScalar.ConvertCoreInplace(values, this.MaximumValue); + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => RgbScalar.ConvertToRgbInplace(values, this.MaximumValue); + + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) + { + r.CopyTo(values.Component0); + g.CopyTo(values.Component1); + b.CopyTo(values.Component2); + } + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => RgbScalar.ConvertFromRgb(values, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs new file mode 100644 index 0000000000..219584b05e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs @@ -0,0 +1,125 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#if SUPPORTS_RUNTIME_INTRINSICS +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static SixLabors.ImageSharp.SimdUtils; + +// ReSharper disable ImpureMethodCallOnReadonlyValueField +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class YCbCrAvx : JpegColorConverterAvx + { + public YCbCrAvx(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(-this.HalfValue); + var scale = Vector256.Create(1 / this.MaximumValue); + var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); + + // Walking 8 elements at one step: + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); + + Vector256 y = c0; + Vector256 cb = Avx.Add(c1, chromaOffset); + Vector256 cr = Avx.Add(c2, chromaOffset); + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); + Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); + Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); + + r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale); + g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale); + b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale); + + c0 = r; + c1 = g; + c2 = b; + } + } + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector256 srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(this.HalfValue); + + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + Vector256 r = Unsafe.Add(ref srcR, i); + Vector256 g = Unsafe.Add(ref srcG, i); + Vector256 b = Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + + Unsafe.Add(ref destY, i) = y; + Unsafe.Add(ref destCb, i) = cb; + Unsafe.Add(ref destCr, i) = cr; + } + } + } + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs new file mode 100644 index 0000000000..7fd6283287 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class YCbCrScalar : JpegColorConverterScalar + { + // derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; + + public YCbCrScalar(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); + + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.HalfValue, r, g, b); + + public static void ConvertToRgbInplace(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + float scale = 1 / maxValue; + + for (int i = 0; i < c0.Length; i++) + { + float y = c0[i]; + float cb = c1[i] - halfValue; + float cr = c2[i] - halfValue; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; + } + } + + public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) + { + Span y = values.Component0; + Span cb = values.Component1; + Span cr = values.Component2; + + for (int i = 0; i < y.Length; i++) + { + float r = rLane[i]; + float g = gLane[i]; + float b = bLane[i]; + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs new file mode 100644 index 0000000000..f747d5523d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// ReSharper disable ImpureMethodCallOnReadonlyValueField +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class YCbCrVector : JpegColorConverterVector + { + public YCbCrVector(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + var chromaOffset = new Vector(-this.HalfValue); + + var scale = new Vector(1 / this.MaximumValue); + var rCrMult = new Vector(YCbCrScalar.RCrMult); + var gCbMult = new Vector(-YCbCrScalar.GCbMult); + var gCrMult = new Vector(-YCbCrScalar.GCrMult); + var bCbMult = new Vector(YCbCrScalar.BCbMult); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + ref Vector c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector c2 = ref Unsafe.Add(ref c2Base, i); + Vector y = Unsafe.Add(ref c0Base, i); + Vector cb = Unsafe.Add(ref c1Base, i) + chromaOffset; + Vector cr = Unsafe.Add(ref c2Base, i) + chromaOffset; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); + + r = r.FastRound(); + g = g.FastRound(); + b = b.FastRound(); + r *= scale; + g *= scale; + b *= scale; + + c0 = r; + c1 = g; + c2 = b; + } + } + + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => YCbCrScalar.ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); + + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + var chromaOffset = new Vector(this.HalfValue); + + var rYMult = new Vector(0.299f); + var gYMult = new Vector(0.587f); + var bYMult = new Vector(0.114f); + + var rCbMult = new Vector(0.168736f); + var gCbMult = new Vector(0.331264f); + var bCbMult = new Vector(0.5f); + + var rCrMult = new Vector(0.5f); + var gCrMult = new Vector(0.418688f); + var bCrMult = new Vector(0.081312f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector r = Unsafe.Add(ref srcR, i); + Vector g = Unsafe.Add(ref srcG, i); + Vector b = Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); + Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); + } + } + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => YCbCrScalar.ConvertFromRgb(values, this.HalfValue, r, g, b); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs new file mode 100644 index 0000000000..f4549911ef --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +#if SUPPORTS_RUNTIME_INTRINSICS +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static SixLabors.ImageSharp.SimdUtils; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class YccKAvx : JpegColorConverterAvx + { + public YccKAvx(int precision) + : base(JpegColorSpace.Ycck, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 kBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(-this.HalfValue); + var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); + var max = Vector256.Create(this.MaximumValue); + var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); + + // Walking 8 elements at one step: + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + // k = kVals[i] / 256F; + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); + Vector256 y = c0; + Vector256 cb = Avx.Add(c1, chromaOffset); + Vector256 cr = Avx.Add(c2, chromaOffset); + Vector256 scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale); + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); + Vector256 g = + HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); + Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); + + r = Avx.Subtract(max, Avx.RoundToNearestInteger(r)); + g = Avx.Subtract(max, Avx.RoundToNearestInteger(g)); + b = Avx.Subtract(max, Avx.RoundToNearestInteger(b)); + + r = Avx.Multiply(r, scaledK); + g = Avx.Multiply(g, scaledK); + b = Avx.Multiply(b, scaledK); + + c0 = r; + c1 = g; + c2 = b; + } + } + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykAvx.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector256 srcR = ref destY; + ref Vector256 srcG = ref destCb; + ref Vector256 srcB = ref destCr; + + // Used for the color conversion + var maxSampleValue = Vector256.Create(this.MaximumValue); + + var chromaOffset = Vector256.Create(this.HalfValue); + + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + Vector256 r = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i)); + Vector256 g = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i)); + Vector256 b = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i)); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + + Unsafe.Add(ref destY, i) = y; + Unsafe.Add(ref destCb, i) = cb; + Unsafe.Add(ref destCr, i) = cr; + } + } + } + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs new file mode 100644 index 0000000000..dc58a70edf --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class YccKScalar : JpegColorConverterScalar + { + // derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; + + public YccKScalar(int precision) + : base(JpegColorSpace.Ycck, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b); + + public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + float scale = 1 / (maxValue * maxValue); + + for (int i = 0; i < values.Component0.Length; i++) + { + float y = c0[i]; + float cb = c1[i] - halfValue; + float cr = c2[i] - halfValue; + float scaledK = c3[i] * scale; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; + } + } + + public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); + + // cmyk -> ycck + Span c = values.Component0; + Span m = values.Component1; + Span y = values.Component2; + + for (int i = 0; i < y.Length; i++) + { + float r = maxValue - c[i]; + float g = maxValue - m[i]; + float b = maxValue - y[i]; + + // k value is passed untouched from rgb -> cmyk conversion + c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs new file mode 100644 index 0000000000..ef43dca408 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -0,0 +1,138 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class YccKVector : JpegColorConverterVector + { + public YccKVector(int precision) + : base(JpegColorSpace.Ycck, precision) + { + } + + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector kBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + var chromaOffset = new Vector(-this.HalfValue); + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); + var max = new Vector(this.MaximumValue); + var rCrMult = new Vector(YCbCrScalar.RCrMult); + var gCbMult = new Vector(-YCbCrScalar.GCbMult); + var gCrMult = new Vector(-YCbCrScalar.GCrMult); + var bCbMult = new Vector(YCbCrScalar.BCbMult); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + // k = kVals[i] / 256F; + ref Vector c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector c2 = ref Unsafe.Add(ref c2Base, i); + + Vector y = c0; + Vector cb = c1 + chromaOffset; + Vector cr = c2 + chromaOffset; + Vector scaledK = Unsafe.Add(ref kBase, i) * scale; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); + + r = (max - r.FastRound()) * scaledK; + g = (max - g.FastRound()) * scaledK; + b = (max - b.FastRound()) * scaledK; + + c0 = r; + c1 = g; + c2 = b; + } + } + + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykVector.ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector srcR = ref destY; + ref Vector srcG = ref destCb; + ref Vector srcB = ref destCr; + + var maxSampleValue = new Vector(this.MaximumValue); + + var chromaOffset = new Vector(this.HalfValue); + + var rYMult = new Vector(0.299f); + var gYMult = new Vector(0.587f); + var bYMult = new Vector(0.114f); + + var rCbMult = new Vector(0.168736f); + var gCbMult = new Vector(0.331264f); + var bCbMult = new Vector(0.5f); + + var rCrMult = new Vector(0.5f); + var gCrMult = new Vector(0.418688f); + var bCrMult = new Vector(0.081312f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector r = maxSampleValue - Unsafe.Add(ref srcR, i); + Vector g = maxSampleValue - Unsafe.Add(ref srcG, i); + Vector b = maxSampleValue - Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); + Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); + } + } + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + { + // rgb -> cmyk + CmykScalar.ConvertFromRgb(in values, this.MaximumValue, r, g, b); + + // cmyk -> ycck + YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs similarity index 86% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs index b333d32680..4af31abac9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs @@ -4,7 +4,7 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { @@ -26,7 +26,9 @@ protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) { } - public sealed override bool IsAvailable => Avx.IsSupported; + public static bool IsSupported => Avx.IsSupported; + + public sealed override bool IsAvailable => IsSupported; public sealed override int ElementsPerBatch => Vector256.Count; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs similarity index 66% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 8339206b4e..c2e01df998 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -3,10 +3,9 @@ using System; using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// /// Encapsulates the conversion of color channels from jpeg image to RGB channels. @@ -88,88 +87,130 @@ public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int /// The input/ouptut as a stack-only struct public abstract void ConvertToRgbInplace(in ComponentValues values); + /// + /// Converts RGB lanes to jpeg component values. + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + public abstract void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane); + /// /// Returns the s for all supported colorspaces and precisions. /// private static JpegColorConverterBase[] CreateConverters() { - var converters = new List(); + // 5 color types with 2 supported precisions: 8 bit & 12 bit + const int colorConvertersCount = 5 * 2; + + var converters = new JpegColorConverterBase[colorConvertersCount]; // 8-bit converters - converters.AddRange(GetYCbCrConverters(8)); - converters.AddRange(GetYccKConverters(8)); - converters.AddRange(GetCmykConverters(8)); - converters.AddRange(GetGrayScaleConverters(8)); - converters.AddRange(GetRgbConverters(8)); + converters[0] = GetYCbCrConverter(8); + converters[1] = GetYccKConverter(8); + converters[2] = GetCmykConverter(8); + converters[3] = GetGrayScaleConverter(8); + converters[4] = GetRgbConverter(8); // 12-bit converters - converters.AddRange(GetYCbCrConverters(12)); - converters.AddRange(GetYccKConverters(12)); - converters.AddRange(GetCmykConverters(12)); - converters.AddRange(GetGrayScaleConverters(12)); - converters.AddRange(GetRgbConverters(12)); + converters[5] = GetYCbCrConverter(12); + converters[6] = GetYccKConverter(12); + converters[7] = GetCmykConverter(12); + converters[8] = GetGrayScaleConverter(12); + converters[9] = GetRgbConverter(12); - return converters.Where(x => x.IsAvailable).ToArray(); + return converters; } /// /// Returns the s for the YCbCr colorspace. /// - private static IEnumerable GetYCbCrConverters(int precision) + private static JpegColorConverterBase GetYCbCrConverter(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromYCbCrAvx(precision); -#endif - yield return new FromYCbCrVector(precision); - yield return new FromYCbCrScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new YCbCrAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new YCbCrVector(precision); + } + + return new YCbCrScalar(precision); } /// /// Returns the s for the YccK colorspace. /// - private static IEnumerable GetYccKConverters(int precision) + private static JpegColorConverterBase GetYccKConverter(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromYccKAvx(precision); -#endif - yield return new FromYccKVector(precision); - yield return new FromYccKScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new YccKAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new YccKVector(precision); + } + + return new YccKScalar(precision); } /// /// Returns the s for the CMYK colorspace. /// - private static IEnumerable GetCmykConverters(int precision) + private static JpegColorConverterBase GetCmykConverter(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromCmykAvx(precision); -#endif - yield return new FromCmykVector(precision); - yield return new FromCmykScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new CmykAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new CmykVector(precision); + } + + return new CmykScalar(precision); } /// /// Returns the s for the gray scale colorspace. /// - private static IEnumerable GetGrayScaleConverters(int precision) + private static JpegColorConverterBase GetGrayScaleConverter(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromGrayscaleAvx(precision); -#endif - yield return new FromGrayScaleVector(precision); - yield return new FromGrayscaleScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new GrayscaleAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new GrayScaleVector(precision); + } + + return new GrayscaleScalar(precision); } /// /// Returns the s for the RGB colorspace. /// - private static IEnumerable GetRgbConverters(int precision) + private static JpegColorConverterBase GetRgbConverter(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromRgbAvx(precision); -#endif - yield return new FromRgbVector(precision); - yield return new FromRgbScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new RgbAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new RgbScalar(precision); + } + + return new GrayscaleScalar(precision); } /// @@ -228,7 +269,26 @@ public ComponentValues(IReadOnlyList> componentBuffers, int row) /// /// List of component color processors. /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) + public ComponentValues(IReadOnlyList processors, int row) + { + DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); + + this.ComponentCount = processors.Count; + + this.Component0 = processors[0].GetColorBufferRowSpan(row); + + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; + } + + /// + /// Initializes a new instance of the struct. + /// + /// List of component color processors. + /// Row to convert + public ComponentValues(IReadOnlyList processors, int row) { DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs similarity index 90% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs index 8cf8ad1d92..9de8e16a27 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs new file mode 100644 index 0000000000..e618652989 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + internal abstract partial class JpegColorConverterBase + { + /// + /// abstract base for implementations + /// based on API. + /// + /// + /// Converters of this family can work with data of any size. + /// Even though real life data is guaranteed to be of size + /// divisible by 8 newer SIMD instructions like AVX512 won't work with + /// such data out of the box. These converters have fallback code + /// for 'remainder' data. + /// + internal abstract class JpegColorConverterVector : JpegColorConverterBase + { + protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) + { + } + + /// + /// Gets a value indicating whether this converter is supported on current hardware. + /// + public static bool IsSupported => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; + + /// + public sealed override bool IsAvailable => IsSupported; + + public override int ElementsPerBatch => Vector.Count; + + /// + public sealed override void ConvertToRgbInplace(in ComponentValues values) + { + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); + + int length = values.Component0.Length; + int remainder = (int)((uint)length % (uint)Vector.Count); + + int simdCount = length - remainder; + if (simdCount > 0) + { + this.ConvertToRgbInplaceVectorized(values.Slice(0, simdCount)); + } + + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX1/AVX2 implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels + if (remainder > 0) + { + this.ConvertToRgbInplaceScalarRemainder(values.Slice(simdCount, remainder)); + } + } + + /// + public sealed override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + { + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); + + int length = values.Component0.Length; + int remainder = (int)((uint)length % (uint)Vector.Count); + + int simdCount = length - remainder; + if (simdCount > 0) + { + this.ConvertFromRgbVectorized( + values.Slice(0, simdCount), + r.Slice(0, simdCount), + g.Slice(0, simdCount), + b.Slice(0, simdCount)); + } + + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX1/AVX2 implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels + if (remainder > 0) + { + this.ConvertFromRgbScalarRemainder( + values.Slice(simdCount, remainder), + r.Slice(simdCount, remainder), + g.Slice(simdCount, remainder), + b.Slice(simdCount, remainder)); + } + } + + /// + /// Converts planar jpeg component values in + /// to RGB color space inplace using API. + /// + /// The input/ouptut as a stack-only struct + protected abstract void ConvertToRgbInplaceVectorized(in ComponentValues values); + + /// + /// Converts remainder of the planar jpeg component values after + /// conversion in . + /// + /// The input/ouptut as a stack-only struct + protected abstract void ConvertToRgbInplaceScalarRemainder(in ComponentValues values); + + /// + /// Converts RGB lanes to jpeg component values using API. + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); + + /// + /// Converts remainder of RGB lanes to jpeg component values after + /// conversion in . + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs deleted file mode 100644 index 7955600b5a..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromCmykAvx : JpegColorConverterAvx - { - public FromCmykAvx(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k = Avx.Multiply(k, scale); - c = Avx.Multiply(c, k); - m = Avx.Multiply(m, k); - y = Avx.Multiply(y, k); - } - } - } - } -} -#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs deleted file mode 100644 index 65301c1b31..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromCmykScalar : JpegColorConverterScalar - { - public FromCmykScalar(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values, this.MaximumValue); - - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i]; - float m = c1[i]; - float y = c2[i]; - float k = c3[i]; - - k *= scale; - c0[i] = c * k; - c1[i] = m * k; - c2[i] = y * k; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs deleted file mode 100644 index 54be7512c1..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromCmykVector : JpegColorConverterVector - { - public FromCmykVector(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { - ref Vector cBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector mBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector yBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - ref Vector c = ref Unsafe.Add(ref cBase, i); - ref Vector m = ref Unsafe.Add(ref mBase, i); - ref Vector y = ref Unsafe.Add(ref yBase, i); - Vector k = Unsafe.Add(ref kBase, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs deleted file mode 100644 index 261d76a31b..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromGrayscaleAvx : JpegColorConverterAvx - { - public FromGrayscaleAvx(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - // Used for the color conversion - var scale = Vector256.Create(1 / this.MaximumValue); - - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - c0 = Avx.Multiply(c0, scale); - } - } - } - } -} -#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs deleted file mode 100644 index e1d94727ca..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromGrayscaleScalar : JpegColorConverterScalar - { - public FromGrayscaleScalar(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values.Component0, this.MaximumValue); - - internal static void ConvertCoreInplace(Span values, float maxValue) - { - ref float valuesRef = ref MemoryMarshal.GetReference(values); - float scale = 1 / maxValue; - - for (nint i = 0; i < values.Length; i++) - { - Unsafe.Add(ref valuesRef, i) *= scale; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs deleted file mode 100644 index a96fc8caf3..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromGrayScaleVector : JpegColorConverterVector - { - public FromGrayScaleVector(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { - ref Vector cBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - var scale = new Vector(1 / this.MaximumValue); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - ref Vector c0 = ref Unsafe.Add(ref cBase, i); - c0 *= scale; - } - } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs deleted file mode 100644 index c97da4f620..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromRgbScalar : JpegColorConverterScalar - { - public FromRgbScalar(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values, this.MaximumValue); - - internal static void ConvertCoreInplace(ComponentValues values, float maxValue) - { - FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue); - FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue); - FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs deleted file mode 100644 index 2167c8686d..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; - -// ReSharper disable ImpureMethodCallOnReadonlyValueField -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromYCbCrAvx : JpegColorConverterAvx - { - public FromYCbCrAvx(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - var chromaOffset = Vector256.Create(-this.HalfValue); - var scale = Vector256.Create(1 / this.MaximumValue); - var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); - var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); - var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); - var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector256 y = c0; - Vector256 cb = Avx.Add(c1, chromaOffset); - Vector256 cr = Avx.Add(c2, chromaOffset); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); - Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); - - r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale); - g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale); - b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale); - - c0 = r; - c1 = g; - c2 = b; - } - } - } - } -} -#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs deleted file mode 100644 index 2f8533c6cd..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromYCbCrScalar : JpegColorConverterScalar - { - // TODO: comments, derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public FromYCbCrScalar(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - float scale = 1 / maxValue; - - for (int i = 0; i < c0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs deleted file mode 100644 index 8559ac9875..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// ReSharper disable ImpureMethodCallOnReadonlyValueField -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromYCbCrVector : JpegColorConverterVector - { - public FromYCbCrVector(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { - ref Vector c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - var chromaOffset = new Vector(-this.HalfValue); - - var scale = new Vector(1 / this.MaximumValue); - var rCrMult = new Vector(FromYCbCrScalar.RCrMult); - var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); - var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); - var bCbMult = new Vector(FromYCbCrScalar.BCbMult); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector c2 = ref Unsafe.Add(ref c2Base, i); - Vector y = Unsafe.Add(ref c0Base, i); - Vector cb = Unsafe.Add(ref c1Base, i) + chromaOffset; - Vector cr = Unsafe.Add(ref c2Base, i) + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector r = y + (cr * rCrMult); - Vector g = y + (cb * gCbMult) + (cr * gCrMult); - Vector b = y + (cb * bCbMult); - - r = r.FastRound(); - g = g.FastRound(); - b = b.FastRound(); - r *= scale; - g *= scale; - b *= scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYCbCrScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs deleted file mode 100644 index 8b5f8463d9..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromYccKAvx : JpegColorConverterAvx - { - public FromYccKAvx(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - var chromaOffset = Vector256.Create(-this.HalfValue); - var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - var max = Vector256.Create(this.MaximumValue); - var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); - var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); - var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); - var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - Vector256 y = c0; - Vector256 cb = Avx.Add(c1, chromaOffset); - Vector256 cr = Avx.Add(c2, chromaOffset); - Vector256 scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); - Vector256 g = - HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); - - r = Avx.Subtract(max, Avx.RoundToNearestInteger(r)); - g = Avx.Subtract(max, Avx.RoundToNearestInteger(g)); - b = Avx.Subtract(max, Avx.RoundToNearestInteger(b)); - - r = Avx.Multiply(r, scaledK); - g = Avx.Multiply(g, scaledK); - b = Avx.Multiply(b, scaledK); - - c0 = r; - c1 = g; - c2 = b; - } - } - } - } -} -#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs deleted file mode 100644 index 90d2ac82dc..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromYccKScalar : JpegColorConverterScalar - { - public FromYccKScalar(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - float scaledK = c3[i] * scale; - - c0[i] = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1[i] = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs deleted file mode 100644 index e2e00c213c..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - internal sealed class FromYccKVector : JpegColorConverterVector - { - public FromYccKVector(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { - ref Vector c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - var chromaOffset = new Vector(-this.HalfValue); - var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); - var max = new Vector(this.MaximumValue); - var rCrMult = new Vector(FromYCbCrScalar.RCrMult); - var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); - var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); - var bCbMult = new Vector(FromYCbCrScalar.BCbMult); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector c2 = ref Unsafe.Add(ref c2Base, i); - - Vector y = c0; - Vector cb = c1 + chromaOffset; - Vector cr = c2 + chromaOffset; - Vector scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector r = y + (cr * rCrMult); - Vector g = y + (cb * gCbMult) + (cr * gCrMult); - Vector b = y + (cb * bCbMult); - - r = (max - r.FastRound()) * scaledK; - g = (max - g.FastRound()) * scaledK; - b = (max - b.FastRound()) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs deleted file mode 100644 index 6e0c0cff34..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Numerics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverterBase - { - /// - /// abstract base for implementations - /// based on API. - /// - /// - /// Converters of this family can work with data of any size. - /// Even though real life data is guaranteed to be of size - /// divisible by 8 newer SIMD instructions like AVX512 won't work with - /// such data out of the box. These converters have fallback code - /// for remainder data. - /// - internal abstract class JpegColorConverterVector : JpegColorConverterBase - { - protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; - - public sealed override int ElementsPerBatch => Vector.Count; - - public sealed override void ConvertToRgbInplace(in ComponentValues values) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide - // Thus there's no need to check whether simdCount is greater than zero - int simdCount = length - remainder; - this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertCoreInplace(values.Slice(simdCount, remainder)); - } - } - - protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); - - protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 5f55a10635..5c6535bd37 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -12,7 +12,7 @@ internal sealed class JpegFrame : IDisposable { public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount) { - this.Extended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9; + this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9; this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10; this.Precision = precision; @@ -27,7 +27,7 @@ public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height /// /// Gets a value indicating whether the frame uses the extended specification. /// - public bool Extended { get; private set; } + public bool IsExtended { get; private set; } /// /// Gets a value indicating whether the frame uses the progressive specification. @@ -40,7 +40,7 @@ public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height /// /// This is true for progressive and baseline non-interleaved images. /// - public bool MultiScan { get; set; } + public bool Interleaved { get; set; } /// /// Gets the precision. @@ -141,7 +141,7 @@ public void Init(int maxSubFactorH, int maxSubFactorV) public void AllocateComponents() { - bool fullScan = this.Progressive || this.MultiScan; + bool fullScan = this.Progressive || !this.Interleaved; for (int i = 0; i < this.ComponentCount; i++) { IJpegComponent component = this.Components[i]; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index bef23d89b3..be59e90fab 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -1,12 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; - namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Converter used to convert jpeg spectral data to color pixels. + /// Converter used to convert jpeg spectral data to pixels. /// internal abstract class SpectralConverter { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index d460d9497c..c96f4acc21 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.Linq; using System.Threading; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs new file mode 100644 index 0000000000..c489520abf --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Represents a single frame component. + /// + internal class Component : IDisposable + { + private readonly MemoryAllocator memoryAllocator; + + public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) + { + this.memoryAllocator = memoryAllocator; + + this.HorizontalSamplingFactor = horizontalFactor; + this.VerticalSamplingFactor = verticalFactor; + this.SamplingFactors = new Size(horizontalFactor, verticalFactor); + + this.QuantizationTableIndex = quantizationTableIndex; + } + + /// + /// Gets or sets DC coefficient predictor. + /// + public int DcPredictor { get; set; } + + /// + /// Gets the horizontal sampling factor. + /// + public int HorizontalSamplingFactor { get; } + + /// + /// Gets the vertical sampling factor. + /// + public int VerticalSamplingFactor { get; } + + public Buffer2D SpectralBlocks { get; private set; } + + public Size SubSamplingDivisors { get; private set; } + + public int QuantizationTableIndex { get; } + + public Size SizeInBlocks { get; private set; } + + public Size SamplingFactors { get; set; } + + /// + /// Gets the number of blocks per line. + /// + public int WidthInBlocks { get; private set; } + + /// + /// Gets the number of blocks per column. + /// + public int HeightInBlocks { get; private set; } + + /// + /// Gets or sets the index for the DC Huffman table. + /// + public int DcTableId { get; set; } + + /// + /// Gets or sets the index for the AC Huffman table. + /// + public int AcTableId { get; set; } + + /// + public void Dispose() + { + this.SpectralBlocks?.Dispose(); + this.SpectralBlocks = null; + } + + /// + /// Initializes component for future buffers initialization. + /// + /// asdfasdf. + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) + { + uint widthInBlocks = ((uint)frame.PixelWidth + 7) / 8; + uint heightInBlocks = ((uint)frame.PixelHeight + 7) / 8; + + this.WidthInBlocks = (int)MathF.Ceiling( + (float)widthInBlocks * this.HorizontalSamplingFactor / maxSubFactorH); + + this.HeightInBlocks = (int)MathF.Ceiling( + (float)heightInBlocks * this.VerticalSamplingFactor / maxSubFactorV); + + int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; + int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; + this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); + + this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); + + if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) + { + JpegThrowHelper.ThrowBadSampling(); + } + } + + public void AllocateSpectral(bool fullScan) + { + int spectralAllocWidth = this.SizeInBlocks.Width; + int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; + + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs new file mode 100644 index 0000000000..f13eedb813 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -0,0 +1,231 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class ComponentProcessor : IDisposable + { + private readonly Size blockAreaSize; + + private readonly Component component; + + private Block8x8F quantTable; + + public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, Size postProcessorBufferSize, Block8x8F quantTable) + { + this.component = component; + this.quantTable = quantTable; + + this.component = component; + this.blockAreaSize = component.SubSamplingDivisors * 8; + + // alignment of 8 so each block stride can be sampled from a single 'ref pointer' + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( + postProcessorBufferSize.Width, + postProcessorBufferSize.Height, + 8, + AllocationOptions.Clean); + } + + /// + /// Gets the temporary working buffer of color values. + /// + public Buffer2D ColorBuffer { get; } + + public void CopyColorBufferToBlocks(int spectralStep) + { + Buffer2D spectralBuffer = this.component.SpectralBlocks; + int destAreaStride = this.ColorBuffer.Width; + int yBlockStart = spectralStep * this.component.SamplingFactors.Height; + + Block8x8F workspaceBlock = default; + + // handle subsampling + Size subsamplingFactors = this.component.SubSamplingDivisors; + if (subsamplingFactors.Width != 1 || subsamplingFactors.Height != 1) + { + this.PackColorBuffer(); + } + + int blocksRowsPerStep = this.component.SamplingFactors.Height; + + for (int y = 0; y < blocksRowsPerStep; y++) + { + int yBuffer = y * this.blockAreaSize.Height; + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + // load 8x8 block from 8 pixel strides + int xColorBufferStart = xBlock * 8; + workspaceBlock.ScaledCopyFrom( + ref colorBufferRow[xColorBufferStart], + destAreaStride); + + // level shift via -128f + workspaceBlock.AddInPlace(-128f); + + // FDCT + FloatingPointDCT.TransformFDCT(ref workspaceBlock); + + // Quantize and save to spectral blocks + Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable); + } + } + } + + public Span GetColorBufferRowSpan(int row) + => this.ColorBuffer.DangerousGetRowSpan(row); + + public void Dispose() + => this.ColorBuffer.Dispose(); + + private void PackColorBuffer() + { + Size factors = this.component.SubSamplingDivisors; + + int packedWidth = this.ColorBuffer.Width / factors.Width; + + float averageMultiplier = 1f / (factors.Width * factors.Height); + for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) + { + Span sourceRow = this.ColorBuffer.DangerousGetRowSpan(i); + + // vertical sum + for (int j = 1; j < factors.Height; j++) + { + SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); + } + + // horizontal sum + SumHorizontal(sourceRow, factors.Width); + + // calculate average + MultiplyToAverage(sourceRow, averageMultiplier); + + // copy to the first 8 slots + sourceRow.Slice(0, packedWidth).CopyTo(this.ColorBuffer.DangerousGetRowSpan(i / factors.Height)); + } + + static void SumVertical(Span target, Span source) + { + if (Avx.IsSupported) + { + ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector256 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + + // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed + nint count = source.Length / Vector256.Count; + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); + } + } + else + { + ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + + nint count = source.Length / Vector.Count; + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) += Unsafe.Add(ref sourceVectorRef, i); + } + + ref float targetRef = ref MemoryMarshal.GetReference(target); + ref float sourceRef = ref MemoryMarshal.GetReference(source); + for (nint i = count * Vector.Count; i < source.Length; i++) + { + Unsafe.Add(ref targetRef, i) += Unsafe.Add(ref sourceRef, i); + } + } + } + + static void SumHorizontal(Span target, int factor) + { + Span source = target; + if (Avx2.IsSupported) + { + ref Vector256 targetRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + + // Ideally we need to use log2: Numerics.Log2((uint)factor) + // but division by 2 works just fine in this case + int haddIterationsCount = (int)((uint)factor / 2); + + // Transform spans so that it only contains 'remainder' + // values for the scalar fallback code + int scalarRemainder = target.Length % (Vector.Count * factor); + int touchedCount = target.Length - scalarRemainder; + source = source.Slice(touchedCount); + target = target.Slice(touchedCount / factor); + + uint length = (uint)touchedCount / (uint)Vector256.Count; + + for (int i = 0; i < haddIterationsCount; i++) + { + length /= 2; + + for (nuint j = 0; j < length; j++) + { + nuint indexLeft = j * 2; + nuint indexRight = indexLeft + 1; + Vector256 sum = Avx.HorizontalAdd(Unsafe.Add(ref targetRef, indexLeft), Unsafe.Add(ref targetRef, indexRight)); + Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle(); + } + } + } + + // scalar remainder + for (int i = 0; i < source.Length / factor; i++) + { + target[i] = source[i * factor]; + for (int j = 1; j < factor; j++) + { + target[i] += source[(i * factor) + j]; + } + } + } + + static void MultiplyToAverage(Span target, float multiplier) + { + if (Avx.IsSupported) + { + ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + + // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed + nint count = target.Length / Vector256.Count; + var multiplierVector = Vector256.Create(multiplier); + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); + } + } + else + { + ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + + nint count = target.Length / Vector.Count; + var multiplierVector = new Vector(multiplier); + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) *= multiplierVector; + } + + ref float targetRef = ref MemoryMarshal.GetReference(target); + for (nint i = count * Vector.Count; i < target.Length; i++) + { + Unsafe.Add(ref targetRef, i) *= multiplier; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs new file mode 100644 index 0000000000..519b8c0b8b --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegComponentConfig + { + public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) + { + this.Id = id; + this.HorizontalSampleFactor = hsf; + this.VerticalSampleFactor = vsf; + this.QuantizatioTableIndex = quantIndex; + this.DcTableSelector = dcIndex; + this.AcTableSelector = acIndex; + } + + public byte Id { get; } + + public int HorizontalSampleFactor { get; } + + public int VerticalSampleFactor { get; } + + public int QuantizatioTableIndex { get; } + + public int DcTableSelector { get; } + + public int AcTableSelector { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs new file mode 100644 index 0000000000..18afff7383 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegFrameConfig + { + public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables) + { + this.ColorType = colorType; + this.EncodingColor = encodingColor; + this.Components = components; + this.HuffmanTables = huffmanTables; + this.QuantizationTables = quantTables; + + this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; + this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; + for (int i = 1; i < components.Length; i++) + { + JpegComponentConfig component = components[i]; + this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor); + this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor); + } + } + + public JpegColorSpace ColorType { get; } + + public JpegEncodingColor EncodingColor { get; } + + public JpegComponentConfig[] Components { get; } + + public JpegHuffmanTableConfig[] HuffmanTables { get; } + + public JpegQuantizationTableConfig[] QuantizationTables { get; } + + public int MaxHorizontalSamplingFactor { get; } + + public int MaxVerticalSamplingFactor { get; } + + public byte? AdobeColorTransformMarkerFlag { get; set; } = null; + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs new file mode 100644 index 0000000000..cf7c152cb0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegHuffmanTableConfig + { + public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table) + { + this.Class = @class; + this.DestinationIndex = destIndex; + this.Table = table; + } + + public int Class { get; } + + public int DestinationIndex { get; } + + public HuffmanSpec Table { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs new file mode 100644 index 0000000000..ada2c464a6 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegQuantizationTableConfig + { + public JpegQuantizationTableConfig(int destIndex, ReadOnlySpan quantizationTable) + { + this.DestinationIndex = destIndex; + this.Table = Block8x8.Load(quantizationTable); + } + + public int DestinationIndex { get; } + + public Block8x8 Table { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs deleted file mode 100644 index cd769b558d..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// Enumerates the Huffman tables - /// - internal enum HuffIndex - { - /// - /// The DC luminance huffman table index - /// - LuminanceDC = 0, - - // ReSharper disable UnusedMember.Local - - /// - /// The AC luminance huffman table index - /// - LuminanceAC = 1, - - /// - /// The DC chrominance huffman table index - /// - ChrominanceDC = 2, - - /// - /// The AC chrominance huffman table index - /// - ChrominanceAC = 3, - - // ReSharper restore UnusedMember.Local - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index 7bdab29cd1..bd4cc5545f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -26,23 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// internal readonly struct HuffmanLut { - /// - /// The compiled representations of theHuffmanSpec. - /// - public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; - - /// - /// Initializes static members of the struct. - /// - static HuffmanLut() - { - // Initialize the Huffman tables - for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++) - { - TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); - } - } - /// /// Initializes a new instance of the struct. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 24ab9f8d1f..f8f7cdfe81 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -60,10 +59,14 @@ internal class HuffmanScanEncoder private const int OutputBufferLengthMultiplier = 2; /// - /// Compiled huffman tree to encode given values. + /// The DC Huffman tables. /// - /// Yields codewords by index consisting of [run length | bitsize]. - private HuffmanLut[] huffmanTables; + private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4]; + + /// + /// The AC Huffman tables. + /// + private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4]; /// /// Emitted bits 'micro buffer' before being transferred to the . @@ -94,8 +97,6 @@ internal class HuffmanScanEncoder private int emitWriteIndex; - private Block8x8 tempBlock; - /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -128,57 +129,67 @@ private bool IsStreamFlushNeeded get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } + public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) + { + HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; + tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); + } + /// - /// Encodes the image with no subsampling. + /// Encodes scan in baseline interleaved mode. /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Luminance quantization table provided by the callee. - /// Chrominance quantization table provided by the callee. - /// The token to monitor for cancellation. - public void Encode444(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) + /// Output color space. + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - FloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable); - FloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable); - - this.huffmanTables = HuffmanLut.TheHuffmanLut; - - // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + switch (color) + { + case JpegEncodingColor.YCbCrRatio444: + case JpegEncodingColor.Rgb: + this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken); + break; + default: + this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken); + break; + } + } - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; + /// + /// Encodes grayscale scan in baseline interleaved mode. + /// + /// Component with grayscale data. + /// Converter from color to spectral. + /// The token to request cancellation. + public void EncodeScanBaselineSingleComponent(Component component, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + int h = component.HeightInBlocks; + int w = component.WidthInBlocks; - var pixelConverter = new YCbCrForwardConverter444(frame); + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - for (int y = 0; y < pixels.Height; y += 8) + for (int i = 0; i < h; i++) { cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); - for (int x = 0; x < pixels.Width; x += 8) + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); + + // Encode spectral to binary + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (nint k = 0; k < w; k++) { - pixelConverter.Convert(x, y, ref currentRows); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.Y, - ref luminanceQuantTable); - - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - ref pixelConverter.Cb, - ref chrominanceQuantTable); - - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - ref pixelConverter.Cr, - ref chrominanceQuantTable); + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); if (this.IsStreamFlushNeeded) { @@ -191,65 +202,33 @@ public void Encode444(Image pixels, ref Block8x8F luminanceQuant } /// - /// Encodes the image with subsampling. The Cb and Cr components are each subsampled - /// at a factor of 2 both horizontally and vertically. + /// Encodes scan with a single component in baseline non-interleaved mode. /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Luminance quantization table provided by the callee. - /// Chrominance quantization table provided by the callee. - /// The token to monitor for cancellation. - public void Encode420(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// Component with grayscale data. + /// The token to request cancellation. + public void EncodeScanBaseline(Component component, CancellationToken cancellationToken) { - FloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable); - FloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable); - - this.huffmanTables = HuffmanLut.TheHuffmanLut; - - // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; + int h = component.HeightInBlocks; + int w = component.WidthInBlocks; - var pixelConverter = new YCbCrForwardConverter420(frame); + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - for (int y = 0; y < pixels.Height; y += 16) + for (int i = 0; i < h; i++) { cancellationToken.ThrowIfCancellationRequested(); - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 2; i++) - { - int yOff = i * 8; - currentRows.Update(pixelBuffer, y + yOff); - pixelConverter.Convert(x, y, ref currentRows, i); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.YLeft, - ref luminanceQuantTable); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.YRight, - ref luminanceQuantTable); - } - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - ref pixelConverter.Cb, - ref chrominanceQuantTable); + // Encode spectral to binary + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - ref pixelConverter.Cr, - ref chrominanceQuantTable); + for (nint k = 0; k < w; k++) + { + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); if (this.IsStreamFlushNeeded) { @@ -262,43 +241,64 @@ public void Encode420(Image pixels, ref Block8x8F luminanceQuant } /// - /// Encodes the image with no chroma, just luminance. + /// Encodes scan in baseline interleaved mode for any amount of component with arbitrary sampling factors. /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Luminance quantization table provided by the callee. - /// The token to monitor for cancellation. - public void EncodeGrayscale(Image pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - FloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable); + nint mcu = 0; + nint mcusPerColumn = frame.McusPerColumn; + nint mcusPerLine = frame.McusPerLine; - this.huffmanTables = HuffmanLut.TheHuffmanLut; + for (int j = 0; j < mcusPerColumn; j++) + { + cancellationToken.ThrowIfCancellationRequested(); - // ReSharper disable once InconsistentNaming - int prevDCY = 0; + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; + // Encode spectral to binary + for (nint i = 0; i < mcusPerLine; i++) + { + // Scan an interleaved mcu... process components in order + nint mcuCol = mcu % mcusPerLine; + for (nint k = 0; k < frame.Components.Length; k++) + { + Component component = frame.Components[k]; - var pixelConverter = new LuminanceForwardConverter(frame); + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - for (int y = 0; y < pixels.Height; y += 8) - { - cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); + nint h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - for (int x = 0; x < pixels.Width; x += 8) - { - pixelConverter.Convert(x, y, ref currentRows); + nint blockColBase = mcuCol * h; - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.Y, - ref luminanceQuantTable); + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (int y = 0; y < v; y++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (nint x = 0; x < h; x++) + { + nint blockCol = blockColBase + x; + + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, blockCol), + ref dcHuffmanTable, + ref acHuffmanTable); + } + } + } + // After all interleaved components, that's an interleaved MCU + mcu++; if (this.IsStreamFlushNeeded) { this.FlushToStream(); @@ -310,54 +310,59 @@ public void EncodeGrayscale(Image pixels, ref Block8x8F luminanc } /// - /// Encodes the image with no subsampling and keeps the pixel data as Rgb24. + /// Encodes scan in baseline interleaved mode with exactly 3 components with no subsampling. /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Quantization table provided by the callee. - /// The token to monitor for cancellation. - public void EncodeRgb(Image pixels, ref Block8x8F quantTable, CancellationToken cancellationToken) + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + private void EncodeThreeComponentBaselineInterleavedScanNoSubsampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - FloatingPointDCT.AdjustToFDCT(ref quantTable); - - this.huffmanTables = HuffmanLut.TheHuffmanLut; + nint mcusPerColumn = frame.McusPerColumn; + nint mcusPerLine = frame.McusPerLine; - // ReSharper disable once InconsistentNaming - int prevDCR = 0, prevDCG = 0, prevDCB = 0; + Component c2 = frame.Components[2]; + Component c1 = frame.Components[1]; + Component c0 = frame.Components[0]; - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; + ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId]; + ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId]; + ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId]; + ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId]; + ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId]; + ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId]; - var pixelConverter = new RgbForwardConverter(frame); + ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0)); + ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0)); + ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0)); - for (int y = 0; y < pixels.Height; y += 8) + for (nint j = 0; j < mcusPerColumn; j++) { cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); - for (int x = 0; x < pixels.Width; x += 8) + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); + + // Encode spectral to binary + for (nint i = 0; i < mcusPerLine; i++) { - pixelConverter.Convert(x, y, ref currentRows); - - prevDCR = this.WriteBlock( - QuantIndex.Luminance, - prevDCR, - ref pixelConverter.R, - ref quantTable); - - prevDCG = this.WriteBlock( - QuantIndex.Luminance, - prevDCG, - ref pixelConverter.G, - ref quantTable); - - prevDCB = this.WriteBlock( - QuantIndex.Luminance, - prevDCB, - ref pixelConverter.B, - ref quantTable); + this.WriteBlock( + c0, + ref Unsafe.Add(ref c0BlockRef, i), + ref c0dcHuffmanTable, + ref c0acHuffmanTable); + + this.WriteBlock( + c1, + ref Unsafe.Add(ref c1BlockRef, i), + ref c1dcHuffmanTable, + ref c1acHuffmanTable); + + this.WriteBlock( + c2, + ref Unsafe.Add(ref c2BlockRef, i), + ref c2dcHuffmanTable, + ref c2acHuffmanTable); if (this.IsStreamFlushNeeded) { @@ -369,44 +374,24 @@ public void EncodeRgb(Image pixels, ref Block8x8F quantTable, Ca this.FlushRemainingBytes(); } - /// - /// Writes a block of pixel data using the given quantization table, - /// returning the post-quantized DC value of the DCT-transformed block. - /// The block is in natural (not zig-zag) order. - /// - /// The quantization table index. - /// The previous DC value. - /// Source block. - /// Quantization table. - /// The . - private int WriteBlock( - QuantIndex index, - int prevDC, - ref Block8x8F block, - ref Block8x8F quant) + private void WriteBlock( + Component component, + ref Block8x8 block, + ref HuffmanLut dcTable, + ref HuffmanLut acTable) { - ref Block8x8 spectralBlock = ref this.tempBlock; - - // Shifting level from 0..255 to -128..127 - block.AddInPlace(-128f); - - // Discrete cosine transform - FloatingPointDCT.TransformFDCT(ref block); - - // Quantization - Block8x8F.Quantize(ref block, ref spectralBlock, ref quant); - // Emit the DC delta. - int dc = spectralBlock[0]; - this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC); + int dc = block[0]; + this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor); + component.DcPredictor = dc; // Emit the AC components. - int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; + int[] acHuffTable = acTable.Values; - nint lastValuableIndex = spectralBlock.GetLastNonZeroIndex(); + nint lastValuableIndex = block.GetLastNonZeroIndex(); int runLength = 0; - ref short blockRef = ref Unsafe.As(ref spectralBlock); + ref short blockRef = ref Unsafe.As(ref block); for (nint zig = 1; zig <= lastValuableIndex; zig++) { const int zeroRun1 = 1 << 4; @@ -437,8 +422,6 @@ private int WriteBlock( { this.EmitHuff(acHuffTable, 0x00); } - - return dc; } /// @@ -655,7 +638,7 @@ private void FlushToStream(int endIndex) /// /// Flushes spectral data bytes after encoding all channel blocks - /// in a single jpeg macroblock using . + /// in a single jpeg macroblock using . /// /// /// This must be called only if is true @@ -684,6 +667,11 @@ private void FlushRemainingBytes() // Flush cached bytes to the output stream with padding bits int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; this.FlushToStream(lastByteIndex); + + // Clear huffman register + // This is needed for for images with multiples scans + this.bitCount = 0; + this.accumulatedBits = 0; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index fb04168b5b..97f051c76c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -6,111 +6,122 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The Huffman encoding specifications. /// - internal readonly struct HuffmanSpec + public readonly struct HuffmanSpec { -#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines + /// + /// Huffman talbe specification for luminance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec LuminanceDC = new( + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }); /// - /// The Huffman encoding specifications. - /// This encoder uses the same Huffman encoding for all images. + /// Huffman talbe specification for luminance AC. /// - public static readonly HuffmanSpec[] TheHuffmanSpecs = + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec LuminanceAC = new( + new byte[] { - // Luminance DC. - new HuffmanSpec( - new byte[] - { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, + 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, + 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, + 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, + 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }); - // Luminance AC. - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, - 0, 1, 125 - }, - new byte[] - { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, - 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, - 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, - 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, - 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, - 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, - 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, - 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, - 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, - 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, - 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, - 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, - 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - }), + /// + /// Huffman talbe specification for chrominance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec ChrominanceDC = new( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }); - // Chrominance DC. - new HuffmanSpec( - new byte[] - { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + /// + /// Huffman talbe specification for chrominance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec ChrominanceAC = new( + new byte[] + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, + 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, + 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, + 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, + 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }); - // Chrominance AC. - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, - 1, 2, 119 - }, - new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, - 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, - 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, - 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, - 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, - 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, - 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, - 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, - 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, - 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, - 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, - 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, - 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, - 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, - 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - }) - }; -#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines /// /// Gets count[i] - The number of codes of length i bits. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs new file mode 100644 index 0000000000..990c218a8c --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Represent a single jpeg frame. + /// + internal sealed class JpegFrame : IDisposable + { + public JpegFrame(Image image, JpegFrameConfig frameConfig, bool interleaved) + { + this.ColorSpace = frameConfig.ColorType; + + this.Interleaved = interleaved; + + this.PixelWidth = image.Width; + this.PixelHeight = image.Height; + + MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; + + JpegComponentConfig[] componentConfigs = frameConfig.Components; + this.Components = new Component[componentConfigs.Length]; + for (int i = 0; i < this.Components.Length; i++) + { + JpegComponentConfig componentConfig = componentConfigs[i]; + this.Components[i] = new Component(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) + { + DcTableId = componentConfig.DcTableSelector, + AcTableId = componentConfig.AcTableSelector, + }; + + this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor; + } + + int maxSubFactorH = frameConfig.MaxHorizontalSamplingFactor; + int maxSubFactorV = frameConfig.MaxVerticalSamplingFactor; + this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8); + this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8); + + for (int i = 0; i < this.Components.Length; i++) + { + Component component = this.Components[i]; + component.Init(this, maxSubFactorH, maxSubFactorV); + } + } + + public JpegColorSpace ColorSpace { get; } + + public bool Interleaved { get; } + + public int PixelHeight { get; } + + public int PixelWidth { get; } + + public Component[] Components { get; } + + public int McusPerLine { get; } + + public int McusPerColumn { get; } + + public int BlocksPerMcu { get; } + + public void Dispose() + { + for (int i = 0; i < this.Components.Length; i++) + { + this.Components[i].Dispose(); + } + } + + public void AllocateComponents(bool fullScan) + { + for (int i = 0; i < this.Components.Length; i++) + { + Component component = this.Components[i]; + component.AllocateSpectral(fullScan); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs deleted file mode 100644 index 29a13d2010..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to efficiently encapsulate the TPixel -> L8 -> Y conversion chain of 8x8 pixel blocks. - /// - /// The pixel type to work on - internal ref struct LuminanceForwardConverter - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 8 * 8; - - /// - /// The Y component - /// - public Block8x8F Y; - - /// - /// Temporal 64-pixel span to hold unconverted TPixel data. - /// - private readonly Span pixelSpan; - - /// - /// Temporal 64-byte span to hold converted data. - /// - private readonly Span l8Span; - - /// - /// Sampled pixel buffer size. - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations. - /// - private readonly Configuration config; - - public LuminanceForwardConverter(ImageFrame frame) - { - this.Y = default; - - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.l8Span = new L8[PixelsPerSample].AsSpan(); - - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(8, 8); - - /// - /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure () - /// - public void Convert(int x, int y, ref RowOctet currentRows) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToL8(this.config, this.pixelSpan, this.l8Span); - - ref Block8x8F yBlock = ref this.Y; - ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span); - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - ConvertAvx(ref l8Start, ref yBlock); - } - else - { - ConvertScalar(ref l8Start, ref yBlock); - } - } - - /// - /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics. - /// - /// Start of span of L8 pixels with size of 64 - /// 8x8 destination matrix of Luminance(Y) converted data - private static void ConvertAvx(ref L8 l8Start, ref Block8x8F yBlock) - { - Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - ref Vector128 l8ByteSpan = ref Unsafe.As>(ref l8Start); - ref Vector256 destRef = ref yBlock.V0; - - const int bytesPerL8Stride = 8; - for (nint i = 0; i < 8; i++) - { - Unsafe.Add(ref destRef, i) = Avx2.ConvertToVector256Single(Avx2.ConvertToVector256Int32(Unsafe.AddByteOffset(ref l8ByteSpan, bytesPerL8Stride * i))); - } -#endif - } - - /// - /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats. - /// - /// Start of span of L8 pixels with size of 64 - /// 8x8 destination matrix of Luminance(Y) converted data - private static void ConvertScalar(ref L8 l8Start, ref Block8x8F yBlock) - { - for (int i = 0; i < Block8x8F.Size; i++) - { - ref L8 c = ref Unsafe.Add(ref l8Start, i); - yBlock[i] = c.PackedValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs deleted file mode 100644 index 7edd9da43a..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// Enumerates the quantization tables. - /// - internal enum QuantIndex - { - /// - /// The luminance quantization table index. - /// - Luminance = 0, - - /// - /// The chrominance quantization table index. - /// - Chrominance = 1, - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs deleted file mode 100644 index 3c1daadb02..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks. - /// - /// The pixel type to work on. - internal ref struct RgbForwardConverter - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 8 * 8; - - /// - /// Total byte size of processed pixels converted from TPixel to - /// - private const int RgbSpanByteSize = PixelsPerSample * 3; - - /// - /// The Red component. - /// - public Block8x8F R; - - /// - /// The Green component. - /// - public Block8x8F G; - - /// - /// The Blue component. - /// - public Block8x8F B; - - /// - /// Temporal 64-byte span to hold unconverted TPixel data. - /// - private readonly Span pixelSpan; - - /// - /// Temporal 64-byte span to hold converted Rgb24 data. - /// - private readonly Span rgbSpan; - - /// - /// Sampled pixel buffer size. - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations. - /// - private readonly Configuration config; - - public RgbForwardConverter(ImageFrame frame) - { - this.R = default; - this.G = default; - this.B = default; - - // temporal pixel buffers - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); - - // frame data - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(8, 8); - - /// - /// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24. - /// - public void Convert(int x, int y, ref RowOctet currentRows) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); - - ref Block8x8F redBlock = ref this.R; - ref Block8x8F greenBlock = ref this.G; - ref Block8x8F blueBlock = ref this.B; - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - ConvertAvx(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); - } - else - { - ConvertScalar(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); - } - } - - /// - /// Converts 8x8 RGB24 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics. - /// - /// Span of Rgb24 pixels with size of 64 - /// 8x8 destination matrix of Red converted data - /// 8x8 destination matrix of Blue converted data - /// 8x8 destination matrix of Green converted data - private static void ConvertAvx(Span rgbSpan, ref Block8x8F rBlock, ref Block8x8F gBlock, ref Block8x8F bBlock) - { - Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - ref Vector256 redRef = ref rBlock.V0; - ref Vector256 greenRef = ref gBlock.V0; - ref Vector256 blueRef = ref bBlock.V0; - var zero = Vector256.Create(0).AsByte(); - - var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.MoveFirst24BytesToSeparateLanes)); - var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.ExtractRgb)); - Vector256 rgb, rg, bx; - - const int bytesPerRgbStride = 24; - for (nint i = 0; i < 8; i++) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, bytesPerRgbStride * i).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - Unsafe.Add(ref redRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - Unsafe.Add(ref greenRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - Unsafe.Add(ref blueRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - } -#endif - } - - private static void ConvertScalar(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock) - { - ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan); - - for (int i = 0; i < Block8x8F.Size; i++) - { - Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i); - - redBlock[i] = c.R; - greenBlock[i] = c.G; - blueBlock[i] = c.B; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs deleted file mode 100644 index c3fc806a7d..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. - /// Methods to build the tables are based on libjpeg implementation. - /// - internal unsafe struct RgbToYCbCrConverterLut - { - /// - /// The red luminance table - /// - public fixed int YRTable[256]; - - /// - /// The green luminance table - /// - public fixed int YGTable[256]; - - /// - /// The blue luminance table - /// - public fixed int YBTable[256]; - - /// - /// The red blue-chrominance table - /// - public fixed int CbRTable[256]; - - /// - /// The green blue-chrominance table - /// - public fixed int CbGTable[256]; - - /// - /// The blue blue-chrominance table - /// B=>Cb and R=>Cr are the same - /// - public fixed int CbBTable[256]; - - /// - /// The green red-chrominance table - /// - public fixed int CrGTable[256]; - - /// - /// The blue red-chrominance table - /// - public fixed int CrBTable[256]; - - // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places. - private const int ScaleBits = 16; - - private const int CBCrOffset = 128 << ScaleBits; - - private const int Half = 1 << (ScaleBits - 1); - - /// - /// Initializes the YCbCr tables - /// - /// The initialized - public static RgbToYCbCrConverterLut Create() - { - RgbToYCbCrConverterLut tables = default; - - for (int i = 0; i <= 255; i++) - { - // The values for the calculations are left scaled up since we must add them together before rounding. - tables.YRTable[i] = Fix(0.299F) * i; - tables.YGTable[i] = Fix(0.587F) * i; - tables.YBTable[i] = (Fix(0.114F) * i) + Half; - tables.CbRTable[i] = (-Fix(0.168735892F)) * i; - tables.CbGTable[i] = (-Fix(0.331264108F)) * i; - - // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr. - // This ensures that the maximum output will round to 255 - // not 256, and thus that we don't have to range-limit. - // - // B=>Cb and R=>Cr tables are the same - tables.CbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1; - - tables.CrGTable[i] = (-Fix(0.418687589F)) * i; - tables.CrBTable[i] = (-Fix(0.081312411F)) * i; - } - - return tables; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateY(byte r, byte g, byte b) - { - // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateCb(byte r, byte g, byte b) - { - // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateCr(byte r, byte g, byte b) - { - // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; - } - - /// - /// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma. - /// - /// Span of Rgb24 pixel data - /// Resulting Y values block - /// Resulting Cb values block - /// Resulting Cr values block - public void Convert444(Span rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) - { - ref Rgb24 rgbStart = ref rgbSpan[0]; - - for (int i = 0; i < Block8x8F.Size; i++) - { - Rgb24 c = Unsafe.Add(ref rgbStart, i); - - yBlock[i] = this.CalculateY(c.R, c.G, c.B); - cbBlock[i] = this.CalculateCb(c.R, c.G, c.B); - crBlock[i] = this.CalculateCr(c.R, c.G, c.B); - } - } - - /// - /// Converts Rgb24 pixels into YCbCr color space with 4:2:0 subsampling of luminance and chroma. - /// - /// Calculates 2 out of 4 luminance blocks and half of chroma blocks. This method must be called twice per 4x 8x8 DCT blocks with different row param. - /// Span of Rgb24 pixel data - /// First or "left" resulting Y block - /// Second or "right" resulting Y block - /// Resulting Cb values block - /// Resulting Cr values block - /// Row index of the 16x16 block, 0 or 1 - public void Convert420(Span rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row) - { - DebugGuard.MustBeBetweenOrEqualTo(row, 0, 1, nameof(row)); - - ref float yBlockLeftRef = ref Unsafe.As(ref yBlockLeft); - ref float yBlockRightRef = ref Unsafe.As(ref yBlockRight); - - // 0-31 or 32-63 - // upper or lower part - int chromaWriteOffset = row * (Block8x8F.Size / 2); - ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As(ref cbBlock), chromaWriteOffset); - ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As(ref crBlock), chromaWriteOffset); - - ref Rgb24 rgbStart = ref rgbSpan[0]; - - for (int i = 0; i < 8; i += 2) - { - int yBlockWriteOffset = i * 8; - ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16); - - int chromaOffset = 8 * (i / 2); - - // left - this.ConvertChunk420( - ref stride, - ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset), - ref Unsafe.Add(ref cbBlockRef, chromaOffset), - ref Unsafe.Add(ref crBlockRef, chromaOffset)); - - // right - this.ConvertChunk420( - ref Unsafe.Add(ref stride, 8), - ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset), - ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4), - ref Unsafe.Add(ref crBlockRef, chromaOffset + 4)); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock) - { - // jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons) - // each row is 16 pixels wide thus +16 stride reference offset - // resulting luminance (Y`) are sampled at original resolution thus +8 reference offset - for (int k = 0; k < 8; k += 2) - { - ref float yBlockRef = ref Unsafe.Add(ref yBlock, k); - - // top row - Rgb24 px0 = Unsafe.Add(ref stride, k); - Rgb24 px1 = Unsafe.Add(ref stride, k + 1); - yBlockRef = this.CalculateY(px0.R, px0.G, px0.B); - Unsafe.Add(ref yBlockRef, 1) = this.CalculateY(px1.R, px1.G, px1.B); - - // bottom row - Rgb24 px2 = Unsafe.Add(ref stride, k + 16); - Rgb24 px3 = Unsafe.Add(ref stride, k + 17); - Unsafe.Add(ref yBlockRef, 8) = this.CalculateY(px2.R, px2.G, px2.B); - Unsafe.Add(ref yBlockRef, 9) = this.CalculateY(px3.R, px3.G, px3.B); - - // chroma average for 2x2 pixel block - Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3); - Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) - { - return 0.25f - * (this.CalculateCb(px0.R, px0.G, px0.B) - + this.CalculateCb(px1.R, px1.G, px1.B) - + this.CalculateCb(px2.R, px2.G, px2.B) - + this.CalculateCb(px3.R, px3.G, px3.B)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) - { - return 0.25f - * (this.CalculateCr(px0.R, px0.G, px0.B) - + this.CalculateCr(px1.R, px1.G, px1.B) - + this.CalculateCr(px2.R, px2.G, px2.B) - + this.CalculateCr(px3.R, px3.G, px3.B)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Fix(float x) - => (int)((x * (1L << ScaleBits)) + 0.5F); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs deleted file mode 100644 index c2bc8cfb83..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Diagnostics; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - internal static class RgbToYCbCrConverterVectorized - { - public static bool IsSupported - { - get - { -#if SUPPORTS_RUNTIME_INTRINSICS - return Avx2.IsSupported; -#else - return false; -#endif - } - } - - public static int AvxCompatibilityPadding - { - // rgb byte matrices contain 8 strides by 8 pixels each, thus 64 pixels total - // Strides are stored sequentially - one big span of 64 * 3 = 192 bytes - // Each stride has exactly 3 * 8 = 24 bytes or 3 * 8 * 8 = 192 bits - // Avx registers are 256 bits so rgb span will be loaded with extra 64 bits from the next stride: - // stride 0 0 - 192 -(+64bits)-> 256 - // stride 1 192 - 384 -(+64bits)-> 448 - // stride 2 384 - 576 -(+64bits)-> 640 - // stride 3 576 - 768 -(+64bits)-> 832 - // stride 4 768 - 960 -(+64bits)-> 1024 - // stride 5 960 - 1152 -(+64bits)-> 1216 - // stride 6 1152 - 1344 -(+64bits)-> 1408 - // stride 7 1344 - 1536 -(+64bits)-> 1600 <-- READ ACCESS VIOLATION - // - // Total size of the 64 pixel rgb span: 64 * 3 * 8 = 1536 bits, avx operations require 1600 bits - // This is not permitted - we are reading foreign memory - // - // 8 byte padding to rgb byte span will solve this problem without extra code in converters - get - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (IsSupported) - { - return 8; - } -#endif - return 0; - } - } - -#if SUPPORTS_RUNTIME_INTRINSICS - - internal static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] - { - 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, - 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 - }; - - internal static ReadOnlySpan ExtractRgb => new byte[] - { - 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, - 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF - }; -#endif - - /// - /// Converts 8x8 Rgb24 pixel matrix to YCbCr pixel matrices with 4:4:4 subsampling - /// - /// Total size of rgb span must be 200 bytes - /// Span of rgb pixels with size of 64 - /// 8x8 destination matrix of Luminance(Y) converted data - /// 8x8 destination matrix of Chrominance(Cb) converted data - /// 8x8 destination matrix of Chrominance(Cr) converted data - public static void Convert444(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) - { - Debug.Assert(IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - var f0299 = Vector256.Create(0.299f); - var f0587 = Vector256.Create(0.587f); - var f0114 = Vector256.Create(0.114f); - var fn0168736 = Vector256.Create(-0.168736f); - var fn0331264 = Vector256.Create(-0.331264f); - var f128 = Vector256.Create(128f); - var fn0418688 = Vector256.Create(-0.418688f); - var fn0081312F = Vector256.Create(-0.081312F); - var f05 = Vector256.Create(0.5f); - var zero = Vector256.Create(0).AsByte(); - - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - ref Vector256 destYRef = ref yBlock.V0; - ref Vector256 destCbRef = ref cbBlock.V0; - ref Vector256 destCrRef = ref crBlock.V0; - - var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); - var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); - Vector256 rgb, rg, bx; - Vector256 r, g, b; - - const int bytesPerRgbStride = 24; - for (int i = 0; i < 8; i++) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Unsafe.Add(ref destYRef, i) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - - // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) - Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - - // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) - Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); - } -#endif - } - - /// - /// Converts 16x8 Rgb24 pixels matrix to 2 Y 8x8 matrices with 4:2:0 subsampling - /// - public static void Convert420(ReadOnlySpan rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row) - { - Debug.Assert(IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - var f0299 = Vector256.Create(0.299f); - var f0587 = Vector256.Create(0.587f); - var f0114 = Vector256.Create(0.114f); - var fn0168736 = Vector256.Create(-0.168736f); - var fn0331264 = Vector256.Create(-0.331264f); - var f128 = Vector256.Create(128f); - var fn0418688 = Vector256.Create(-0.418688f); - var fn0081312F = Vector256.Create(-0.081312F); - var f05 = Vector256.Create(0.5f); - var zero = Vector256.Create(0).AsByte(); - - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - - int destOffset = row * 4; - - ref Vector256 destCbRef = ref Unsafe.Add(ref Unsafe.As>(ref cbBlock), destOffset); - ref Vector256 destCrRef = ref Unsafe.Add(ref Unsafe.As>(ref crBlock), destOffset); - - var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); - var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); - Vector256 rgb, rg, bx; - Vector256 r, g, b; - - Span> rDataLanes = stackalloc Vector256[4]; - Span> gDataLanes = stackalloc Vector256[4]; - Span> bDataLanes = stackalloc Vector256[4]; - - const int bytesPerRgbStride = 24; - for (int i = 0; i < 4; i++) - { - // 16x2 => 8x1 - // left 8x8 column conversions - for (int j = 0; j < 4; j += 2) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - - int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1); - - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Unsafe.Add(ref yBlockLeft.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - - rDataLanes[j] = r; - gDataLanes[j] = g; - bDataLanes[j] = b; - } - - // 16x2 => 8x1 - // right 8x8 column conversions - for (int j = 1; j < 4; j += 2) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - - int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1); - - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Unsafe.Add(ref yBlockRight.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - - rDataLanes[j] = r; - gDataLanes[j] = g; - bDataLanes[j] = b; - } - - r = Scale16x2_8x1(rDataLanes); - g = Scale16x2_8x1(gDataLanes); - b = Scale16x2_8x1(bDataLanes); - - // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) - Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - - // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) - Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); - } -#endif - } - -#if SUPPORTS_RUNTIME_INTRINSICS - /// - /// Scales 16x2 matrix to 8x1 using 2x2 average - /// - /// Input matrix consisting of 4 256bit vectors - /// 256bit vector containing upper and lower scaled parts of the input matrix - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector256 Scale16x2_8x1(ReadOnlySpan> v) - { - Debug.Assert(Avx2.IsSupported, "AVX2 is required to run this converter"); - DebugGuard.IsTrue(v.Length == 4, "Input span must consist of 4 elements"); - - var f025 = Vector256.Create(0.25f); - - Vector256 left = Avx.Add(v[0], v[2]); - Vector256 right = Avx.Add(v[1], v[3]); - Vector256 avg2x2 = Avx.Multiply(Avx.HorizontalAdd(left, right), f025); - - return Avx2.Permute4x64(avg2x2.AsDouble(), 0b11_01_10_00).AsSingle(); - } -#endif - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs new file mode 100644 index 0000000000..5cc8a0e34f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Converter used to convert pixel data to jpeg spectral data. + /// + internal abstract class SpectralConverter + { + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs new file mode 100644 index 0000000000..07f9e2e49f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -0,0 +1,149 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using System.Buffers; +using System.Linq; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + internal class SpectralConverter : SpectralConverter, IDisposable + where TPixel : unmanaged, IPixel + { + private readonly ComponentProcessor[] componentProcessors; + + private readonly int pixelRowsPerStep; + + private int pixelRowCounter; + + private readonly Buffer2D pixelBuffer; + + private readonly IMemoryOwner redLane; + + private readonly IMemoryOwner greenLane; + + private readonly IMemoryOwner blueLane; + + private readonly int alignedPixelWidth; + + private readonly JpegColorConverterBase colorConverter; + + public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequantTables) + { + MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; + + // iteration data + int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); + int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); + + const int blockPixelHeight = 8; + this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; + + // pixel buffer of the image + this.pixelBuffer = image.GetRootFramePixelBuffer(); + + // component processors from spectral to Rgb24 + const int blockPixelWidth = 8; + this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; + var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep); + this.componentProcessors = new ComponentProcessor[frame.Components.Length]; + for (int i = 0; i < this.componentProcessors.Length; i++) + { + Component component = frame.Components[i]; + this.componentProcessors[i] = new ComponentProcessor( + allocator, + component, + postProcessorBufferSize, + dequantTables[component.QuantizationTableIndex]); + } + + this.redLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + this.greenLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + this.blueLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + + // color converter from Rgb24 to YCbCr + this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); + } + + public void ConvertStrideBaseline() + { + // Codestyle suggests expression body but it + // also requires empty line before comments + // which looks ugly with expression bodies thus this warning disable +#pragma warning disable IDE0022 + // Convert next pixel stride using single spectral `stride' + // Note that zero passing eliminates the need of virtual call + // from JpegComponentPostProcessor + this.ConvertStride(spectralStep: 0); +#pragma warning restore IDE0022 + } + + public void ConvertFull() + { + int steps = (int)Numerics.DivideCeil((uint)this.pixelBuffer.Height, (uint)this.pixelRowsPerStep); + for (int i = 0; i < steps; i++) + { + this.ConvertStride(i); + } + } + + private void ConvertStride(int spectralStep) + { + int start = this.pixelRowCounter; + int end = start + this.pixelRowsPerStep; + + int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1; + + // Pixel strides must be padded with the last pixel of the stride + int paddingStartIndex = this.pixelBuffer.Width; + int paddedPixelsCount = this.alignedPixelWidth - this.pixelBuffer.Width; + + Span rLane = this.redLane.GetSpan(); + Span gLane = this.greenLane.GetSpan(); + Span bLane = this.blueLane.GetSpan(); + + for (int yy = start; yy < end; yy++) + { + int y = yy - this.pixelRowCounter; + + // Unpack TPixel to r/g/b planes + int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex); + Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex); + PixelOperations.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow); + + rLane.Slice(paddingStartIndex).Fill(rLane[paddingStartIndex - 1]); + gLane.Slice(paddingStartIndex).Fill(gLane[paddingStartIndex - 1]); + bLane.Slice(paddingStartIndex).Fill(bLane[paddingStartIndex - 1]); + + // Convert from rgb24 to target pixel type + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + this.colorConverter.ConvertFromRgb(values, rLane, gLane, bLane); + } + + // Convert pixels to spectral + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); + } + + this.pixelRowCounter = end; + } + + /// + public void Dispose() + { + foreach (ComponentProcessor cpp in this.componentProcessors) + { + cpp.Dispose(); + } + + this.redLane.Dispose(); + this.greenLane.Dispose(); + this.blueLane.Dispose(); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs deleted file mode 100644 index ddf3c52925..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. - /// - /// The pixel type to work on - internal ref struct YCbCrForwardConverter420 - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 16 * 8; - - /// - /// Total byte size of processed pixels converted from TPixel to - /// - private const int RgbSpanByteSize = PixelsPerSample * 3; - - /// - /// The left Y component - /// - public Block8x8F YLeft; - - /// - /// The left Y component - /// - public Block8x8F YRight; - - /// - /// The Cb component - /// - public Block8x8F Cb; - - /// - /// The Cr component - /// - public Block8x8F Cr; - - /// - /// The color conversion tables - /// - private RgbToYCbCrConverterLut colorTables; - - /// - /// Temporal 16x8 block to hold TPixel data - /// - private readonly Span pixelSpan; - - /// - /// Temporal RGB block - /// - private readonly Span rgbSpan; - - /// - /// Sampled pixel buffer size - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations - /// - private readonly Configuration config; - - public YCbCrForwardConverter420(ImageFrame frame) - { - // matrices would be filled during convert calls - this.YLeft = default; - this.YRight = default; - this.Cb = default; - this.Cr = default; - - // temporal pixel buffers - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); - - // frame data - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - - // conversion vector fallback data - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.colorTables = RgbToYCbCrConverterLut.Create(); - } - else - { - this.colorTables = default; - } - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(16, 8); - - public void Convert(int x, int y, ref RowOctet currentRows, int idx) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); - } - else - { - this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs deleted file mode 100644 index 96a5569dac..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. - /// - /// The pixel type to work on - internal ref struct YCbCrForwardConverter444 - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 8 * 8; - - /// - /// Total byte size of processed pixels converted from TPixel to - /// - private const int RgbSpanByteSize = PixelsPerSample * 3; - - /// - /// The Y component - /// - public Block8x8F Y; - - /// - /// The Cb component - /// - public Block8x8F Cb; - - /// - /// The Cr component - /// - public Block8x8F Cr; - - /// - /// The color conversion tables - /// - private RgbToYCbCrConverterLut colorTables; - - /// - /// Temporal 64-byte span to hold unconverted TPixel data - /// - private readonly Span pixelSpan; - - /// - /// Temporal 64-byte span to hold converted Rgb24 data - /// - private readonly Span rgbSpan; - - /// - /// Sampled pixel buffer size - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations - /// - private readonly Configuration config; - - public YCbCrForwardConverter444(ImageFrame frame) - { - // matrices would be filled during convert calls - this.Y = default; - this.Cb = default; - this.Cr = default; - - // temporal pixel buffers - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); - - // frame data - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - - // conversion vector fallback data - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.colorTables = RgbToYCbCrConverterLut.Create(); - } - else - { - this.colorTables = default; - } - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(8, 8); - - /// - /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) - /// - public void Convert(int x, int y, ref RowOctet currentRows) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); - - ref Block8x8F yBlock = ref this.Y; - ref Block8x8F cbBlock = ref this.Cb; - ref Block8x8F crBlock = ref this.Cr; - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); - } - else - { - this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs deleted file mode 100644 index 48aacd7ff6..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - internal static class YCbCrForwardConverter - where TPixel : unmanaged, IPixel - { - public static void LoadAndStretchEdges(RowOctet source, Span dest, Point start, Size sampleSize, Size totalSize) - { - DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X)); - DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y)); - - int width = Math.Min(sampleSize.Width, totalSize.Width - start.X); - int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y); - - uint byteWidth = (uint)(width * Unsafe.SizeOf()); - int remainderXCount = sampleSize.Width - width; - - ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(dest)); - int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf(); - - for (int y = 0; y < height; y++) - { - Span row = source[y]; - - ref byte s = ref Unsafe.As(ref row[start.X]); - ref byte d = ref Unsafe.Add(ref blockStart, y * rowSizeInBytes); - - Unsafe.CopyBlock(ref d, ref s, byteWidth); - - ref TPixel last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1); - - for (int x = 1; x <= remainderXCount; x++) - { - Unsafe.Add(ref last, x) = last; - } - } - - int remainderYCount = sampleSize.Height - height; - - if (remainderYCount == 0) - { - return; - } - - ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * rowSizeInBytes); - - for (int y = 1; y <= remainderYCount; y++) - { - ref byte remStart = ref Unsafe.Add(ref lastRowStart, rowSizeInBytes * y); - Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)rowSizeInBytes); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs rename to src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs index 287f36991a..05494f68a8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs @@ -1,15 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// /// Identifies the colorspace of a Jpeg image. /// internal enum JpegColorSpace { - Undefined = 0, - /// /// Color space with 1 component. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs index fa3fede339..5fd7da35e5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs @@ -176,7 +176,7 @@ private static int QualityToScale(int quality) return quality < 50 ? (5000 / quality) : (200 - (quality * 2)); } - private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan unscaledTable) + public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan unscaledTable) { Block8x8F table = default; for (int j = 0; j < Block8x8F.Size; j++) @@ -188,6 +188,19 @@ private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan un return table; } + public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable) + { + int scale = QualityToScale(quality); + Block8x8 table = default; + for (int j = 0; j < Block8x8.Size; j++) + { + int x = ((unscaledTable[j] * scale) + 50) / 100; + table[j] = (short)(uint)Numerics.Clamp(x, 1, 255); + } + + return table; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Block8x8F ScaleLuminanceTable(int quality) => ScaleQuantizationTable(scale: QualityToScale(quality), LuminanceTable); diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index d14207ef07..f9c7828caf 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -16,8 +16,17 @@ internal interface IJpegEncoderOptions public int? Quality { get; set; } /// - /// Gets the color type, that will be used to encode the image. + /// Gets or sets the component encoding mode. /// - JpegColorType? ColorType { get; } + /// + /// Interleaved encoding mode encodes all color components in a single scan. + /// Non-interleaved encoding mode encodes each color component in a separate scan. + /// + public bool? Interleaved { get; set; } + + /// + /// Gets or sets jpeg color for encoding. + /// + public JpegEncodingColor? ColorType { get; set; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 7f23d1ac85..b2ad00e077 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -31,17 +31,6 @@ public Image Decode(Configuration configuration, Stream stream, public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - /// - /// Placeholder summary. - /// - /// Placeholder2 - /// Placeholder3 - /// Placeholder4 - /// Placeholder5 - /// Placeholder6 - internal Image DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken) - => this.DecodeInto(configuration, stream, targetSize, cancellationToken); - /// /// Decodes and downscales the image from the specified stream if possible. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 11187fb910..7fc64bd057 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -504,6 +504,8 @@ internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralC // Read on. fileMarker = FindNextFileMarker(stream); } + + this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved; } /// @@ -583,57 +585,58 @@ internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount) /// Returns the jpeg color type based on the colorspace and subsampling used. /// /// Jpeg color type. - private JpegColorType DeduceJpegColorType() + private JpegEncodingColor DeduceJpegColorType() { switch (this.ColorSpace) { case JpegColorSpace.Grayscale: - return JpegColorType.Luminance; + return JpegEncodingColor.Luminance; case JpegColorSpace.RGB: - return JpegColorType.Rgb; + return JpegEncodingColor.Rgb; case JpegColorSpace.YCbCr: if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio444; + return JpegEncodingColor.YCbCrRatio444; } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && + else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio420; + return JpegEncodingColor.YCbCrRatio422; } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 2 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2) + else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && + this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && + this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio422; + return JpegEncodingColor.YCbCrRatio420; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio411; + return JpegEncodingColor.YCbCrRatio411; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio410; + return JpegEncodingColor.YCbCrRatio410; } else { - return JpegColorType.YCbCrRatio420; + return JpegEncodingColor.YCbCrRatio420; } case JpegColorSpace.Cmyk: - return JpegColorType.Cmyk; - + return JpegEncodingColor.Cmyk; + case JpegColorSpace.Ycck: + return JpegEncodingColor.Ycck; default: - return JpegColorType.YCbCrRatio420; + return JpegEncodingColor.YCbCrRatio420; } } @@ -1216,6 +1219,7 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, } this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); + this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive; remaining -= length; @@ -1426,7 +1430,7 @@ private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining) // selectorsCount*2 bytes: component index + huffman tables indices stream.Read(this.temp, 0, selectorsBytes); - this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; + this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount; for (int i = 0; i < selectorsBytes; i += 2) { // 1 byte: Component id diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index e4e68512a3..7fe6a4990d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -13,11 +14,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { + /// + /// Backing field for . + /// + private int? quality; + + /// + public int? Quality + { + get => this.quality; + set + { + if (value is < 1 or > 100) + { + throw new ArgumentException("Quality factor must be in [1..100] range."); + } + + this.quality = value; + } + } + /// - public int? Quality { get; set; } + public bool? Interleaved { get; set; } /// - public JpegColorType? ColorType { get; set; } + public JpegEncodingColor? ColorType { get; set; } /// /// Encodes the image to the specified stream from the . diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs new file mode 100644 index 0000000000..2397c75639 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs @@ -0,0 +1,196 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Image encoder for writing an image to a stream as a jpeg. + /// + internal sealed unsafe partial class JpegEncoderCore + { + private static JpegFrameConfig[] CreateFrameConfigs() + { + var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.LuminanceDC); + var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.LuminanceAC); + var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.ChrominanceDC); + var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.ChrominanceAC); + + var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Quantization.LuminanceTable); + var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Quantization.ChrominanceTable); + + var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC, + defaultChrominanceHuffmanDC, + defaultChrominanceHuffmanAC, + }; + + var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable, + defaultChrominanceQuantTable, + }; + + return new JpegFrameConfig[] + { + // YCbCr 4:4:4 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio444, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:2 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio422, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio420, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:1 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio411, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio410, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // Luminance + new JpegFrameConfig( + JpegColorSpace.Grayscale, + JpegEncodingColor.Luminance, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + + // Rgb + new JpegFrameConfig( + JpegColorSpace.RGB, + JpegEncodingColor.Rgb, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown + }, + + // Cmyk + new JpegFrameConfig( + JpegColorSpace.Cmyk, + JpegEncodingColor.Cmyk, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, + }, + + // YccK + new JpegFrameConfig( + JpegColorSpace.Ycck, + JpegEncodingColor.Ycck, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, + }, + }; + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index c29b34d674..ea29e071ce 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -8,7 +8,6 @@ using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -22,27 +21,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Image encoder for writing an image to a stream as a jpeg. /// - internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals + internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals { /// - /// The number of quantization tables. + /// The available encodable frame configs. /// - private const int QuantizationTableCount = 2; + private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); /// /// A scratch buffer to reduce allocations. /// private readonly byte[] buffer = new byte[20]; - /// - /// The quality, that will be used to encode the image. - /// - private readonly int? quality; - - /// - /// Gets or sets the colorspace to use. - /// - private JpegColorType? colorType; + private readonly IJpegEncoderOptions options; /// /// The output stream. All attempted writes after the first error become no-ops. @@ -54,14 +45,9 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// /// The options. public JpegEncoderCore(IJpegEncoderOptions options) - { - this.quality = options.Quality; + => this.options = options; - if (IsSupportedColorType(options.ColorType)) - { - this.colorType = options.ColorType; - } - } + public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; /// /// Encode writes the image to the jpeg baseline format with the given options. @@ -84,72 +70,45 @@ public void Encode(Image image, Stream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); this.outputStream = stream; + ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); + JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata); - // If the color type was not specified by the user, preserve the color type of the input image. - if (!this.colorType.HasValue) - { - this.colorType = GetFallbackColorType(image); - } - - // Compute number of components based on color type in options. - int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3; - ReadOnlySpan componentIds = this.GetComponentIds(); - - // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that - // Initialize the quantization tables. - this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable); + bool interleaved = this.options.Interleaved ?? jpegMetadata.Interleaved ?? true; + using var frame = new JpegFrame(image, frameConfig, interleaved); // Write the Start Of Image marker. this.WriteStartOfImage(); - // Do not write APP0 marker for RGB colorspace. - if (this.colorType != JpegColorType.Rgb) + // Write APP0 marker + if (frameConfig.AdobeColorTransformMarkerFlag is null) { this.WriteJfifApplicationHeader(metadata); } - // Write Exif, XMP, ICC and IPTC profiles - this.WriteProfiles(metadata); - - if (this.colorType == JpegColorType.Rgb) + // Write APP14 marker with adobe color extension + else { - // Write App14 marker to indicate RGB color space. - this.WriteApp14Marker(); + this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value); } - // Write the quantization tables. - this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); + // Write Exif, XMP, ICC and IPTC profiles + this.WriteProfiles(metadata); // Write the image dimensions. - this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds); + this.WriteStartOfFrame(image.Width, image.Height, frameConfig); // Write the Huffman tables. - this.WriteDefineHuffmanTables(componentCount); + var scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); + this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder); - // Write the scan header. - this.WriteStartOfScan(componentCount, componentIds); + // Write the quantization tables. + this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.options.Quality, jpegMetadata); - // Write the scan compressed data. - switch (this.colorType) - { - case JpegColorType.YCbCrRatio444: - new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); - break; - case JpegColorType.YCbCrRatio420: - new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); - break; - case JpegColorType.Luminance: - new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); - break; - case JpegColorType.Rgb: - new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken); - break; - default: - // all other non-supported color types are checked at the start of this method - break; - } + // Write scans with actual pixel data + using var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables); + this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); @@ -157,75 +116,6 @@ public void Encode(Image image, Stream stream, CancellationToken stream.Flush(); } - /// - /// If color type was not set, set it based on the given image. - /// Note, if there is no metadata and the image has multiple components this method - /// returns defering the field assignment - /// to . - /// - private static JpegColorType? GetFallbackColorType(Image image) - where TPixel : unmanaged, IPixel - { - // First inspect the image metadata. - JpegColorType? colorType = null; - JpegMetadata metadata = image.Metadata.GetJpegMetadata(); - if (IsSupportedColorType(metadata.ColorType)) - { - return metadata.ColorType; - } - - // Secondly, inspect the pixel type. - // TODO: PixelTypeInfo should contain a component count! - bool isGrayscale = - typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) || - typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32); - - // We don't set multi-component color types here since we can set it based upon - // the quality in InitQuantizationTables. - if (isGrayscale) - { - colorType = JpegColorType.Luminance; - } - - return colorType; - } - - /// - /// Returns true, if the color type is supported by the encoder. - /// - /// The color type. - /// true, if color type is supported. - private static bool IsSupportedColorType(JpegColorType? colorType) - => colorType == JpegColorType.YCbCrRatio444 - || colorType == JpegColorType.YCbCrRatio420 - || colorType == JpegColorType.Luminance - || colorType == JpegColorType.Rgb; - - /// - /// Gets the component ids. - /// For color space RGB this will be RGB as ASCII, otherwise 1, 2, 3. - /// - /// The component Ids. - private ReadOnlySpan GetComponentIds() => this.colorType == JpegColorType.Rgb - ? new ReadOnlySpan(new byte[] { 82, 71, 66 }) - : new ReadOnlySpan(new byte[] { 1, 2, 3 }); - - /// - /// Writes data to "Define Quantization Tables" block for QuantIndex. - /// - /// The "Define Quantization Tables" block. - /// Offset in "Define Quantization Tables" block. - /// The quantization index. - /// The quantization table to copy data from. - private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant) - { - dqt[offset++] = (byte)i; - for (int j = 0; j < Block8x8F.Size; j++) - { - dqt[offset++] = (byte)quant[ZigZag.ZigZagOrder[j]]; - } - } - /// /// Write the start of image marker. /// @@ -286,71 +176,40 @@ private void WriteJfifApplicationHeader(ImageMetadata meta) /// /// Writes the Define Huffman Table marker and tables. /// - /// The number of components to write. - private void WriteDefineHuffmanTables(int componentCount) + private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - // Table identifiers. - ReadOnlySpan headers = new byte[] + if (tableConfigs is null) { - 0x00, - 0x10, - 0x01, - 0x11 - }; + throw new ArgumentNullException(nameof(tableConfigs)); + } int markerlen = 2; - HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs; - if (componentCount == 1) + for (int i = 0; i < tableConfigs.Length; i++) { - // Drop the Chrominance tables. - specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] }; - } - - for (int i = 0; i < specs.Length; i++) - { - ref HuffmanSpec s = ref specs[i]; - markerlen += 1 + 16 + s.Values.Length; + markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length; } this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); - for (int i = 0; i < specs.Length; i++) + for (int i = 0; i < tableConfigs.Length; i++) { - this.outputStream.WriteByte(headers[i]); - this.outputStream.Write(specs[i].Count); - this.outputStream.Write(specs[i].Values); - } - } + JpegHuffmanTableConfig tableConfig = tableConfigs[i]; - /// - /// Writes the Define Quantization Marker and tables. - /// - private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable) - { - // Marker + quantization table lengths. - int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size)); - this.WriteMarkerHeader(JpegConstants.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.Size) + QuantizationTableCount; - byte[] dqt = new byte[dqtCount]; - int offset = 0; - - WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref luminanceQuantTable); - WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref chrominanceQuantTable); + int header = (tableConfig.Class << 4) | tableConfig.DestinationIndex; + this.outputStream.WriteByte((byte)header); + this.outputStream.Write(tableConfig.Table.Count); + this.outputStream.Write(tableConfig.Table.Values); - this.outputStream.Write(dqt, 0, dqtCount); + scanEncoder.BuildHuffmanTable(tableConfig); + } } /// /// Writes the APP14 marker to indicate the image is in RGB color space. /// - private void WriteApp14Marker() + private void WriteApp14Marker(byte colorTransform) { - this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + AdobeMarker.Length); + this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length); // Identifier: ASCII "Adobe". this.buffer[0] = 0x41; @@ -368,8 +227,8 @@ private void WriteApp14Marker() // Flags1 BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0); - // Transform byte, 0 in combination with three components means the image is in RGB colorspace. - this.buffer[11] = 0; + // Color transform byte + this.buffer[11] = colorTransform; this.outputStream.Write(this.buffer.AsSpan(0, 12)); } @@ -385,8 +244,8 @@ private void WriteExifProfile(ExifProfile exifProfile) return; } - const int MaxBytesApp1 = 65533; // 64k - 2 padding bytes - const int MaxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. + const int maxBytesApp1 = 65533; // 64k - 2 padding bytes + const int maxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. byte[] data = exifProfile.ToByteArray(); @@ -396,27 +255,27 @@ private void WriteExifProfile(ExifProfile exifProfile) } // We can write up to a maximum of 64 data to the initial marker so calculate boundaries. - int exifMarkerLength = ProfileResolver.ExifMarker.Length; + int exifMarkerLength = Components.Decoder.ProfileResolver.ExifMarker.Length; int remaining = exifMarkerLength + data.Length; - int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; + int bytesToWrite = remaining > maxBytesApp1 ? maxBytesApp1 : remaining; int app1Length = bytesToWrite + 2; // Write the app marker, EXIF marker, and data this.WriteApp1Header(app1Length); - this.outputStream.Write(ProfileResolver.ExifMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength); remaining -= bytesToWrite; // If the exif data exceeds 64K, write it in multiple APP1 Markers - for (int idx = MaxBytesWithExifId; idx < data.Length; idx += MaxBytesWithExifId) + for (int idx = maxBytesWithExifId; idx < data.Length; idx += maxBytesWithExifId) { - bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining; + bytesToWrite = remaining > maxBytesWithExifId ? maxBytesWithExifId : remaining; app1Length = bytesToWrite + 2 + exifMarkerLength; this.WriteApp1Header(app1Length); // Write Exif00 marker - this.outputStream.Write(ProfileResolver.ExifMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); // Write the exif data this.outputStream.Write(data, idx, bytesToWrite); @@ -434,7 +293,7 @@ private void WriteExifProfile(ExifProfile exifProfile) /// private void WriteIptcProfile(IptcProfile iptcProfile) { - const int Max = 65533; + const int maxBytes = 65533; if (iptcProfile is null || !iptcProfile.Values.Any()) { return; @@ -447,19 +306,19 @@ private void WriteIptcProfile(IptcProfile iptcProfile) return; } - if (data.Length > Max) + if (data.Length > maxBytes) { - throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes"); + throw new ImageFormatException($"Iptc profile size exceeds limit of {maxBytes} bytes"); } - int app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length + - ProfileResolver.AdobeImageResourceBlockMarker.Length + - ProfileResolver.AdobeIptcMarker.Length + + int app13Length = 2 + Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker.Length + + Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker.Length + + Components.Decoder.ProfileResolver.AdobeIptcMarker.Length + 2 + 4 + data.Length; this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13); - this.outputStream.Write(ProfileResolver.AdobePhotoshopApp13Marker); - this.outputStream.Write(ProfileResolver.AdobeImageResourceBlockMarker); - this.outputStream.Write(ProfileResolver.AdobeIptcMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeIptcMarker); this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) this.outputStream.WriteByte(0); BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length); @@ -481,9 +340,9 @@ private void WriteXmpProfile(XmpProfile xmpProfile) return; } - const int XmpOverheadLength = 29; - const int Max = 65533; - const int MaxData = Max - XmpOverheadLength; + const int xmpOverheadLength = 29; + const int maxBytes = 65533; + const int maxData = maxBytes - xmpOverheadLength; byte[] data = xmpProfile.Data; @@ -499,16 +358,16 @@ private void WriteXmpProfile(XmpProfile xmpProfile) { int length = dataLength; // Number of bytes to write. - if (length > MaxData) + if (length > maxData) { - length = MaxData; + length = maxData; } dataLength -= length; - int app1Length = 2 + ProfileResolver.XmpMarker.Length + length; + int app1Length = 2 + Components.Decoder.ProfileResolver.XmpMarker.Length + length; this.WriteApp1Header(app1Length); - this.outputStream.Write(ProfileResolver.XmpMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.XmpMarker); this.outputStream.Write(data, offset, length); offset += length; @@ -551,9 +410,9 @@ private void WriteIccProfile(IccProfile iccProfile) return; } - const int IccOverheadLength = 14; - const int Max = 65533; - const int MaxData = Max - IccOverheadLength; + const int iccOverheadLength = 14; + const int maxBytes = 65533; + const int maxData = maxBytes - iccOverheadLength; byte[] data = iccProfile.ToByteArray(); @@ -564,9 +423,9 @@ private void WriteIccProfile(IccProfile iccProfile) // Calculate the number of markers we'll need, rounding up of course. int dataLength = data.Length; - int count = dataLength / MaxData; + int count = dataLength / maxData; - if (count * MaxData != dataLength) + if (count * maxData != dataLength) { count++; } @@ -579,9 +438,9 @@ private void WriteIccProfile(IccProfile iccProfile) { int length = dataLength; // Number of bytes to write. - if (length > MaxData) + if (length > maxData) { - length = MaxData; + length = maxData; } dataLength -= length; @@ -609,7 +468,7 @@ private void WriteIccProfile(IccProfile iccProfile) this.buffer[12] = (byte)current; // The position within the collection. this.buffer[13] = (byte)count; // The total number of profiles. - this.outputStream.Write(this.buffer, 0, IccOverheadLength); + this.outputStream.Write(this.buffer, 0, iccOverheadLength); this.outputStream.Write(data, offset, length); current++; @@ -643,124 +502,46 @@ private void WriteProfiles(ImageMetadata metadata) /// /// Writes the Start Of Frame (Baseline) marker. /// - /// The width of the image. - /// The height of the image. - /// The number of components in a pixel. - /// The component Id's. - private void WriteStartOfFrame(int width, int height, int componentCount, ReadOnlySpan componentIds) + private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - // "default" to 4:2:0 - ReadOnlySpan subsamples = new byte[] - { - 0x22, - 0x11, - 0x11 - }; - - ReadOnlySpan chroma = new byte[] - { - 0x00, - 0x01, - 0x01 - }; - - if (this.colorType == JpegColorType.Luminance) - { - subsamples = new byte[] - { - 0x11, - 0x00, - 0x00 - }; - } - else - { - switch (this.colorType) - { - case JpegColorType.YCbCrRatio444: - case JpegColorType.Rgb: - subsamples = new byte[] - { - 0x11, - 0x11, - 0x11 - }; - - if (this.colorType == JpegColorType.Rgb) - { - chroma = new byte[] - { - 0x00, - 0x00, - 0x00 - }; - } - - break; - case JpegColorType.YCbCrRatio420: - subsamples = new byte[] - { - 0x22, - 0x11, - 0x11 - }; - break; - } - } + JpegComponentConfig[] components = frame.Components; // Length (high byte, low byte), 8 + components * 3. - int markerlen = 8 + (3 * componentCount); + int markerlen = 8 + (3 * components.Length); this.WriteMarkerHeader(JpegConstants.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 this.buffer[3] = (byte)(width >> 8); this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - this.buffer[5] = (byte)componentCount; + this.buffer[5] = (byte)components.Length; - for (int i = 0; i < componentCount; i++) + // Components data + for (int i = 0; i < components.Length; i++) { int i3 = 3 * i; - - // Component ID. Span bufferSpan = this.buffer.AsSpan(i3 + 6, 3); - bufferSpan[2] = chroma[i]; - bufferSpan[1] = subsamples[i]; - bufferSpan[0] = componentIds[i]; + + // Quantization table selector + bufferSpan[2] = (byte)components[i].QuantizatioTableIndex; + + // Sampling factors + // 4 bits + int samplingFactors = (components[i].HorizontalSampleFactor << 4) | components[i].VerticalSampleFactor; + bufferSpan[1] = (byte)samplingFactors; + + // Id + bufferSpan[0] = components[i].Id; } - this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); + this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9); } /// /// Writes the StartOfScan marker. /// - /// The number of components in a pixel. - /// The componentId's. - private void WriteStartOfScan(int componentCount, ReadOnlySpan componentIds) + private void WriteStartOfScan(Span components) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - ReadOnlySpan huffmanId = new byte[] - { - 0x00, - 0x11, - 0x11 - }; - - // Use the same DC/AC tables for all channels for RGB. - if (this.colorType == JpegColorType.Rgb) - { - huffmanId = new byte[] - { - 0x00, - 0x00, - 0x00 - }; - } - // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: // - the marker length "\x00\x0c", // - the number of components "\x03", @@ -774,15 +555,22 @@ private void WriteStartOfScan(int componentCount, ReadOnlySpan componentId this.buffer[1] = JpegConstants.Markers.SOS; // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) - int sosSize = 6 + (2 * componentCount); + int sosSize = 6 + (2 * components.Length); this.buffer[2] = 0x00; this.buffer[3] = (byte)sosSize; - this.buffer[4] = (byte)componentCount; // Number of components in a scan - for (int i = 0; i < componentCount; i++) + this.buffer[4] = (byte)components.Length; // Number of components in a scan + + // Components data + for (int i = 0; i < components.Length; i++) { int i2 = 2 * i; - this.buffer[i2 + 5] = componentIds[i]; // Component Id - this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table + + // Id + this.buffer[i2 + 5] = components[i].Id; + + // Table selectors + int tableSelectors = (components[i].DcTableSelector << 4) | components[i].AcTableSelector; + this.buffer[i2 + 6] = (byte)tableSelectors; } this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection. @@ -801,6 +589,40 @@ private void WriteEndOfImageMarker() this.outputStream.Write(this.buffer, 0, 2); } + /// + /// Writes scans for given config. + /// + private void WriteHuffmanScans(JpegFrame frame, JpegFrameConfig frameConfig, SpectralConverter spectralConverter, HuffmanScanEncoder encoder, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + if (frame.Components.Length == 1) + { + frame.AllocateComponents(fullScan: false); + + this.WriteStartOfScan(frameConfig.Components); + encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); + } + else if (frame.Interleaved) + { + frame.AllocateComponents(fullScan: false); + + this.WriteStartOfScan(frameConfig.Components); + encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken); + } + else + { + frame.AllocateComponents(fullScan: true); + spectralConverter.ConvertFull(); + + Span components = frameConfig.Components; + for (int i = 0; i < frame.Components.Length; i++) + { + this.WriteStartOfScan(components.Slice(i, 1)); + encoder.EncodeScanBaseline(frame.Components[i], cancellationToken); + } + } + } + /// /// Writes the header for a marker with the given length. /// @@ -817,54 +639,78 @@ private void WriteMarkerHeader(byte marker, int length) } /// - /// Initializes quantization tables. + /// Writes the Define Quantization Marker and prepares tables for encoding. /// /// - /// - /// Zig-zag ordering is NOT applied to the resulting tables. - /// - /// /// We take quality values in a hierarchical order: - /// 1. Check if encoder has set quality - /// 2. Check if metadata has set quality - /// 3. Take default quality value - 75 - /// + /// + /// Check if encoder has set quality. + /// Check if metadata has set quality. + /// Take default quality value from + /// /// - /// Color components count. + /// Quantization tables configs. + /// Optional quality value from the options. /// Jpeg metadata instance. - /// Output luminance quantization table. - /// Output chrominance quantization table. - private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable) + private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata) { - int lumaQuality; - int chromaQuality; - if (this.quality.HasValue) - { - lumaQuality = this.quality.Value; - chromaQuality = this.quality.Value; - } - else - { - lumaQuality = metadata.LuminanceQuality; - chromaQuality = metadata.ChrominanceQuality; - } + int dataLen = configs.Length * (1 + Block8x8.Size); + + // Marker + quantization table lengths. + int markerlen = 2 + dataLen; + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - // Luminance - lumaQuality = Numerics.Clamp(lumaQuality, 1, 100); - luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality); + byte[] buffer = new byte[dataLen]; + int offset = 0; + + Block8x8F workspaceBlock = default; - // Chrominance - chrominanceQuantTable = default; - if (componentCount > 1) + for (int i = 0; i < configs.Length; i++) { - chromaQuality = Numerics.Clamp(chromaQuality, 1, 100); - chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality); + JpegQuantizationTableConfig config = configs[i]; + + int quality = GetQualityForTable(config.DestinationIndex, optionsQuality, metadata); + Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table); - if (!this.colorType.HasValue) + // write to the output stream + buffer[offset++] = (byte)config.DestinationIndex; + + for (int j = 0; j < Block8x8.Size; j++) { - this.colorType = chromaQuality >= 91 ? JpegColorType.YCbCrRatio444 : JpegColorType.YCbCrRatio420; + buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]]; } + + // apply FDCT multipliers and inject to the destination index + workspaceBlock.LoadFrom(ref scaledTable); + FloatingPointDCT.AdjustToFDCT(ref workspaceBlock); + + this.QuantizationTables[config.DestinationIndex] = workspaceBlock; } + + // write filled buffer to the stream + this.outputStream.Write(buffer); + + static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch + { + 0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor, + 1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor, + _ => encoderQuality ?? metadata.Quality, + }; + } + + private JpegFrameConfig GetFrameConfig(JpegMetadata metadata) + { + JpegEncodingColor color = this.options.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420; + JpegFrameConfig frameConfig = Array.Find( + FrameConfigs, + cfg => cfg.EncodingColor == color); + + if (frameConfig == null) + { + throw new ArgumentException(nameof(color)); + } + + return frameConfig; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs similarity index 88% rename from src/ImageSharp/Formats/Jpeg/JpegColorType.cs rename to src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs index f4c54819ac..e6a83b0a5d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Provides enumeration of available JPEG color types. /// - public enum JpegColorType : byte + public enum JpegEncodingColor : byte { /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. @@ -25,24 +25,18 @@ public enum JpegColorType : byte /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution. - /// - /// Note: Not supported by the encoder. /// YCbCrRatio422 = 2, /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered. - /// - /// Note: Not supported by the encoder. /// YCbCrRatio411 = 3, /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. - /// - /// Note: Not supported by the encoder. /// YCbCrRatio410 = 4, @@ -58,9 +52,12 @@ public enum JpegColorType : byte /// /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. - /// - /// Note: Not supported by the encoder. /// Cmyk = 7, + + /// + /// YCCK colorspace (Y, Cb, Cr, and key black). + /// + Ycck = 8, } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 235d8b487b..63127f1eca 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -11,16 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public class JpegMetadata : IDeepCloneable { - /// - /// Backing field for - /// - private int? luminanceQuality; - - /// - /// Backing field for - /// - private int? chrominanceQuality; - /// /// Initializes a new instance of the class. /// @@ -36,8 +26,8 @@ private JpegMetadata(JpegMetadata other) { this.ColorType = other.ColorType; - this.luminanceQuality = other.luminanceQuality; - this.chrominanceQuality = other.chrominanceQuality; + this.LuminanceQuality = other.LuminanceQuality; + this.ChrominanceQuality = other.ChrominanceQuality; } /// @@ -47,11 +37,7 @@ private JpegMetadata(JpegMetadata other) /// This value might not be accurate if it was calculated during jpeg decoding /// with non-complient ITU quantization tables. /// - internal int LuminanceQuality - { - get => this.luminanceQuality ?? Quantization.DefaultQualityFactor; - set => this.luminanceQuality = value; - } + internal int? LuminanceQuality { get; set; } /// /// Gets or sets the jpeg chrominance quality. @@ -60,55 +46,61 @@ internal int LuminanceQuality /// This value might not be accurate if it was calculated during jpeg decoding /// with non-complient ITU quantization tables. /// - internal int ChrominanceQuality - { - get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor; - set => this.chrominanceQuality = value; - } + internal int? ChrominanceQuality { get; set; } /// - /// Gets or sets the encoded quality. + /// Gets the encoded quality. /// /// /// Note that jpeg image can have different quality for luminance and chrominance components. - /// This property returns maximum value of luma/chroma qualities. + /// This property returns maximum value of luma/chroma qualities if both are present. /// public int Quality { get { - // Jpeg always has a luminance table thus it must have a luminance quality derived from it - if (!this.luminanceQuality.HasValue) + if (this.LuminanceQuality.HasValue) { - return Quantization.DefaultQualityFactor; - } + if (this.ChrominanceQuality.HasValue) + { + return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value); + } - int lumaQuality = this.luminanceQuality.Value; - - // Jpeg might not have a chrominance table - return luminance quality (grayscale images) - if (!this.chrominanceQuality.HasValue) - { - return lumaQuality; + return this.LuminanceQuality.Value; } + else + { + if (this.ChrominanceQuality.HasValue) + { + return this.ChrominanceQuality.Value; + } - int chromaQuality = this.chrominanceQuality.Value; - - // Theoretically, luma quality would always be greater or equal to chroma quality - // But we've already encountered images which can have higher quality of chroma components - return Math.Max(lumaQuality, chromaQuality); - } - - set - { - this.LuminanceQuality = value; - this.ChrominanceQuality = value; + return Quantization.DefaultQualityFactor; + } } } /// - /// Gets or sets the color type. + /// Gets the color type. + /// + public JpegEncodingColor? ColorType { get; internal set; } + + /// + /// Gets the component encoding mode. /// - public JpegColorType? ColorType { get; set; } + /// + /// Interleaved encoding mode encodes all color components in a single scan. + /// Non-interleaved encoding mode encodes each color component in a separate scan. + /// + public bool? Interleaved { get; internal set; } + + /// + /// Gets the scan encoding mode. + /// + /// + /// Progressive jpeg images encode component data across multiple scans. + /// + public bool? Progressive { get; internal set; } /// public IDeepCloneable DeepClone() => new JpegMetadata(this); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs index 8bd669b581..2d20553b3c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs @@ -35,7 +35,7 @@ public override void CompressStrip(Span rows, int height) var image = Image.LoadPixelData(rows, width, height); image.Save(memoryStream, new JpegEncoder() { - ColorType = JpegColorType.Rgb + ColorType = JpegEncodingColor.Rgb }); memoryStream.Position = 0; memoryStream.WriteTo(this.Output); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs index fc244afc62..2a0b8dfc2c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs new file mode 100644 index 0000000000..10ded4db65 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Spectral converter for YCbCr TIFF's which use the JPEG compression. + /// The jpeg data should be always treated as RGB color space. + /// + /// The type of the pixel. + internal sealed class RgbJpegSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + /// + /// Initializes a new instance of the class. + /// This Spectral converter will always convert the pixel data to RGB color. + /// + /// The configuration. + public RgbJpegSpectralConverter(Configuration configuration) + : base(configuration) + { + } + + /// + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs index ced6b9027c..3b86d1e87a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs index 07923c8aea..80b967dcbb 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs @@ -36,6 +36,14 @@ internal override void PackFromRgbPlanes( SimdUtils.PackFromRgbPlanes(configuration, redChannel, greenChannel, blueChannel, destination); } + + /// + internal override void UnpackIntoRgbPlanes( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 94dc69388b..db8c98733c 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -198,6 +198,37 @@ internal virtual void PackFromRgbPlanes( } } + /// + /// Bulk operation that unpacks pixels from + /// into 3 seperate RGB channels. The destination must have a padding of 3. + /// + /// A to the red values. + /// A to the green values. + /// A to the blue values. + /// A to the destination pixels. + internal virtual void UnpackIntoRgbPlanes( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + { + int count = redChannel.Length; + + Rgba32 rgba32 = default; + + ref float r = ref MemoryMarshal.GetReference(redChannel); + ref float g = ref MemoryMarshal.GetReference(greenChannel); + ref float b = ref MemoryMarshal.GetReference(blueChannel); + ref TPixel src = ref MemoryMarshal.GetReference(source); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref src, i).ToRgba32(ref rgba32); + Unsafe.Add(ref r, i) = rgba32.R; + Unsafe.Add(ref g, i) = rgba32.G; + Unsafe.Add(ref b, i) = rgba32.B; + } + } + [MethodImpl(InliningOptions.ShortMethod)] internal static void GuardPackFromRgbPlanes(ReadOnlySpan greenChannel, ReadOnlySpan blueChannel, Span destination, int count) { diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index b838453cc1..c0256caf6e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.CmykVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 00550ccae0..25ca884c14 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromGrayscaleScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.GrayscaleScalar(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -28,7 +28,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromGrayscaleAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 4d2bf1d8fc..4cfe9534db 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.RgbVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.RgbAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index e8144bf5a1..a01c5d6e29 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YCbCrVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs deleted file mode 100644 index 5f7375eef9..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder -{ - public class YCbCrForwardConverterBenchmark - { - private RgbToYCbCrConverterLut converter; - private Rgb24[] data; - - [GlobalSetup] - public void Setup() - { - this.converter = RgbToYCbCrConverterLut.Create(); - - var r = new Random(42); - this.data = new Rgb24[64]; - - var d = new byte[3]; - for (int i = 0; i < this.data.Length; i++) - { - r.NextBytes(d); - this.data[i] = new Rgb24(d[0], d[1], d[2]); - } - } - - [Benchmark(Baseline = true)] - public void ConvertLut() - { - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - this.converter.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr); - } - - [Benchmark] - public void ConvertVectorized() - { - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - RgbToYCbCrConverterVectorized.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr); - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index 19cbfc1f5f..c4f9633cae 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YccKVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx2() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs index 734706c0b6..4280996ffc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs @@ -42,7 +42,7 @@ public void SetupImageSharp() using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.imageImageSharp = Image.Load(imageBinaryStream); - this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 }; + this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingColor.YCbCrRatio420 }; this.destinationStream = new MemoryStream(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs index 1021fb4bef..231ba6c250 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs @@ -22,14 +22,19 @@ public class EncodeJpegFeatures // No metadata private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - public static IEnumerable ColorSpaceValues => - new[] { JpegColorType.Luminance, JpegColorType.Rgb, JpegColorType.YCbCrRatio420, JpegColorType.YCbCrRatio444 }; + public static IEnumerable ColorSpaceValues => new[] + { + JpegEncodingColor.Luminance, + JpegEncodingColor.Rgb, + JpegEncodingColor.YCbCrRatio420, + JpegEncodingColor.YCbCrRatio444, + }; [Params(75, 90, 100)] public int Quality; [ParamsSource(nameof(ColorSpaceValues), Priority = -100)] - public JpegColorType TargetColorSpace; + public JpegEncodingColor TargetColorSpace; private Image bmpCore; private JpegEncoder encoder; @@ -41,7 +46,12 @@ public void Setup() { using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.bmpCore = Image.Load(imageBinaryStream); - this.encoder = new JpegEncoder { Quality = this.Quality, ColorType = this.TargetColorSpace }; + this.encoder = new JpegEncoder + { + Quality = this.Quality, + ColorType = this.TargetColorSpace, + Interleaved = true, + }; this.destinationStream = new MemoryStream(); } @@ -67,23 +77,23 @@ public void Benchmark() /* BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT - DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT +.NET SDK=6.0.202 + [Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT + DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT | Method | TargetColorSpace | Quality | Mean | Error | StdDev | |---------- |----------------- |-------- |----------:|----------:|----------:| -| Benchmark | Luminance | 75 | 7.055 ms | 0.1411 ms | 0.3297 ms | -| Benchmark | Rgb | 75 | 12.139 ms | 0.0645 ms | 0.0538 ms | -| Benchmark | YCbCrRatio420 | 75 | 6.463 ms | 0.0282 ms | 0.0235 ms | -| Benchmark | YCbCrRatio444 | 75 | 8.616 ms | 0.0422 ms | 0.0374 ms | -| Benchmark | Luminance | 90 | 7.011 ms | 0.0361 ms | 0.0301 ms | -| Benchmark | Rgb | 90 | 13.119 ms | 0.0947 ms | 0.0886 ms | -| Benchmark | YCbCrRatio420 | 90 | 6.786 ms | 0.0328 ms | 0.0274 ms | -| Benchmark | YCbCrRatio444 | 90 | 8.672 ms | 0.0772 ms | 0.0722 ms | -| Benchmark | Luminance | 100 | 9.554 ms | 0.1211 ms | 0.1012 ms | -| Benchmark | Rgb | 100 | 19.475 ms | 0.1080 ms | 0.0958 ms | -| Benchmark | YCbCrRatio420 | 100 | 10.146 ms | 0.0585 ms | 0.0519 ms | -| Benchmark | YCbCrRatio444 | 100 | 15.317 ms | 0.0709 ms | 0.0592 ms | +| Benchmark | Luminance | 75 | 4.618 ms | 0.0263 ms | 0.0233 ms | +| Benchmark | Rgb | 75 | 12.543 ms | 0.0650 ms | 0.0608 ms | +| Benchmark | YCbCrRatio420 | 75 | 6.639 ms | 0.0778 ms | 0.1256 ms | +| Benchmark | YCbCrRatio444 | 75 | 8.590 ms | 0.0570 ms | 0.0505 ms | +| Benchmark | Luminance | 90 | 4.902 ms | 0.0307 ms | 0.0288 ms | +| Benchmark | Rgb | 90 | 13.447 ms | 0.0468 ms | 0.0415 ms | +| Benchmark | YCbCrRatio420 | 90 | 7.218 ms | 0.0586 ms | 0.0548 ms | +| Benchmark | YCbCrRatio444 | 90 | 9.150 ms | 0.0779 ms | 0.0729 ms | +| Benchmark | Luminance | 100 | 6.731 ms | 0.0325 ms | 0.0304 ms | +| Benchmark | Rgb | 100 | 19.831 ms | 0.1009 ms | 0.0788 ms | +| Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0423 ms | 0.0396 ms | +| Benchmark | YCbCrRatio444 | 100 | 15.345 ms | 0.3276 ms | 0.3065 ms | */ diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs deleted file mode 100644 index e9db6b2c85..0000000000 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Benchmarks -{ - public partial class RgbToYCbCr - { - /// - /// Scaled integer RGBA to YCbCr lookup tables - /// - private static class LookupTables - { - public static readonly int[] Y0 = - { - 0, 306, 612, 918, 1224, 1530, 1836, 2142, 2448, 2754, 3060, 3366, 3672, 3978, 4284, - 4590, 4896, 5202, 5508, 5814, 6120, 6426, 6732, 7038, 7344, 7650, 7956, 8262, 8568, - 8874, 9180, 9486, 9792, 10098, 10404, 10710, 11016, 11322, 11628, 11934, 12240, - 12546, 12852, 13158, 13464, 13770, 14076, 14382, 14688, 14994, 15300, 15606, 15912, - 16218, 16524, 16830, 17136, 17442, 17748, 18054, 18360, 18666, 18972, 19278, 19584, - 19890, 20196, 20502, 20808, 21114, 21420, 21726, 22032, 22338, 22644, 22950, 23256, - 23562, 23868, 24174, 24480, 24786, 25092, 25398, 25704, 26010, 26316, 26622, 26928, - 27234, 27540, 27846, 28152, 28458, 28764, 29070, 29376, 29682, 29988, 30294, 30600, - 30906, 31212, 31518, 31824, 32130, 32436, 32742, 33048, 33354, 33660, 33966, 34272, - 34578, 34884, 35190, 35496, 35802, 36108, 36414, 36720, 37026, 37332, 37638, 37944, - 38250, 38556, 38862, 39168, 39474, 39780, 40086, 40392, 40698, 41004, 41310, 41616, - 41922, 42228, 42534, 42840, 43146, 43452, 43758, 44064, 44370, 44676, 44982, 45288, - 45594, 45900, 46206, 46512, 46818, 47124, 47430, 47736, 48042, 48348, 48654, 48960, - 49266, 49572, 49878, 50184, 50490, 50796, 51102, 51408, 51714, 52020, 52326, 52632, - 52938, 53244, 53550, 53856, 54162, 54468, 54774, 55080, 55386, 55692, 55998, 56304, - 56610, 56916, 57222, 57528, 57834, 58140, 58446, 58752, 59058, 59364, 59670, 59976, - 60282, 60588, 60894, 61200, 61506, 61812, 62118, 62424, 62730, 63036, 63342, 63648, - 63954, 64260, 64566, 64872, 65178, 65484, 65790, 66096, 66402, 66708, 67014, 67320, - 67626, 67932, 68238, 68544, 68850, 69156, 69462, 69768, 70074, 70380, 70686, 70992, - 71298, 71604, 71910, 72216, 72522, 72828, 73134, 73440, 73746, 74052, 74358, 74664, - 74970, 75276, 75582, 75888, 76194, 76500, 76806, 77112, 77418, 77724, 78030 - }; - - public static readonly int[] Y1 = - { - 0, 601, 1202, 1803, 2404, 3005, 3606, 4207, 4808, 5409, 6010, 6611, 7212, 7813, 8414, - 9015, 9616, 10217, 10818, 11419, 12020, 12621, 13222, 13823, 14424, 15025, 15626, - 16227, 16828, 17429, 18030, 18631, 19232, 19833, 20434, 21035, 21636, 22237, 22838, - 23439, 24040, 24641, 25242, 25843, 26444, 27045, 27646, 28247, 28848, 29449, 30050, - 30651, 31252, 31853, 32454, 33055, 33656, 34257, 34858, 35459, 36060, 36661, 37262, - 37863, 38464, 39065, 39666, 40267, 40868, 41469, 42070, 42671, 43272, 43873, 44474, - 45075, 45676, 46277, 46878, 47479, 48080, 48681, 49282, 49883, 50484, 51085, 51686, - 52287, 52888, 53489, 54090, 54691, 55292, 55893, 56494, 57095, 57696, 58297, 58898, - 59499, 60100, 60701, 61302, 61903, 62504, 63105, 63706, 64307, 64908, 65509, 66110, - 66711, 67312, 67913, 68514, 69115, 69716, 70317, 70918, 71519, 72120, 72721, 73322, - 73923, 74524, 75125, 75726, 76327, 76928, 77529, 78130, 78731, 79332, 79933, 80534, - 81135, 81736, 82337, 82938, 83539, 84140, 84741, 85342, 85943, 86544, 87145, 87746, - 88347, 88948, 89549, 90150, 90751, 91352, 91953, 92554, 93155, 93756, 94357, 94958, - 95559, 96160, 96761, 97362, 97963, 98564, 99165, 99766, 100367, 100968, 101569, - 102170, 102771, 103372, 103973, 104574, 105175, 105776, 106377, 106978, 107579, - 108180, 108781, 109382, 109983, 110584, 111185, 111786, 112387, 112988, 113589, - 114190, 114791, 115392, 115993, 116594, 117195, 117796, 118397, 118998, 119599, - 120200, 120801, 121402, 122003, 122604, 123205, 123806, 124407, 125008, 125609, - 126210, 126811, 127412, 128013, 128614, 129215, 129816, 130417, 131018, 131619, - 132220, 132821, 133422, 134023, 134624, 135225, 135826, 136427, 137028, 137629, - 138230, 138831, 139432, 140033, 140634, 141235, 141836, 142437, 143038, 143639, - 144240, 144841, 145442, 146043, 146644, 147245, 147846, 148447, 149048, 149649, - 150250, 150851, 151452, 152053, 152654, 153255 - }; - - public static readonly int[] Y2 = - { - 0, 117, 234, 351, 468, 585, 702, 819, 936, 1053, 1170, 1287, 1404, 1521, 1638, 1755, - 1872, 1989, 2106, 2223, 2340, 2457, 2574, 2691, 2808, 2925, 3042, 3159, 3276, 3393, - 3510, 3627, 3744, 3861, 3978, 4095, 4212, 4329, 4446, 4563, 4680, 4797, 4914, 5031, - 5148, 5265, 5382, 5499, 5616, 5733, 5850, 5967, 6084, 6201, 6318, 6435, 6552, 6669, - 6786, 6903, 7020, 7137, 7254, 7371, 7488, 7605, 7722, 7839, 7956, 8073, 8190, 8307, - 8424, 8541, 8658, 8775, 8892, 9009, 9126, 9243, 9360, 9477, 9594, 9711, 9828, 9945, - 10062, 10179, 10296, 10413, 10530, 10647, 10764, 10881, 10998, 11115, 11232, 11349, - 11466, 11583, 11700, 11817, 11934, 12051, 12168, 12285, 12402, 12519, 12636, 12753, - 12870, 12987, 13104, 13221, 13338, 13455, 13572, 13689, 13806, 13923, 14040, 14157, - 14274, 14391, 14508, 14625, 14742, 14859, 14976, 15093, 15210, 15327, 15444, 15561, - 15678, 15795, 15912, 16029, 16146, 16263, 16380, 16497, 16614, 16731, 16848, 16965, - 17082, 17199, 17316, 17433, 17550, 17667, 17784, 17901, 18018, 18135, 18252, 18369, - 18486, 18603, 18720, 18837, 18954, 19071, 19188, 19305, 19422, 19539, 19656, 19773, - 19890, 20007, 20124, 20241, 20358, 20475, 20592, 20709, 20826, 20943, 21060, 21177, - 21294, 21411, 21528, 21645, 21762, 21879, 21996, 22113, 22230, 22347, 22464, 22581, - 22698, 22815, 22932, 23049, 23166, 23283, 23400, 23517, 23634, 23751, 23868, 23985, - 24102, 24219, 24336, 24453, 24570, 24687, 24804, 24921, 25038, 25155, 25272, 25389, - 25506, 25623, 25740, 25857, 25974, 26091, 26208, 26325, 26442, 26559, 26676, 26793, - 26910, 27027, 27144, 27261, 27378, 27495, 27612, 27729, 27846, 27963, 28080, 28197, - 28314, 28431, 28548, 28665, 28782, 28899, 29016, 29133, 29250, 29367, 29484, 29601, - 29718, 29835 - }; - - public static readonly int[] Cb0 = - { - 0, -172, -344, -516, -688, -860, -1032, -1204, -1376, -1548, -1720, -1892, - -2064, -2236, -2408, -2580, -2752, -2924, -3096, -3268, -3440, -3612, - -3784, -3956, -4128, -4300, -4472, -4644, -4816, -4988, -5160, -5332, - -5504, -5676, -5848, -6020, -6192, -6364, -6536, -6708, -6880, -7052, - -7224, -7396, -7568, -7740, -7912, -8084, -8256, -8428, -8600, -8772, - -8944, -9116, -9288, -9460, -9632, -9804, -9976, -10148, -10320, -10492, - -10664, -10836, -11008, -11180, -11352, -11524, -11696, -11868, -12040, - -12212, -12384, -12556, -12728, -12900, -13072, -13244, -13416, -13588, - -13760, -13932, -14104, -14276, -14448, -14620, -14792, -14964, -15136, - -15308, -15480, -15652, -15824, -15996, -16168, -16340, -16512, -16684, - -16856, -17028, -17200, -17372, -17544, -17716, -17888, -18060, -18232, - -18404, -18576, -18748, -18920, -19092, -19264, -19436, -19608, -19780, - -19952, -20124, -20296, -20468, -20640, -20812, -20984, -21156, -21328, - -21500, -21672, -21844, -22016, -22188, -22360, -22532, -22704, -22876, - -23048, -23220, -23392, -23564, -23736, -23908, -24080, -24252, -24424, - -24596, -24768, -24940, -25112, -25284, -25456, -25628, -25800, -25972, - -26144, -26316, -26488, -26660, -26832, -27004, -27176, -27348, -27520, - -27692, -27864, -28036, -28208, -28380, -28552, -28724, -28896, -29068, - -29240, -29412, -29584, -29756, -29928, -30100, -30272, -30444, -30616, - -30788, -30960, -31132, -31304, -31476, -31648, -31820, -31992, -32164, - -32336, -32508, -32680, -32852, -33024, -33196, -33368, -33540, -33712, - -33884, -34056, -34228, -34400, -34572, -34744, -34916, -35088, -35260, - -35432, -35604, -35776, -35948, -36120, -36292, -36464, -36636, -36808, - -36980, -37152, -37324, -37496, -37668, -37840, -38012, -38184, -38356, - -38528, -38700, -38872, -39044, -39216, -39388, -39560, -39732, -39904, - -40076, -40248, -40420, -40592, -40764, -40936, -41108, -41280, -41452, - -41624, -41796, -41968, -42140, -42312, -42484, -42656, -42828, -43000, - -43172, -43344, -43516, -43688, -43860 - }; - - public static readonly int[] Cb1 = - { - 0, 339, 678, 1017, 1356, 1695, 2034, 2373, 2712, 3051, 3390, 3729, 4068, - 4407, 4746, 5085, 5424, 5763, 6102, 6441, 6780, 7119, 7458, 7797, 8136, - 8475, 8814, 9153, 9492, 9831, 10170, 10509, 10848, 11187, 11526, 11865, - 12204, 12543, 12882, 13221, 13560, 13899, 14238, 14577, 14916, 15255, - 15594, 15933, 16272, 16611, 16950, 17289, 17628, 17967, 18306, 18645, - 18984, 19323, 19662, 20001, 20340, 20679, 21018, 21357, 21696, 22035, - 22374, 22713, 23052, 23391, 23730, 24069, 24408, 24747, 25086, 25425, - 25764, 26103, 26442, 26781, 27120, 27459, 27798, 28137, 28476, 28815, - 29154, 29493, 29832, 30171, 30510, 30849, 31188, 31527, 31866, 32205, - 32544, 32883, 33222, 33561, 33900, 34239, 34578, 34917, 35256, 35595, - 35934, 36273, 36612, 36951, 37290, 37629, 37968, 38307, 38646, 38985, - 39324, 39663, 40002, 40341, 40680, 41019, 41358, 41697, 42036, 42375, - 42714, 43053, 43392, 43731, 44070, 44409, 44748, 45087, 45426, 45765, - 46104, 46443, 46782, 47121, 47460, 47799, 48138, 48477, 48816, 49155, - 49494, 49833, 50172, 50511, 50850, 51189, 51528, 51867, 52206, 52545, - 52884, 53223, 53562, 53901, 54240, 54579, 54918, 55257, 55596, 55935, - 56274, 56613, 56952, 57291, 57630, 57969, 58308, 58647, 58986, 59325, - 59664, 60003, 60342, 60681, 61020, 61359, 61698, 62037, 62376, 62715, - 63054, 63393, 63732, 64071, 64410, 64749, 65088, 65427, 65766, 66105, - 66444, 66783, 67122, 67461, 67800, 68139, 68478, 68817, 69156, 69495, - 69834, 70173, 70512, 70851, 71190, 71529, 71868, 72207, 72546, 72885, - 73224, 73563, 73902, 74241, 74580, 74919, 75258, 75597, 75936, 76275, - 76614, 76953, 77292, 77631, 77970, 78309, 78648, 78987, 79326, 79665, - 80004, 80343, 80682, 81021, 81360, 81699, 82038, 82377, 82716, 83055, - 83394, 83733, 84072, 84411, 84750, 85089, 85428, 85767, 86106, 86445 - }; - - public static readonly int[] Cb2Cr0 = - { - 0, 512, 1024, 1536, 2048, 2560, 3072, 3584, 4096, 4608, 5120, 5632, 6144, - 6656, 7168, 7680, 8192, 8704, 9216, 9728, 10240, 10752, 11264, 11776, - 12288, 12800, 13312, 13824, 14336, 14848, 15360, 15872, 16384, 16896, - 17408, 17920, 18432, 18944, 19456, 19968, 20480, 20992, 21504, 22016, - 22528, 23040, 23552, 24064, 24576, 25088, 25600, 26112, 26624, 27136, - 27648, 28160, 28672, 29184, 29696, 30208, 30720, 31232, 31744, 32256, - 32768, 33280, 33792, 34304, 34816, 35328, 35840, 36352, 36864, 37376, - 37888, 38400, 38912, 39424, 39936, 40448, 40960, 41472, 41984, 42496, - 43008, 43520, 44032, 44544, 45056, 45568, 46080, 46592, 47104, 47616, - 48128, 48640, 49152, 49664, 50176, 50688, 51200, 51712, 52224, 52736, - 53248, 53760, 54272, 54784, 55296, 55808, 56320, 56832, 57344, 57856, - 58368, 58880, 59392, 59904, 60416, 60928, 61440, 61952, 62464, 62976, - 63488, 64000, 64512, 65024, 65536, 66048, 66560, 67072, 67584, 68096, - 68608, 69120, 69632, 70144, 70656, 71168, 71680, 72192, 72704, 73216, - 73728, 74240, 74752, 75264, 75776, 76288, 76800, 77312, 77824, 78336, - 78848, 79360, 79872, 80384, 80896, 81408, 81920, 82432, 82944, 83456, - 83968, 84480, 84992, 85504, 86016, 86528, 87040, 87552, 88064, 88576, - 89088, 89600, 90112, 90624, 91136, 91648, 92160, 92672, 93184, 93696, - 94208, 94720, 95232, 95744, 96256, 96768, 97280, 97792, 98304, 98816, - 99328, 99840, 100352, 100864, 101376, 101888, 102400, 102912, 103424, - 103936, 104448, 104960, 105472, 105984, 106496, 107008, 107520, 108032, - 108544, 109056, 109568, 110080, 110592, 111104, 111616, 112128, 112640, - 113152, 113664, 114176, 114688, 115200, 115712, 116224, 116736, 117248, - 117760, 118272, 118784, 119296, 119808, 120320, 120832, 121344, 121856, - 122368, 122880, 123392, 123904, 124416, 124928, 125440, 125952, 126464, - 126976, 127488, 128000, 128512, 129024, 129536, 130048, 130560 - }; - - public static readonly int[] Cr1 = - { - 0, 429, 858, 1287, 1716, 2145, 2574, 3003, 3432, 3861, 4290, 4719, 5148, - 5577, 6006, 6435, 6864, 7293, 7722, 8151, 8580, 9009, 9438, 9867, 10296, - 10725, 11154, 11583, 12012, 12441, 12870, 13299, 13728, 14157, 14586, - 15015, 15444, 15873, 16302, 16731, 17160, 17589, 18018, 18447, 18876, - 19305, 19734, 20163, 20592, 21021, 21450, 21879, 22308, 22737, 23166, - 23595, 24024, 24453, 24882, 25311, 25740, 26169, 26598, 27027, 27456, - 27885, 28314, 28743, 29172, 29601, 30030, 30459, 30888, 31317, 31746, - 32175, 32604, 33033, 33462, 33891, 34320, 34749, 35178, 35607, 36036, - 36465, 36894, 37323, 37752, 38181, 38610, 39039, 39468, 39897, 40326, - 40755, 41184, 41613, 42042, 42471, 42900, 43329, 43758, 44187, 44616, - 45045, 45474, 45903, 46332, 46761, 47190, 47619, 48048, 48477, 48906, - 49335, 49764, 50193, 50622, 51051, 51480, 51909, 52338, 52767, 53196, - 53625, 54054, 54483, 54912, 55341, 55770, 56199, 56628, 57057, 57486, - 57915, 58344, 58773, 59202, 59631, 60060, 60489, 60918, 61347, 61776, - 62205, 62634, 63063, 63492, 63921, 64350, 64779, 65208, 65637, 66066, - 66495, 66924, 67353, 67782, 68211, 68640, 69069, 69498, 69927, 70356, - 70785, 71214, 71643, 72072, 72501, 72930, 73359, 73788, 74217, 74646, - 75075, 75504, 75933, 76362, 76791, 77220, 77649, 78078, 78507, 78936, - 79365, 79794, 80223, 80652, 81081, 81510, 81939, 82368, 82797, 83226, - 83655, 84084, 84513, 84942, 85371, 85800, 86229, 86658, 87087, 87516, - 87945, 88374, 88803, 89232, 89661, 90090, 90519, 90948, 91377, 91806, - 92235, 92664, 93093, 93522, 93951, 94380, 94809, 95238, 95667, 96096, - 96525, 96954, 97383, 97812, 98241, 98670, 99099, 99528, 99957, 100386, - 100815, 101244, 101673, 102102, 102531, 102960, 103389, 103818, 104247, - 104676, 105105, 105534, 105963, 106392, 106821, 107250, 107679, 108108, - 108537, 108966, 109395 - }; - - public static readonly int[] Cr2 = - { - 0, 83, 166, 249, 332, 415, 498, 581, 664, 747, 830, 913, 996, 1079, 1162, - 1245, 1328, 1411, 1494, 1577, 1660, 1743, 1826, 1909, 1992, 2075, 2158, - 2241, 2324, 2407, 2490, 2573, 2656, 2739, 2822, 2905, 2988, 3071, 3154, - 3237, 3320, 3403, 3486, 3569, 3652, 3735, 3818, 3901, 3984, 4067, 4150, - 4233, 4316, 4399, 4482, 4565, 4648, 4731, 4814, 4897, 4980, 5063, 5146, - 5229, 5312, 5395, 5478, 5561, 5644, 5727, 5810, 5893, 5976, 6059, 6142, - 6225, 6308, 6391, 6474, 6557, 6640, 6723, 6806, 6889, 6972, 7055, 7138, - 7221, 7304, 7387, 7470, 7553, 7636, 7719, 7802, 7885, 7968, 8051, 8134, - 8217, 8300, 8383, 8466, 8549, 8632, 8715, 8798, 8881, 8964, 9047, 9130, - 9213, 9296, 9379, 9462, 9545, 9628, 9711, 9794, 9877, 9960, 10043, 10126, - 10209, 10292, 10375, 10458, 10541, 10624, 10707, 10790, 10873, 10956, - 11039, 11122, 11205, 11288, 11371, 11454, 11537, 11620, 11703, 11786, - 11869, 11952, 12035, 12118, 12201, 12284, 12367, 12450, 12533, 12616, - 12699, 12782, 12865, 12948, 13031, 13114, 13197, 13280, 13363, 13446, - 13529, 13612, 13695, 13778, 13861, 13944, 14027, 14110, 14193, 14276, - 14359, 14442, 14525, 14608, 14691, 14774, 14857, 14940, 15023, 15106, - 15189, 15272, 15355, 15438, 15521, 15604, 15687, 15770, 15853, 15936, - 16019, 16102, 16185, 16268, 16351, 16434, 16517, 16600, 16683, 16766, - 16849, 16932, 17015, 17098, 17181, 17264, 17347, 17430, 17513, 17596, - 17679, 17762, 17845, 17928, 18011, 18094, 18177, 18260, 18343, 18426, - 18509, 18592, 18675, 18758, 18841, 18924, 19007, 19090, 19173, 19256, - 19339, 19422, 19505, 19588, 19671, 19754, 19837, 19920, 20003, 20086, - 20169, 20252, 20335, 20418, 20501, 20584, 20667, 20750, 20833, 20916, - 20999, 21082, 21165 - }; - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs deleted file mode 100644 index c546268bbd..0000000000 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public partial class RgbToYCbCr - { - private const int InputColorCount = 64; - - private const int InputByteCount = InputColorCount * 3; - - private static readonly Vector3 VectorY = new Vector3(0.299F, 0.587F, 0.114F); - - private static readonly Vector3 VectorCb = new Vector3(-0.168736F, 0.331264F, 0.5F); - - private static readonly Vector3 VectorCr = new Vector3(0.5F, 0.418688F, 0.081312F); - - private static class ScaledCoeffs - { - public static readonly int[] Y = - { - 306, 601, 117, 0, - 306, 601, 117, 0, - }; - - public static readonly int[] Cb = - { - -172, 339, 512, 0, - -172, 339, 512, 0, - }; - - public static readonly int[] Cr = - { - 512, 429, 83, 0, - 512, 429, 83, 0, - }; - - public static class SelectLeft - { - public static readonly int[] Y = - { - 1, 1, 1, 0, - 0, 0, 0, 0, - }; - - public static readonly int[] Cb = - { - 1, -1, 1, 0, - 0, 0, 0, 0, - }; - - public static readonly int[] Cr = - { - 1, -1, -1, 0, - 0, 0, 0, 0, - }; - } - - public static class SelectRight - { - public static readonly int[] Y = - { - 0, 0, 0, 0, - 1, 1, 1, 0, - }; - - public static readonly int[] Cb = - { - 0, 0, 0, 0, - 1, -1, 1, 0, - }; - - public static readonly int[] Cr = - { - 0, 0, 0, 0, - 1, -1, -1, 0, - }; - } - } - - private static class OnStackInputCache - { - public unsafe struct Byte - { - public fixed byte Data[InputByteCount * 3]; - - public static Byte Create(byte[] data) - { - Byte result = default; - for (int i = 0; i < data.Length; i++) - { - result.Data[i] = data[i]; - } - - return result; - } - } - } - - public struct Result - { - internal Block8x8F Y; - internal Block8x8F Cb; - internal Block8x8F Cr; - } - - // The operation is defined as "RGBA -> YCbCr Transform a stream of bytes into a stream of floats" - // We need to benchmark the whole operation, to get true results, not missing any side effects! - private byte[] inputSourceRGB; - - private int[] inputSourceRGBAsInteger; - - [GlobalSetup] - public void Setup() - { - // Console.WriteLine("Vector.Count: " + Vector.Count); - this.inputSourceRGB = new byte[InputByteCount]; - for (int i = 0; i < this.inputSourceRGB.Length; i++) - { - this.inputSourceRGB[i] = (byte)(42 + i); - } - - this.inputSourceRGBAsInteger = new int[InputByteCount + Vector.Count]; // Filling this should be part of the measured operation - } - - [Benchmark(Baseline = true, Description = "Floating Point Conversion")] - public unsafe void RgbaToYcbCrScalarFloat() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - var yPtr = (float*)&result.Y; - var cbPtr = (float*)&result.Cb; - var crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - float r = input.Data[i3 + 0]; - float g = input.Data[i3 + 1]; - float b = input.Data[i3 + 2]; - - *yPtr++ = (0.299F * r) + (0.587F * g) + (0.114F * b); - *cbPtr++ = 128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - *crPtr++ = 128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - } - } - - [Benchmark(Description = "Simd Floating Point Conversion")] - public unsafe void RgbaToYcbCrSimdFloat() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - var yPtr = (float*)&result.Y; - var cbPtr = (float*)&result.Cb; - var crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - - var vectorRgb = new Vector3( - input.Data[i3 + 0], - input.Data[i3 + 1], - input.Data[i3 + 2]); - - Vector3 vectorY = VectorY * vectorRgb; - Vector3 vectorCb = VectorCb * vectorRgb; - Vector3 vectorCr = VectorCr * vectorRgb; - - *yPtr++ = vectorY.X + vectorY.Y + vectorY.Z; - *cbPtr++ = 128 + (vectorCb.X - vectorCb.Y + vectorCb.Z); - *crPtr++ = 128 + (vectorCr.X - vectorCr.Y - vectorCr.Z); - } - } - - [Benchmark(Description = "Scaled Integer Conversion + Vector")] - public unsafe void RgbaToYcbCrScaledIntegerSimd() - { - // Copy the input to the stack: - - // On-stack output: - Result result = default; - var yPtr = (float*)&result.Y; - var cbPtr = (float*)&result.Cb; - var crPtr = (float*)&result.Cr; - - var yCoeffs = new Vector(ScaledCoeffs.Y); - var cbCoeffs = new Vector(ScaledCoeffs.Cb); - var crCoeffs = new Vector(ScaledCoeffs.Cr); - - for (int i = 0; i < this.inputSourceRGB.Length; i++) - { - this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i]; - } - - for (int i = 0; i < InputColorCount; i += 2) - { - var rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); - - Vector y = yCoeffs * rgb; - Vector cb = cbCoeffs * rgb; - Vector cr = crCoeffs * rgb; - - *yPtr++ = (y[0] + y[1] + y[2]) >> 10; - *cbPtr++ = 128 + ((cb[0] - cb[1] + cb[2]) >> 10); - *crPtr++ = 128 + ((cr[0] - cr[1] - cr[2]) >> 10); - - *yPtr++ = (y[4] + y[5] + y[6]) >> 10; - *cbPtr++ = 128 + ((cb[4] - cb[5] + cb[6]) >> 10); - *crPtr++ = 128 + ((cr[4] - cr[5] - cr[6]) >> 10); - } - } - - /// - /// This should perform better. Coreclr emitted Vector.Dot() code lacks the vectorization even with IsHardwareAccelerated == true. - /// Kept this benchmark because maybe it will be improved in a future CLR release. - /// - /// https://www.gamedev.net/topic/673396-c-systemnumericsvectors-slow/ - /// - /// - [Benchmark(Description = "Scaled Integer Conversion + Vector + Dot Product")] - public unsafe void RgbaToYcbCrScaledIntegerSimdWithDotProduct() - { - // Copy the input to the stack: - - // On-stack output: - Result result = default; - float* yPtr = (float*)&result.Y; - float* cbPtr = (float*)&result.Cb; - float* crPtr = (float*)&result.Cr; - - var yCoeffs = new Vector(ScaledCoeffs.Y); - var cbCoeffs = new Vector(ScaledCoeffs.Cb); - var crCoeffs = new Vector(ScaledCoeffs.Cr); - - var leftY = new Vector(ScaledCoeffs.SelectLeft.Y); - var leftCb = new Vector(ScaledCoeffs.SelectLeft.Cb); - var leftCr = new Vector(ScaledCoeffs.SelectLeft.Cr); - - var rightY = new Vector(ScaledCoeffs.SelectRight.Y); - var rightCb = new Vector(ScaledCoeffs.SelectRight.Cb); - var rightCr = new Vector(ScaledCoeffs.SelectRight.Cr); - - for (int i = 0; i < this.inputSourceRGB.Length; i++) - { - this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i]; - } - - for (int i = 0; i < InputColorCount; i += 2) - { - var rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); - - Vector y = yCoeffs * rgb; - Vector cb = cbCoeffs * rgb; - Vector cr = crCoeffs * rgb; - - VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, leftY, leftCb, leftCr); - VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, rightY, rightCb, rightCr); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void VectorizedConvertImpl( - ref float* yPtr, - ref float* cbPtr, - ref float* crPtr, - Vector y, - Vector cb, - Vector cr, - Vector yAgg, - Vector cbAgg, - Vector crAgg) - { - int ySum = Vector.Dot(y, yAgg); - int cbSum = Vector.Dot(cb, cbAgg); - int crSum = Vector.Dot(cr, crAgg); - *yPtr++ = ySum >> 10; - *cbPtr++ = 128 + (cbSum >> 10); - *crPtr++ = 128 + (crSum >> 10); - } - - [Benchmark(Description = "Scaled Integer Conversion")] - public unsafe void RgbaToYcbCrScaledInteger() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - float* yPtr = (float*)&result.Y; - float* cbPtr = (float*)&result.Cb; - float* crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - int r = input.Data[i3 + 0]; - int g = input.Data[i3 + 1]; - int b = input.Data[i3 + 2]; - - // Scale by 1024, add .5F and truncate value - int y0 = 306 * r; // (0.299F * 1024) + .5F - int y1 = 601 * g; // (0.587F * 1024) + .5F - int y2 = 117 * b; // (0.114F * 1024) + .5F - - int cb0 = -172 * r; // (-0.168736F * 1024) + .5F - int cb1 = 339 * g; // (0.331264F * 1024) + .5F - int cb2 = 512 * b; // (0.5F * 1024) + .5F - - int cr0 = 512 * r; // (0.5F * 1024) + .5F - int cr1 = 429 * g; // (0.418688F * 1024) + .5F - int cr2 = 83 * b; // (0.081312F * 1024) + .5F - - *yPtr++ = (y0 + y1 + y2) >> 10; - *cbPtr++ = 128 + ((cb0 - cb1 + cb2) >> 10); - *crPtr++ = 128 + ((cr0 - cr1 - cr2) >> 10); - } - } - - [Benchmark(Description = "Scaled Integer LUT Conversion")] - public unsafe void RgbaToYcbCrScaledIntegerLut() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - float* yPtr = (float*)&result.Y; - float* cbPtr = (float*)&result.Cb; - float* crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - - int r = input.Data[i3 + 0]; - int g = input.Data[i3 + 1]; - int b = input.Data[i3 + 2]; - - // TODO: Maybe concatenating all the arrays in LookupTables to a flat one can improve this! - *yPtr++ = (LookupTables.Y0[r] + LookupTables.Y1[g] + LookupTables.Y2[b]) >> 10; - *cbPtr++ = 128 + ((LookupTables.Cb0[r] - LookupTables.Cb1[g] + LookupTables.Cb2Cr0[b]) >> 10); - *crPtr++ = 128 + ((LookupTables.Cb2Cr0[r] - LookupTables.Cr1[g] - LookupTables.Cr2[b]) >> 10); - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index c26de91590..50abcf89d8 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -10,10 +10,12 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; using SkiaSharp; @@ -210,7 +212,7 @@ public void ImageSharpResize(string input) // Resize it to fit a 150x150 square var targetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize); var decoder = new JpegDecoder(); - using ImageSharpImage image = decoder.DecodeInto(Configuration.Default, inputStream, targetSize, default); + using ImageSharpImage image = decoder.DecodeInto(Configuration.Default, inputStream, targetSize, CancellationToken.None); this.LogImageProcessed(image.Width, image.Height); image.Mutate(i => i.Resize(new ResizeOptions diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 56f00bbec1..5b5dedaab3 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -66,12 +66,6 @@ private static Version GetNetCoreVersion() return null; } - private static void RunJpegEncoderProfilingTests() - { - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - benchmarks.EncodeJpeg_SingleMidSize(); - } - private static void RunResizeProfilingTest() { var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); @@ -83,19 +77,5 @@ private static void RunToVector4ProfilingTest() var tests = new PixelOperationsTests.Rgba32_OperationsTests(new ConsoleOutput()); tests.Benchmark_ToVector4(); } - - private static void RunDecodeJpegProfilingTests() - { - Console.WriteLine("RunDecodeJpegProfilingTests..."); - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) - { - string fileName = (string)data[0]; - int executionCount = (int)data[1]; - benchmarks.DecodeJpeg(fileName, executionCount); - } - - Console.WriteLine("DONE."); - } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs deleted file mode 100644 index 24d89b5d57..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -// Uncomment this to turn unit tests into benchmarks: -// #define BENCHMARKING -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -using Xunit; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - [Trait("Format", "Jpg")] - public partial class Block8x8FTests - { - 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]); - } - } - } - } - - [Fact] - public void Copy1x1Scale() - { - Block8x8F block = CreateRandomFloatBlock(0, 100); - - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(20, 20, AllocationOptions.Clean)) - { - Buffer2DRegion region = buffer.GetRegion(5, 10, 8, 8); - block.Copy1x1Scale(ref region.GetReferenceToOrigin(), region.Stride); - - 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); - } - } - - [Theory] - [InlineData(1, 1)] - [InlineData(1, 2)] - [InlineData(2, 1)] - [InlineData(2, 2)] - [InlineData(4, 2)] - [InlineData(4, 4)] - public void CopyTo(int horizontalFactor, int verticalFactor) - { - Block8x8F block = CreateRandomFloatBlock(0, 100); - - var start = new Point(50, 50); - - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(100, 100, AllocationOptions.Clean)) - { - Buffer2DRegion region = buffer.GetRegion(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); - block.ScaledCopyTo(region, 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 = region[x, y]; - - Assert.Equal(expected, actual); - } - } - - VerifyAllZeroOutsideSubArea(buffer, start.X, start.Y, horizontalFactor, verticalFactor); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index ce8482b791..70b921da2d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -2,11 +2,9 @@ // Licensed under the Six Labors Split License. using System; -using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -24,11 +22,7 @@ public class JpegColorConverterTests private const int TestBufferLength = 40; -#if SUPPORTS_RUNTIME_INTRINSICS private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX; -#else - private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll; -#endif private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); @@ -46,14 +40,16 @@ public JpegColorConverterTests(ITestOutputHelper output) [Fact] public void GetConverterThrowsExceptionOnInvalidColorSpace() { - Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.Undefined, 8)); + var invalidColorSpace = (JpegColorSpace)(-1); + Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); } [Fact] public void GetConverterThrowsExceptionOnInvalidPrecision() { // Valid precisions: 8 & 12 bit - Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 9)); + int invalidPrecision = 9; + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); } [Theory] @@ -95,13 +91,13 @@ internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int compon [Theory] [MemberData(nameof(Seeds))] public void FromYCbCrBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYCbCrScalar(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromYCbCrVector(int seed) { - var converter = new JpegColorConverterBase.FromYCbCrVector(8); + var converter = new JpegColorConverterBase.YCbCrVector(8); if (!converter.IsAvailable) { @@ -117,7 +113,7 @@ public void FromYCbCrVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromYCbCrVector(8), + new JpegColorConverterBase.YCbCrVector(8), 3, FeatureTestRunner.Deserialize(arg)); } @@ -125,13 +121,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromCmykBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromCmykScalar(8), 4, seed); + this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] public void FromCmykVector(int seed) { - var converter = new JpegColorConverterBase.FromCmykVector(8); + var converter = new JpegColorConverterBase.CmykVector(8); if (!converter.IsAvailable) { @@ -147,7 +143,7 @@ public void FromCmykVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromCmykVector(8), + new JpegColorConverterBase.CmykVector(8), 4, FeatureTestRunner.Deserialize(arg)); } @@ -155,13 +151,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromGrayscaleBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromGrayscaleScalar(8), 1, seed); + this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed); [Theory] [MemberData(nameof(Seeds))] public void FromGrayscaleVector(int seed) { - var converter = new JpegColorConverterBase.FromGrayScaleVector(8); + var converter = new JpegColorConverterBase.GrayScaleVector(8); if (!converter.IsAvailable) { @@ -177,7 +173,7 @@ public void FromGrayscaleVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromGrayScaleVector(8), + new JpegColorConverterBase.GrayScaleVector(8), 1, FeatureTestRunner.Deserialize(arg)); } @@ -185,13 +181,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromRgbBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromRgbScalar(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromRgbVector(int seed) { - var converter = new JpegColorConverterBase.FromRgbVector(8); + var converter = new JpegColorConverterBase.RgbVector(8); if (!converter.IsAvailable) { @@ -207,7 +203,7 @@ public void FromRgbVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromRgbVector(8), + new JpegColorConverterBase.RgbVector(8), 3, FeatureTestRunner.Deserialize(arg)); } @@ -215,13 +211,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromYccKBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYccKScalar(8), 4, seed); + this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] public void FromYccKVector(int seed) { - var converter = new JpegColorConverterBase.FromYccKVector(8); + var converter = new JpegColorConverterBase.YccKVector(8); if (!converter.IsAvailable) { @@ -237,37 +233,35 @@ public void FromYccKVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromYccKVector(8), + new JpegColorConverterBase.YccKVector(8), 4, FeatureTestRunner.Deserialize(arg)); } -#if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(Seeds))] public void FromYCbCrAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromCmykAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed); + this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] public void FromGrayscaleAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed); + this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed); [Theory] [MemberData(nameof(Seeds))] public void FromRgbAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromYccKAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYccKAvx(8), 4, seed); -#endif + this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed); private void TestConverter( JpegColorConverterBase converter, @@ -357,7 +351,6 @@ private static void Validate( case JpegColorSpace.YCbCr: ValidateYCbCr(original, result, i); break; - case JpegColorSpace.Undefined: default: Assert.True(false, $"Invalid Colorspace enum value: {colorSpace}."); break; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs index 6bf7ae88fa..564e191bbd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs @@ -1,22 +1,11 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 74cda1f3a5..d9cacfacb1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -139,15 +139,14 @@ public void Decode_VerifyQuality(string imagePath, int quality) } [Theory] - [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegColorType.Luminance)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegColorType.YCbCrRatio444)] - [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegColorType.Rgb)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorType.Cmyk)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegColorType.YCbCrRatio410)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegColorType.YCbCrRatio422)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegColorType.YCbCrRatio411)] - public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType) + [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingColor.Luminance)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingColor.YCbCrRatio420)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingColor.YCbCrRatio444)] + [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)] + public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) @@ -159,12 +158,12 @@ public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType exp } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegColorType.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegColorType.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegColorType.Cmyk)] - public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegColorType expectedColorType) + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] + public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(JpegDecoder)) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 1a3f4659b9..60f45664d3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -2,8 +2,13 @@ // Licensed under the Six Labors Split License. using System; +using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,6 +18,83 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public partial class JpegEncoderTests { + public static readonly TheoryData RatioFiles = + new() + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; + + public static readonly TheoryData QualityFiles = + new() + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; + + [Fact] + public void Encode_PreservesIptcProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IptcProfile = new IptcProfile(); + input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IptcProfile actual = output.Metadata.IptcProfile; + Assert.NotNull(actual); + IEnumerable values = input.Metadata.IptcProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesExifProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.ExifProfile = new ExifProfile(); + input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ExifProfile actual = output.Metadata.ExifProfile; + Assert.NotNull(actual); + IReadOnlyList values = input.Metadata.ExifProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesIccProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IccProfile actual = output.Metadata.IccProfile; + Assert.NotNull(actual); + IccProfile values = input.Metadata.IccProfile; + Assert.Equal(values.Entries, actual.Entries); + } + [Theory] [WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)] public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageProvider provider) @@ -29,5 +111,71 @@ public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageP Assert.Null(ex); } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } + + [Theory] + [MemberData(nameof(QualityFiles))] + public void Encode_PreservesQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } + } + } + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(JpegDecoder); + using var memoryStream = new MemoryStream(); + + // act + input.Save(memoryStream, JpegEncoder); + + // assert + memoryStream.Position = 0; + using var output = Image.Load(memoryStream); + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index c25c66fe25..8a78ef6485 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -26,189 +21,149 @@ public partial class JpegEncoderTests private static JpegDecoder JpegDecoder => new(); - public static readonly TheoryData QualityFiles = - new() - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; - - public static readonly TheoryData BitsPerPixel_Quality = - new() - { - { JpegColorType.YCbCrRatio420, 40 }, - { JpegColorType.YCbCrRatio420, 60 }, - { JpegColorType.YCbCrRatio420, 100 }, - { JpegColorType.YCbCrRatio444, 40 }, - { JpegColorType.YCbCrRatio444, 60 }, - { JpegColorType.YCbCrRatio444, 100 }, - { JpegColorType.Rgb, 40 }, - { JpegColorType.Rgb, 60 }, - { JpegColorType.Rgb, 100 } - }; + private static readonly TheoryData TestQualities = new() + { + 40, + 80, + 100, + }; - public static readonly TheoryData Grayscale_Quality = - new() - { - { 40 }, - { 60 }, - { 100 } - }; + public static readonly TheoryData NonSubsampledEncodingSetups = new() + { + { JpegEncodingColor.Rgb, 100, 0.0238f / 100 }, + { JpegEncodingColor.Rgb, 80, 1.3044f / 100 }, + { JpegEncodingColor.Rgb, 40, 2.9879f / 100 }, - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; + { JpegEncodingColor.YCbCrRatio444, 100, 0.0780f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 80, 1.4585f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 40, 3.1413f / 100 }, + }; - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegColorType.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegColorType.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegColorType.Rgb)] - public void Encode_PreservesColorType(TestImageProvider provider, JpegColorType expectedColorType) - where TPixel : unmanaged, IPixel + public static readonly TheoryData SubsampledEncodingSetups = new() { - // arrange - using Image input = provider.GetImage(JpegDecoder); - using var memoryStream = new MemoryStream(); - - // act - input.Save(memoryStream, JpegEncoder); - - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } + { JpegEncodingColor.YCbCrRatio422, 100, 0.4895f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 80, 1.6043f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 40, 3.1996f / 100 }, - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg410, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg411, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg422, PixelTypes.Rgba32)] - public void Encode_WithUnsupportedColorType_FromInputImage_DefaultsToYCbCr420(TestImageProvider provider) - where TPixel : unmanaged, IPixel + { JpegEncodingColor.YCbCrRatio420, 100, 0.5790f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 80, 1.6692f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 40, 3.2324f / 100 }, + + { JpegEncodingColor.YCbCrRatio411, 100, 0.6868f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 80, 1.7139f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 40, 3.2634f / 100 }, + + { JpegEncodingColor.YCbCrRatio410, 100, 0.7357f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 80, 1.7495f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 40, 3.2911f / 100 }, + }; + + public static readonly TheoryData CmykEncodingSetups = new() { - // arrange - using Image input = provider.GetImage(JpegDecoder); - using var memoryStream = new MemoryStream(); + { JpegEncodingColor.Cmyk, 100, 0.0159f / 100 }, + { JpegEncodingColor.Cmyk, 80, 0.3922f / 100 }, + { JpegEncodingColor.Cmyk, 40, 0.6488f / 100 }, + }; - // act - input.Save(memoryStream, new JpegEncoder() - { - Quality = 75 - }); + public static readonly TheoryData YcckEncodingSetups = new() + { + { JpegEncodingColor.Ycck, 100, 0.0356f / 100 }, + { JpegEncodingColor.Ycck, 80, 0.1245f / 100 }, + { JpegEncodingColor.Ycck, 40, 0.2663f / 100 }, + }; - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType); - } + public static readonly TheoryData LuminanceEncodingSetups = new() + { + { JpegEncodingColor.Luminance, 100, 0.0175f / 100 }, + { JpegEncodingColor.Luminance, 80, 0.6730f / 100 }, + { JpegEncodingColor.Luminance, 40, 0.9941f / 100 }, + }; [Theory] - [InlineData(JpegColorType.Cmyk)] - [InlineData(JpegColorType.YCbCrRatio410)] - [InlineData(JpegColorType.YCbCrRatio411)] - [InlineData(JpegColorType.YCbCrRatio422)] - public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegColorType colorType) - { - // arrange - var jpegEncoder = new JpegEncoder() { ColorType = colorType }; - using var input = new Image(10, 10); - using var memoryStream = new MemoryStream(); - - // act - input.Save(memoryStream, jpegEncoder); - - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType); - } + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_Interleaved(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, tolerance); [Theory] - [MemberData(nameof(QualityFiles))] - public void Encode_PreservesQuality(string imagePath, int quality) + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_NonInterleavedMode(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + using Image image = provider.GetImage(); + + var encoder = new JpegEncoder { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - } - } - } + Quality = quality, + ColorType = colorType, + Interleaved = false, + }; + string info = $"{colorType}-Q{quality}"; - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - public void EncodeBaseline_CalliphoraPartial(TestImageProvider provider, JpegColorType colorType, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); + ImageComparer comparer = new TolerantImageComparer(tolerance); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); + } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)] - public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegColorType colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 600, 400, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 158, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)] + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] - [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] - [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegColorType colorType, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f)); + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 255, 100, 50, 255, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 7, 5, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 48, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 73, 71, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)] + public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f)); [Theory] - [WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgba32, 100)] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgb24, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)] public void EncodeBaseline_Grayscale(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegColorType.Luminance, quality); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality); [Theory] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 96, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegColorType colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegColorType colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)] - [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)] - [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegColorType.YCbCrRatio420)] - [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegColorType.YCbCrRatio420)] - public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegColorType colorType) + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingColor colorType) where TPixel : unmanaged, IPixel { - ImageComparer comparer = colorType == JpegColorType.YCbCrRatio444 + ImageComparer comparer = colorType == JpegEncodingColor.YCbCrRatio444 ? ImageComparer.TolerantPercentage(0.1f) : ImageComparer.TolerantPercentage(5f); @@ -216,10 +171,40 @@ public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvid TestJpegEncoderCore(provider, colorType, 100, comparer); } + [Theory] + [InlineData(JpegEncodingColor.YCbCrRatio420)] + [InlineData(JpegEncodingColor.YCbCrRatio444)] + public async Task Encode_IsCancellable(JpegEncodingColor colorType) + { + var cts = new CancellationTokenSource(); + using var pausedStream = new PausedStream(new MemoryStream()); + pausedStream.OnWaiting(s => + { + // after some writing + if (s.Position >= 500) + { + cts.Cancel(); + pausedStream.Release(); + } + else + { + // allows this/next wait to unblock + pausedStream.Next(); + } + }); + + using var image = new Image(5000, 5000); + await Assert.ThrowsAsync(async () => + { + var encoder = new JpegEncoder() { ColorType = colorType }; + await image.SaveAsync(pausedStream, encoder, cts.Token); + }); + } + /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation /// - private static ImageComparer GetComparer(int quality, JpegColorType? colorType) + private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) { float tolerance = 0.015f; // ~1.5% @@ -227,10 +212,10 @@ private static ImageComparer GetComparer(int quality, JpegColorType? colorType) { tolerance *= 4.5f; } - else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420) + else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) { tolerance *= 2.0f; - if (colorType == JpegColorType.YCbCrRatio420) + if (colorType == JpegEncodingColor.YCbCrRatio420) { tolerance *= 2.0f; } @@ -239,18 +224,19 @@ private static ImageComparer GetComparer(int quality, JpegColorType? colorType) return ImageComparer.Tolerant(tolerance); } - private static void TestJpegEncoderCore( - TestImageProvider provider, - JpegColorType colorType = JpegColorType.YCbCrRatio420, - int quality = 100, - ImageComparer comparer = null) + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType)); + + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance)); + + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, ImageComparer comparer) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); - var encoder = new JpegEncoder { Quality = quality, @@ -258,171 +244,8 @@ private static void TestJpegEncoderCore( }; string info = $"{colorType}-Q{quality}"; - comparer ??= GetComparer(quality, colorType); - // Does DebugSave & load reference CompareToReferenceInput(): image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } - - [Fact] - public void Quality_0_And_1_Are_Identical() - { - var options = new JpegEncoder - { - Quality = 0 - }; - - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - - using (Image input = testFile.CreateRgba32Image()) - using (var memStream0 = new MemoryStream()) - using (var memStream1 = new MemoryStream()) - { - input.SaveAsJpeg(memStream0, options); - - options.Quality = 1; - input.SaveAsJpeg(memStream1, options); - - Assert.Equal(memStream0.ToArray(), memStream1.ToArray()); - } - } - - [Fact] - public void Quality_0_And_100_Are_Not_Identical() - { - var options = new JpegEncoder - { - Quality = 0 - }; - - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - - using (Image input = testFile.CreateRgba32Image()) - using (var memStream0 = new MemoryStream()) - using (var memStream1 = new MemoryStream()) - { - input.SaveAsJpeg(memStream0, options); - - options.Quality = 100; - input.SaveAsJpeg(memStream1, options); - - Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); - } - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - } - } - - [Fact] - public void Encode_PreservesIptcProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.IptcProfile = new IptcProfile(); - input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IptcProfile actual = output.Metadata.IptcProfile; - Assert.NotNull(actual); - IEnumerable values = input.Metadata.IptcProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesExifProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.ExifProfile = new ExifProfile(); - input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - ExifProfile actual = output.Metadata.ExifProfile; - Assert.NotNull(actual); - IReadOnlyList values = input.Metadata.ExifProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesIccProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IccProfile actual = output.Metadata.IccProfile; - Assert.NotNull(actual); - IccProfile values = input.Metadata.IccProfile; - Assert.Equal(values.Entries, actual.Entries); - } - - [Theory] - [InlineData(JpegColorType.YCbCrRatio420)] - [InlineData(JpegColorType.YCbCrRatio444)] - public async Task Encode_IsCancellable(JpegColorType colorType) - { - var cts = new CancellationTokenSource(); - using var pausedStream = new PausedStream(new MemoryStream()); - pausedStream.OnWaiting(s => - { - // after some writing - if (s.Position >= 500) - { - cts.Cancel(); - pausedStream.Release(); - } - else - { - // allows this/next wait to unblock - pausedStream.Next(); - } - }); - - using var image = new Image(5000, 5000); - await Assert.ThrowsAsync(async () => - { - var encoder = new JpegEncoder() { ColorType = colorType }; - await image.SaveAsync(pausedStream, encoder, cts.Token); - }); - } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 9f401fe8b3..ce9ce79795 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -12,13 +12,11 @@ public class JpegMetadataTests [Fact] public void CloneIsDeep() { - var meta = new JpegMetadata { Quality = 50, ColorType = JpegColorType.Luminance }; + var meta = new JpegMetadata { ColorType = JpegEncodingColor.Luminance }; var clone = (JpegMetadata)meta.DeepClone(); - clone.Quality = 99; - clone.ColorType = JpegColorType.YCbCrRatio420; + clone.ColorType = JpegEncodingColor.YCbCrRatio420; - Assert.False(meta.Quality.Equals(clone.Quality)); Assert.False(meta.ColorType.Equals(clone.ColorType)); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 50c2a08bbd..31acb3b882 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public partial class ReferenceImplementationsTests { - public class FastFloatingPointDCT : JpegFixture + public class FloatingPointDCT : JpegFixture { - public FastFloatingPointDCT(ITestOutputHelper output) + public FloatingPointDCT(ITestOutputHelper output) : base(output) { } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs deleted file mode 100644 index 0f24e6c126..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; -using Xunit; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - public class RgbToYCbCrConverterTests - { - public RgbToYCbCrConverterTests(ITestOutputHelper output) - { - this.Output = output; - } - - private ITestOutputHelper Output { get; } - - [Fact] - public void TestConverterLut444() - { - int dataSize = 8 * 8; - Rgb24[] data = CreateTestData(dataSize); - var target = RgbToYCbCrConverterLut.Create(); - - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - target.Convert444(data.AsSpan(), ref y, ref cb, ref cr); - - Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); - } - - [Fact] - public void TestConverterVectorized444() - { - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); - return; - } - - int dataSize = 8 * 8; - Rgb24[] data = CreateTestData(dataSize); - - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - RgbToYCbCrConverterVectorized.Convert444(data.AsSpan(), ref y, ref cb, ref cr); - - Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); - } - - [Fact] - public void TestConverterLut420() - { - int dataSize = 16 * 16; - Span data = CreateTestData(dataSize).AsSpan(); - var target = RgbToYCbCrConverterLut.Create(); - - var yBlocks = new Block8x8F[4]; - var cb = default(Block8x8F); - var cr = default(Block8x8F); - - target.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0); - target.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1); - - Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F)); - } - - [Fact] - public void TestConverterVectorized420() - { - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); - return; - } - - int dataSize = 16 * 16; - Span data = CreateTestData(dataSize).AsSpan(); - - var yBlocks = new Block8x8F[4]; - var cb = default(Block8x8F); - var cr = default(Block8x8F); - - RgbToYCbCrConverterVectorized.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0); - RgbToYCbCrConverterVectorized.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1); - - Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F)); - } - -#if SUPPORTS_RUNTIME_INTRINSICS - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Scale16x2_8x1(int seed) - { - if (!Avx2.IsSupported) - { - return; - } - - Span data = new Random(seed).GenerateRandomFloatArray(Vector256.Count * 4, -1000, 1000); - - // Act: - Vector256 resultVector = RgbToYCbCrConverterVectorized.Scale16x2_8x1(MemoryMarshal.Cast>(data)); - ref float result = ref Unsafe.As, float>(ref resultVector); - - // Assert: - // Comparison epsilon is tricky but 10^(-4) is good enough (?) - var comparer = new ApproximateFloatComparer(0.0001f); - for (int i = 0; i < Vector256.Count; i++) - { - float actual = Unsafe.Add(ref result, i); - float expected = CalculateAverage16x2_8x1(data, i); - - Assert.True(comparer.Equals(actual, expected), $"Pos {i}, Expected: {expected}, Actual: {actual}"); - } - - static float CalculateAverage16x2_8x1(Span data, int index) - { - int upIdx = index * 2; - int lowIdx = (index + 8) * 2; - return 0.25f * (data[upIdx] + data[upIdx + 1] + data[lowIdx] + data[lowIdx + 1]); - } - } -#endif - - private static void Verify444( - ReadOnlySpan data, - ref Block8x8F yResult, - ref Block8x8F cbResult, - ref Block8x8F crResult, - ApproximateColorSpaceComparer comparer) - { - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - RgbToYCbCr(data, ref y, ref cb, ref cr); - - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.True(comparer.Equals(new YCbCr(y[i], cb[i], cr[i]), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y[i]} == {yResult[i]}, {cb[i]} == {cbResult[i]}, {cr[i]} == {crResult[i]}"); - } - } - - private static void Verify420( - ReadOnlySpan data, - Block8x8F[] yResult, - ref Block8x8F cbResult, - ref Block8x8F crResult, - ApproximateFloatComparer comparer) - { - var trueBlock = default(Block8x8F); - var cbTrue = new Block8x8F[4]; - var crTrue = new Block8x8F[4]; - - Span tempData = new Rgb24[8 * 8].AsSpan(); - - // top left - Copy8x8(data, tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[0], ref crTrue[0]); - VerifyBlock(ref yResult[0], ref trueBlock, comparer); - - // top right - Copy8x8(data.Slice(8), tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[1], ref crTrue[1]); - VerifyBlock(ref yResult[1], ref trueBlock, comparer); - - // bottom left - Copy8x8(data.Slice(8 * 16), tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[2], ref crTrue[2]); - VerifyBlock(ref yResult[2], ref trueBlock, comparer); - - // bottom right - Copy8x8(data.Slice((8 * 16) + 8), tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[3], ref crTrue[3]); - VerifyBlock(ref yResult[3], ref trueBlock, comparer); - - // verify Cb - Scale16X16To8X8(ref trueBlock, cbTrue); - VerifyBlock(ref cbResult, ref trueBlock, comparer); - - // verify Cr - Scale16X16To8X8(ref trueBlock, crTrue); - VerifyBlock(ref crResult, ref trueBlock, comparer); - - // extracts 8x8 blocks from 16x8 memory region - static void Copy8x8(ReadOnlySpan source, Span dest) - { - for (int i = 0; i < 8; i++) - { - source.Slice(i * 16, 8).CopyTo(dest.Slice(i * 8)); - } - } - - // scales 16x16 to 8x8, used in chroma subsampling tests - static void Scale16X16To8X8(ref Block8x8F dest, ReadOnlySpan source) - { - for (int i = 0; i < 4; i++) - { - int dstOff = ((i & 2) << 4) | ((i & 1) << 2); - Block8x8F iSource = source[i]; - - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - int j = (16 * y) + (2 * x); - float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9]; - dest[(8 * y) + x + dstOff] = (sum + 2) * .25F; - } - } - } - } - } - - private static void RgbToYCbCr(ReadOnlySpan data, ref Block8x8F y, ref Block8x8F cb, ref Block8x8F cr) - { - for (int i = 0; i < data.Length; i++) - { - int r = data[i].R; - int g = data[i].G; - int b = data[i].B; - - y[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); - cb[i] = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - cr[i] = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - } - } - - private static void VerifyBlock(ref Block8x8F res, ref Block8x8F target, ApproximateFloatComparer comparer) - { - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.True(comparer.Equals(res[i], target[i]), $"Pos {i}, Expected: {target[i]}, Got: {res[i]}"); - } - } - - private static Rgb24[] CreateTestData(int size) - { - var data = new Rgb24[size]; - var r = new Random(); - - var random = new byte[3]; - for (int i = 0; i < data.Length; i++) - { - r.NextBytes(random); - data[i] = new Rgb24(random[0], random[1], random[2]); - } - - return data; - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs index 008ca20c3f..6ca3a728f5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using Xunit; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 9824315fff..7ab7accdfa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -153,7 +153,7 @@ public LibJpegTools.SpectralData SpectralData { // Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing // Progressive and multi-scan images must be loaded manually - if (this.frame.Progressive || this.frame.MultiScan) + if (this.frame.Progressive || !this.frame.Interleaved) { this.PrepareForDecoding(); LibJpegTools.ComponentData[] components = this.spectralData.Components; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 5593128ae7..509f1956bd 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -129,13 +129,13 @@ public void IsNotBoundToSinglePixelType(TestImageProvider provid foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - pngColorType, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - appendPixelType: true, - appendPngColorType: true); + provider, + pngColorType, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); } } @@ -204,14 +204,14 @@ public void WorksWithAllBitDepths(TestImageProvider provider, Pn foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - pngColorType, - (PngFilterMethod)filterMethod[0], - pngBitDepth, - interlaceMode, - appendPngColorType: true, - appendPixelType: true, - appendPngBitDepth: true); + provider, + pngColorType, + (PngFilterMethod)filterMethod[0], + pngBitDepth, + interlaceMode, + appendPngColorType: true, + appendPixelType: true, + appendPngBitDepth: true); } } } @@ -314,13 +314,13 @@ public void PaletteColorType_WuQuantizer(TestImageProvider provi foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - PngColorType.Palette, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - paletteSize: paletteSize, - appendPaletteSize: true); + provider, + PngColorType.Palette, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + paletteSize: paletteSize, + appendPaletteSize: true); } } @@ -615,7 +615,6 @@ private static void TestPngEncoderCore( string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - // Compare to the Magick reference decoder. IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); // We compare using both our decoder and the reference decoder as pixel transformation diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs deleted file mode 100644 index e4f2bdd25d..0000000000 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -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; - -// in this file, comments are used for disabling stuff for local execution -#pragma warning disable SA1515 - -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks -{ - public class JpegProfilingBenchmarks : MeasureFixture - { - public JpegProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - } - - public static readonly TheoryData DecodeJpegData = new TheoryData - { - { TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, 20 }, - { TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, 20 }, - { TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, 40 }, - // { TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, 10 }, - // { TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, 5 }, - { TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, 5 } - }; - - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [MemberData(nameof(DecodeJpegData))] - public void DecodeJpeg(string fileName, int executionCount) - { - var decoder = new JpegDecoder() - { - IgnoreMetadata = true - }; - this.DecodeJpegBenchmarkImpl(fileName, decoder, executionCount); - } - - private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder, int executionCount) - { - // do not run this on CI even by accident - if (TestEnvironment.RunsOnCI) - { - return; - } - - if (!Vector.IsHardwareAccelerated) - { - throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); - } - - string path = TestFile.GetInputFileFullPath(fileName); - byte[] bytes = File.ReadAllBytes(path); - - this.Measure( - executionCount, - () => - { - var img = Image.Load(bytes, decoder); - img.Dispose(); - }, -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // ReSharper disable once ExplicitCallerInfoArgument - $"Decode {fileName}"); -#pragma warning restore SA1515 // Single-line comment should be preceded by blank line - } - - [Fact(Skip = ProfilingSetup.SkipProfilingTests)] - public void EncodeJpeg_SingleMidSize() - { - string path = TestFile.GetInputFileFullPath(TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr); - using var image = Image.Load(path); - image.Metadata.ExifProfile = null; - - using var ms = new MemoryStream(); - for (int i = 0; i < 30; i++) - { - image.SaveAsJpeg(ms); - ms.Seek(0, SeekOrigin.Begin); - } - } - - // Benchmark, enable manually! - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(1, 75, JpegColorType.YCbCrRatio420)] - [InlineData(30, 75, JpegColorType.YCbCrRatio420)] - [InlineData(30, 75, JpegColorType.YCbCrRatio444)] - [InlineData(30, 100, JpegColorType.YCbCrRatio444)] - public void EncodeJpeg(int executionCount, int quality, JpegColorType colorType) - { - // do not run this on CI even by accident - if (TestEnvironment.RunsOnCI) - { - return; - } - - string[] testFiles = TestImages.Bmp.Benchmark - .Concat(new[] { TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk }).ToArray(); - - Image[] testImages = testFiles.Select( - tf => TestImageProvider.File(tf, pixelTypeOverride: PixelTypes.Rgba32).GetImage()).ToArray(); - - using (var ms = new MemoryStream()) - { - this.Measure( - executionCount, - () => - { - foreach (Image img in testImages) - { - var options = new JpegEncoder { Quality = quality, ColorType = colorType }; - img.Save(ms, options); - ms.Seek(0, SeekOrigin.Begin); - } - }, -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // ReSharper disable once ExplicitCallerInfoArgument - $@"Encode {testFiles.Length} images"); -#pragma warning restore SA1515 // Single-line comment should be preceded by blank line - } - - foreach (Image image in testImages) - { - image.Dispose(); - } - } - } -}