YIQ to RGB converter
This YIQ to RGB converter turns the luminance and two chrominance components used by the NTSC color model into practical red, green, and blue values.
Run — free
It applies a fixed inverse matrix, reports both normalized and 8-bit channels, and shows when a channel had to be clipped to the displayable RGB range. Strict validation catches missing, nonnumeric, infinite, and out-of-domain inputs before calculation. The conversion is deterministic, needs no network access, and produces the same result for the same Y, I, and Q components every time.
Enter normalized YIQ components correctly
Provide three numeric components: Y for luminance, I for in-phase chrominance, and Q for quadrature chrominance. Y must be between 0 and 1, I must be between -0.5957 and 0.5957, and Q must be between -0.5226 and 0.5226. These are normalized NTSC YIQ limits, not byte values and not percentages. A Y value near zero represents low luminance, while a value near one represents high luminance. The signed I and Q components carry color information around that luminance. Enter actual JSON numbers rather than numeric strings so the contract remains unambiguous across the API, SDKs, and browser form. The converter rejects missing values, strings, NaN-like values, infinities, and numbers outside the declared component ranges. That strict behavior prevents a typing or scaling mistake from producing an apparently plausible color. Aliases such as luminance, in_phase, and quadrature are accepted for integrations that use descriptive names, but y, i, and q are the canonical fields shown in examples and documentation.
Understand the inverse NTSC matrix and clamping
The calculation uses the fixed inverse transformation R = Y + 0.956I + 0.621Q, G = Y - 0.272I - 0.647Q, and B = Y - 1.106I + 1.703Q. Each equation first produces an unclamped normalized channel. Valid YIQ components can still describe a color outside the RGB cube because the legal YIQ region is not identical to the set of red, green, and blue combinations bounded by zero and one. For that reason, each raw output is clamped independently: values below zero become zero, and values above one become one. The converter preserves the pre-clamp values in unclamped_rgb and identifies every affected channel in clipped, so clipping never happens silently. It then multiplies each clamped normalized component by 255 and rounds to the nearest integer to create familiar 8-bit red, green, and blue channels. The same integer channels are formatted as a lowercase six-digit hexadecimal color and as a CSS rgb() value. Fixed coefficients and stable decimal rounding make repeated results predictable.
Read the result and use it safely
Use red, green, and blue when an application expects separate integer channels, rgb when it expects a three-element array, hex for design tools and color pickers, or css for a stylesheet declaration. normalized_rgb is useful in graphics code that consumes channels from zero through one. unclamped_rgb is diagnostic rather than directly displayable: it reveals what the matrix produced before gamut limiting. Check clipped whenever color fidelity matters. A clipped channel means the requested YIQ point falls outside displayable normalized RGB and the returned color is the nearest result under independent channel clipping, not an exact reversible representation. This distinction is important when analyzing legacy broadcast signals, porting image-processing formulas, or comparing implementations that use different coefficient conventions. Record the chosen matrix alongside stored results if another system may use a different YIQ definition. The endpoint performs one conversion per request and costs $0.002 through the API; the pure arithmetic has no network dependency, randomness, clock access, hidden state, or locale-sensitive formatting.
What you can do with it
Decode legacy NTSC color data
Convert normalized luminance and chrominance samples from an archival or educational YIQ dataset into displayable RGB channels.
Verify a graphics implementation
Compare your inverse-matrix output, gamut clipping, hexadecimal formatting, and integer rounding against a deterministic reference result.
Inspect out-of-gamut colors
Use the unclamped values and per-channel clipping flags to see why a valid YIQ point cannot be represented exactly in the RGB cube.
FAQ
What input ranges are accepted?
Y accepts 0 through 1, I accepts -0.5957 through 0.5957, and Q accepts -0.5226 through 0.5226. All three must be finite JSON numbers.
Which inverse matrix does the converter use?
It uses R = Y + 0.956I + 0.621Q, G = Y - 0.272I - 0.647Q, and B = Y - 1.106I + 1.703Q.
Why can valid YIQ input produce clipping?
The valid component ranges form a space that can extend beyond the normalized RGB cube. Raw channels outside 0 through 1 are clamped for display.
How are 8-bit RGB channels produced?
Each normalized channel is clamped to 0 through 1, multiplied by 255, and rounded to the nearest integer.
Is this conversion reversible?
It is reversible only when coefficient and rounding conventions match and no channel is clipped. Integer rounding also discards precision.
What does an API request cost?
Each request costs $0.002. The conversion processes one YIQ color item per request.
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/yiq-rgb \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"y":0.5,"i":0.2,"q":-0.1}'const res = await fetch("https://api.kit.forhosting.com/color/yiq-rgb", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"y": 0.5,
"i": 0.2,
"q": -0.1
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/color/yiq-rgb",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"y": 0.5,
"i": 0.2,
"q": -0.1
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/color/yiq-rgb", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"y":0.5,"i":0.2,"q":-0.1}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"y":0.5,"i":0.2,"q":-0.1}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/color/yiq-rgb", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"y": 0.5,
"i": 0.2,
"q": -0.1
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "color.yiq_rgb",
"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. |