mirror of https://github.com/SixLabors/ImageSharp
6 changed files with 3 additions and 244 deletions
@ -1,144 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using System.Numerics; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Allows the conversion between ICC profiles.
|
|
||||
/// </summary>
|
|
||||
internal static class IccProfileConverter |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Performs a conversion of the image pixels based on the input and output ICC profiles.
|
|
||||
/// </summary>
|
|
||||
/// <param name="image">The image to convert.</param>
|
|
||||
/// <param name="inputIccProfile">The input ICC profile.</param>
|
|
||||
/// <param name="outputIccProfile">The output ICC profile. </param>
|
|
||||
public static void Convert(Image image, IccProfile inputIccProfile, IccProfile outputIccProfile) |
|
||||
=> image.AcceptVisitor(new IccProfileConverterVisitor(inputIccProfile, outputIccProfile)); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Performs a conversion of the image pixels based on the input and output ICC profiles.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of pixel.</typeparam>
|
|
||||
/// <param name="image">The image to convert.</param>
|
|
||||
/// <param name="inputIccProfile">The input ICC profile.</param>
|
|
||||
/// <param name="outputIccProfile">The output ICC profile. </param>
|
|
||||
public static void Convert<TPixel>(Image<TPixel> image, IccProfile inputIccProfile, IccProfile outputIccProfile) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
IccDataToPcsConverter converterDataToPcs = new(inputIccProfile); |
|
||||
IccPcsToDataConverter converterPcsToData = new(outputIccProfile); |
|
||||
Configuration configuration = image.Configuration; |
|
||||
|
|
||||
image.ProcessPixelRows(accessor => |
|
||||
{ |
|
||||
ColorProfileConverter converter = new(new ColorConversionOptions() |
|
||||
{ |
|
||||
SourceWhitePoint = new CieXyz(inputIccProfile.Header.PcsIlluminant), |
|
||||
TargetWhitePoint = new CieXyz(outputIccProfile.Header.PcsIlluminant), |
|
||||
}); |
|
||||
|
|
||||
// TODO: Our Xxy/Lab conversion are dependent on the version number. We are applying the conversion using V4
|
|
||||
// but we should use the correct algorithm per version. This includes Lab/Lab Xyz/Xyz.
|
|
||||
using IMemoryOwner<Vector4> vectors = configuration.MemoryAllocator.Allocate<Vector4>(accessor.Width); |
|
||||
Span<Vector4> vectorsSpan = vectors.GetSpan(); |
|
||||
|
|
||||
// TODO: For debugging - remove.
|
|
||||
// It appears we have a scaling problem. The pcs values differ by on average 0.000001.
|
|
||||
Span<Vector4> temp = new Vector4[vectorsSpan.Length]; |
|
||||
|
|
||||
for (int y = 0; y < accessor.Height; y++) |
|
||||
{ |
|
||||
Span<TPixel> row = accessor.GetRowSpan(y); |
|
||||
PixelOperations<TPixel>.Instance.ToVector4(configuration, row, vectorsSpan, PixelConversionModifiers.Scale); |
|
||||
|
|
||||
if (inputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieLab && |
|
||||
outputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieXyz) |
|
||||
{ |
|
||||
for (int x = 0; x < vectorsSpan.Length; x++) |
|
||||
{ |
|
||||
Vector4 pcs = converterDataToPcs.Calculate(vectorsSpan[x]); |
|
||||
temp[x] = pcs; |
|
||||
pcs = PcsToLab(pcs); |
|
||||
CieLab lab = new(pcs.X, pcs.Y, pcs.Z); |
|
||||
CieXyz xyz = converter.Convert<CieLab, CieXyz>(in lab); |
|
||||
pcs = XyzToPcs(pcs, xyz); |
|
||||
|
|
||||
vectorsSpan[x] = converterPcsToData.Calculate(pcs); |
|
||||
} |
|
||||
} |
|
||||
else if (inputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieXyz && |
|
||||
outputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieLab) |
|
||||
{ |
|
||||
for (int x = 0; x < vectorsSpan.Length; x++) |
|
||||
{ |
|
||||
Vector4 pcs = converterDataToPcs.Calculate(vectorsSpan[x]); |
|
||||
CieXyz xyz = new(pcs.X, pcs.Y, pcs.Z); |
|
||||
CieLab lab = converter.Convert<CieXyz, CieLab>(in xyz); |
|
||||
pcs = LabToPcs(pcs, lab); |
|
||||
vectorsSpan[x] = converterPcsToData.Calculate(pcs); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
for (int x = 0; x < vectorsSpan.Length; x++) |
|
||||
{ |
|
||||
Vector4 pcs = converterDataToPcs.Calculate(vectorsSpan[x]); |
|
||||
vectorsSpan[x] = converterPcsToData.Calculate(pcs); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorsSpan, row, PixelConversionModifiers.Scale); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
image.Metadata.IccProfile = outputIccProfile; |
|
||||
} |
|
||||
|
|
||||
private static unsafe Vector4 PcsToLab(Vector4 input) |
|
||||
{ |
|
||||
Vector3* v = (Vector3*)&input; |
|
||||
v[0] *= new Vector3(100f, 255, 255); |
|
||||
v[0] -= new Vector3(0, 128F, 128F); |
|
||||
return input; |
|
||||
} |
|
||||
|
|
||||
private static unsafe Vector4 LabToPcs(Vector4 input, CieLab lab) |
|
||||
{ |
|
||||
Vector3* v = (Vector3*)&input; |
|
||||
v[0] = new Vector3(lab.L, lab.A + 128F, lab.B + 128F); |
|
||||
v[0] /= 100F; |
|
||||
return input; |
|
||||
} |
|
||||
|
|
||||
private static unsafe Vector4 XyzToPcs(Vector4 input, CieXyz xyz) |
|
||||
{ |
|
||||
Vector3* v = (Vector3*)&input; |
|
||||
v[0] = xyz.ToVector3(); |
|
||||
v[0] *= 32768 / 65535f; |
|
||||
return input; |
|
||||
} |
|
||||
|
|
||||
private readonly struct IccProfileConverterVisitor : IImageVisitor |
|
||||
{ |
|
||||
private readonly IccProfile inputIccProfile; |
|
||||
private readonly IccProfile outputIccProfile; |
|
||||
|
|
||||
public IccProfileConverterVisitor(IccProfile inputIccProfile, IccProfile outputIccProfile) |
|
||||
{ |
|
||||
this.inputIccProfile = inputIccProfile; |
|
||||
this.outputIccProfile = outputIccProfile; |
|
||||
} |
|
||||
|
|
||||
public void Visit<TPixel>(Image<TPixel> image) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> => Convert(image, this.inputIccProfile, this.outputIccProfile); |
|
||||
} |
|
||||
} |
|
||||
@ -1,68 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|
||||
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; |
|
||||
using SixLabors.ImageSharp.Formats.Png; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc; |
|
||||
|
|
||||
public class IccProfileConverterTests |
|
||||
{ |
|
||||
private static readonly PngEncoder Encoder = new(); |
|
||||
|
|
||||
[Theory(Skip = "Skip for now while we refactor the library")] |
|
||||
[WithFile(TestImages.Jpeg.ICC.AdobeRgb, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.ColorMatch, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.SRgb, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.CMYK, PixelTypes.Rgb24)] |
|
||||
public void CanRoundTripProfile<TPixel>(TestImageProvider<TPixel> provider) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
using Image<TPixel> image = provider.GetImage(); |
|
||||
|
|
||||
IccProfile profile = image.Metadata.IccProfile; |
|
||||
|
|
||||
TPixel expected = image[0, 0]; |
|
||||
|
|
||||
IccProfileConverter.Convert(image, profile, profile); |
|
||||
|
|
||||
image.DebugSave(provider, extension: "png", appendPixelTypeToFileName: false, appendSourceFileOrDescription: true, encoder: Encoder); |
|
||||
|
|
||||
TPixel actual = image[0, 0]; |
|
||||
|
|
||||
Assert.Equal(expected, actual); |
|
||||
} |
|
||||
|
|
||||
[Theory(Skip = "Skip for now while we refactor the library")] |
|
||||
[WithFile(TestImages.Jpeg.ICC.AdobeRgb, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.ColorMatch, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.SRgb, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgb24)] |
|
||||
[WithFile(TestImages.Jpeg.ICC.CMYK, PixelTypes.Rgb24)] |
|
||||
public void CanConvertToSRGB<TPixel>(TestImageProvider<TPixel> provider) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
using Image<TPixel> image = provider.GetImage(); |
|
||||
IccProfile profile = image.Metadata.IccProfile; |
|
||||
|
|
||||
IccProfile sRGBProfile = SrgbV4Profile.GetProfile(); |
|
||||
|
|
||||
IccProfileConverter.Convert(image, profile, sRGBProfile); |
|
||||
|
|
||||
Assert.Equal(image.Metadata.IccProfile, sRGBProfile); |
|
||||
|
|
||||
image.DebugSave(provider, extension: "png", appendPixelTypeToFileName: false, appendSourceFileOrDescription: true, encoder: Encoder); |
|
||||
|
|
||||
// Mac reports a difference of 0.0000%
|
|
||||
image.CompareToReferenceOutput(ImageComparer.Tolerant(0.0001F), provider, appendPixelTypeToFileName: false); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue