Toolmingo
Guides9 min read

How to Use a Color Picker: Pick, Sample, and Convert Colors

Learn how color pickers work — pick colors from the screen with the EyeDropper API, sample pixels from images with Canvas, convert between HEX/RGB/HSL, and build your own color picker in JS, Python, Go, and PHP.

How to Use a Color Picker: Pick, Sample, and Convert Colors

A color picker is a tool that lets you select or sample a color and get its exact code — in HEX, RGB, HSL, or another format. Whether you're matching a brand color from a logo, sampling a hue from a photograph, or building a UI palette, a color picker removes the guesswork.

This guide explains how color pickers work, how to use browser APIs to pick colors from the screen, how to sample pixels from images with Canvas, and how to convert between color formats in JavaScript, Python, Go, and PHP.


What Is a Color Picker?

A color picker is an interface — in a design tool, a browser DevTools panel, or a standalone web app — that lets you:

  1. Choose a color visually (saturation/lightness gradient + hue slider)
  2. Sample a color from the screen or an image (eyedropper)
  3. Convert a color between formats (HEX ↔ RGB ↔ HSL ↔ OKLCH)

The output is always a numeric color value you can paste directly into CSS, code, or a design file.


The Three Main Color Formats

Format Example Best for
HEX #3b82f6 CSS, HTML attributes, most design tools
RGB rgb(59, 130, 246) When you need channel values (e.g., opacity math)
HSL hsl(217, 91%, 60%) When you want to adjust hue, saturation, or lightness intuitively

They all describe the same color. A color picker lets you see and copy any of them.


Picking a Color from the Screen — EyeDropper API

Modern browsers (Chrome 95+, Edge 95+, Opera 81+) ship the EyeDropper API, which lets a web page open a native screen sampler:

// Works in Chrome/Edge/Opera — check support first
async function pickFromScreen() {
  if (!window.EyeDropper) {
    alert('EyeDropper API not supported in this browser');
    return null;
  }

  const eyeDropper = new EyeDropper();
  try {
    // Opens the OS-level colour sampler cursor
    const result = await eyeDropper.open();
    // result.sRGBHex → "#3b82f6"
    return result.sRGBHex;
  } catch (err) {
    // User pressed Escape or cancelled
    return null;
  }
}

// Usage
document.getElementById('pick-btn').addEventListener('click', async () => {
  const hex = await pickFromScreen();
  if (hex) {
    document.getElementById('preview').style.background = hex;
    document.getElementById('output').textContent = hex;
  }
});

Firefox note: Firefox does not support EyeDropper as of 2026. For cross-browser support, fall back to a canvas-based picker (see below).


Sampling a Color from an Image — Canvas API

To read the color of a pixel at position (x, y) in an image:

function sampleColorFromImage(imgElement, x, y) {
  const canvas = document.createElement('canvas');
  canvas.width  = imgElement.naturalWidth;
  canvas.height = imgElement.naturalHeight;

  const ctx = canvas.getContext('2d');
  // drawImage requires the image to be fully loaded
  ctx.drawImage(imgElement, 0, 0);

  // getImageData returns [R, G, B, A] for each pixel
  const [r, g, b, a] = ctx.getImageData(x, y, 1, 1).data;

  return {
    hex: `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`,
    rgb: `rgb(${r}, ${g}, ${b})`,
    alpha: (a / 255).toFixed(2),
  };
}

// Click handler on an <img> element
imgElement.addEventListener('click', (e) => {
  const rect = imgElement.getBoundingClientRect();
  // Scale click position to natural image dimensions
  const x = Math.round((e.clientX - rect.left) * (imgElement.naturalWidth  / rect.width));
  const y = Math.round((e.clientY - rect.top)  * (imgElement.naturalHeight / rect.height));

  const color = sampleColorFromImage(imgElement, x, y);
  console.log(color); // { hex: "#3b82f6", rgb: "rgb(59, 130, 246)", alpha: "1.00" }
});

CORS warning: If the image is on a different domain, getImageData throws a SecurityError unless the server sends Access-Control-Allow-Origin: * and you set imgElement.crossOrigin = 'anonymous' before setting src.


Converting Between HEX, RGB, and HSL

