mirror of https://github.com/SixLabors/ImageSharp
53 changed files with 993 additions and 100 deletions
@ -0,0 +1,256 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Buffers; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.ColorProfiles.Icc; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.ColorProfiles; |
||||
|
|
||||
|
internal static class ColorProfileConverterExtensionsIcc |
||||
|
{ |
||||
|
internal static TTo ConvertUsingIccProfile<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
||||
|
where TFrom : struct, IColorProfile<TFrom> |
||||
|
where TTo : struct, IColorProfile<TTo> |
||||
|
{ |
||||
|
// TODO: Validation of ICC Profiles against color profile. Is this possible?
|
||||
|
if (converter.Options.SourceIccProfile is null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Source ICC profile is missing."); |
||||
|
} |
||||
|
|
||||
|
if (converter.Options.TargetIccProfile is null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Target ICC profile is missing."); |
||||
|
} |
||||
|
|
||||
|
ColorProfileConverter pcsConverter = new(new ColorConversionOptions() |
||||
|
{ |
||||
|
MemoryAllocator = converter.Options.MemoryAllocator, |
||||
|
|
||||
|
// TODO: Double check this but I think these are normalized values.
|
||||
|
SourceWhitePoint = CieXyz.FromScaledVector4(new(converter.Options.SourceIccProfile.Header.PcsIlluminant, 1F)), |
||||
|
TargetWhitePoint = CieXyz.FromScaledVector4(new(converter.Options.TargetIccProfile.Header.PcsIlluminant, 1F)), |
||||
|
}); |
||||
|
|
||||
|
IccDataToPcsConverter sourceConverter = new(converter.Options.SourceIccProfile); |
||||
|
IccPcsToDataConverter targetConverter = new(converter.Options.TargetIccProfile); |
||||
|
IccColorSpaceType sourcePcsType = converter.Options.SourceIccProfile.Header.ProfileConnectionSpace; |
||||
|
IccColorSpaceType targetPcsType = converter.Options.TargetIccProfile.Header.ProfileConnectionSpace; |
||||
|
IccVersion sourceVersion = converter.Options.SourceIccProfile.Header.Version; |
||||
|
IccVersion targetVersion = converter.Options.TargetIccProfile.Header.Version; |
||||
|
|
||||
|
Vector4 pcs = sourceConverter.Calculate(source.ToScaledVector4()); |
||||
|
|
||||
|
// Profile connecting spaces can only be Lab, XYZ.
|
||||
|
if (sourcePcsType is IccColorSpaceType.CieLab && targetPcsType is IccColorSpaceType.CieXyz) |
||||
|
{ |
||||
|
// Convert from Lab to XYZ.
|
||||
|
CieLab lab = CieLab.FromScaledVector4(pcs); |
||||
|
CieXyz xyz = pcsConverter.Convert<CieLab, CieXyz>(in lab); |
||||
|
pcs = xyz.ToScaledVector4(); |
||||
|
} |
||||
|
else if (sourcePcsType is IccColorSpaceType.CieXyz && targetPcsType is IccColorSpaceType.CieLab) |
||||
|
{ |
||||
|
// Convert from XYZ to Lab.
|
||||
|
CieXyz xyz = CieXyz.FromScaledVector4(pcs); |
||||
|
CieLab lab = pcsConverter.Convert<CieXyz, CieLab>(in xyz); |
||||
|
pcs = lab.ToScaledVector4(); |
||||
|
} |
||||
|
else if (sourcePcsType is IccColorSpaceType.CieXyz && targetPcsType is IccColorSpaceType.CieXyz) |
||||
|
{ |
||||
|
// Convert from XYZ to XYZ.
|
||||
|
CieXyz xyz = CieXyz.FromScaledVector4(pcs); |
||||
|
CieXyz targetXyz = pcsConverter.Convert<CieXyz, CieXyz>(in xyz); |
||||
|
pcs = targetXyz.ToScaledVector4(); |
||||
|
} |
||||
|
else if (sourcePcsType is IccColorSpaceType.CieLab && targetPcsType is IccColorSpaceType.CieLab) |
||||
|
{ |
||||
|
// Convert from Lab to Lab.
|
||||
|
if (sourceVersion.Major == 4 && targetVersion.Major == 2) |
||||
|
{ |
||||
|
// Convert from Lab v4 to Lab v2.
|
||||
|
pcs = LabToLabV2(pcs); |
||||
|
} |
||||
|
else if (sourceVersion.Major == 2 && targetVersion.Major == 4) |
||||
|
{ |
||||
|
// Convert from Lab v2 to Lab v4.
|
||||
|
pcs = LabV2ToLab(pcs); |
||||
|
} |
||||
|
|
||||
|
CieLab lab = CieLab.FromScaledVector4(pcs); |
||||
|
CieLab targetLab = pcsConverter.Convert<CieLab, CieLab>(in lab); |
||||
|
pcs = targetLab.ToScaledVector4(); |
||||
|
} |
||||
|
|
||||
|
// Convert to the target space.
|
||||
|
return TTo.FromScaledVector4(targetConverter.Calculate(pcs)); |
||||
|
} |
||||
|
|
||||
|
internal static void ConvertUsingIccProfile<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
||||
|
where TFrom : struct, IColorProfile<TFrom> |
||||
|
where TTo : struct, IColorProfile<TTo> |
||||
|
{ |
||||
|
// TODO: Validation of ICC Profiles against color profile. Is this possible?
|
||||
|
if (converter.Options.SourceIccProfile is null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Source ICC profile is missing."); |
||||
|
} |
||||
|
|
||||
|
if (converter.Options.TargetIccProfile is null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Target ICC profile is missing."); |
||||
|
} |
||||
|
|
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(destination)); |
||||
|
|
||||
|
ColorProfileConverter pcsConverter = new(new ColorConversionOptions() |
||||
|
{ |
||||
|
MemoryAllocator = converter.Options.MemoryAllocator, |
||||
|
|
||||
|
// TODO: Double check this but I think these are normalized values.
|
||||
|
SourceWhitePoint = CieXyz.FromScaledVector4(new(converter.Options.SourceIccProfile.Header.PcsIlluminant, 1F)), |
||||
|
TargetWhitePoint = CieXyz.FromScaledVector4(new(converter.Options.TargetIccProfile.Header.PcsIlluminant, 1F)), |
||||
|
}); |
||||
|
|
||||
|
IccDataToPcsConverter sourceConverter = new(converter.Options.SourceIccProfile); |
||||
|
IccPcsToDataConverter targetConverter = new(converter.Options.TargetIccProfile); |
||||
|
IccColorSpaceType sourcePcsType = converter.Options.SourceIccProfile.Header.ProfileConnectionSpace; |
||||
|
IccColorSpaceType targetPcsType = converter.Options.TargetIccProfile.Header.ProfileConnectionSpace; |
||||
|
IccVersion sourceVersion = converter.Options.SourceIccProfile.Header.Version; |
||||
|
IccVersion targetVersion = converter.Options.TargetIccProfile.Header.Version; |
||||
|
|
||||
|
using IMemoryOwner<Vector4> pcsBuffer = converter.Options.MemoryAllocator.Allocate<Vector4>(source.Length); |
||||
|
Span<Vector4> pcsNormalized = pcsBuffer.GetSpan(); |
||||
|
|
||||
|
// First normalize the values.
|
||||
|
TFrom.ToScaledVector4(source, pcsNormalized); |
||||
|
|
||||
|
// Now convert to the PCS space.
|
||||
|
sourceConverter.Calculate(pcsNormalized, pcsNormalized); |
||||
|
|
||||
|
// Profile connecting spaces can only be Lab, XYZ.
|
||||
|
if (sourcePcsType is IccColorSpaceType.CieLab && targetPcsType is IccColorSpaceType.CieXyz) |
||||
|
{ |
||||
|
// Convert from Lab to XYZ.
|
||||
|
using IMemoryOwner<CieLab> pcsFromBuffer = converter.Options.MemoryAllocator.Allocate<CieLab>(source.Length); |
||||
|
Span<CieLab> pcsFrom = pcsFromBuffer.GetSpan(); |
||||
|
CieLab.FromScaledVector4(pcsNormalized, pcsFrom); |
||||
|
|
||||
|
using IMemoryOwner<CieXyz> pcsToBuffer = converter.Options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
||||
|
Span<CieXyz> pcsTo = pcsToBuffer.GetSpan(); |
||||
|
pcsConverter.Convert<CieLab, CieXyz>(pcsFrom, pcsTo); |
||||
|
|
||||
|
// Convert to the target normalized PCS space.
|
||||
|
CieXyz.ToScaledVector4(pcsTo, pcsNormalized); |
||||
|
} |
||||
|
else if (sourcePcsType is IccColorSpaceType.CieXyz && targetPcsType is IccColorSpaceType.CieLab) |
||||
|
{ |
||||
|
// Convert from XYZ to Lab.
|
||||
|
using IMemoryOwner<CieXyz> pcsFromBuffer = converter.Options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
||||
|
Span<CieXyz> pcsFrom = pcsFromBuffer.GetSpan(); |
||||
|
CieXyz.FromScaledVector4(pcsNormalized, pcsFrom); |
||||
|
|
||||
|
using IMemoryOwner<CieLab> pcsToBuffer = converter.Options.MemoryAllocator.Allocate<CieLab>(source.Length); |
||||
|
Span<CieLab> pcsTo = pcsToBuffer.GetSpan(); |
||||
|
pcsConverter.Convert<CieXyz, CieLab>(pcsFrom, pcsTo); |
||||
|
|
||||
|
// Convert to the target normalized PCS space.
|
||||
|
CieLab.ToScaledVector4(pcsTo, pcsNormalized); |
||||
|
} |
||||
|
else if (sourcePcsType is IccColorSpaceType.CieXyz && targetPcsType is IccColorSpaceType.CieXyz) |
||||
|
{ |
||||
|
// Convert from XYZ to XYZ.
|
||||
|
using IMemoryOwner<CieXyz> pcsFromToBuffer = converter.Options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
||||
|
Span<CieXyz> pcsFromTo = pcsFromToBuffer.GetSpan(); |
||||
|
CieXyz.FromScaledVector4(pcsNormalized, pcsFromTo); |
||||
|
|
||||
|
pcsConverter.Convert<CieXyz, CieXyz>(pcsFromTo, pcsFromTo); |
||||
|
|
||||
|
// Convert to the target normalized PCS space.
|
||||
|
CieXyz.ToScaledVector4(pcsFromTo, pcsNormalized); |
||||
|
} |
||||
|
else if (sourcePcsType is IccColorSpaceType.CieLab && targetPcsType is IccColorSpaceType.CieLab) |
||||
|
{ |
||||
|
// Convert from Lab to Lab.
|
||||
|
if (sourceVersion.Major == 4 && targetVersion.Major == 2) |
||||
|
{ |
||||
|
// Convert from Lab v4 to Lab v2.
|
||||
|
LabToLabV2(pcsNormalized, pcsNormalized); |
||||
|
} |
||||
|
else if (sourceVersion.Major == 2 && targetVersion.Major == 4) |
||||
|
{ |
||||
|
// Convert from Lab v2 to Lab v4.
|
||||
|
LabV2ToLab(pcsNormalized, pcsNormalized); |
||||
|
} |
||||
|
|
||||
|
using IMemoryOwner<CieLab> pcsFromToBuffer = converter.Options.MemoryAllocator.Allocate<CieLab>(source.Length); |
||||
|
Span<CieLab> pcsFromTo = pcsFromToBuffer.GetSpan(); |
||||
|
CieLab.FromScaledVector4(pcsNormalized, pcsFromTo); |
||||
|
|
||||
|
pcsConverter.Convert<CieLab, CieLab>(pcsFromTo, pcsFromTo); |
||||
|
|
||||
|
// Convert to the target normalized PCS space.
|
||||
|
CieLab.ToScaledVector4(pcsFromTo, pcsNormalized); |
||||
|
} |
||||
|
|
||||
|
// Convert to the target space.
|
||||
|
targetConverter.Calculate(pcsNormalized, pcsNormalized); |
||||
|
TTo.FromScaledVector4(pcsNormalized, destination); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector4 LabToLabV2(Vector4 input) |
||||
|
=> input * 65280F / 65535F; |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector4 LabV2ToLab(Vector4 input) |
||||
|
=> input * 65535F / 65280F; |
||||
|
|
||||
|
private static void LabToLabV2(Span<Vector4> source, Span<Vector4> destination) |
||||
|
=> LabToLab(source, destination, 65280F / 65535F); |
||||
|
|
||||
|
private static void LabV2ToLab(Span<Vector4> source, Span<Vector4> destination) |
||||
|
=> LabToLab(source, destination, 65535F / 65280F); |
||||
|
|
||||
|
private static void LabToLab(Span<Vector4> source, Span<Vector4> destination, [ConstantExpected] float scale) |
||||
|
{ |
||||
|
if (Vector.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector<float> vScale = new(scale); |
||||
|
int i = 0; |
||||
|
|
||||
|
// SIMD loop
|
||||
|
int simdBatchSize = Vector<float>.Count / 4; // Number of Vector4 elements per SIMD batch
|
||||
|
for (; i <= source.Length - simdBatchSize; i += simdBatchSize) |
||||
|
{ |
||||
|
// Load the vector from source span
|
||||
|
Vector<float> v = Unsafe.ReadUnaligned<Vector<float>>(ref Unsafe.As<Vector4, byte>(ref source[i])); |
||||
|
|
||||
|
// Scale the vector
|
||||
|
v *= vScale; |
||||
|
|
||||
|
// Write the scaled vector to the destination span
|
||||
|
Unsafe.WriteUnaligned(ref Unsafe.As<Vector4, byte>(ref destination[i]), v); |
||||
|
} |
||||
|
|
||||
|
// Scalar fallback for remaining elements
|
||||
|
for (; i < source.Length; i++) |
||||
|
{ |
||||
|
destination[i] = source[i] * scale; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Scalar fallback if SIMD is not supported
|
||||
|
for (int i = 0; i < source.Length; i++) |
||||
|
{ |
||||
|
destination[i] = source[i] * scale; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue