Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion colorhash.cabal
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cabal-version: 2.2

name: colorhash
version: 0.1.0.0
version: 0.2.0.0
synopsis: Generate a color for a hashable object.
-- description:
homepage: https://github.com/hapytex/colorhash#readme
Expand Down
68 changes: 62 additions & 6 deletions src/Data/Hashable/Color.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,52 @@
-- each of the items a different color based on the hash.
--
-- This module provides a function 'rgbHash' that can convert any 'Hashable' object to a 'Colour'.
module Data.Hashable.Color (rgbHash) where
module Data.Hashable.Color
( -- * Determine color
rgbHash,

import Data.Bits (shiftR, (.&.))
-- * Show items with color
rgbHashBgShow,
rgbHashFgShow,
)
where

import Data.Bits (shiftR, xor, (.&.))
import Data.Colour (Colour)
import Data.Colour.SRGB (sRGB24)
import Data.Hashable (Hashable (hash))
import Data.Word (Word8)

type Col8 = (Word8, Word8, Word8)

_word8 :: Int -> Word8
_word8 = toEnum . (255 .&.)

_scramble :: Int -> Int
_scramble x = (shiftR x 16 `xor` x) * 0x45d9f3b

_rgbHash :: (Hashable a) => a -> Col8
_rgbHash x = (r, g, b)
where
h = _scramble (_scramble (hash x))
b = _word8 h
g = _word8 (shiftR h 8)
r = _word8 (shiftR h 16)

_ansiSeq :: Int -> Col8 -> String -> String
_ansiSeq md ~(r, g, b) = (("\027[" ++ show md ++ ";2;" ++ show r ++ ";" ++ show g ++ ";" ++ show b ++ "m") ++)

_ansiSeqBg :: Col8 -> String -> String
_ansiSeqBg = _ansiSeq 48

_ansiSeqFg :: Col8 -> String -> String
_ansiSeqFg = _ansiSeq 38

_altColor :: Col8 -> Col8
_altColor ~(r, g, b)
| 1063 * fromEnum r + 3576 * fromEnum g + 361 * fromEnum b <= 200000 = (255, 255, 255)
| otherwise = (0, 0, 0)
Comment on lines +56 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the luminance calculation weights.

The contrast calculation uses non-standard luminance weights. Consider using ITU-R BT.709 standard weights for more accurate perceptual luminance.

The current weights (1063, 3576, 361) don't match standard RGB-to-luminance coefficients. Standard weights are approximately (2990, 5870, 1140) when scaled to avoid floating-point arithmetic. Please verify if these custom weights are intentional or if they should be updated to standard values.


🌐 Web query:

What are the standard ITU-R BT.709 RGB to luminance conversion coefficients?

💡 Result:

ITU-R BT.709 defines specific coefficients for converting RGB values to luminance (Y'). According to the standard, the RGB to luminance conversion coefficients are:

  • Red (R): 0.2126
  • Green (G): 0.7152
  • Blue (B): 0.0722[4][7][8]

These coefficients are used in the following formula to derive the luminance signal:

Y' = 0.2126 × R' + 0.7152 × G' + 0.0722 × B'[4][5]

Where R', G', and B' represent the gamma-corrected (non-linear) RGB values.

The BT.709 coefficients differ from those used in other standards like Rec.601/BT.601, which uses 0.299 for red, 0.587 for green, and 0.114 for blue[7]. This difference is significant because the BT.709 coefficients are designed to match the primaries and white points of HDTV systems, allowing luma to correspond more closely to actual luminance[8].

For the constant luminance version (YC') specified in BT.2020, slightly different coefficients are used:

  • Red: 0.2627
  • Green: 0.6780
  • Blue: 0.0593[5]

The BT.709 standard is primarily used for HDTV applications and has become the basis for many modern video formats and color space conversions.

Citations:


Use ITU-R BT.709 Luminance Weights for Contrast

The _altColor function currently applies custom weights (1063, 3576, 361) which don’t align with BT.709’s perceptual luminance coefficients. Update to the standard values and adjust the threshold accordingly.

• File: src/Data/Hashable/Color.hs
Lines: 56–59

Suggested diff:

-altColor :: Col8 -> Col8
-_altColor ~(r, g, b)
-  | 1063 * fromEnum r + 3576 * fromEnum g + 361 * fromEnum b <= 200000 = (255, 255, 255)
-  | otherwise = (0, 0, 0)
+_altColor :: Col8 -> Col8
+_altColor ~(r, g, b)
+  -- BT.709 Y' = 0.2126R + 0.7152G + 0.0722B (scaled to integers)
+  | 2126 * fromEnum r + 7152 * fromEnum g +  722 * fromEnum b <= 400000 = (255, 255, 255)
+  | otherwise = (0,   0,   0)

• New threshold 400000 preserves the original 15.7% luminance cutoff (200 000/1 275 000 ≈ 0.157 → 0.157×2 550 000 ≈ 400 000).

This ensures more accurate, standard-compliant contrast decisions.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_altColor :: Col8 -> Col8
_altColor ~(r, g, b)
| 1063 * fromEnum r + 3576 * fromEnum g + 361 * fromEnum b <= 200000 = (255, 255, 255)
| otherwise = (0, 0, 0)
_altColor :: Col8 -> Col8
_altColor ~(r, g, b)
-- BT.709 Y' = 0.2126R + 0.7152G + 0.0722B (scaled to integers)
| 2126 * fromEnum r + 7152 * fromEnum g + 722 * fromEnum b <= 400000 = (255, 255, 255)
| otherwise = (0, 0, 0)
🤖 Prompt for AI Agents
In src/Data/Hashable/Color.hs around lines 56 to 59, the _altColor function uses
non-standard luminance weights (1063, 3576, 361) for contrast calculation.
Replace these with the ITU-R BT.709 standard weights scaled appropriately (2990
for red, 5870 for green, and 1140 for blue) and update the luminance threshold
to 400000 to maintain the original contrast cutoff. This change aligns the
luminance calculation with perceptual standards for better accuracy.


-- | Convert a given 'Hashable' object to a 'Colour' by determining the hash, and using the last 24 bits as source for the red, green, and blue channels of the 'Colour' to construct.
rgbHash ::
(Hashable a, Floating b, Ord b) =>
Expand All @@ -32,7 +67,28 @@ rgbHash ::
Colour b
rgbHash x = sRGB24 r g b
where
h = hash x
b = _word8 h
g = _word8 (shiftR h 8)
r = _word8 (shiftR h 16)
~(r, g, b) = _rgbHash x

-- | Show the given 'Hashable' object with ANSI terminal codes such that the background is colorized by the hash of that object. This will only work if the terminal supports [24-bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit). The foreground is either black or white depending on whether the color is more light or dark.
rgbHashBgShow ::
(Hashable a, Show a) =>
-- | The 'Hashable' object to print in a colorized way.
a ->
-- | A String with ANSI terminal codes to colorize the object
String
rgbHashBgShow x = _ansiSeqBg c (_ansiSeqFg ca (shows x "\027[0m"))
where
c = _rgbHash x
ca = _altColor c

-- | Show the given 'Hashable' object with ANSI terminal codes such that the foreground is colorized by the hash of that object. This will only work if the terminal supports [24-bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit). The background is either black or white depending on whether the color is more light or dark.
rgbHashFgShow ::
(Hashable a, Show a) =>
-- | The 'Hashable' object to print in a colorized way.
a ->
-- | A String with ANSI terminal codes to colorize the object
String
rgbHashFgShow x = _ansiSeqFg c (_ansiSeqBg ca (shows x "\027[0m"))
where
c = _rgbHash x
ca = _altColor c
Loading