Most color pickers need conversion functions. Here's a compact set:

// HEX → RGB
function hexToRgb(hex) {
  const clean = hex.replace('#', '');
  const full  = clean.length === 3
    ? clean.split('').map(c => c + c).join('')
    : clean;
  return {
    r: parseInt(full.slice(0, 2), 16),
    g: parseInt(full.slice(2, 4), 16),
    b: parseInt(full.slice(4, 6), 16),
  };
}

// RGB → HEX
function rgbToHex(r, g, b) {
  return '#' + [r, g, b]
    .map(v => Math.round(v).toString(16).padStart(2, '0'))
    .join('');
}

// RGB → HSL  (H: 0–360, S/L: 0–100)
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);
    switch (max) {
      case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
      case g: h = ((b - r) / d + 2) / 6; break;
      case b: h = ((r - g) / d + 4) / 6; break;
    }
  }

  return {
    h: Math.round(h * 360),
    s: Math.round(s * 100),
    l: Math.round(l * 100),
  };
}

// HSL → RGB
function hslToRgb(h, s, l) {
  s /= 100; l /= 100;
  const k = n => (n + h / 30) % 12;
  const a = s * Math.min(l, 1 - l);
  const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
  return {
    r: Math.round(f(0) * 255),
    g: Math.round(f(8) * 255),
    b: Math.round(f(4) * 255),
  };
}

// Example: full round-trip
const rgb  = hexToRgb('#3b82f6');          // { r: 59, g: 130, b: 246 }
const hsl  = rgbToHsl(rgb.r, rgb.g, rgb.b); // { h: 217, s: 91, l: 60 }
const back = hslToRgb(hsl.h, hsl.s, hsl.l); // { r: 59, g: 130, b: 246 }
console.log(rgbToHex(back.r, back.g, back.b)); // "#3b82f6"

Python — Sample a Color from an Image

from PIL import Image

def sample_color(image_path: str, x: int, y: int) -> dict:
    img = Image.open(image_path).convert("RGBA")
    r, g, b, a = img.getpixel((x, y))
    return {
        "hex": f"#{r:02x}{g:02x}{b:02x}",
        "rgb": f"rgb({r}, {g}, {b})",
        "alpha": round(a / 255, 2),
    }

# Get the dominant color (most frequent pixel after resizing to 1×1)
def dominant_color(image_path: str) -> str:
    img = Image.open(image_path).convert("RGB")
    # Resize to 1×1 — Pillow averages all pixels
    tiny = img.resize((1, 1), Image.LANCZOS)
    r, g, b = tiny.getpixel((0, 0))
    return f"#{r:02x}{g:02x}{b:02x}"

print(sample_color("photo.jpg", 100, 200))
print(dominant_color("logo.png"))

Go — Sample a Pixel from an Image

package main

import (
    "fmt"
    "image"
    _ "image/jpeg"
    _ "image/png"
    "os"
)

func sampleColor(path string, x, y int) (string, error) {
    f, err := os.Open(path)
    if err != nil {
        return "", err
    }
    defer f.Close()

    img, _, err := image.Decode(f)
    if err != nil {
        return "", err
    }

    c := img.At(x, y)
    r, g, b, _ := c.RGBA()
    // RGBA() returns 0–65535; shift right 8 to get 0–255
    return fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8), nil
}

func main() {
    hex, err := sampleColor("photo.jpg", 100, 200)
    if err != nil {
        panic(err)
    }
    fmt.Println(hex) // e.g. "#3b82f6"
}

PHP — Sample a Color from an Image

function sampleColor(string $path, int $x, int $y): array {
    $img = imagecreatefromstring(file_get_contents($path));
    if ($img === false) {
        throw new RuntimeException("Cannot load image: $path");
    }

    $colorIndex = imagecolorat($img, $x, $y);
    $rgb = imagecolorsforindex($img, $colorIndex);
    imagedestroy($img);

    $r = $rgb['red'];
    $g = $rgb['green'];
    $b = $rgb['blue'];

    return [
        'hex' => sprintf('#%02x%02x%02x', $r, $g, $b),
        'rgb' => "rgb($r, $g, $b)",
    ];
}

