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