diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs index cae283d62..2e5ca3137 100644 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -37,12 +37,11 @@ namespace SixLabors.ImageSharp.Tuples this.B += other.B; } - /// - /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! - /// TODO: Move it somewhere else. + /// . + /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscalePreAvx2() + internal void RoundAndDownscalePreAvx2(float downscaleFactor) { ref Vector a = ref Unsafe.As>(ref this.A); a = a.FastRound(); @@ -50,8 +49,8 @@ namespace SixLabors.ImageSharp.Tuples ref Vector b = ref Unsafe.As>(ref this.B); b = b.FastRound(); - // Downscale by 1/255 - var scale = new Vector4(1 / 255f); + // Downscale by 1/factor + var scale = new Vector4(1 / downscaleFactor); this.A *= scale; this.B *= scale; } @@ -61,14 +60,14 @@ namespace SixLabors.ImageSharp.Tuples /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleAvx2() + internal void RoundAndDownscaleAvx2(float downscaleFactor) { ref Vector self = ref Unsafe.As>(ref this); Vector v = self; v = v.FastRound(); - // Downscale by 1/255 - v *= new Vector(1 / 255f); + // Downscale by 1/factor + v *= new Vector(1 / downscaleFactor); self = v; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 09ed6408d..1f47de594 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,10 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal partial struct Block8x8F { - private static readonly Vector4 CMin4 = new Vector4(0F); - private static readonly Vector4 CMax4 = new Vector4(255F); - private static readonly Vector4 COff4 = new Vector4(128F); - /// /// Transpose the block into the destination block. /// @@ -94,10 +91,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Level shift by +128, clip to [0, 255] + /// Level shift by +maximum/2, clip to [0, maximum] /// - public void NormalizeColorsInplace() + public void NormalizeColorsInplace(float maximum) { + Vector4 CMin4 = new Vector4(0F); + Vector4 CMax4 = new Vector4(maximum); + Vector4 COff4 = new Vector4((float)Math.Ceiling(maximum/2)); + this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4); @@ -120,10 +121,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2() + public void NormalizeColorsAndRoundInplaceAvx2(float maximum) { - Vector off = new Vector(128f); - Vector max = new Vector(255F); + Vector off = new Vector((float)Math.Ceiling(maximum/2)); + Vector max = new Vector(maximum); ref Vector row0 = ref Unsafe.As>(ref this.V0L); row0 = NormalizeAndRound(row0, off, max); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index f93ee6522..ec4e06e42 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -11,6 +11,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -22,10 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal partial struct Block8x8F { - private static readonly Vector4 CMin4 = new Vector4(0F); - private static readonly Vector4 CMax4 = new Vector4(255F); - private static readonly Vector4 COff4 = new Vector4(128F); - /// /// Transpose the block into the destination block. /// @@ -59,10 +56,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Level shift by +128, clip to [0, 255] + /// Level shift by +maximum/2, clip to [0, maximum] /// - public void NormalizeColorsInplace() + public void NormalizeColorsInplace(float maximum) { + Vector4 CMin4 = new Vector4(0F); + Vector4 CMax4 = new Vector4(maximum); + Vector4 COff4 = new Vector4((float)Math.Ceiling(maximum/2)); + <# PushIndent(" "); @@ -83,10 +84,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2() + public void NormalizeColorsAndRoundInplaceAvx2(float maximum) { - Vector off = new Vector(128f); - Vector max = new Vector(255F); + Vector off = new Vector((float)Math.Ceiling(maximum/2)); + Vector max = new Vector(maximum); <# for (int i = 0; i < 8; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 81393342d..c9c886f05 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -467,17 +467,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - /// Level shift by +128, clip to [0..255], and round all the values in the block. + /// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block. /// - public void NormalizeColorsAndRoundInplace() + public void NormalizeColorsAndRoundInplace(float maximum) { if (SimdUtils.IsAvx2CompatibleArchitecture) { - this.NormalizeColorsAndRoundInplaceAvx2(); + this.NormalizeColorsAndRoundInplaceAvx2(maximum); } else { - this.NormalizeColorsInplace(); + this.NormalizeColorsInplace(maximum); this.RoundInplace(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs index 7a14d072e..d4dc31fe0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs @@ -10,8 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal class FromCmyk : JpegColorConverter { - public FromCmyk() - : base(JpegColorSpace.Cmyk) + public FromCmyk(int precision) + : base(JpegColorSpace.Cmyk, precision) { } @@ -25,14 +25,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); for (int i = 0; i < result.Length; i++) { float c = cVals[i]; float m = mVals[i]; float y = yVals[i]; - float k = kVals[i] / 255F; + float k = kVals[i] / this.MaximumValue; v.X = c * k; v.Y = m * k; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs index 7424145c3..4a5dfa632 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs @@ -12,14 +12,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal class FromGrayscale : JpegColorConverter { - public FromGrayscale() - : base(JpegColorSpace.Grayscale) + public FromGrayscale(int precision) + : base(JpegColorSpace.Grayscale, precision) { } public override void ConvertToRgba(in ComponentValues values, Span result) { - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); ref float sBase = ref MemoryMarshal.GetReference(values.Component0); ref Vector4 dBase = ref MemoryMarshal.GetReference(result); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs index 7cd97c414..516dfb39f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs @@ -10,8 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal class FromRgb : JpegColorConverter { - public FromRgb() - : base(JpegColorSpace.RGB) + public FromRgb(int precision) + : base(JpegColorSpace.RGB, precision) { } @@ -24,7 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); for (int i = 0; i < result.Length; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs index cb71889bc..124aac122 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs @@ -10,17 +10,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal class FromYCbCrBasic : JpegColorConverter { - public FromYCbCrBasic() - : base(JpegColorSpace.YCbCr) + public FromYCbCrBasic(int precision) + : base(JpegColorSpace.YCbCr, precision) { } public override void ConvertToRgba(in ComponentValues values, Span result) { - ConvertCore(values, result); + ConvertCore(values, result, this.MaximumValue, this.HalfValue); } - internal static void ConvertCore(in ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) { // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! ReadOnlySpan yVals = values.Component0; @@ -29,13 +29,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4(1 / maxValue, 1 / maxValue, 1 / maxValue, 1F); for (int i = 0; i < result.Length; i++) { float y = yVals[i]; - float cb = cbVals[i] - 128F; - float cr = crVals[i] - 128F; + float cb = cbVals[i] - halfValue; + float cr = crVals[i] - halfValue; v.X = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); v.Y = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs index 23aa1acbe..10ef02a93 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -14,8 +14,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal class FromYCbCrSimd : JpegColorConverter { - public FromYCbCrSimd() - : base(JpegColorSpace.YCbCr) + public FromYCbCrSimd(int precision) + : base(JpegColorSpace.YCbCr, precision) { } @@ -25,16 +25,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters int simdCount = result.Length - remainder; if (simdCount > 0) { - ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount)); + ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue); } - FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder)); + FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue); } /// /// SIMD convert using buffers of sizes divisible by 8. /// - internal static void ConvertCore(in ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) { DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!"); @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector4Octet resultBase = ref Unsafe.As(ref MemoryMarshal.GetReference(result)); - var chromaOffset = new Vector4(-128f); + var chromaOffset = new Vector4(-halfValue); // Walking 8 elements at one step: int n = result.Length / 8; @@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // y = yVals[i]; Vector4Pair y = Unsafe.Add(ref yBase, i); - // cb = cbVals[i] - 128F; + // cb = cbVals[i] - halfValue); Vector4Pair cb = Unsafe.Add(ref cbBase, i); cb.AddInplace(chromaOffset); - // cr = crVals[i] - 128F; + // cr = crVals[i] - halfValue; Vector4Pair cr = Unsafe.Add(ref crBase, i); cr.AddInplace(chromaOffset); @@ -90,15 +90,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters if (Vector.Count == 4) { // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?) - r.RoundAndDownscalePreAvx2(); - g.RoundAndDownscalePreAvx2(); - b.RoundAndDownscalePreAvx2(); + r.RoundAndDownscalePreAvx2(maxValue); + g.RoundAndDownscalePreAvx2(maxValue); + b.RoundAndDownscalePreAvx2(maxValue); } else if (SimdUtils.IsAvx2CompatibleArchitecture) { - r.RoundAndDownscaleAvx2(); - g.RoundAndDownscaleAvx2(); - b.RoundAndDownscaleAvx2(); + r.RoundAndDownscaleAvx2(maxValue); + g.RoundAndDownscaleAvx2(maxValue); + b.RoundAndDownscaleAvx2(maxValue); } else { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs index f0a70a6f3..9953f78c1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -15,8 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal class FromYCbCrSimdAvx2 : JpegColorConverter { - public FromYCbCrSimdAvx2() - : base(JpegColorSpace.YCbCr) + public FromYCbCrSimdAvx2(int precision) + : base(JpegColorSpace.YCbCr, precision) { } @@ -28,16 +28,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters int simdCount = result.Length - remainder; if (simdCount > 0) { - ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount)); + ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount), this.MaximumValue, this.HalfValue); } - FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder)); + FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder), this.MaximumValue, this.HalfValue); } /// /// SIMD convert using buffers of sizes divisible by 8. /// - internal static void ConvertCore(in ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue, float halfValue) { // This implementation is actually AVX specific. // An AVX register is capable of storing 8 float-s. @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector4Octet resultBase = ref Unsafe.As(ref MemoryMarshal.GetReference(result)); - var chromaOffset = new Vector(-128f); + var chromaOffset = new Vector(-halfValue); // Walking 8 elements at one step: int n = result.Length / 8; @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector ggRefAsVector = ref Unsafe.As>(ref gg); ref Vector bbRefAsVector = ref Unsafe.As>(ref bb); - var scale = new Vector(1 / 255f); + var scale = new Vector(1 / maxValue); for (int i = 0; i < n; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs index 6f940f62f..94be11e23 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs @@ -10,8 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal class FromYccK : JpegColorConverter { - public FromYccK() - : base(JpegColorSpace.Ycck) + public FromYccK(int precision) + : base(JpegColorSpace.Ycck, precision) { } @@ -25,18 +25,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + var scale = new Vector4( + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1 / this.MaximumValue, + 1F); for (int i = 0; i < result.Length; i++) { float y = yVals[i]; - float cb = cbVals[i] - 128F; - float cr = crVals[i] - 128F; - float k = kVals[i] / 255F; + float cb = cbVals[i] - this.HalfValue; + float cr = crVals[i] - this.HalfValue; + float k = kVals[i] / this.MaximumValue; - v.X = (255F - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - v.Y = (255F - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.X = (this.MaximumValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (this.MaximumValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; + v.Z = (this.MaximumValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; v.W = 1F; v *= scale; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index a44ebf89d..c2e390c59 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -3,12 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tuples; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { @@ -22,15 +20,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// private static readonly JpegColorConverter[] Converters = { - GetYCbCrConverter(), new FromYccK(), new FromCmyk(), new FromGrayscale(), new FromRgb() + // 8-bit converters + GetYCbCrConverter(8), + new FromYccK(8), + new FromCmyk(8), + new FromGrayscale(8), + new FromRgb(8), + + // 12-bit converters + GetYCbCrConverter(12), + new FromYccK(12), + new FromCmyk(12), + new FromGrayscale(12), + new FromRgb(12), }; /// /// Initializes a new instance of the class. /// - protected JpegColorConverter(JpegColorSpace colorSpace) + protected JpegColorConverter(JpegColorSpace colorSpace, int precision) { this.ColorSpace = colorSpace; + this.Precision = precision; + this.MaximumValue = (float)Math.Pow(2, precision) - 1; + this.HalfValue = (float)Math.Ceiling(this.MaximumValue / 2); } /// @@ -38,12 +51,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// public JpegColorSpace ColorSpace { get; } + /// + /// Gets the Precision of this converter in bits. + /// + public int Precision { get; } + + /// + /// Gets the maximum value of a sample + /// + private float MaximumValue { get; } + + /// + /// Gets the half of the maximum value of a sample + /// + private float HalfValue { get; } + /// /// Returns the corresponding to the given /// - public static JpegColorConverter GetConverter(JpegColorSpace colorSpace) + public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision) { - JpegColorConverter converter = Converters.FirstOrDefault(c => c.ColorSpace == colorSpace); + JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace + && c.Precision == precision); if (converter is null) { @@ -63,8 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// Returns the for the YCbCr colorspace that matches the current CPU architecture. /// - private static JpegColorConverter GetYCbCrConverter() => - FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2() : new FromYCbCrSimd(); + private static JpegColorConverter GetYCbCrConverter(int precision) => + FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision); /// /// A stack-only struct to reference the input buffers using -s. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index 1454bb5b1..ace8d7215 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -29,10 +29,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// JpegColorSpace ColorSpace { get; } + /// + /// Gets the number of bits used for precision. + /// + int Precision { get; } + /// /// Gets the components. /// - IEnumerable Components { get; } + IJpegComponent[] Components { get; } /// /// Gets the quantization tables, in zigzag order. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs index da4b2847b..fe39f4188 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.Primitives; @@ -38,6 +39,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// private Size subSamplingDivisors; + /// + /// Defines the maximum value derived from the bitdepth + /// + private int maximumValue; + /// /// Initializes a new instance of the struct. /// @@ -48,6 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int qtIndex = component.QuantizationTableIndex; this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); this.subSamplingDivisors = component.SubSamplingDivisors; + this.maximumValue = (int)Math.Pow(2, decoder.Precision) - 1; this.SourceBlock = default; this.WorkspaceBlock1 = default; @@ -58,14 +65,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values: /// - Dequantize /// - Applying IDCT - /// - Level shift by +128, clip to [0, 255] + /// - Level shift by +maximumValue/2, clip to [0, maximumValue] /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in /// /// The source block. /// The destination buffer area. + /// The maximum value derived from the bitdepth. public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, - in BufferArea destArea) + in BufferArea destArea, + float maximumValue) { ref Block8x8F b = ref this.SourceBlock; b.LoadFrom(ref sourceBlock); @@ -78,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // To conform better to libjpeg we actually NEED TO loose precision here. // This is because they store blocks as Int16 between all the operations. // To be "more accurate", we need to emulate this by rounding! - this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(); + this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(maximumValue); this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 94ec600dd..e7f3e4fda 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -78,6 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public void CopyBlocksToColorBuffer() { var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); + float maximumValue = (float)Math.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; for (int y = 0; y < this.BlockRowsPerStep; y++) { @@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.blockAreaSize.Width, this.blockAreaSize.Height); - blockPp.ProcessBlockColorsInto(ref block, destArea); + blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 7ce86b4c9..438749abf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Linq; using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -57,14 +56,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.configuration = configuration; this.RawJpeg = rawJpeg; - IJpegComponent c0 = rawJpeg.Components.First(); + IJpegComponent c0 = rawJpeg.Components[0]; this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryAllocator, this, c)).ToArray(); + + this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length]; + for (int i = 0; i < rawJpeg.Components.Length; i++) + { + this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]); + } + this.rgbaBuffer = memoryAllocator.Allocate(rawJpeg.ImageSizeInPixels.Width); - this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace); + this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision); } /// @@ -152,7 +157,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); - Buffer2D[] buffers = this.ComponentProcessors.Select(cp => cp.ColorBuffer).ToArray(); + var buffers = new Buffer2D[this.ComponentProcessors.Length]; + for (int i = 0; i < this.ComponentProcessors.Length; i++) + { + buffers[i] = this.ComponentProcessors[i].ColorBuffer; + } for (int yy = this.PixelRowCounter; yy < maxY; yy++) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index faa7c3bc9..02ae022e1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -3,7 +3,6 @@ using System; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -33,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The only supported precision /// - public const int SupportedPrecision = 8; + private readonly int[] supportedPrecisions = { 8, 12 }; /// /// The global configuration @@ -137,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Gets the color depth, in number of bits per pixel. /// - public int BitsPerPixel => this.ComponentCount * SupportedPrecision; + public int BitsPerPixel => this.ComponentCount * this.Frame.Precision; /// /// Gets the input stream. @@ -160,13 +159,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public JpegColorSpace ColorSpace { get; private set; } + /// + public int Precision { get; private set; } + /// /// Gets the components. /// public JpegComponent[] Components => this.Frame.Components; /// - IEnumerable IRawJpegData.Components => this.Components; + IJpegComponent[] IRawJpegData.Components => this.Components; /// public Block8x8F[] QuantizationTables { get; private set; } @@ -720,12 +722,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InputStream.Read(this.temp, 0, remaining); - // We only support 8-bit precision. - if (this.temp[0] != SupportedPrecision) + // We only support 8-bit and 12-bit precision. + if (!this.supportedPrecisions.Contains(this.temp[0])) { - throw new ImageFormatException("Only 8-Bit precision supported."); + throw new ImageFormatException("Only 8-Bit and 12-Bit precision supported."); } + this.Precision = this.temp[0]; + this.Frame = new JpegFrame { Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs index 05edd2791..8417b32f2 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output); + JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F); } [Benchmark] @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output); + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, this.output, 255F, 128F); } [Benchmark] @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output); + JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output, 255F, 128F); } private static Buffer2D[] CreateRandomValues( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 81c76390c..7e7218c9d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.PrintLinearData(input); Block8x8F dest = block; - dest.NormalizeColorsInplace(); + dest.NormalizeColorsInplace(255); float[] array = new float[64]; dest.CopyTo(array); @@ -253,11 +253,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); Block8x8F expected = source; - expected.NormalizeColorsInplace(); + expected.NormalizeColorsInplace(255); expected.RoundInplace(); Block8x8F actual = source; - actual.NormalizeColorsAndRoundInplaceAvx2(); + actual.NormalizeColorsAndRoundInplaceAvx2(255); this.Output.WriteLine(expected.ToString()); this.Output.WriteLine(actual.ToString()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 8e30eb9e5..caaad73c9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) { ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrBasic(), + new JpegColorConverter.FromYCbCrBasic(8), 3, inputBufferLength, resultBufferLength, @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg JpegColorConverter.ComponentValues values = CreateRandomValues(3, size, seed); var result = new Vector4[size]; - JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result); + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result, 255, 128); for (int i = 0; i < size; i++) { @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed) { ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrSimd(), + new JpegColorConverter.FromYCbCrSimd(8), 3, inputBufferLength, resultBufferLength, @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrSimdAvx2(), + new JpegColorConverter.FromYCbCrSimdAvx2(8), 3, inputBufferLength, resultBufferLength, @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); var result = new Vector4[count]; - JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic(); + JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd(8) : new JpegColorConverter.FromYCbCrBasic(8); // Warm up: converter.ConvertToRgba(values, result); @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed) { - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed) { - var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck, 8); JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); var result = new Vector4[resultBufferLength]; @@ -308,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int seed) { ValidateRgbToYCbCrConversion( - JpegColorConverter.GetConverter(colorSpace), + JpegColorConverter.GetConverter(colorSpace,8), componentCount, inputBufferLength, resultBufferLength, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 40de25b30..03f1826ed 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -28,7 +28,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.ExifResizeOutOfRange696, TestImages.Jpeg.Issues.InvalidAPP0721, TestImages.Jpeg.Issues.ExifGetString750Load, - TestImages.Jpeg.Issues.ExifGetString750Transform + TestImages.Jpeg.Issues.ExifGetString750Transform, + + // High depth images + TestImages.Jpeg.Baseline.Testorig12bit, }; public static string[] ProgressiveTestJpegs = diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 7452d6e49..e20f1514c 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -27,7 +27,7 @@ - + diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e5b93ab77..6141e9e2a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -135,12 +135,14 @@ namespace SixLabors.ImageSharp.Tests public const string Testorig420 = "Jpg/baseline/testorig.jpg"; public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg"; + public const string Testorig12bit = "Jpg/baseline/testorig12.jpg"; public static readonly string[] All = { Cmyk, Ycck, Exif, Floorplan, Calliphora, Turtle, GammaDalaiLamaGray, - Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1 + Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, + Ratio1x1, Testorig12bit }; } diff --git a/tests/Images/External b/tests/Images/External index 69603ee5b..7ada45bc3 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 69603ee5b6f7dd64114fc44d321e50d9b2d439be +Subproject commit 7ada45bc3484f40e28a50817386ca93f293acd11 diff --git a/tests/Images/Input/Jpg/baseline/testorig12.jpg b/tests/Images/Input/Jpg/baseline/testorig12.jpg new file mode 100644 index 000000000..5cc7a0ada --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/testorig12.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34790770f76db8e60a7765b52ca4edf5f16bc21bcb8c6045ca2efef39a8a013e +size 12394