// Dominant colour via GD resize to 1×1
function dominantColor(string $path): string {
    $src = imagecreatefromstring(file_get_contents($path));
    $tiny = imagecreatetruecolor(1, 1);
    imagecopyresampled($tiny, $src, 0, 0, 0, 0, 1, 1, imagesx($src), imagesy($src));

    $idx  = imagecolorat($tiny, 0, 0);
    $rgba = imagecolorsforindex($tiny, $idx);
    imagedestroy($src);
    imagedestroy($tiny);

    return sprintf('#%02x%02x%02x', $rgba['red'], $rgba['green'], $rgba['blue']);
}

$color = sampleColor('photo.jpg', 100, 200);
echo $color['hex']; // #3b82f6
echo dominantColor('logo.png');

Quick Reference

Task Browser Node.js Python Go PHP
Pick from screen EyeDropper API
Sample pixel from image Canvas getImageData sharp + metadata Pillow getpixel image.At(x,y) imagecolorat
Dominant color Canvas resize 1×1 sharp.resize(1,1) img.resize((1,1)) manual loop imagecopyresampled 1×1
HEX → RGB hexToRgb() above same PIL.ImageColor manual parse imagecolorat + imagecolorsforindex
RGB → HSL rgbToHsl() above same colorsys.rgb_to_hls manual math manual math

6 Common Mistakes

1. Forgetting CORS when sampling images from another domain getImageData throws SecurityError on cross-origin images. Always set img.crossOrigin = 'anonymous' before setting src, and the server must send CORS headers.

2. Not accounting for device pixel ratio On Retina displays, getBoundingClientRect() gives CSS pixels but getImageData works in physical pixels. Multiply click coordinates by window.devicePixelRatio when the canvas is rendered at full resolution.

3. Treating EyeDropper as universally supported Firefox and Safari do not support the EyeDropper API. Always check window.EyeDropper before calling it and provide a canvas-based fallback.

4. Ignoring the alpha channel getImageData returns [R, G, B, A]. If the image has transparency (PNG with alpha), the A value affects the apparent color. For a 6-digit hex output, you're discarding alpha — document this if precision matters.

5. Using Math.random() to generate color swatches for design Math.random() gives colors spread across the full gamut, most of which look muddy or washed out. For readable swatches, generate in HSL with constrained saturation (55–70%) and lightness (45–60%).

6. Rounding errors in HEX ↔ RGB round-trips parseInt('3b', 16) === 59 but (59).toString(16) === '3b' — fine. The problem is HSL conversion: intermediate floating-point steps accumulate errors. Always clamp each channel to [0, 255] and use Math.round before converting back to hex.


Frequently Asked Questions

What is the EyeDropper API? It's a browser API (available in Chromium-based browsers) that opens the OS-native colour sampler cursor and returns the hex code of whatever pixel the user clicks on — anywhere on the screen, including outside the browser window.

Can I pick a color from a video frame? Yes. Draw the current video frame onto a <canvas> with ctx.drawImage(videoElement, 0, 0), then use getImageData at the click position. You must handle CORS the same way as with images if the video is cross-origin.

What's the difference between HSL and HSV? HSL (Hue–Saturation–Lightness): white is L=100%, black is L=0%, pure hue is S=100%, L=50%. HSV (Hue–Saturation–Value): pure hue is S=100%, V=100%; black is V=0%. Most web color pickers use HSL or HSB (HSB = HSV). CSS uses HSL.

How do I get the dominant color from an image? The simplest way is to resize the image to 1×1 pixel and read that pixel — the resize filter averages all colors. For a palette of 5–10 dominant colors, use k-means clustering (libraries: node-vibrant for JS, colorthief for Python/JS).

Why does my color look different on screen vs in print? Screens use additive RGB (light); printers use subtractive CMYK (ink). A vivid RGB blue like #0000ff can't be reproduced exactly in CMYK. For print-accurate work, always proof in a CMYK-aware tool and convert with ICC profiles.

Can I use a color picker to check contrast ratios? Yes. Once you have two hex colors, compute the WCAG relative luminance of each and divide to get the contrast ratio. WCAG AA requires ≥4.5:1 for normal text, ≥3:1 for large text. The color-converter tool can do this for you.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools