packages feed

colorhash-0.2.0.0: src/Data/Hashable/Color.hs

{-# LANGUAGE Safe #-}

-- |
-- Module      : Data.Hashable.Color
-- Description : A module to work convert a 'Hashable' object to a 'Colour'
-- Maintainer  : hapytexeu+gh@gmail.com
-- Stability   : experimental
-- Portability : POSIX
--
-- Hashes are an easy way to distinguish between two objects since it is not impossible that two random objects have the same hash, but it is very unlikely.
-- But for humans, a hash is often still not very convenient. Colors are an easier way to distinguish. For example one can make a list of items, and give
-- 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
  ( -- * Determine color
    rgbHash,

    -- * 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)

-- | 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) =>
  -- | The 'Hashable' object to convert to a color.
  a ->
  -- | The corresponding color to use.
  Colour b
rgbHash x = sRGB24 r g b
  where
    ~(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