XYZ to xyY converter
The XYZ to xyY converter transforms three CIE tristimulus values into two chromaticity coordinates and a separate luminance value.
Run — free
This makes it easier to compare color independent of brightness while retaining the original Y component for luminance-sensitive work. Enter non-negative X, Y, and Z values on one consistent scale; the converter validates them, rejects undefined black chromaticity, and returns stable numeric results using the standard CIE normalization equations for dependable colorimetric workflows.
Understand what changes between XYZ and xyY
CIE XYZ represents a stimulus with three tristimulus values, but the magnitude of those values and their ratios are mixed together. The xyY representation separates those ideas. Its lowercase x and y coordinates describe chromaticity: broadly, the color independent of overall intensity. Its uppercase Y retains the original luminance-related tristimulus component. This separation is useful when a workflow needs to plot colors on a CIE 1931 chromaticity diagram, compare two measurements at different brightness levels, or store luminance beside a compact pair of chromaticity coordinates. The conversion does not apply a white-point adaptation, gamma curve, RGB matrix, or perceptual correction. It only normalizes the supplied XYZ values. Therefore, the observer, illuminant, measurement conditions, and XYZ scaling remain responsibilities of the surrounding color workflow. If two XYZ triples were produced under different reference conditions, converting both to xyY does not make those conditions equivalent. Supply values that already belong to the same intended colorimetric context, and interpret the returned coordinates within that context.
How the deterministic calculation works
The converter first checks that X, Y, and Z are numbers, finite, and non-negative. It then calculates the sum X + Y + Z. Lowercase x equals X divided by that sum, and lowercase y equals Y divided by the same sum. Uppercase Y in the result is copied from the input without normalization, preserving the luminance-related magnitude and its original scale. Because both chromaticity coordinates use a common denominator, x and y are scale-invariant: multiplying all three XYZ inputs by the same positive factor leaves x and y unchanged while changing uppercase Y by that factor. Results are rounded to a stable significant precision so repeated executions produce consistent JSON values. No network request, random value, clock, lookup table, or environment-specific color profile participates in the calculation. The triple X = 0, Y = 0, Z = 0 is rejected because its denominator is zero and a perfectly dark stimulus has no defined chromaticity. Very large values whose sum overflows the finite numeric range are also rejected instead of returning misleading zeroes or non-finite JSON values.
Prepare inputs and interpret the output safely
Use one consistent scale for all three inputs. XYZ is commonly expressed with a reference white near Y = 100, but normalized values near Y = 1 are equally valid; this converter does not assume either convention. The returned uppercase Y stays on whichever scale you supplied. Lowercase x and y are ratios and therefore have no unit. For valid non-negative XYZ inputs, each coordinate lies from zero to one, and x + y does not exceed one; the implied third chromaticity coordinate is z = 1 - x - y if your downstream calculation needs it. Do not confuse lowercase y with uppercase Y: lowercase y is a normalized chromaticity coordinate, while uppercase Y is the original luminance-related tristimulus value. Also remember that xyY is not perceptually uniform, so equal geometric distances on an xy diagram do not necessarily look equally different. Use this conversion for standards-based coordinate exchange, plotting, instrumentation, or intermediate color calculations. For perceptual difference measurements, continue into an appropriate space such as CIELAB after applying the required reference-white and adaptation rules.
What you can do with it
Plot a measured color
Convert spectrophotometer XYZ output into x and y coordinates for a CIE 1931 chromaticity diagram while retaining luminance.
Compare chromaticity across brightness levels
Normalize proportionally scaled XYZ measurements so their chromaticity coordinates can be compared separately from Y.
Prepare colorimetric data
Transform XYZ records into the xyY representation expected by lighting, display, imaging, or calibration software.
FAQ
What does the conversion cost?
The API price is $0.002 per request. The browser version is free.
Which XYZ scale should I use?
Any consistent scale is valid. Use the same scale for X, Y, and Z; uppercase Y in the output retains that scale.
Why is an all-zero XYZ input rejected?
The formulas divide by X + Y + Z. For an all-zero stimulus that sum is zero, so chromaticity is mathematically undefined.
Does this converter perform chromatic adaptation?
No. It only normalizes XYZ into xy coordinates and preserves Y. It does not change white points or observer conditions.
Why are lowercase y and uppercase Y different?
Lowercase y is a unitless chromaticity ratio. Uppercase Y is the original luminance-related XYZ component.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/color/xyz-xyy \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"x_value":41.24,"y_value":21.26,"z_value":1.93}'const res = await fetch("https://api.kit.forhosting.com/color/xyz-xyy", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"x_value": 41.24,
"y_value": 21.26,
"z_value": 1.93
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/color/xyz-xyy",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"x_value": 41.24,
"y_value": 21.26,
"z_value": 1.93
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/color/xyz-xyy", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"x_value":41.24,"y_value":21.26,"z_value":1.93}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"x_value":41.24,"y_value":21.26,"z_value":1.93}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/color/xyz-xyy", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"x_value": 41.24,
"y_value": 21.26,
"z_value": 1.93
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "color.xyz_xyy",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |