Есть ли способ проверить, является ли триплет XYZ действительным цветом? - PullRequest
2 голосов
/ 09 ноября 2019

Цветовое пространство XYZ охватывает все возможные цвета, а не только те, которые могут быть созданы конкретным устройством, таким как монитор. Не все триплеты XYZ представляют физически возможный цвет. Есть ли способ, учитывая триплет XYZ, определить, представляет ли он настоящий цвет?

Я хотел сгенерировать диаграмму цветности CIE 1931 для себя, но не знал, как это сделать. Это. Например, легко взять все комбинации триплетов sRGB, а затем преобразовать их в координаты xy диаграммы цветности и затем построить их. Вы не можете использовать этот же подход в цветовом пространстве XYZ, поскольку не все комбинации являются допустимыми цветами. На данный момент лучшее, что я придумал, - это стохастический подход, в котором я генерирую случайное спектральное распределение, суммируя случайное число случайных гауссианов, а затем преобразовывая его в XYZ, используя стандартные функции наблюдателя.

enter image description here

1 Ответ

0 голосов
/ 10 ноября 2019

Подумав об этом немного больше, я почувствовал, что очевидным решением является создание списка точек xy по краю спектрального локуса, соответствующих чистым монохроматическим цветам. Мне кажется, что это можно сделать путем непосредственного встраивания видимых частот (~ 380-780 нм) в стандартные функции соответствия цветов наблюдателя CIE XYZ. Обрабатывая эти точки как выпуклый многоугольник, вы можете определить, находится ли точка в спектральном локусе, используя тот или иной алгоритм. В моем случае, поскольку я действительно хотел просто сгенерировать диаграмму цветности, я просто вводил эти точки в процедуру рисования многоугольника графической библиотеки, а затем для каждого пикселя многоугольника я мог преобразовать ее в sRGB.

Я считаю, что это решение похоже на то, которое использовала библиотека, которую Кел связал в комментарии. Я не совсем уверен, так как я не знаком с Python.

function RGBfromXYZ(X, Y, Z) {
    const R = 3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z
    const G = -0.969266 * X + 1.8760108 * Y + 0.0415560 * Z
    const B = 0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z
    return [R, G, B]
}

function XYZfromYxy(Y, x, y) {
    const X = Y / y * x
    const Z = Y / y * (1 - x - y)
    return [X, Y, Z]
}

function srgb_from_linear(x) {
    if (x <= 0.0031308) {
        return x * 12.92
    } else {
        return 1.055 * Math.pow(x, 1/2.4) - 0.055
    }
}

// Analytic Approximations to the CIE XYZ Color Matching Functions
// from Sloan http://jcgt.org/published/0002/02/01/paper.pdf

function xFit_1931(x) {
    const t1 = (x - 442) * (x < 442 ? 0.0624 : 0.0374)
    const t2 = (x -599.8) * (x < 599.8 ? 0.0264 : 0.0323)
    const t3 = (x - 501.1) * (x < 501.1 ? 0.0490 : 0.0382)
    return 0.362 * Math.exp(-0.5 * t1 * t1) + 1.056 * Math.exp(-0.5 * t2 * t2) - 0.065 * Math.exp(-0.5 * t3 * t3)
}

function yFit_1931(x) {
    const t1 = (x - 568.8) * (x < 568.8 ? 0.0213 : 0.0247)
    const t2 = (x - 530.9) * (x < 530.9 ? 0.0613 : 0.0322)
    return 0.821 * Math.exp(-0.5 * t1 * t1) + 0.286 * Math.exp(-0.5 * t2 * t2)
}

function zFit_1931(x) {
    const t1 = (x - 437) * (x < 437 ? 0.0845 : 0.0278)
    const t2 = (x - 459) * (x < 459 ? 0.0385 : 0.0725)
    return 1.217 * Math.exp(-0.5 * t1 * t1) + 0.681 * Math.exp(-0.5 * t2 * t2)
}

const canvas = document.createElement("canvas")
document.body.append(canvas)
canvas.width = canvas.height = 512
const ctx = canvas.getContext("2d")

const locus_points = []

for (let i = 440; i < 650; ++i) {
    const [X, Y, Z] = [xFit_1931(i), yFit_1931(i), zFit_1931(i)]
    const x = (X / (X + Y + Z)) * canvas.width
    const y = (Y / (X + Y + Z)) * canvas.height
    locus_points.push([x, y])
}

ctx.beginPath()
ctx.moveTo(...locus_points[0])
locus_points.slice(1).forEach(point => ctx.lineTo(...point))
ctx.closePath()
ctx.fill()

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)

for (let y = 0; y < canvas.height; ++y) {
    for (let x = 0; x < canvas.width; ++x) {
        const alpha = imageData.data[(y * canvas.width + x) * 4 + 3]
        if (alpha > 0) {
            const [X, Y, Z] = XYZfromYxy(1, x / canvas.width, y / canvas.height)
            const [R, G, B] = RGBfromXYZ(X, Y, Z)
            const r = Math.round(srgb_from_linear(R / Math.sqrt(R**2 + G**2 + B**2)) * 255)
            const g = Math.round(srgb_from_linear(G / Math.sqrt(R**2 + G**2 + B**2)) * 255)
            const b = Math.round(srgb_from_linear(B / Math.sqrt(R**2 + G**2 + B**2)) * 255)
            imageData.data[(y * canvas.width + x) * 4 + 0] = r
            imageData.data[(y * canvas.width + x) * 4 + 1] = g
            imageData.data[(y * canvas.width + x) * 4 + 2] = b
        }
    }
}

ctx.putImageData(imageData, 0, 0)
...