LAB 到 RGB 使用 Lcms2 给出奇怪的结果

LAB to RGB using Lcms2 gives odd result

我正在做一个需要做 lab->rgb 的项目。 我正在使用名为 lcms2 的色彩管理库,link: https://github.com/mm2/Little-CMS

我的项目是在 c# 中,我使用 interop 与 dll 进行通信。该项目必须 运行 linux Ubuntu Core 16,大多数 c# 颜色管理库都不支持,这就是我使用 lcms2 的原因。

尝试转换 LAB L:99、A:-122、B:-66 时,我得到了非常奇怪的结果,RGB 值:

R:-3769.14044380188 客服:304.88466560840607 B: 378.99470329284668

这是(简化的)数据类,以及测试中使用的扩展方法,当然还有测试:

[Test()]
public void LabThatGaveWeirdResultDoesntDoItAnymore()
{
    var lab = new LabColor(99, -112, -66);
    var rgb = lab.ToRgb();

    Assert.IsTrue(new[] { rgb.R, rgb.G, rgb.B }.All(v => v >= 0 && v <= 255));
}

public class LabColor
{
    public double L { get; }
    public double A { get; }
    public double B { get; }

    public LabColor(int l, int a, int b)
    {
        L = l;
        A = a;
        B = b;
    }
}

public class RgbColor
{
    public double R { get; }
    public double G { get; }
    public double B { get; }

    public RgbColor(double r, double g, double b)
    {
        R = r;
        G = g;
        B = b;
    }
}
public static RgbColor ToRgb(this LabColor lab, Intent intent = Intent.Perceptual)
{
    var labProfile = Lcms2.GetLabProfile();
    var hRgbProfile = Lcms2.GetRgbProfile();
    var transform = Lcms2.GetTransform(labProfile, hRgbProfile, intent);

    var rgbBuffer = new double[3];
    var labBuffer = new[]
    {
        lab.L,
        lab.A,
        lab.B
    };

    Lcms2.Transform(transform, labBuffer, ref rgbBuffer, 1);

    Lcms2.FreeMemory(labProfile);
    Lcms2.FreeMemory(hRgbProfile);
    Lcms2.FreeMemory(transform);

    return new RgbColor(rgbBuffer[0] * 255, rgbBuffer[1] * 255, rgbBuffer[2] * 255);
}

lcms2 封装代码:

internal enum ProfileFormat
{
    Rgb = 4456472,
    Cmyk = 4587552,
    Lab = 4849688
}

public enum Intent
{
    Perceptual = 0,
    RelativeColorimetric = 1,
    Saturation = 2,
    AbsoluteColorimetric = 3
}

internal sealed class Profile
{
    public Profile(IntPtr pointer, ProfileFormat format)
    {
        Pointer = pointer;
        Format = format;
    }

    internal ProfileFormat Format { get; }

    internal IntPtr Pointer { get; }
}

internal sealed class Transform
{
    public Transform(IntPtr pointer)
    {
        Pointer = pointer;
    }

    internal IntPtr Pointer { get; }
}

internal sealed class Lcms2
{
    [DllImport("lcms2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cmsCreate_sRGBProfile")]
    static extern IntPtr cmsCreate_sRGBProfile();

    public static Profile GetRgbProfile()
    {
        return new Profile(cmsCreate_sRGBProfile(), ProfileFormat.Rgb);
    }

    [DllImport("lcms2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cmsCreateLab4Profile")]
    static extern IntPtr cmsCreateLab4Profile();

    public static Profile GetLabProfile()
    {
        return new Profile(cmsCreateLab4Profile(), ProfileFormat.Lab);
    }

    [DllImport("lcms2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cmsCreateTransform")]
    static extern IntPtr cmsCreateTransform(IntPtr inProfilePtr, uint inputFormat, IntPtr outPutProfilePtr, uint outputFormat, uint intent, uint dword);

    public static Transform GetTransform(Profile inProfile, Profile outProfile, Intent intent)
    {
        return new Transform(cmsCreateTransform(inProfile.Pointer, (uint)inProfile.Format, outProfile.Pointer, (uint)outProfile.Format, (uint)intent, 0));
    }

    [DllImport("lcms2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cmsDoTransform")]
    static extern void cmsDoTransform(IntPtr transformPtr, double[] inputBuffer, double[] outputBuffer, uint size);

    public static void Transform(Transform transform, double[] inputBuffer, ref double[] outputBuffer, uint size)
    {
        cmsDoTransform(transform.Pointer, inputBuffer, outputBuffer, size);
    }

    [DllImport("lcms2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cmsDeleteTransform")]
    static extern void cmsDeleteTransform(IntPtr transformPtr);

    public static void FreeMemory(Transform transform)
    {
        cmsDeleteTransform(transform.Pointer);
    }

    [DllImport("lcms2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cmsCloseProfile")]
    static extern void cmsCloseProfile(IntPtr profilePtr);
    public static void FreeMemory(Profile profile)
    {
        cmsCloseProfile(profile.Pointer);
    }

我通过在 c++ lcms2 项目中重新创建代码来计算 profileformat 整数。 他们被定义为

#define TYPE_RGB_DBL          (FLOAT_SH(1)|COLORSPACE_SH(PT_RGB)|CHANNELS_SH(3)|BYTES_SH(0))
#define TYPE_Lab_DBL          (FLOAT_SH(1)|COLORSPACE_SH(PT_Lab)|CHANNELS_SH(3)|BYTES_SH(0))

我模仿了代码,创建了:

        uint FLOAT_SH(uint i) => (i) << 22;
        uint COLORSPACE_SH(uint i) => (i) << 16;
        uint PT_Lab = 10;
        uint PT_RGB = 4;
        uint CHANNELS_SH(uint i) => (i) << 3;
        uint BYTES_SH(uint i) => (i);

        var TYPE_RGB_DBL = (FLOAT_SH(1) | COLORSPACE_SH(PT_RGB) | CHANNELS_SH(3) | BYTES_SH(0)); //4456472
        var TYPE_Lab_DBL = (FLOAT_SH(1) | COLORSPACE_SH(PT_Lab) | CHANNELS_SH(3) | BYTES_SH(0)); //4849688

恰好 Lab (99, -122, -66) 超出了 sRGB 色域。您不能在 sRGB 中表示此颜色。如果裁剪值是 RGB=(0, 255, 255),但这不是您要求的颜色。

否则 sRGB 的无限值是:

C:\github\Little-CMS\bin>transicc -t1 -i*Lab LittleCMS ColorSpace 转换计算器 - 4.3 [LittleCMS 2.08]

输入值,'q'退出 大号*? 99 一种*? -122 乙*? -66

R=-4111.8278 G=307.7362 B=378.8372