mirror of https://github.com/SixLabors/ImageSharp
15 changed files with 3217 additions and 198 deletions
@ -0,0 +1,245 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements inverted JPEG CMYK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct CmykOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.Cmyk; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref float c0, |
||||
|
ref float c1, |
||||
|
ref float c2, |
||||
|
float c3, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale) |
||||
|
{ |
||||
|
// Adobe-style CMYK stores inverted component samples. Multiplying K by scale twice folds the
|
||||
|
// two sample-domain divisions into one factor before it modulates the C, M, and Y planes.
|
||||
|
float scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector128<float> c0, |
||||
|
ref Vector128<float> c1, |
||||
|
ref Vector128<float> c2, |
||||
|
Vector128<float> c3, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale) |
||||
|
{ |
||||
|
// Each K lane supplies the common modulation factor for the corresponding C, M, and Y lanes.
|
||||
|
Vector128<float> scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector256<float> c0, |
||||
|
ref Vector256<float> c1, |
||||
|
ref Vector256<float> c2, |
||||
|
Vector256<float> c3, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight independent CMYK samples remain lane-aligned throughout the modulation.
|
||||
|
Vector256<float> scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector512<float> c0, |
||||
|
ref Vector512<float> c1, |
||||
|
ref Vector512<float> c2, |
||||
|
Vector512<float> c3, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen independent CMYK samples remain lane-aligned throughout the modulation.
|
||||
|
Vector512<float> scaledK = c3 * scale * scale; |
||||
|
c0 *= scaledK; |
||||
|
c1 *= scaledK; |
||||
|
c2 *= scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
float r, |
||||
|
float g, |
||||
|
float b, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale, |
||||
|
out float c0, |
||||
|
out float c1, |
||||
|
out float c2, |
||||
|
out float c3) |
||||
|
{ |
||||
|
float c = maximumValue - r; |
||||
|
float m = maximumValue - g; |
||||
|
float y = maximumValue - b; |
||||
|
float k = MathF.Min(c, MathF.Min(m, y)); |
||||
|
|
||||
|
// Pure black makes the chromatic divisor zero. In that case chromatic ink is defined as zero;
|
||||
|
// otherwise remove K and normalize the remaining C, M, and Y contributions.
|
||||
|
if (k >= maximumValue) |
||||
|
{ |
||||
|
c = 0; |
||||
|
m = 0; |
||||
|
y = 0; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// The same remaining range normalizes every chromatic channel. Computing its reciprocal once
|
||||
|
// replaces three divisions with one division and three multiplies.
|
||||
|
float reciprocal = 1F / (maximumValue - k); |
||||
|
c = (c - k) * reciprocal; |
||||
|
m = (m - k) * reciprocal; |
||||
|
y = (y - k) * reciprocal; |
||||
|
} |
||||
|
|
||||
|
// JPEG CMYK is inverted, including K, so normalized chromatic values are reflected around max.
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector128<float> r, |
||||
|
Vector128<float> g, |
||||
|
Vector128<float> b, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale, |
||||
|
out Vector128<float> c0, |
||||
|
out Vector128<float> c1, |
||||
|
out Vector128<float> c2, |
||||
|
out Vector128<float> c3) |
||||
|
{ |
||||
|
Vector128<float> c = maximumValue - r; |
||||
|
Vector128<float> m = maximumValue - g; |
||||
|
Vector128<float> y = maximumValue - b; |
||||
|
Vector128<float> k = Vector128.Min(c, Vector128.Min(m, y)); |
||||
|
|
||||
|
// The all-bits mask clears the undefined zero-divisor result for pure-black lanes without a branch.
|
||||
|
Vector128<float> nonBlack = ~Vector128.Equals(k, maximumValue); |
||||
|
Vector128<float> reciprocal = Vector128<float>.One / (maximumValue - k); |
||||
|
c = ((c - k) * reciprocal) & nonBlack; |
||||
|
m = ((m - k) * reciprocal) & nonBlack; |
||||
|
y = ((y - k) * reciprocal) & nonBlack; |
||||
|
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector256<float> r, |
||||
|
Vector256<float> g, |
||||
|
Vector256<float> b, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale, |
||||
|
out Vector256<float> c0, |
||||
|
out Vector256<float> c1, |
||||
|
out Vector256<float> c2, |
||||
|
out Vector256<float> c3) |
||||
|
{ |
||||
|
Vector256<float> c = maximumValue - r; |
||||
|
Vector256<float> m = maximumValue - g; |
||||
|
Vector256<float> y = maximumValue - b; |
||||
|
Vector256<float> k = Vector256.Min(c, Vector256.Min(m, y)); |
||||
|
|
||||
|
// Masking preserves lane independence when a vector mixes pure black with chromatic pixels.
|
||||
|
Vector256<float> nonBlack = ~Vector256.Equals(k, maximumValue); |
||||
|
Vector256<float> reciprocal = Vector256<float>.One / (maximumValue - k); |
||||
|
c = ((c - k) * reciprocal) & nonBlack; |
||||
|
m = ((m - k) * reciprocal) & nonBlack; |
||||
|
y = ((y - k) * reciprocal) & nonBlack; |
||||
|
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector512<float> r, |
||||
|
Vector512<float> g, |
||||
|
Vector512<float> b, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale, |
||||
|
out Vector512<float> c0, |
||||
|
out Vector512<float> c1, |
||||
|
out Vector512<float> c2, |
||||
|
out Vector512<float> c3) |
||||
|
{ |
||||
|
Vector512<float> c = maximumValue - r; |
||||
|
Vector512<float> m = maximumValue - g; |
||||
|
Vector512<float> y = maximumValue - b; |
||||
|
Vector512<float> k = Vector512.Min(c, Vector512.Min(m, y)); |
||||
|
|
||||
|
// AVX-512 still uses a full floating-point mask value here because bitwise clearing exactly matches
|
||||
|
// the narrower operator semantics and lets the JIT select the most suitable native instructions.
|
||||
|
Vector512<float> nonBlack = ~Vector512.Equals(k, maximumValue); |
||||
|
Vector512<float> reciprocal = Vector512<float>.One / (maximumValue - k); |
||||
|
c = ((c - k) * reciprocal) & nonBlack; |
||||
|
m = ((m - k) * reciprocal) & nonBlack; |
||||
|
y = ((y - k) * reciprocal) & nonBlack; |
||||
|
|
||||
|
c0 = maximumValue - (c * maximumValue); |
||||
|
c1 = maximumValue - (m * maximumValue); |
||||
|
c2 = maximumValue - (y * maximumValue); |
||||
|
c3 = maximumValue - k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static void ConvertToRgbInPlaceWithIcc( |
||||
|
Configuration configuration, |
||||
|
IccProfile profile, |
||||
|
in ComponentValues values, |
||||
|
float maximumValue) |
||||
|
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,203 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements grayscale expansion and RGB luminance reduction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct GrayScaleOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.Grayscale; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 1; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref float c0, |
||||
|
ref float c1, |
||||
|
ref float c2, |
||||
|
float c3, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale) |
||||
|
{ |
||||
|
// JPEG stores luminance in the integer sample domain. Normalize it once, then duplicate the
|
||||
|
// same value into all three RGB planes. Keeping it local also prevents potentially aliasing
|
||||
|
// byref stores from forcing the JIT to reload c0 between assignments.
|
||||
|
float luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector128<float> c0, |
||||
|
ref Vector128<float> c1, |
||||
|
ref Vector128<float> c2, |
||||
|
Vector128<float> c3, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale) |
||||
|
{ |
||||
|
// Each XMM lane is one independent luminance sample. Reusing the normalized vector for R, G,
|
||||
|
// and B avoids recomputing the scale and keeps it live across potentially aliasing byref stores.
|
||||
|
Vector128<float> luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector256<float> c0, |
||||
|
ref Vector256<float> c1, |
||||
|
ref Vector256<float> c2, |
||||
|
Vector256<float> c3, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight luminance samples occupy the YMM lanes. The local retains the normalized vector across
|
||||
|
// all three output stores even when the destination planes alias.
|
||||
|
Vector256<float> luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector512<float> c0, |
||||
|
ref Vector512<float> c1, |
||||
|
ref Vector512<float> c2, |
||||
|
Vector512<float> c3, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen luminance samples occupy the ZMM lanes. The local retains the normalized vector across
|
||||
|
// all three output stores without shuffles, interleaving, or source reloads.
|
||||
|
Vector512<float> luminance = c0 * scale; |
||||
|
c0 = luminance; |
||||
|
c1 = luminance; |
||||
|
c2 = luminance; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
float r, |
||||
|
float g, |
||||
|
float b, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale, |
||||
|
out float c0, |
||||
|
out float c1, |
||||
|
out float c2, |
||||
|
out float c3) |
||||
|
{ |
||||
|
// Rec.601 luma weights operate directly in the encoder sample domain. Only c0 is stored for a
|
||||
|
// one-component model; the remaining out values exist solely to satisfy the common operator shape.
|
||||
|
c0 = (0.299F * r) + (0.587F * g) + (0.114F * b); |
||||
|
c1 = 0; |
||||
|
c2 = 0; |
||||
|
c3 = 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector128<float> r, |
||||
|
Vector128<float> g, |
||||
|
Vector128<float> b, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale, |
||||
|
out Vector128<float> c0, |
||||
|
out Vector128<float> c1, |
||||
|
out Vector128<float> c2, |
||||
|
out Vector128<float> c3) |
||||
|
{ |
||||
|
// The nested estimate gives each pixel the same multiply-add grouping as the scalar Rec.601 formula.
|
||||
|
c0 = Vector128_.MultiplyAddEstimate( |
||||
|
Vector128.Create(0.299F), |
||||
|
r, |
||||
|
Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); |
||||
|
c1 = default; |
||||
|
c2 = default; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector256<float> r, |
||||
|
Vector256<float> g, |
||||
|
Vector256<float> b, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale, |
||||
|
out Vector256<float> c0, |
||||
|
out Vector256<float> c1, |
||||
|
out Vector256<float> c2, |
||||
|
out Vector256<float> c3) |
||||
|
{ |
||||
|
// YMM lanes evaluate the same Rec.601 equation independently, with no horizontal lane reduction.
|
||||
|
c0 = Vector256_.MultiplyAddEstimate( |
||||
|
Vector256.Create(0.299F), |
||||
|
r, |
||||
|
Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); |
||||
|
c1 = default; |
||||
|
c2 = default; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector512<float> r, |
||||
|
Vector512<float> g, |
||||
|
Vector512<float> b, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale, |
||||
|
out Vector512<float> c0, |
||||
|
out Vector512<float> c1, |
||||
|
out Vector512<float> c2, |
||||
|
out Vector512<float> c3) |
||||
|
{ |
||||
|
// ZMM lanes retain the same arithmetic order as narrower paths so only SIMD width changes.
|
||||
|
c0 = Vector512_.MultiplyAddEstimate( |
||||
|
Vector512.Create(0.299F), |
||||
|
r, |
||||
|
Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); |
||||
|
c1 = default; |
||||
|
c2 = default; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static void ConvertToRgbInPlaceWithIcc( |
||||
|
Configuration configuration, |
||||
|
IccProfile profile, |
||||
|
in ComponentValues values, |
||||
|
float maximumValue) |
||||
|
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,594 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the color-model-specific arithmetic used by <see cref="JpegColorConverter{TOperator}"/>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Each overload describes the same lane-wise transform. The generic traversal selects the widest
|
||||
|
/// available overload and the JIT resolves these static interface calls for each closed converter type.
|
||||
|
/// </remarks>
|
||||
|
internal interface IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the JPEG color space handled by the operator.
|
||||
|
/// </summary>
|
||||
|
static abstract JpegColorSpace ColorSpace { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of component planes used by the color space.
|
||||
|
/// </summary>
|
||||
|
static abstract int ComponentCount { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one JPEG sample to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/>.</param>
|
||||
|
static abstract void ConvertToRgb( |
||||
|
ref float c0, |
||||
|
ref float c1, |
||||
|
ref float c2, |
||||
|
float c3, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts four JPEG samples to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component lanes, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component lanes, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component lanes, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component lanes, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
static abstract void ConvertToRgb( |
||||
|
ref Vector128<float> c0, |
||||
|
ref Vector128<float> c1, |
||||
|
ref Vector128<float> c2, |
||||
|
Vector128<float> c3, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts eight JPEG samples to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component lanes, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component lanes, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component lanes, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component lanes, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
static abstract void ConvertToRgb( |
||||
|
ref Vector256<float> c0, |
||||
|
ref Vector256<float> c1, |
||||
|
ref Vector256<float> c2, |
||||
|
Vector256<float> c3, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts sixteen JPEG samples to normalized RGB.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c0">The first component lanes, replaced by red.</param>
|
||||
|
/// <param name="c1">The second component lanes, replaced by green.</param>
|
||||
|
/// <param name="c2">The third component lanes, replaced by blue.</param>
|
||||
|
/// <param name="c3">The fourth component lanes, or zero for a three-component color space.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
static abstract void ConvertToRgb( |
||||
|
ref Vector512<float> c0, |
||||
|
ref Vector512<float> c1, |
||||
|
ref Vector512<float> c2, |
||||
|
Vector512<float> c3, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one RGB sample to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red value.</param>
|
||||
|
/// <param name="g">The green value.</param>
|
||||
|
/// <param name="b">The blue value.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/>.</param>
|
||||
|
/// <param name="c0">The first converted component.</param>
|
||||
|
/// <param name="c1">The second converted component.</param>
|
||||
|
/// <param name="c2">The third converted component.</param>
|
||||
|
/// <param name="c3">The fourth converted component, if used.</param>
|
||||
|
static abstract void ConvertFromRgb( |
||||
|
float r, |
||||
|
float g, |
||||
|
float b, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale, |
||||
|
out float c0, |
||||
|
out float c1, |
||||
|
out float c2, |
||||
|
out float c3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts four RGB samples to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red lanes.</param>
|
||||
|
/// <param name="g">The green lanes.</param>
|
||||
|
/// <param name="b">The blue lanes.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
/// <param name="c0">The first converted component lanes.</param>
|
||||
|
/// <param name="c1">The second converted component lanes.</param>
|
||||
|
/// <param name="c2">The third converted component lanes.</param>
|
||||
|
/// <param name="c3">The fourth converted component lanes, if used.</param>
|
||||
|
static abstract void ConvertFromRgb( |
||||
|
Vector128<float> r, |
||||
|
Vector128<float> g, |
||||
|
Vector128<float> b, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale, |
||||
|
out Vector128<float> c0, |
||||
|
out Vector128<float> c1, |
||||
|
out Vector128<float> c2, |
||||
|
out Vector128<float> c3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts eight RGB samples to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red lanes.</param>
|
||||
|
/// <param name="g">The green lanes.</param>
|
||||
|
/// <param name="b">The blue lanes.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
/// <param name="c0">The first converted component lanes.</param>
|
||||
|
/// <param name="c1">The second converted component lanes.</param>
|
||||
|
/// <param name="c2">The third converted component lanes.</param>
|
||||
|
/// <param name="c3">The fourth converted component lanes, if used.</param>
|
||||
|
static abstract void ConvertFromRgb( |
||||
|
Vector256<float> r, |
||||
|
Vector256<float> g, |
||||
|
Vector256<float> b, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale, |
||||
|
out Vector256<float> c0, |
||||
|
out Vector256<float> c1, |
||||
|
out Vector256<float> c2, |
||||
|
out Vector256<float> c3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts sixteen RGB samples to JPEG components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="r">The red lanes.</param>
|
||||
|
/// <param name="g">The green lanes.</param>
|
||||
|
/// <param name="b">The blue lanes.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
/// <param name="halfValue">The midpoint component value for the configured precision.</param>
|
||||
|
/// <param name="scale">The reciprocal of <paramref name="maximumValue"/> in every lane.</param>
|
||||
|
/// <param name="c0">The first converted component lanes.</param>
|
||||
|
/// <param name="c1">The second converted component lanes.</param>
|
||||
|
/// <param name="c2">The third converted component lanes.</param>
|
||||
|
/// <param name="c3">The fourth converted component lanes, if used.</param>
|
||||
|
static abstract void ConvertFromRgb( |
||||
|
Vector512<float> r, |
||||
|
Vector512<float> g, |
||||
|
Vector512<float> b, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale, |
||||
|
out Vector512<float> c0, |
||||
|
out Vector512<float> c1, |
||||
|
out Vector512<float> c2, |
||||
|
out Vector512<float> c3); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts JPEG component values to RGB using the supplied ICC profile.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration used to allocate temporary storage.</param>
|
||||
|
/// <param name="profile">The source ICC profile.</param>
|
||||
|
/// <param name="values">The component values to convert.</param>
|
||||
|
/// <param name="maximumValue">The maximum component value for the configured precision.</param>
|
||||
|
static abstract void ConvertToRgbInPlaceWithIcc( |
||||
|
Configuration configuration, |
||||
|
IccProfile profile, |
||||
|
in ComponentValues values, |
||||
|
float maximumValue); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts a JPEG color model using a single operator-driven traversal for all SIMD widths.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The color-model-specific arithmetic.</typeparam>
|
||||
|
internal sealed class JpegColorConverter<TOperator> : JpegColorConverterBase |
||||
|
where TOperator : struct, IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="JpegColorConverter{TOperator}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="precision">The precision in bits.</param>
|
||||
|
public JpegColorConverter(int precision) |
||||
|
: base(TOperator.ColorSpace, precision) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool IsAvailable => true; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override int ElementsPerBatch |
||||
|
=> Vector512.IsHardwareAccelerated |
||||
|
? Vector512<float>.Count |
||||
|
: Vector256.IsHardwareAccelerated |
||||
|
? Vector256<float>.Count |
||||
|
: Vector128.IsHardwareAccelerated |
||||
|
? Vector128<float>.Count |
||||
|
: 1; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void ConvertToRgbInPlace(in ComponentValues values) |
||||
|
{ |
||||
|
// JPEG component processors own equally sized planar buffers. Capturing their first elements
|
||||
|
// as byrefs lets every width share the same offset without introducing Span bounds checks in
|
||||
|
// the hot loops. Component3 may be empty; its byref is only dereferenced for four-component operators.
|
||||
|
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); |
||||
|
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); |
||||
|
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); |
||||
|
ref float c3Base = ref MemoryMarshal.GetReference(values.Component3); |
||||
|
|
||||
|
int length = values.Component0.Length; |
||||
|
int i = 0; |
||||
|
float scale = 1F / this.MaximumValue; |
||||
|
|
||||
|
// Descending widths keep one traversal while allowing an AVX-512 machine to process
|
||||
|
// an eight-pixel JPEG block with AVX2 rather than sending the entire block to scalar code.
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// Subtracting the lane count turns the loop condition into a single signed comparison.
|
||||
|
// A negative value naturally skips this width, and i <= end proves every unaligned
|
||||
|
// 64-byte reinterpretation remains entirely inside its component buffer.
|
||||
|
int oneVectorFromEnd = length - Vector512<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Precision-derived values are broadcast only when this width has work. Keeping them outside
|
||||
|
// the loop avoids repeated setup without penalizing rows handled entirely by narrower widths.
|
||||
|
Vector512<float> maximumValue = Vector512.Create(this.MaximumValue); |
||||
|
Vector512<float> halfValue = Vector512.Create(this.HalfValue); |
||||
|
Vector512<float> scaleVector = Vector512.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
||||
|
{ |
||||
|
ref Vector512<float> c0 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c0Base, i)); |
||||
|
ref Vector512<float> c1 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c1Base, i)); |
||||
|
ref Vector512<float> c2 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c2Base, i)); |
||||
|
|
||||
|
// ComponentCount is a static property on the closed operator type, so the JIT removes
|
||||
|
// this choice. Three-component models never dereference the empty Component3 byref.
|
||||
|
Vector512<float> c3 = TOperator.ComponentCount == 4 |
||||
|
? Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c3Base, i)) |
||||
|
: default; |
||||
|
|
||||
|
// c0-c2 alias the planar source vectors and are replaced in place with normalized RGB.
|
||||
|
// c3 is passed by value because the fourth JPEG component must remain unchanged.
|
||||
|
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// The shared offset continues where AVX-512 stopped. At this point fewer than sixteen
|
||||
|
// samples remain, so this stage consumes the complete eight-sample remainder when present.
|
||||
|
int oneVectorFromEnd = length - Vector256<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// YMM precision state is materialized only for an eight-sample remainder or an AVX2-only loop.
|
||||
|
Vector256<float> maximumValue = Vector256.Create(this.MaximumValue); |
||||
|
Vector256<float> halfValue = Vector256.Create(this.HalfValue); |
||||
|
Vector256<float> scaleVector = Vector256.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
||||
|
{ |
||||
|
ref Vector256<float> c0 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c0Base, i)); |
||||
|
ref Vector256<float> c1 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c1Base, i)); |
||||
|
ref Vector256<float> c2 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c2Base, i)); |
||||
|
|
||||
|
// The closed operator makes this a compile-time color-model choice, not a per-vector
|
||||
|
// runtime abstraction or interface dispatch.
|
||||
|
Vector256<float> c3 = TOperator.ComponentCount == 4 |
||||
|
? Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c3Base, i)) |
||||
|
: default; |
||||
|
|
||||
|
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// SSE/AdvSimd handles the final four complete samples. This also gives non-AVX machines
|
||||
|
// the same traversal without duplicating the control flow for another register width.
|
||||
|
int oneVectorFromEnd = length - Vector128<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// XMM state is likewise created only when four samples remain for this stage.
|
||||
|
Vector128<float> maximumValue = Vector128.Create(this.MaximumValue); |
||||
|
Vector128<float> halfValue = Vector128.Create(this.HalfValue); |
||||
|
Vector128<float> scaleVector = Vector128.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
ref Vector128<float> c0 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c0Base, i)); |
||||
|
ref Vector128<float> c1 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c1Base, i)); |
||||
|
ref Vector128<float> c2 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c2Base, i)); |
||||
|
|
||||
|
// As at the wider stages, the fourth vector is loaded only for CMYK-shaped operators.
|
||||
|
Vector128<float> c3 = TOperator.ComponentCount == 4 |
||||
|
? Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c3Base, i)) |
||||
|
: default; |
||||
|
|
||||
|
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scaleVector); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fewer than four samples remain after the SIMD cascade. Processing from the shared offset
|
||||
|
// guarantees each sample is visited exactly once for arbitrary test lengths and JPEG block rows.
|
||||
|
for (; i < length; i++) |
||||
|
{ |
||||
|
float c3 = TOperator.ComponentCount == 4 ? Unsafe.Add(ref c3Base, i) : 0; |
||||
|
|
||||
|
TOperator.ConvertToRgb( |
||||
|
ref Unsafe.Add(ref c0Base, i), |
||||
|
ref Unsafe.Add(ref c1Base, i), |
||||
|
ref Unsafe.Add(ref c2Base, i), |
||||
|
c3, |
||||
|
this.MaximumValue, |
||||
|
this.HalfValue, |
||||
|
scale); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) |
||||
|
=> TOperator.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) |
||||
|
{ |
||||
|
// The encoder supplies equally sized RGB planes and destination component planes. Byrefs preserve
|
||||
|
// contiguous access and allow the same proven vector boundary to govern every participating lane.
|
||||
|
// Component3 is empty for three-component formats and is only written by four-component operators.
|
||||
|
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0); |
||||
|
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1); |
||||
|
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2); |
||||
|
ref float c3Base = ref MemoryMarshal.GetReference(values.Component3); |
||||
|
ref float rBase = ref MemoryMarshal.GetReference(rLane); |
||||
|
ref float gBase = ref MemoryMarshal.GetReference(gLane); |
||||
|
ref float bBase = ref MemoryMarshal.GetReference(bLane); |
||||
|
|
||||
|
int length = values.Component0.Length; |
||||
|
int i = 0; |
||||
|
float scale = 1F / this.MaximumValue; |
||||
|
|
||||
|
// Each vector overload returns planar component vectors. Storing them here keeps the
|
||||
|
// operator concerned only with color arithmetic and preserves contiguous lane access.
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// The end offset proves all three 64-byte RGB reads and all component writes are in range.
|
||||
|
// A short row yields a negative end and falls through to the next supported width.
|
||||
|
int oneVectorFromEnd = length - Vector512<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Operators receive width-matched precision state only when this width has work, keeping
|
||||
|
// invariant broadcasts outside the loop without charging narrower or scalar rows for them.
|
||||
|
Vector512<float> maximumValue = Vector512.Create(this.MaximumValue); |
||||
|
Vector512<float> halfValue = Vector512.Create(this.HalfValue); |
||||
|
Vector512<float> scaleVector = Vector512.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
||||
|
{ |
||||
|
Vector512<float> r = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref rBase, i)); |
||||
|
Vector512<float> g = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref gBase, i)); |
||||
|
Vector512<float> b = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref bBase, i)); |
||||
|
|
||||
|
TOperator.ConvertFromRgb( |
||||
|
r, |
||||
|
g, |
||||
|
b, |
||||
|
maximumValue, |
||||
|
halfValue, |
||||
|
scaleVector, |
||||
|
out Vector512<float> c0, |
||||
|
out Vector512<float> c1, |
||||
|
out Vector512<float> c2, |
||||
|
out Vector512<float> c3); |
||||
|
|
||||
|
// Outputs remain planar: each vector contains sixteen consecutive samples from one
|
||||
|
// JPEG component. Static count checks prevent grayscale from touching absent planes
|
||||
|
// while disappearing completely from three- and four-component specializations.
|
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c0Base, i)) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c1Base, i)) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c2Base, i)) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref c3Base, i)) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// Continue from the AVX-512 offset so an eight-sample tail stays vectorized on AVX-512 CPUs.
|
||||
|
int oneVectorFromEnd = length - Vector256<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Materialize YMM state only for an eight-sample remainder or an AVX2-only loop.
|
||||
|
Vector256<float> maximumValue = Vector256.Create(this.MaximumValue); |
||||
|
Vector256<float> halfValue = Vector256.Create(this.HalfValue); |
||||
|
Vector256<float> scaleVector = Vector256.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
||||
|
{ |
||||
|
Vector256<float> r = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref rBase, i)); |
||||
|
Vector256<float> g = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref gBase, i)); |
||||
|
Vector256<float> b = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref bBase, i)); |
||||
|
|
||||
|
TOperator.ConvertFromRgb( |
||||
|
r, |
||||
|
g, |
||||
|
b, |
||||
|
maximumValue, |
||||
|
halfValue, |
||||
|
scaleVector, |
||||
|
out Vector256<float> c0, |
||||
|
out Vector256<float> c1, |
||||
|
out Vector256<float> c2, |
||||
|
out Vector256<float> c3); |
||||
|
|
||||
|
// Static count checks write only planes owned by this color model.
|
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c0Base, i)) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c1Base, i)) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c2Base, i)) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref c3Base, i)) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
// The final SIMD stage consumes four complete RGB samples on SSE or AdvSimd hardware.
|
||||
|
int oneVectorFromEnd = length - Vector128<float>.Count; |
||||
|
|
||||
|
if (i <= oneVectorFromEnd) |
||||
|
{ |
||||
|
// Materialize XMM state only when the final SIMD stage can consume four samples.
|
||||
|
Vector128<float> maximumValue = Vector128.Create(this.MaximumValue); |
||||
|
Vector128<float> halfValue = Vector128.Create(this.HalfValue); |
||||
|
Vector128<float> scaleVector = Vector128.Create(scale); |
||||
|
|
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
||||
|
{ |
||||
|
Vector128<float> r = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref rBase, i)); |
||||
|
Vector128<float> g = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref gBase, i)); |
||||
|
Vector128<float> b = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref bBase, i)); |
||||
|
|
||||
|
TOperator.ConvertFromRgb( |
||||
|
r, |
||||
|
g, |
||||
|
b, |
||||
|
maximumValue, |
||||
|
halfValue, |
||||
|
scaleVector, |
||||
|
out Vector128<float> c0, |
||||
|
out Vector128<float> c1, |
||||
|
out Vector128<float> c2, |
||||
|
out Vector128<float> c3); |
||||
|
|
||||
|
// Four results are stored only for the planes represented by the closed operator.
|
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c0Base, i)) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c1Base, i)) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c2Base, i)) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref c3Base, i)) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Scalar conversion is reserved for the zero-to-three samples that cannot fill Vector128.
|
||||
|
for (; i < length; i++) |
||||
|
{ |
||||
|
TOperator.ConvertFromRgb( |
||||
|
Unsafe.Add(ref rBase, i), |
||||
|
Unsafe.Add(ref gBase, i), |
||||
|
Unsafe.Add(ref bBase, i), |
||||
|
this.MaximumValue, |
||||
|
this.HalfValue, |
||||
|
scale, |
||||
|
out float c0, |
||||
|
out float c1, |
||||
|
out float c2, |
||||
|
out float c3); |
||||
|
|
||||
|
Unsafe.Add(ref c0Base, i) = c0; |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 2) |
||||
|
{ |
||||
|
Unsafe.Add(ref c1Base, i) = c1; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 3) |
||||
|
{ |
||||
|
Unsafe.Add(ref c2Base, i) = c2; |
||||
|
} |
||||
|
|
||||
|
if (TOperator.ComponentCount >= 4) |
||||
|
{ |
||||
|
Unsafe.Add(ref c3Base, i) = c3; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,184 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements direct JPEG RGB normalization and planar RGB copying for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct RgbOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.RGB; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 3; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref float c0, |
||||
|
ref float c1, |
||||
|
ref float c2, |
||||
|
float c3, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale) |
||||
|
{ |
||||
|
// The JPEG planes already represent R, G, and B. Conversion therefore consists only of moving
|
||||
|
// each integer-domain sample into the normalized floating-point domain consumed by pixel packing.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector128<float> c0, |
||||
|
ref Vector128<float> c1, |
||||
|
ref Vector128<float> c2, |
||||
|
Vector128<float> c3, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale) |
||||
|
{ |
||||
|
// Four samples from each planar channel remain in their lanes while sharing one normalization vector.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector256<float> c0, |
||||
|
ref Vector256<float> c1, |
||||
|
ref Vector256<float> c2, |
||||
|
Vector256<float> c3, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight samples per plane are normalized independently without channel shuffles.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector512<float> c0, |
||||
|
ref Vector512<float> c1, |
||||
|
ref Vector512<float> c2, |
||||
|
Vector512<float> c3, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen samples per plane are normalized independently without changing planar ordering.
|
||||
|
c0 *= scale; |
||||
|
c1 *= scale; |
||||
|
c2 *= scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
float r, |
||||
|
float g, |
||||
|
float b, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale, |
||||
|
out float c0, |
||||
|
out float c1, |
||||
|
out float c2, |
||||
|
out float c3) |
||||
|
{ |
||||
|
// Encoder RGB lanes already use the JPEG sample domain, so the direct color model copies them.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector128<float> r, |
||||
|
Vector128<float> g, |
||||
|
Vector128<float> b, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale, |
||||
|
out Vector128<float> c0, |
||||
|
out Vector128<float> c1, |
||||
|
out Vector128<float> c2, |
||||
|
out Vector128<float> c3) |
||||
|
{ |
||||
|
// The planar vectors map one-to-one to JPEG components; the fourth result is statically discarded.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector256<float> r, |
||||
|
Vector256<float> g, |
||||
|
Vector256<float> b, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale, |
||||
|
out Vector256<float> c0, |
||||
|
out Vector256<float> c1, |
||||
|
out Vector256<float> c2, |
||||
|
out Vector256<float> c3) |
||||
|
{ |
||||
|
// The planar vectors map one-to-one to JPEG components; no arithmetic or rearrangement is required.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector512<float> r, |
||||
|
Vector512<float> g, |
||||
|
Vector512<float> b, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale, |
||||
|
out Vector512<float> c0, |
||||
|
out Vector512<float> c1, |
||||
|
out Vector512<float> c2, |
||||
|
out Vector512<float> c3) |
||||
|
{ |
||||
|
// The widest path is likewise a register-to-register planar copy for sixteen pixels.
|
||||
|
c0 = r; |
||||
|
c1 = g; |
||||
|
c2 = b; |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static void ConvertToRgbInPlaceWithIcc( |
||||
|
Configuration configuration, |
||||
|
IccProfile profile, |
||||
|
in ComponentValues values, |
||||
|
float maximumValue) |
||||
|
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements non-inverted TIFF JPEG CMYK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct TiffCmykOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.TiffCmyk; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
// TIFF stores conventional CMYK rather than Adobe's inverted representation. Normalize every
|
||||
|
// component, invert C/M/Y, and let the remaining light after K modulate each RGB channel.
|
||||
|
float k = 1F - (c3 * scale); |
||||
|
c0 = (1F - (c0 * scale)) * k; |
||||
|
c1 = (1F - (c1 * scale)) * k; |
||||
|
c2 = (1F - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
// K remains lane-aligned with its C/M/Y sample while one-minus performs the non-inverted CMYK mapping.
|
||||
|
Vector128<float> k = Vector128<float>.One - (c3 * scale); |
||||
|
c0 = (Vector128<float>.One - (c0 * scale)) * k; |
||||
|
c1 = (Vector128<float>.One - (c1 * scale)) * k; |
||||
|
c2 = (Vector128<float>.One - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
// Eight conventional CMYK samples convert independently without channel rearrangement.
|
||||
|
Vector256<float> k = Vector256<float>.One - (c3 * scale); |
||||
|
c0 = (Vector256<float>.One - (c0 * scale)) * k; |
||||
|
c1 = (Vector256<float>.One - (c1 * scale)) * k; |
||||
|
c2 = (Vector256<float>.One - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
// Sixteen conventional CMYK samples convert independently without channel rearrangement.
|
||||
|
Vector512<float> k = Vector512<float>.One - (c3 * scale); |
||||
|
c0 = (Vector512<float>.One - (c0 * scale)) * k; |
||||
|
c1 = (Vector512<float>.One - (c1 * scale)) * k; |
||||
|
c2 = (Vector512<float>.One - (c2 * scale)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
float c = maximumValue - r; |
||||
|
float m = maximumValue - g; |
||||
|
float y = maximumValue - b; |
||||
|
float k = MathF.Min(c, MathF.Min(m, y)); |
||||
|
|
||||
|
// Removing the shared black contribution requires division by the remaining range. Pure black
|
||||
|
// consumes that range completely, so its chromatic components are defined as zero.
|
||||
|
if (k >= maximumValue) |
||||
|
{ |
||||
|
c = 0; |
||||
|
m = 0; |
||||
|
y = 0; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// One reciprocal normalizes C, M, and Y against their shared remaining range.
|
||||
|
float reciprocal = 1F / (maximumValue - k); |
||||
|
c = (c - k) * reciprocal; |
||||
|
m = (m - k) * reciprocal; |
||||
|
y = (y - k) * reciprocal; |
||||
|
} |
||||
|
|
||||
|
// TIFF stores conventional CMYK: scale normalized C/M/Y back into the sample domain and retain K.
|
||||
|
c0 = c * maximumValue; |
||||
|
c1 = m * maximumValue; |
||||
|
c2 = y * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
Vector128<float> c = maximumValue - r; |
||||
|
Vector128<float> m = maximumValue - g; |
||||
|
Vector128<float> y = maximumValue - b; |
||||
|
Vector128<float> k = Vector128.Min(c, Vector128.Min(m, y)); |
||||
|
|
||||
|
// The all-bits mask clears the undefined zero-divisor result only in pure-black lanes.
|
||||
|
Vector128<float> nonBlack = ~Vector128.Equals(k, maximumValue); |
||||
|
Vector128<float> reciprocal = Vector128<float>.One / (maximumValue - k); |
||||
|
c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
Vector256<float> c = maximumValue - r; |
||||
|
Vector256<float> m = maximumValue - g; |
||||
|
Vector256<float> y = maximumValue - b; |
||||
|
Vector256<float> k = Vector256.Min(c, Vector256.Min(m, y)); |
||||
|
|
||||
|
// Eight lanes independently clear the pure-black singularity before returning conventional CMYK.
|
||||
|
Vector256<float> nonBlack = ~Vector256.Equals(k, maximumValue); |
||||
|
Vector256<float> reciprocal = Vector256<float>.One / (maximumValue - k); |
||||
|
c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
Vector512<float> c = maximumValue - r; |
||||
|
Vector512<float> m = maximumValue - g; |
||||
|
Vector512<float> y = maximumValue - b; |
||||
|
Vector512<float> k = Vector512.Min(c, Vector512.Min(m, y)); |
||||
|
|
||||
|
// Sixteen lanes retain the same branchless singularity handling and component layout.
|
||||
|
Vector512<float> nonBlack = ~Vector512.Equals(k, maximumValue); |
||||
|
Vector512<float> reciprocal = Vector512<float>.One / (maximumValue - k); |
||||
|
c0 = (((c - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c1 = (((m - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c2 = (((y - k) * reciprocal) & nonBlack) * maximumValue; |
||||
|
c3 = k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) |
||||
|
=> TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,187 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements non-inverted TIFF JPEG YccK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct TiffYccKOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
private const float SourceScale = 1F / 255F; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.TiffYccK; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
float y = c0 * scale; |
||||
|
float cb = (c1 - halfValue) * scale; |
||||
|
float cr = (c2 - halfValue) * scale; |
||||
|
float k = 1F - (c3 * scale); |
||||
|
|
||||
|
// TIFF YccK is non-inverted: decode normalized YCbCr without integer rounding, then let the
|
||||
|
// remaining light after K modulate all three channels.
|
||||
|
c0 = (y + (YCbCrScalar.RCrMult * cr)) * k; |
||||
|
c1 = (y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr)) * k; |
||||
|
c2 = (y + (YCbCrScalar.BCbMult * cb)) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
Vector128<float> y = c0 * scale; |
||||
|
Vector128<float> cb = (c1 - halfValue) * scale; |
||||
|
Vector128<float> cr = (c2 - halfValue) * scale; |
||||
|
Vector128<float> k = Vector128<float>.One - (c3 * scale); |
||||
|
|
||||
|
// Four lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
|
||||
|
c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y) * k; |
||||
|
c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)) * k; |
||||
|
c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
Vector256<float> y = c0 * scale; |
||||
|
Vector256<float> cb = (c1 - halfValue) * scale; |
||||
|
Vector256<float> cr = (c2 - halfValue) * scale; |
||||
|
Vector256<float> k = Vector256<float>.One - (c3 * scale); |
||||
|
|
||||
|
// Eight lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
|
||||
|
c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y) * k; |
||||
|
c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)) * k; |
||||
|
c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
Vector512<float> y = c0 * scale; |
||||
|
Vector512<float> cb = (c1 - halfValue) * scale; |
||||
|
Vector512<float> cr = (c2 - halfValue) * scale; |
||||
|
Vector512<float> k = Vector512<float>.One - (c3 * scale); |
||||
|
|
||||
|
// Sixteen lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
|
||||
|
c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y) * k; |
||||
|
c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)) * k; |
||||
|
c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y) * k; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
r *= SourceScale; |
||||
|
g *= SourceScale; |
||||
|
b *= SourceScale; |
||||
|
float k = 1F - MathF.Max(r, MathF.Max(g, b)); |
||||
|
|
||||
|
// Dividing by the brightest channel removes K before YCbCr projection. Pure black has no
|
||||
|
// chromatic direction, so it maps to zero luma and the neutral chroma midpoint.
|
||||
|
if (k >= 1F) |
||||
|
{ |
||||
|
c0 = 0; |
||||
|
c1 = halfValue; |
||||
|
c2 = halfValue; |
||||
|
c3 = maximumValue; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
float divisor = 1F / (1F - k); |
||||
|
r *= divisor; |
||||
|
g *= divisor; |
||||
|
b *= divisor; |
||||
|
c0 = ((0.299F * r) + (0.587F * g) + (0.114F * b)) * maximumValue; |
||||
|
c1 = halfValue + (((-0.168736F * r) + (-0.331264F * g) + (0.5F * b)) * maximumValue); |
||||
|
c2 = halfValue + (((0.5F * r) + (-0.418688F * g) + (-0.081312F * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
Vector128<float> sourceScale = Vector128.Create(SourceScale); |
||||
|
r *= sourceScale; |
||||
|
g *= sourceScale; |
||||
|
b *= sourceScale; |
||||
|
Vector128<float> k = Vector128<float>.One - Vector128.Max(r, Vector128.Max(g, b)); |
||||
|
|
||||
|
// The mask assigns no chromatic direction to pure-black lanes while preserving neighboring pixels.
|
||||
|
Vector128<float> nonBlack = ~Vector128.Equals(k, Vector128<float>.One); |
||||
|
Vector128<float> divisor = Vector128<float>.One / (Vector128<float>.One - k); |
||||
|
r = (r * divisor) & nonBlack; |
||||
|
g = (g * divisor) & nonBlack; |
||||
|
b = (b * divisor) & nonBlack; |
||||
|
c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)) * maximumValue; |
||||
|
c1 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)) * maximumValue); |
||||
|
c2 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
Vector256<float> sourceScale = Vector256.Create(SourceScale); |
||||
|
r *= sourceScale; |
||||
|
g *= sourceScale; |
||||
|
b *= sourceScale; |
||||
|
Vector256<float> k = Vector256<float>.One - Vector256.Max(r, Vector256.Max(g, b)); |
||||
|
|
||||
|
// Eight lanes normalize chromatic direction independently and retain neutral chroma for black.
|
||||
|
Vector256<float> nonBlack = ~Vector256.Equals(k, Vector256<float>.One); |
||||
|
Vector256<float> divisor = Vector256<float>.One / (Vector256<float>.One - k); |
||||
|
r = (r * divisor) & nonBlack; |
||||
|
g = (g * divisor) & nonBlack; |
||||
|
b = (b * divisor) & nonBlack; |
||||
|
c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)) * maximumValue; |
||||
|
c1 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)) * maximumValue); |
||||
|
c2 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
Vector512<float> sourceScale = Vector512.Create(SourceScale); |
||||
|
r *= sourceScale; |
||||
|
g *= sourceScale; |
||||
|
b *= sourceScale; |
||||
|
Vector512<float> k = Vector512<float>.One - Vector512.Max(r, Vector512.Max(g, b)); |
||||
|
|
||||
|
// Sixteen lanes normalize chromatic direction independently and retain neutral chroma for black.
|
||||
|
Vector512<float> nonBlack = ~Vector512.Equals(k, Vector512<float>.One); |
||||
|
Vector512<float> divisor = Vector512<float>.One / (Vector512<float>.One - k); |
||||
|
r = (r * divisor) & nonBlack; |
||||
|
g = (g * divisor) & nonBlack; |
||||
|
b = (b * divisor) & nonBlack; |
||||
|
c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)) * maximumValue; |
||||
|
c1 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)) * maximumValue); |
||||
|
c2 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)) * maximumValue); |
||||
|
c3 = k * maximumValue; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) |
||||
|
=> TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,263 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements the JPEG YCbCr conversion formula for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct YCbCrOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.YCbCr; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 3; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref float c0, |
||||
|
ref float c1, |
||||
|
ref float c2, |
||||
|
float c3, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale) |
||||
|
{ |
||||
|
float y = c0; |
||||
|
float cb = c1 - halfValue; |
||||
|
float cr = c2 - halfValue; |
||||
|
|
||||
|
// c0/c1/c2 initially mean Y/Cb/Cr. Chroma is centered around zero before applying
|
||||
|
// the BT.601 matrix, then integer-domain RGB is rounded away from zero and normalized
|
||||
|
// to [nominally] 0..1. Values intentionally remain unclamped because quantizing RGB into the
|
||||
|
// destination pixel format owns saturation; retaining overshoot avoids discarding color information.
|
||||
|
c0 = MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
||||
|
c1 = MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero) * scale; |
||||
|
c2 = MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector128<float> c0, |
||||
|
ref Vector128<float> c1, |
||||
|
ref Vector128<float> c2, |
||||
|
Vector128<float> c3, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale) |
||||
|
{ |
||||
|
Vector128<float> y = c0; |
||||
|
Vector128<float> cb = c1 - halfValue; |
||||
|
Vector128<float> cr = c2 - halfValue; |
||||
|
|
||||
|
// Lanes are four independent Y/Cb/Cr samples. MultiplyAddEstimate maps to FMA where available:
|
||||
|
// R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample
|
||||
|
// domain before the common normalization scale so all precisions use integer JPEG sample semantics.
|
||||
|
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); |
||||
|
Vector128<float> g = Vector128_.MultiplyAddEstimate( |
||||
|
cr, |
||||
|
Vector128.Create(-YCbCrScalar.GCrMult), |
||||
|
Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); |
||||
|
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); |
||||
|
|
||||
|
c0 = Vector128_.RoundToNearestInteger(r) * scale; |
||||
|
c1 = Vector128_.RoundToNearestInteger(g) * scale; |
||||
|
c2 = Vector128_.RoundToNearestInteger(b) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector256<float> c0, |
||||
|
ref Vector256<float> c1, |
||||
|
ref Vector256<float> c2, |
||||
|
Vector256<float> c3, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale) |
||||
|
{ |
||||
|
Vector256<float> y = c0; |
||||
|
Vector256<float> cb = c1 - halfValue; |
||||
|
Vector256<float> cr = c2 - halfValue; |
||||
|
|
||||
|
// These eight lanes have the same layout and BT.601 arithmetic as the Vector128 overload.
|
||||
|
// Keeping an explicit overload allows the JIT to emit native YMM operations without a width
|
||||
|
// switch or decomposing the vector into smaller values.
|
||||
|
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); |
||||
|
Vector256<float> g = Vector256_.MultiplyAddEstimate( |
||||
|
cr, |
||||
|
Vector256.Create(-YCbCrScalar.GCrMult), |
||||
|
Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); |
||||
|
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); |
||||
|
|
||||
|
c0 = Vector256_.RoundToNearestInteger(r) * scale; |
||||
|
c1 = Vector256_.RoundToNearestInteger(g) * scale; |
||||
|
c2 = Vector256_.RoundToNearestInteger(b) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb( |
||||
|
ref Vector512<float> c0, |
||||
|
ref Vector512<float> c1, |
||||
|
ref Vector512<float> c2, |
||||
|
Vector512<float> c3, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale) |
||||
|
{ |
||||
|
Vector512<float> y = c0; |
||||
|
Vector512<float> cb = c1 - halfValue; |
||||
|
Vector512<float> cr = c2 - halfValue; |
||||
|
|
||||
|
// Sixteen independent samples occupy the ZMM lanes. The explicit constants are broadcasts;
|
||||
|
// assembly inspection verifies the JIT hoists them from the loop and retains fused operations.
|
||||
|
// The formula and rounding order remain identical to the narrower overloads.
|
||||
|
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); |
||||
|
Vector512<float> g = Vector512_.MultiplyAddEstimate( |
||||
|
cr, |
||||
|
Vector512.Create(-YCbCrScalar.GCrMult), |
||||
|
Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); |
||||
|
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); |
||||
|
|
||||
|
c0 = Vector512_.RoundToNearestInteger(r) * scale; |
||||
|
c1 = Vector512_.RoundToNearestInteger(g) * scale; |
||||
|
c2 = Vector512_.RoundToNearestInteger(b) * scale; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
float r, |
||||
|
float g, |
||||
|
float b, |
||||
|
float maximumValue, |
||||
|
float halfValue, |
||||
|
float scale, |
||||
|
out float c0, |
||||
|
out float c1, |
||||
|
out float c2, |
||||
|
out float c3) |
||||
|
{ |
||||
|
// The RGB inputs are unnormalized 0..255 encoder lanes. The BT.601 luma weights form Y,
|
||||
|
// while the signed chroma projections are biased by halfValue into the JPEG sample domain.
|
||||
|
// YCbCr has no fourth component, so c3 is a compile-time-unused placeholder for the shared loop.
|
||||
|
c0 = (0.299F * r) + (0.587F * g) + (0.114F * b); |
||||
|
c1 = halfValue - (0.168736F * r) - (0.331264F * g) + (0.5F * b); |
||||
|
c2 = halfValue + (0.5F * r) - (0.418688F * g) - (0.081312F * b); |
||||
|
c3 = 0; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector128<float> r, |
||||
|
Vector128<float> g, |
||||
|
Vector128<float> b, |
||||
|
Vector128<float> maximumValue, |
||||
|
Vector128<float> halfValue, |
||||
|
Vector128<float> scale, |
||||
|
out Vector128<float> c0, |
||||
|
out Vector128<float> c1, |
||||
|
out Vector128<float> c2, |
||||
|
out Vector128<float> c3) |
||||
|
{ |
||||
|
// Each vector holds four consecutive values from one RGB plane. The nested multiply-add sequence
|
||||
|
// produces four Y lanes, four Cb lanes, and four Cr lanes without transposition. The association
|
||||
|
// exposes two FMA opportunities per output while preserving the scalar formula's term grouping.
|
||||
|
c0 = Vector128_.MultiplyAddEstimate( |
||||
|
Vector128.Create(0.299F), |
||||
|
r, |
||||
|
Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)); |
||||
|
c1 = halfValue + Vector128_.MultiplyAddEstimate( |
||||
|
Vector128.Create(-0.168736F), |
||||
|
r, |
||||
|
Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)); |
||||
|
c2 = halfValue + Vector128_.MultiplyAddEstimate( |
||||
|
Vector128.Create(0.5F), |
||||
|
r, |
||||
|
Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)); |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector256<float> r, |
||||
|
Vector256<float> g, |
||||
|
Vector256<float> b, |
||||
|
Vector256<float> maximumValue, |
||||
|
Vector256<float> halfValue, |
||||
|
Vector256<float> scale, |
||||
|
out Vector256<float> c0, |
||||
|
out Vector256<float> c1, |
||||
|
out Vector256<float> c2, |
||||
|
out Vector256<float> c3) |
||||
|
{ |
||||
|
// Eight planar RGB samples use the identical association as Vector128, allowing direct YMM FMA
|
||||
|
// generation while preserving the component-per-vector output layout.
|
||||
|
c0 = Vector256_.MultiplyAddEstimate( |
||||
|
Vector256.Create(0.299F), |
||||
|
r, |
||||
|
Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)); |
||||
|
c1 = halfValue + Vector256_.MultiplyAddEstimate( |
||||
|
Vector256.Create(-0.168736F), |
||||
|
r, |
||||
|
Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)); |
||||
|
c2 = halfValue + Vector256_.MultiplyAddEstimate( |
||||
|
Vector256.Create(0.5F), |
||||
|
r, |
||||
|
Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)); |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb( |
||||
|
Vector512<float> r, |
||||
|
Vector512<float> g, |
||||
|
Vector512<float> b, |
||||
|
Vector512<float> maximumValue, |
||||
|
Vector512<float> halfValue, |
||||
|
Vector512<float> scale, |
||||
|
out Vector512<float> c0, |
||||
|
out Vector512<float> c1, |
||||
|
out Vector512<float> c2, |
||||
|
out Vector512<float> c3) |
||||
|
{ |
||||
|
// Sixteen planar RGB samples use the same nested form. Constants are lane broadcasts and c3 is
|
||||
|
// deliberately zero because the shared traversal removes the unused fourth store for this operator.
|
||||
|
c0 = Vector512_.MultiplyAddEstimate( |
||||
|
Vector512.Create(0.299F), |
||||
|
r, |
||||
|
Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)); |
||||
|
c1 = halfValue + Vector512_.MultiplyAddEstimate( |
||||
|
Vector512.Create(-0.168736F), |
||||
|
r, |
||||
|
Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)); |
||||
|
c2 = halfValue + Vector512_.MultiplyAddEstimate( |
||||
|
Vector512.Create(0.5F), |
||||
|
r, |
||||
|
Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)); |
||||
|
c3 = default; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static void ConvertToRgbInPlaceWithIcc( |
||||
|
Configuration configuration, |
||||
|
IccProfile profile, |
||||
|
in ComponentValues values, |
||||
|
float maximumValue) |
||||
|
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
internal abstract partial class JpegColorConverterBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements inverted JPEG YccK conversion for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct YccKOperator : IJpegColorConverterOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static JpegColorSpace ColorSpace => JpegColorSpace.Ycck; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static int ComponentCount => 4; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref float c0, ref float c1, ref float c2, float c3, float maximumValue, float halfValue, float scale) |
||||
|
{ |
||||
|
float y = c0; |
||||
|
float cb = c1 - halfValue; |
||||
|
float cr = c2 - halfValue; |
||||
|
float scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// YccK first reconstructs inverted RGB in the integer sample domain. Rounding must occur before
|
||||
|
// subtracting from max and applying K because changing that order changes encoded JPEG semantics.
|
||||
|
c0 = (maximumValue - MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; |
||||
|
c1 = (maximumValue - MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; |
||||
|
c2 = (maximumValue - MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector128<float> c0, ref Vector128<float> c1, ref Vector128<float> c2, Vector128<float> c3, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale) |
||||
|
{ |
||||
|
Vector128<float> y = c0; |
||||
|
Vector128<float> cb = c1 - halfValue; |
||||
|
Vector128<float> cr = c2 - halfValue; |
||||
|
Vector128<float> scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// Four lanes reconstruct YCbCr concurrently; each rounded result is inverted and modulated by its K lane.
|
||||
|
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); |
||||
|
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); |
||||
|
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); |
||||
|
c0 = (maximumValue - Vector128_.RoundToNearestInteger(r)) * scaledK; |
||||
|
c1 = (maximumValue - Vector128_.RoundToNearestInteger(g)) * scaledK; |
||||
|
c2 = (maximumValue - Vector128_.RoundToNearestInteger(b)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector256<float> c0, ref Vector256<float> c1, ref Vector256<float> c2, Vector256<float> c3, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale) |
||||
|
{ |
||||
|
Vector256<float> y = c0; |
||||
|
Vector256<float> cb = c1 - halfValue; |
||||
|
Vector256<float> cr = c2 - halfValue; |
||||
|
Vector256<float> scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// Eight lanes retain planar alignment from Y/Cb/Cr/K through normalized RGB.
|
||||
|
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); |
||||
|
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); |
||||
|
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); |
||||
|
c0 = (maximumValue - Vector256_.RoundToNearestInteger(r)) * scaledK; |
||||
|
c1 = (maximumValue - Vector256_.RoundToNearestInteger(g)) * scaledK; |
||||
|
c2 = (maximumValue - Vector256_.RoundToNearestInteger(b)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertToRgb(ref Vector512<float> c0, ref Vector512<float> c1, ref Vector512<float> c2, Vector512<float> c3, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale) |
||||
|
{ |
||||
|
Vector512<float> y = c0; |
||||
|
Vector512<float> cb = c1 - halfValue; |
||||
|
Vector512<float> cr = c2 - halfValue; |
||||
|
Vector512<float> scaledK = c3 * scale * scale; |
||||
|
|
||||
|
// Sixteen lanes use the same matrix, rounding, inversion, and K modulation order as scalar code.
|
||||
|
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); |
||||
|
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); |
||||
|
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); |
||||
|
c0 = (maximumValue - Vector512_.RoundToNearestInteger(r)) * scaledK; |
||||
|
c1 = (maximumValue - Vector512_.RoundToNearestInteger(g)) * scaledK; |
||||
|
c2 = (maximumValue - Vector512_.RoundToNearestInteger(b)) * scaledK; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(float r, float g, float b, float maximumValue, float halfValue, float scale, out float c0, out float c1, out float c2, out float c3) |
||||
|
{ |
||||
|
// CMYK extraction supplies inverted chromatic samples and K. Reflecting the first three results
|
||||
|
// reconstructs the chromatic RGB that YCbCr encodes, while K passes through untouched.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out float c, out float m, out float y, out c3); |
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3) |
||||
|
{ |
||||
|
// Static constrained calls inline both stages, keeping four pixels in registers without materializing CMYK planes.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector128<float> c, out Vector128<float> m, out Vector128<float> y, out c3); |
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3) |
||||
|
{ |
||||
|
// Eight pixels flow through CMYK extraction and YCbCr projection entirely in YMM registers.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector256<float> c, out Vector256<float> m, out Vector256<float> y, out c3); |
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3) |
||||
|
{ |
||||
|
// Sixteen pixels flow through both mathematical stages in registers without materializing intermediate planes.
|
||||
|
CmykOperator.ConvertFromRgb(r, g, b, maximumValue, halfValue, scale, out Vector512<float> c, out Vector512<float> m, out Vector512<float> y, out c3); |
||||
|
YCbCrOperator.ConvertFromRgb(maximumValue - c, maximumValue - m, maximumValue - y, maximumValue, halfValue, scale, out c0, out c1, out c2, out _); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) |
||||
|
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,239 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using BenchmarkDotNet.Columns; |
||||
|
using BenchmarkDotNet.Configs; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares each shared operator converter with the Vector512 converter it replaces.
|
||||
|
/// </summary>
|
||||
|
[Config(typeof(Config.Standard))] |
||||
|
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
||||
|
[CategoriesColumn] |
||||
|
public class JpegColorConverterOperatorComparison |
||||
|
{ |
||||
|
private JpegColorConverterBase legacy; |
||||
|
private JpegColorConverterBase operatorConverter; |
||||
|
private float[] legacyC0; |
||||
|
private float[] legacyC1; |
||||
|
private float[] legacyC2; |
||||
|
private float[] legacyC3; |
||||
|
private float[] operatorC0; |
||||
|
private float[] operatorC1; |
||||
|
private float[] operatorC2; |
||||
|
private float[] operatorC3; |
||||
|
private float[] r; |
||||
|
private float[] g; |
||||
|
private float[] b; |
||||
|
private int componentCount; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the color model measured by the current benchmark case.
|
||||
|
/// </summary>
|
||||
|
[Params( |
||||
|
JpegColorModel.Grayscale, |
||||
|
JpegColorModel.Rgb, |
||||
|
JpegColorModel.Cmyk, |
||||
|
JpegColorModel.YCbCr, |
||||
|
JpegColorModel.YccK, |
||||
|
JpegColorModel.TiffCmyk, |
||||
|
JpegColorModel.TiffYccK)] |
||||
|
public JpegColorModel ColorModel { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the number of pixels converted by each invocation.
|
||||
|
/// </summary>
|
||||
|
[Params(128, 1024)] |
||||
|
public int Count { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates equivalent legacy and operator converters and their independent component buffers.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
(JpegColorConverterBase Legacy, JpegColorConverterBase Operator, int ComponentCount) converters = |
||||
|
this.ColorModel switch |
||||
|
{ |
||||
|
JpegColorModel.Grayscale => ( |
||||
|
new JpegColorConverterBase.GrayScaleVector512(8), |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.GrayScaleOperator>(8), |
||||
|
1), |
||||
|
JpegColorModel.Rgb => ( |
||||
|
new JpegColorConverterBase.RgbVector512(8), |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.RgbOperator>(8), |
||||
|
3), |
||||
|
JpegColorModel.Cmyk => ( |
||||
|
new JpegColorConverterBase.CmykVector512(8), |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.CmykOperator>(8), |
||||
|
4), |
||||
|
JpegColorModel.YCbCr => ( |
||||
|
new JpegColorConverterBase.YCbCrVector512(8), |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator>(8), |
||||
|
3), |
||||
|
JpegColorModel.YccK => ( |
||||
|
new JpegColorConverterBase.YccKVector512(8), |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YccKOperator>(8), |
||||
|
4), |
||||
|
JpegColorModel.TiffCmyk => ( |
||||
|
new JpegColorConverterBase.TiffCmykVector512(8), |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffCmykOperator>(8), |
||||
|
4), |
||||
|
JpegColorModel.TiffYccK => ( |
||||
|
new JpegColorConverterBase.TiffYccKVector512(8), |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffYccKOperator>(8), |
||||
|
4), |
||||
|
_ => throw new InvalidOperationException(), |
||||
|
}; |
||||
|
|
||||
|
(this.legacy, this.operatorConverter, this.componentCount) = converters; |
||||
|
|
||||
|
Random random = new(42); |
||||
|
this.legacyC0 = CreateRandomValues(this.Count, random); |
||||
|
this.legacyC1 = CreateRandomValues(this.Count, random); |
||||
|
this.legacyC2 = CreateRandomValues(this.Count, random); |
||||
|
this.legacyC3 = CreateRandomValues(this.Count, random); |
||||
|
this.operatorC0 = this.legacyC0.ToArray(); |
||||
|
this.operatorC1 = this.legacyC1.ToArray(); |
||||
|
this.operatorC2 = this.legacyC2.ToArray(); |
||||
|
this.operatorC3 = this.legacyC3.ToArray(); |
||||
|
this.r = CreateRandomValues(this.Count, random); |
||||
|
this.g = CreateRandomValues(this.Count, random); |
||||
|
this.b = CreateRandomValues(this.Count, random); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts JPEG components to RGB using the replaced Vector512 implementation.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public void LegacyToRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); |
||||
|
|
||||
|
this.legacy.ConvertToRgbInPlace(values); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts JPEG components to RGB using the shared operator traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public void OperatorToRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); |
||||
|
|
||||
|
this.operatorConverter.ConvertToRgbInPlace(values); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts RGB to JPEG components using the replaced Vector512 implementation.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public void LegacyFromRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); |
||||
|
|
||||
|
this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts RGB to JPEG components using the shared operator traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public void OperatorFromRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); |
||||
|
|
||||
|
this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a component view over the buffers owned by the legacy converter.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The component view for the configured color model.</returns>
|
||||
|
private JpegColorConverterBase.ComponentValues CreateLegacyValues() |
||||
|
=> new( |
||||
|
this.componentCount, |
||||
|
this.legacyC0, |
||||
|
this.componentCount > 1 ? this.legacyC1 : this.legacyC0, |
||||
|
this.componentCount > 2 ? this.legacyC2 : this.legacyC0, |
||||
|
this.componentCount > 3 ? this.legacyC3 : []); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a component view over the buffers owned by the operator converter.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The component view for the configured color model.</returns>
|
||||
|
private JpegColorConverterBase.ComponentValues CreateOperatorValues() |
||||
|
=> new( |
||||
|
this.componentCount, |
||||
|
this.operatorC0, |
||||
|
this.componentCount > 1 ? this.operatorC1 : this.operatorC0, |
||||
|
this.componentCount > 2 ? this.operatorC2 : this.operatorC0, |
||||
|
this.componentCount > 3 ? this.operatorC3 : []); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates deterministic sample-domain values for one component plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="length">The number of samples to create.</param>
|
||||
|
/// <param name="random">The deterministic random source shared by setup.</param>
|
||||
|
/// <returns>The populated component plane.</returns>
|
||||
|
private static float[] CreateRandomValues(int length, Random random) |
||||
|
{ |
||||
|
float[] values = new float[length]; |
||||
|
|
||||
|
for (int i = 0; i < values.Length; i++) |
||||
|
{ |
||||
|
values[i] = (float)random.NextDouble() * 255F; |
||||
|
} |
||||
|
|
||||
|
return values; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies the JPEG color model used by a benchmark case.
|
||||
|
/// </summary>
|
||||
|
public enum JpegColorModel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// One luminance component.
|
||||
|
/// </summary>
|
||||
|
Grayscale, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Three direct RGB components.
|
||||
|
/// </summary>
|
||||
|
Rgb, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Four inverted Adobe CMYK components.
|
||||
|
/// </summary>
|
||||
|
Cmyk, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Three JPEG YCbCr components.
|
||||
|
/// </summary>
|
||||
|
YCbCr, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Four inverted Adobe YCCK components.
|
||||
|
/// </summary>
|
||||
|
YccK, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Four non-inverted TIFF CMYK components.
|
||||
|
/// </summary>
|
||||
|
TiffCmyk, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Four non-inverted TIFF YCCK components.
|
||||
|
/// </summary>
|
||||
|
TiffYccK, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,325 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using BenchmarkDotNet.Columns; |
||||
|
using BenchmarkDotNet.Configs; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes every closed JPEG operator traversal beside the Vector512 implementation it replaces.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// A 63-pixel buffer leaves 256-bit, 128-bit, and scalar remainders after the 512-bit loop, making
|
||||
|
/// every operator overload visible in the generated traversal assembly on AVX-512 hardware.
|
||||
|
/// </remarks>
|
||||
|
[Config(typeof(Config.Analysis))] |
||||
|
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
||||
|
[CategoriesColumn] |
||||
|
public class JpegColorConverterTraversalAssembly |
||||
|
{ |
||||
|
private const int Count = 63; |
||||
|
|
||||
|
private readonly JpegColorConverterBase.GrayScaleVector512 grayscaleLegacy = new(8); |
||||
|
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.GrayScaleOperator> grayscaleOperator = new(8); |
||||
|
private readonly JpegColorConverterBase.RgbVector512 rgbLegacy = new(8); |
||||
|
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.RgbOperator> rgbOperator = new(8); |
||||
|
private readonly JpegColorConverterBase.CmykVector512 cmykLegacy = new(8); |
||||
|
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.CmykOperator> cmykOperator = new(8); |
||||
|
private readonly JpegColorConverterBase.YCbCrVector512 yCbCrLegacy = new(8); |
||||
|
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator> yCbCrOperator = new(8); |
||||
|
private readonly JpegColorConverterBase.YccKVector512 yccKLegacy = new(8); |
||||
|
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YccKOperator> yccKOperator = new(8); |
||||
|
private readonly JpegColorConverterBase.TiffCmykVector512 tiffCmykLegacy = new(8); |
||||
|
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffCmykOperator> tiffCmykOperator = new(8); |
||||
|
private readonly JpegColorConverterBase.TiffYccKVector512 tiffYccKLegacy = new(8); |
||||
|
private readonly JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.TiffYccKOperator> tiffYccKOperator = new(8); |
||||
|
|
||||
|
private readonly float[] legacyC0 = new float[Count]; |
||||
|
private readonly float[] legacyC1 = new float[Count]; |
||||
|
private readonly float[] legacyC2 = new float[Count]; |
||||
|
private readonly float[] legacyC3 = new float[Count]; |
||||
|
private readonly float[] operatorC0 = new float[Count]; |
||||
|
private readonly float[] operatorC1 = new float[Count]; |
||||
|
private readonly float[] operatorC2 = new float[Count]; |
||||
|
private readonly float[] operatorC3 = new float[Count]; |
||||
|
private readonly float[] r = new float[Count]; |
||||
|
private readonly float[] g = new float[Count]; |
||||
|
private readonly float[] b = new float[Count]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Populates the component and RGB planes with deterministic sample-domain values.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
Random random = new(42); |
||||
|
|
||||
|
for (int i = 0; i < Count; i++) |
||||
|
{ |
||||
|
// Independent non-constant lanes prevent the JIT from folding arithmetic or mask decisions.
|
||||
|
this.legacyC0[i] = this.operatorC0[i] = (float)random.NextDouble() * 255F; |
||||
|
this.legacyC1[i] = this.operatorC1[i] = (float)random.NextDouble() * 255F; |
||||
|
this.legacyC2[i] = this.operatorC2[i] = (float)random.NextDouble() * 255F; |
||||
|
this.legacyC3[i] = this.operatorC3[i] = (float)random.NextDouble() * 255F; |
||||
|
this.r[i] = (float)random.NextDouble() * 255F; |
||||
|
this.g[i] = (float)random.NextDouble() * 255F; |
||||
|
this.b[i] = (float)random.NextDouble() * 255F; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced grayscale component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Grayscale.ToRgb")] |
||||
|
public void GrayscaleLegacyToRgb() |
||||
|
=> this.grayscaleLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(1)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared grayscale component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Grayscale.ToRgb")] |
||||
|
public void GrayscaleOperatorToRgb() |
||||
|
=> this.grayscaleOperator.ConvertToRgbInPlace(this.CreateOperatorValues(1)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced grayscale RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Grayscale.FromRgb")] |
||||
|
public void GrayscaleLegacyFromRgb() |
||||
|
=> this.grayscaleLegacy.ConvertFromRgb(this.CreateLegacyValues(1), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared grayscale RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Grayscale.FromRgb")] |
||||
|
public void GrayscaleOperatorFromRgb() |
||||
|
=> this.grayscaleOperator.ConvertFromRgb(this.CreateOperatorValues(1), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced RGB component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Rgb.ToRgb")] |
||||
|
public void RgbLegacyToRgb() |
||||
|
=> this.rgbLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared RGB component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Rgb.ToRgb")] |
||||
|
public void RgbOperatorToRgb() |
||||
|
=> this.rgbOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced RGB RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Rgb.FromRgb")] |
||||
|
public void RgbLegacyFromRgb() |
||||
|
=> this.rgbLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared RGB RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Rgb.FromRgb")] |
||||
|
public void RgbOperatorFromRgb() |
||||
|
=> this.rgbOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced CMYK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Cmyk.ToRgb")] |
||||
|
public void CmykLegacyToRgb() |
||||
|
=> this.cmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared CMYK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Cmyk.ToRgb")] |
||||
|
public void CmykOperatorToRgb() |
||||
|
=> this.cmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced CMYK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("Cmyk.FromRgb")] |
||||
|
public void CmykLegacyFromRgb() |
||||
|
=> this.cmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared CMYK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("Cmyk.FromRgb")] |
||||
|
public void CmykOperatorFromRgb() |
||||
|
=> this.cmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced YCbCr component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("YCbCr.ToRgb")] |
||||
|
public void YCbCrLegacyToRgb() |
||||
|
=> this.yCbCrLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared YCbCr component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("YCbCr.ToRgb")] |
||||
|
public void YCbCrOperatorToRgb() |
||||
|
=> this.yCbCrOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced YCbCr RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("YCbCr.FromRgb")] |
||||
|
public void YCbCrLegacyFromRgb() |
||||
|
=> this.yCbCrLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared YCbCr RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("YCbCr.FromRgb")] |
||||
|
public void YCbCrOperatorFromRgb() |
||||
|
=> this.yCbCrOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced YCCK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("YccK.ToRgb")] |
||||
|
public void YccKLegacyToRgb() |
||||
|
=> this.yccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared YCCK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("YccK.ToRgb")] |
||||
|
public void YccKOperatorToRgb() |
||||
|
=> this.yccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced YCCK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("YccK.FromRgb")] |
||||
|
public void YccKLegacyFromRgb() |
||||
|
=> this.yccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared YCCK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("YccK.FromRgb")] |
||||
|
public void YccKOperatorFromRgb() |
||||
|
=> this.yccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced TIFF CMYK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("TiffCmyk.ToRgb")] |
||||
|
public void TiffCmykLegacyToRgb() |
||||
|
=> this.tiffCmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared TIFF CMYK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("TiffCmyk.ToRgb")] |
||||
|
public void TiffCmykOperatorToRgb() |
||||
|
=> this.tiffCmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced TIFF CMYK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("TiffCmyk.FromRgb")] |
||||
|
public void TiffCmykLegacyFromRgb() |
||||
|
=> this.tiffCmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared TIFF CMYK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("TiffCmyk.FromRgb")] |
||||
|
public void TiffCmykOperatorFromRgb() |
||||
|
=> this.tiffCmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced TIFF YCCK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("TiffYccK.ToRgb")] |
||||
|
public void TiffYccKLegacyToRgb() |
||||
|
=> this.tiffYccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared TIFF YCCK component-to-RGB traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("TiffYccK.ToRgb")] |
||||
|
public void TiffYccKOperatorToRgb() |
||||
|
=> this.tiffYccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the replaced TIFF YCCK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("TiffYccK.FromRgb")] |
||||
|
public void TiffYccKLegacyFromRgb() |
||||
|
=> this.tiffYccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Runs the shared TIFF YCCK RGB-to-component traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("TiffYccK.FromRgb")] |
||||
|
public void TiffYccKOperatorFromRgb() |
||||
|
=> this.tiffYccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a correctly aliased component view over the legacy planes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="componentCount">The number of component planes owned by the color model.</param>
|
||||
|
/// <returns>The legacy component view.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private JpegColorConverterBase.ComponentValues CreateLegacyValues(int componentCount) |
||||
|
=> new( |
||||
|
componentCount, |
||||
|
this.legacyC0, |
||||
|
componentCount > 1 ? this.legacyC1 : this.legacyC0, |
||||
|
componentCount > 2 ? this.legacyC2 : this.legacyC0, |
||||
|
componentCount > 3 ? this.legacyC3 : []); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a correctly aliased component view over the operator planes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="componentCount">The number of component planes owned by the color model.</param>
|
||||
|
/// <returns>The operator component view.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private JpegColorConverterBase.ComponentValues CreateOperatorValues(int componentCount) |
||||
|
=> new( |
||||
|
componentCount, |
||||
|
this.operatorC0, |
||||
|
componentCount > 1 ? this.operatorC1 : this.operatorC0, |
||||
|
componentCount > 2 ? this.operatorC2 : this.operatorC0, |
||||
|
componentCount > 3 ? this.operatorC3 : []); |
||||
|
} |
||||
@ -0,0 +1,244 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.Intrinsics; |
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using BenchmarkDotNet.Columns; |
||||
|
using BenchmarkDotNet.Configs; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes every YCbCr operator overload directly to the disassembly diagnoser.
|
||||
|
/// </summary>
|
||||
|
[Config(typeof(Config.Analysis))] |
||||
|
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
||||
|
[CategoriesColumn] |
||||
|
public class YCbCrOperatorAssembly |
||||
|
{ |
||||
|
private const float MaximumValue = 255F; |
||||
|
private const float HalfValue = 128F; |
||||
|
private const float Scale = 1F / MaximumValue; |
||||
|
|
||||
|
private float scalarC0 = 64F; |
||||
|
private float scalarC1 = 96F; |
||||
|
private float scalarC2 = 160F; |
||||
|
|
||||
|
private readonly Vector128<float> vector128C0 = Vector128.Create(64F); |
||||
|
private readonly Vector128<float> vector128C1 = Vector128.Create(96F); |
||||
|
private readonly Vector128<float> vector128C2 = Vector128.Create(160F); |
||||
|
private readonly Vector128<float> vector128Maximum = Vector128.Create(MaximumValue); |
||||
|
private readonly Vector128<float> vector128Half = Vector128.Create(HalfValue); |
||||
|
private readonly Vector128<float> vector128Scale = Vector128.Create(Scale); |
||||
|
|
||||
|
private readonly Vector256<float> vector256C0 = Vector256.Create(64F); |
||||
|
private readonly Vector256<float> vector256C1 = Vector256.Create(96F); |
||||
|
private readonly Vector256<float> vector256C2 = Vector256.Create(160F); |
||||
|
private readonly Vector256<float> vector256Maximum = Vector256.Create(MaximumValue); |
||||
|
private readonly Vector256<float> vector256Half = Vector256.Create(HalfValue); |
||||
|
private readonly Vector256<float> vector256Scale = Vector256.Create(Scale); |
||||
|
|
||||
|
private readonly Vector512<float> vector512C0 = Vector512.Create(64F); |
||||
|
private readonly Vector512<float> vector512C1 = Vector512.Create(96F); |
||||
|
private readonly Vector512<float> vector512C2 = Vector512.Create(160F); |
||||
|
private readonly Vector512<float> vector512Maximum = Vector512.Create(MaximumValue); |
||||
|
private readonly Vector512<float> vector512Half = Vector512.Create(HalfValue); |
||||
|
private readonly Vector512<float> vector512Scale = Vector512.Create(Scale); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the scalar JPEG-to-RGB operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all three converted channels.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public float ToRgbScalar() |
||||
|
{ |
||||
|
float c0 = this.scalarC0; |
||||
|
float c1 = this.scalarC1; |
||||
|
float c2 = this.scalarC2; |
||||
|
|
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
||||
|
ref c0, |
||||
|
ref c1, |
||||
|
ref c2, |
||||
|
0, |
||||
|
MaximumValue, |
||||
|
HalfValue, |
||||
|
Scale); |
||||
|
|
||||
|
// Returning the channel sum keeps every output live in the generated assembly.
|
||||
|
return c0 + c1 + c2; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the Vector128 JPEG-to-RGB operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all three converted channel vectors.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public Vector128<float> ToRgbVector128() |
||||
|
{ |
||||
|
Vector128<float> c0 = this.vector128C0; |
||||
|
Vector128<float> c1 = this.vector128C1; |
||||
|
Vector128<float> c2 = this.vector128C2; |
||||
|
|
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
||||
|
ref c0, |
||||
|
ref c1, |
||||
|
ref c2, |
||||
|
default, |
||||
|
this.vector128Maximum, |
||||
|
this.vector128Half, |
||||
|
this.vector128Scale); |
||||
|
|
||||
|
// The vector sum makes all RGB results observable without adding stores to the measured body.
|
||||
|
return c0 + c1 + c2; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the Vector256 JPEG-to-RGB operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all three converted channel vectors.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public Vector256<float> ToRgbVector256() |
||||
|
{ |
||||
|
Vector256<float> c0 = this.vector256C0; |
||||
|
Vector256<float> c1 = this.vector256C1; |
||||
|
Vector256<float> c2 = this.vector256C2; |
||||
|
|
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
||||
|
ref c0, |
||||
|
ref c1, |
||||
|
ref c2, |
||||
|
default, |
||||
|
this.vector256Maximum, |
||||
|
this.vector256Half, |
||||
|
this.vector256Scale); |
||||
|
|
||||
|
// The vector sum makes all RGB results observable without adding stores to the measured body.
|
||||
|
return c0 + c1 + c2; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the Vector512 JPEG-to-RGB operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all three converted channel vectors.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public Vector512<float> ToRgbVector512() |
||||
|
{ |
||||
|
Vector512<float> c0 = this.vector512C0; |
||||
|
Vector512<float> c1 = this.vector512C1; |
||||
|
Vector512<float> c2 = this.vector512C2; |
||||
|
|
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
||||
|
ref c0, |
||||
|
ref c1, |
||||
|
ref c2, |
||||
|
default, |
||||
|
this.vector512Maximum, |
||||
|
this.vector512Half, |
||||
|
this.vector512Scale); |
||||
|
|
||||
|
// The vector sum makes all RGB results observable without adding stores to the measured body.
|
||||
|
return c0 + c1 + c2; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the scalar RGB-to-JPEG operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all converted components.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public float FromRgbScalar() |
||||
|
{ |
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
||||
|
this.scalarC0, |
||||
|
this.scalarC1, |
||||
|
this.scalarC2, |
||||
|
MaximumValue, |
||||
|
HalfValue, |
||||
|
Scale, |
||||
|
out float c0, |
||||
|
out float c1, |
||||
|
out float c2, |
||||
|
out float c3); |
||||
|
|
||||
|
// c3 is deliberately included so a future four-component implementation remains observable.
|
||||
|
return c0 + c1 + c2 + c3; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the Vector128 RGB-to-JPEG operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all converted component vectors.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public Vector128<float> FromRgbVector128() |
||||
|
{ |
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
||||
|
this.vector128C0, |
||||
|
this.vector128C1, |
||||
|
this.vector128C2, |
||||
|
this.vector128Maximum, |
||||
|
this.vector128Half, |
||||
|
this.vector128Scale, |
||||
|
out Vector128<float> c0, |
||||
|
out Vector128<float> c1, |
||||
|
out Vector128<float> c2, |
||||
|
out Vector128<float> c3); |
||||
|
|
||||
|
// Include all planar results in the returned vector so the JIT retains every calculation.
|
||||
|
return c0 + c1 + c2 + c3; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the Vector256 RGB-to-JPEG operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all converted component vectors.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public Vector256<float> FromRgbVector256() |
||||
|
{ |
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
||||
|
this.vector256C0, |
||||
|
this.vector256C1, |
||||
|
this.vector256C2, |
||||
|
this.vector256Maximum, |
||||
|
this.vector256Half, |
||||
|
this.vector256Scale, |
||||
|
out Vector256<float> c0, |
||||
|
out Vector256<float> c1, |
||||
|
out Vector256<float> c2, |
||||
|
out Vector256<float> c3); |
||||
|
|
||||
|
// Include all planar results in the returned vector so the JIT retains every calculation.
|
||||
|
return c0 + c1 + c2 + c3; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the Vector512 RGB-to-JPEG operator.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A checksum containing all converted component vectors.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public Vector512<float> FromRgbVector512() |
||||
|
{ |
||||
|
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
||||
|
this.vector512C0, |
||||
|
this.vector512C1, |
||||
|
this.vector512C2, |
||||
|
this.vector512Maximum, |
||||
|
this.vector512Half, |
||||
|
this.vector512Scale, |
||||
|
out Vector512<float> c0, |
||||
|
out Vector512<float> c1, |
||||
|
out Vector512<float> c2, |
||||
|
out Vector512<float> c3); |
||||
|
|
||||
|
// Include all planar results in the returned vector so the JIT retains every calculation.
|
||||
|
return c0 + c1 + c2 + c3; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,127 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using BenchmarkDotNet.Columns; |
||||
|
using BenchmarkDotNet.Configs; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares the shared YCbCr operator traversal with the Vector512 implementation it replaces.
|
||||
|
/// </summary>
|
||||
|
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
||||
|
[CategoriesColumn] |
||||
|
public class YCbCrOperatorComparison |
||||
|
{ |
||||
|
private JpegColorConverterBase.YCbCrVector512 legacy; |
||||
|
private JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator> operatorConverter; |
||||
|
private float[] legacyC0; |
||||
|
private float[] legacyC1; |
||||
|
private float[] legacyC2; |
||||
|
private float[] operatorC0; |
||||
|
private float[] operatorC1; |
||||
|
private float[] operatorC2; |
||||
|
private float[] r; |
||||
|
private float[] g; |
||||
|
private float[] b; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the number of pixels converted by each invocation.
|
||||
|
/// </summary>
|
||||
|
[Params(8, 128, 1024)] |
||||
|
public int Count { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates equivalent converter inputs in independent component buffers.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
this.legacy = new JpegColorConverterBase.YCbCrVector512(8); |
||||
|
this.operatorConverter = |
||||
|
new JpegColorConverterBase.JpegColorConverter<JpegColorConverterBase.YCbCrOperator>(8); |
||||
|
|
||||
|
Random random = new(42); |
||||
|
this.legacyC0 = CreateRandomValues(this.Count, random); |
||||
|
this.legacyC1 = CreateRandomValues(this.Count, random); |
||||
|
this.legacyC2 = CreateRandomValues(this.Count, random); |
||||
|
this.operatorC0 = this.legacyC0.ToArray(); |
||||
|
this.operatorC1 = this.legacyC1.ToArray(); |
||||
|
this.operatorC2 = this.legacyC2.ToArray(); |
||||
|
this.r = CreateRandomValues(this.Count, random); |
||||
|
this.g = CreateRandomValues(this.Count, random); |
||||
|
this.b = CreateRandomValues(this.Count, random); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts YCbCr components to RGB using the Vector512 implementation.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public void LegacyToRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = |
||||
|
new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); |
||||
|
|
||||
|
this.legacy.ConvertToRgbInPlace(values); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts YCbCr components to RGB using the shared operator traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("ToRgb")] |
||||
|
public void OperatorToRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = |
||||
|
new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); |
||||
|
|
||||
|
this.operatorConverter.ConvertToRgbInPlace(values); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts RGB to YCbCr components using the Vector512 implementation.
|
||||
|
/// </summary>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public void LegacyFromRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = |
||||
|
new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); |
||||
|
|
||||
|
this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts RGB to YCbCr components using the shared operator traversal.
|
||||
|
/// </summary>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("FromRgb")] |
||||
|
public void OperatorFromRgb() |
||||
|
{ |
||||
|
JpegColorConverterBase.ComponentValues values = |
||||
|
new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); |
||||
|
|
||||
|
this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates deterministic sample-domain values for one component plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="length">The number of samples to create.</param>
|
||||
|
/// <param name="random">The deterministic random source shared by setup.</param>
|
||||
|
/// <returns>The populated component plane.</returns>
|
||||
|
private static float[] CreateRandomValues(int length, Random random) |
||||
|
{ |
||||
|
float[] values = new float[length]; |
||||
|
|
||||
|
for (int i = 0; i < values.Length; i++) |
||||
|
{ |
||||
|
values[i] = (float)random.NextDouble() * 255F; |
||||
|
} |
||||
|
|
||||
|
return values; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue