Perceived brightness calculator
The perceived brightness calculator turns red, green, and blue channel values into one practical luminance score.
Run — free
It uses the familiar weighted RGB formula that gives green the greatest influence, red the next greatest, and blue the least. The result is classified as light or dark against a documented threshold, then paired with a suggested black or white text color. Use it when a theme, badge, chart, or user-selected background needs an immediate and repeatable foreground choice.
Why perceived brightness uses weighted RGB channels
A simple average treats red, green, and blue as if human vision responded equally to each channel. It does not. Green contributes much more to the brightness people perceive, red contributes a moderate amount, and blue contributes less. This calculator therefore applies the classic BT.601-style luma weights: 0.299 for red, 0.587 for green, and 0.114 for blue. Each channel must be an integer from 0 through 255. The weighted values are added to produce a score on the same approximate 0-to-255 scale, where black is zero and white is 255. The returned score is rounded to three decimal places so results remain readable while preserving useful precision. This operation is deterministic: identical RGB inputs always produce identical output, with no color profile lookup, network request, random choice, or device-specific adjustment. The calculation is especially useful for interface decisions that need a fast, understandable estimate rather than a complete color-management or accessibility analysis.
How the light or dark classification chooses text
After calculating perceived brightness, the capability compares the score with a threshold of 128. A score at or above 128 is classified as light, while a score below 128 is classified as dark. For a light background, the response recommends black text; for a dark background, it recommends white text. The output includes the original channels, the precise brightness score, the classification, the recommended text color, the threshold, and the formula, so an application can store or audit the decision instead of relying on an undocumented boolean. The boundary is intentionally explicit: a score of exactly 128 belongs to the light class. This makes behavior predictable around the cutoff and avoids slightly different results across clients. The recommendation is a convenient binary foreground choice for labels, chips, generated avatars, and theme previews. It is not a claim that every font size or visual treatment meets a particular accessibility standard; use a contrast-ratio checker when formal conformance is required.
Validate channels and use the result safely
Send the RGB components as the r, g, and b fields. Every field is required, must be numeric, must be a whole integer, and must remain within the inclusive range from 0 to 255. Values such as -1, 256, a fractional channel, a numeric string, NaN, or a missing field are rejected as invalid input rather than clamped silently. Strict rejection matters because clamping can hide upstream conversion bugs and make a saved design differ from its preview. Once a valid response arrives, use classification when only a light-or-dark branch is needed, or use recommended_text_color directly when the interface accepts a hexadecimal foreground. Use perceived_brightness when sorting swatches or exposing the calculation to users. The API costs $0.002 per request, while the deterministic browser path can run the same calculation locally. For large palettes, call the calculation once per color and retain the result alongside the source RGB values so later rendering does not need to guess how the decision was made.
What you can do with it
Choose text for generated badges
Select black or white label text when badge backgrounds come from user data or generated palettes.
Classify theme swatches
Mark saved colors as light or dark so theme editors can preview sensible foregrounds immediately.
Audit automatic foreground decisions
Store the score, threshold, and formula with a design decision so its result can be reproduced later.
FAQ
What formula does the calculator use?
It uses 0.299 × red + 0.587 × green + 0.114 × blue, with each channel in the inclusive range from 0 to 255.
When is a color classified as light?
A perceived brightness score of 128 or greater is light. Any lower score is dark.
Does this guarantee WCAG contrast compliance?
No. It provides a quick binary choice based on perceived brightness. Use a contrast-ratio checker for formal WCAG evaluation.
What happens when a channel is outside the valid range?
The request fails with an invalid input error. Values are never silently clamped into range.
How much does an API calculation cost?
Each API request costs $0.002. The browser version can perform the same deterministic calculation locally.
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/luminance-perceived \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"r":52,"g":152,"b":219}'const res = await fetch("https://api.kit.forhosting.com/color/luminance-perceived", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"r": 52,
"g": 152,
"b": 219
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/color/luminance-perceived",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"r": 52,
"g": 152,
"b": 219
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/color/luminance-perceived", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"r":52,"g":152,"b":219}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"r":52,"g":152,"b":219}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/color/luminance-perceived", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"r": 52,
"g": 152,
"b": 219
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "color.luminance_perceived",
"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. |