RGB to HSL Converter

Optional shortcut — sets the fields below. No transparency support (a browser limitation of this control), so use the HEX/RGB/HSL fields directly for a translucent color.

#RGB, #RGBA, #RRGGBB, or #RRGGBBAA — the # is optional

rgb(R, G, B) or rgba(R, G, B, A) — R/G/B 0-255, A 0-1

hsl(H, S%, L%) or hsla(H, S%, L%, A) — H 0-360, A 0-1

RGB and HSL describe the same color space through two different mathematical lenses, and converting between them is where that relationship becomes concrete rather than conceptual. RGB describes a color as three independent light intensities — how much red, how much green, how much blue — while HSL describes the same point in color space as a position on a wheel (hue) plus how far from gray it is (saturation) and how close to black or white (lightness). Neither is more “correct”; they’re two coordinate systems for the same underlying space, the way Cartesian and polar coordinates both describe the same point on a plane.

The formula, worked through concretely: lightness is simply the average of the largest and smallest channel — for rgb(180, 140, 60), that’s (180 + 60) / 2 = 120 out of 255, roughly 47%. Saturation measures the gap between the largest and smallest channel relative to that lightness — a wide gap (like here, 180 vs. 60) means a more saturated color; three equal channels would collapse to 0% regardless of how bright or dark they are, since equal RGB values are always some shade of gray. Hue depends on which channel wins: red is largest here, which places the hue somewhere in the 0°-60° slice of the wheel (red toward yellow), and the exact position — 40° — comes from how much green (the second-largest channel) has caught up to red relative to how far blue has fallen behind. The result, hsl(40, 50%, 47%), is a moderately saturated amber-brown — which is exactly what rgb(180, 140, 60) looks like.

This conversion shows up constantly in image and computer-vision code: a decoded photo or video frame is RGB data (one byte per channel, straight from the sensor or file format), but tasks like sorting pixels by dominant color, thresholding a mask by hue range, or building a color histogram are all far easier to reason about in HSL than in raw RGB — “select the orange-ish pixels” is a hue range, not three separate channel comparisons.

Converting RGB to HSL programmatically

JavaScript, the formula above as code:

function rgbToHsl(r, g, b) {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  let h, s;
  const l = (max + min) / 2;
  if (max === min) {
    h = s = 0;
  } else {
    const d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
    else if (max === g) h = (b - r) / d + 2;
    else h = (r - g) / d + 4;
    h *= 60;
  }
  return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };
}

console.log(rgbToHsl(180, 140, 60));
// { h: 40, s: 50, l: 47 }

Python, via the standard library’s colorsys.rgb_to_hls (note: hue, lightness, saturation order, and 0-1 fractions rather than 0-255/percent):

import colorsys

h, l, s = colorsys.rgb_to_hls(180 / 255, 140 / 255, 60 / 255)
print(round(h * 360), round(s * 100), round(l * 100))
# 40 50 47

Frequently asked questions

What's actually happening mathematically when RGB converts to HSL?
Lightness comes first: it's the average of the largest and smallest of the three channels. Saturation measures how far apart the largest and smallest channels are relative to that lightness — three equal channels (gray) give 0% saturation, while a channel that's much higher than the others gives a saturation close to 100%. Hue depends on *which* channel is largest and how the other two compare to it — red-largest, green-largest, and blue-largest each map to a different 120°-wide slice of the color wheel, with the exact position inside that slice set by the gap between the other two channels.
Why did this particular pixel — rgb(180, 140, 60) — land at hue 40?
Red (180) is the largest channel here, green (140) is second, and blue (60) is smallest — that ordering places the hue in the red-to-yellow slice of the wheel (0° to 60°), and specifically close to 40° because green is fairly close to red relative to how far blue trails behind. Visually this is a brownish/amber tone, which matches: hues in the high-30s to low-40s read as orange/brown territory, between pure red (0°) and pure yellow (60°).
Does Python have RGB-to-HSL conversion built in?
Yes, via the standard library's colorsys module — though it's named rgb_to_hls (hue, lightness, saturation, in that argument order) rather than hsl, and it expects/returns every value as a 0-1 fraction rather than the 0-255 channels and 0-100% you'd write in CSS, so both ends need a small unit conversion.
Is 50% saturation for this color considered high or low?
Middling — right at the midpoint between 0% (gray) and 100% (as vivid as that hue/lightness combination can get). A pixel sampled from a real photo lands in this range constantly; fully saturated colors (90-100%) are comparatively rare outside of solid, deliberately-chosen brand or UI colors.
Is my color sent to a server?
No. The conversion runs entirely in your browser using plain arithmetic — nothing is uploaded, logged, or stored.

Related Color Converter pages

Looking for a different pair or algorithm? Here are the other specific versions, plus the general tool for everything else: