JavaScript ハッシュ化して適当な色をつけよう

まえがき

特定の名称やカテゴリーごとに、表示色を変えたい事はあるでしょう。
ただ、その数が多すぎるといちいち色を考えるのも面倒になってしまいます。

そんな時は、ハッシュ値をもとに、
カラーコードを生成して利用すると効率が良いです。

この記事では、JavaScriptで
好きな文字列をハッシュ化して色コードを生成する方法と、
背景とフォントの視認性を配慮した、色の生成方法なども紹介します。

実は以前、Java実装で書いたものですが、
JavaScript実装でも欲しいなぁと思い書きなおしてみました。

Hashとは

ハッシュ値とは、任意の文字列やバイト配列を
特定のバイト長に変換したものです。
しかも同じ入力値であれば毎回、同じ出力が得られます。

しかし、JavaScript にはハッシュ関数が、
デフォルトで用意されてないので、
サードパーティのライブラリを使うしかなさそうです。

RGBのカラーコードを作るには、
8ビット(0-255)の数値が3つ必要になります。

今回は、4バイト(32bit)のハッシュを作ってくれる、
CRC32というハッシュアルゴリズムを、使っていきます。

js-crc32 ライブラリ

js-crc32
CRC32用のJSライブラリです。
https://github.com/SheetJS/js-crc32

上記のライブラリは、CDNでも提供されてるので、
今回はそれを利用したいと思います。

CDNで利用する場合は、↓リンクを参照
https://cdnjs.com/libraries/crc-32

HTMLのヘッダーに、scriptタグで指定できます。

<!-- CRC32ライブラリを取得 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crc-32/1.2.0/crc32.min.js"></script>

js-crc32ライブラリには、
下記のとおり、3つのメソッドが用意されています。

  • CRC32.buf(...)
    8-bit unsigned integerの配列を渡す用

  • CRC32.bstr(...)
    バイナリ文字列を指定する用

  • CRC32.str(...)
    文字列を指定する用

今回は、文字列からハッシュ化したいので、strメソッドを使います。

作ってみよう

ここからが実装の説明となります。
必要なメソッドなど書いていきます。

Hash化とRGB値の取得

/**
 * 指定文字列をCRC32でハッシュ化
 * 1バイトずつ、配列で取得。(4バイト分)
 *
 * @param  text string
 * @returns {number[]}
 */
function hashBytes(text) {
    let i32 = CRC32.str(text);

    return [
        (i32 & 0xFF000000) >>> 24,
        (i32 & 0x00FF0000) >>> 16,
        (i32 & 0x0000FF00) >>> 8,
        (i32 & 0x000000FF) >>> 0,
    ];
}

CRC32のstrメソッドを使って、文字列をハッシュ化します。
ハッシュ化すると、32ビットのintergerになるので、
ビット演算で、8ビットずつ取り出し、配列にしています。

上位ビットから順番に、R・G・Bとしています。
4バイト目は、余ってしまうんですが含めちゃってます。

反転色の生成

実装サンプルでは、背景色をハッシュ化した値をもとに色付けするつもりです。
そして、背景色の上に描画するテキストのフォント色は反転色を使います。

反転色は、ビットを反転させるだけです。

// 反転色
let fc = [
    ~bg[0] & 0xff,
    ~bg[1] & 0xff,
    ~bg[2] & 0xff,
];

ビット反転後は、0xff (8bit分)でマスクしています。
JavaScriptのnumberは実際には8bitじゃないので、
上位ビットが邪魔になるからですね〜。

明度差で視認性を配慮する

明度差が小さいと、文字と背景の区別が付きにくくなり、
視認性が下がってしまいます。
そこで、明度差が小さい場合には、
フォント色を、白または、黒に差し替えると、視認性を確保することができます。

下記はその為の、明るさ計算メソッドですね。
RGBの値をもとに、明るさって計算できるんですよ。

/**
 * RGB値から、明るさをもとめます。
 * (計算の仕方はW3C参照)
 *
 * @param r number 0-255
 * @param g number 0-255
 * @param b number 0-255
 * @returns {number}
 */
function brightness(r, g, b) {
    return Math.floor(((r * 299) + (g * 587) + (b * 114)) / 1000);
}

W3Cによると、
明度差が125未満の場合に、視認性が低いのだと言っています。
(明度も0-255の数値で表現されます)

// 背景とフォントの明るさ計算
let bgL = brightness(bg[0], bg[1], bg[2]);
let fcL = brightness(fc[0], fc[1], fc[2]);

// 背景とフォントの明度差が125未満なら、フォント色は白または黒にする。
// (明度差が大きい方を採用)
if (Math.abs(bgL - fcL) < 125) {
    fc[0] = fc[1] = fc[2] = ((0xff - bgL) > bgL) ? 0xff : 0x00;
}

白にするのか、黒にするのかは、
どちらが明度差が大きいのかで、判断すると良いでしょう。

実行結果

これまでのコードを利用して、
適当な文字列をハッシュ化して、
背景色と、反転色でフォント色を設定してみた結果です。

実装コード全体像 

全コードも載せておきます。

index.html

<!doctype html>
<html lang="ja">
<head>
    <title>HashColor Sample</title>
    <meta charset="utf-8">

    <!-- CRC32ライブラリを取得 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crc-32/1.2.0/crc32.min.js"></script>

</head>

<body>
<script>

    /**
     * 指定文字列をCRC32でハッシュ化
     * 1バイトずつ、配列で取得。(4バイト分)
     *
     * @param  text string
     * @returns {number[]}
     */
    function hashBytes(text) {
        let i32 = CRC32.str(text);

        return [
            (i32 & 0xFF000000) >>> 24,
            (i32 & 0x00FF0000) >>> 16,
            (i32 & 0x0000FF00) >>> 8,
            (i32 & 0x000000FF) >>> 0,
        ];
    }

    /**
     * 1バイトの数字を16進数の文字列に変換
     * 2桁になるようにゼロ埋めします
     *
     * @param b number
     * @returns {string}
     */
    function toHex(b) {
        let str = b.toString(16);
        if (2 <= str.length) {
            return str;
        }

        return "0" + str;
    }

    /**
     * スタイル属性に指定できるように、
     * バイト配列を色コードの文字列に変換します。
     *
     * @param bytes number[]
     * @returns {string}
     */
    function toColorCode(bytes) {
        return "#" + toHex(bytes[0]) + toHex(bytes[1]) + toHex(bytes[2]);
    }

    /**
     * RGB値から、明るさをもとめます。
     * (計算の仕方はW3C参照)
     *
     * @param r number 0-255
     * @param g number 0-255
     * @param b number 0-255
     * @returns {number}
     */
    function brightness(r, g, b) {
        return Math.floor(((r * 299) + (g * 587) + (b * 114)) / 1000);
    }

    // サンプル用の適当な文字列
    let textList = [
        "overwhelm",
        "population",
        "litigation",
        "package",
        "net",
        "particular",
        "west",
        "current",
        "witch",
        "wilderness",
        "wriggle",
        "scheme",
        "bulletin",
        "theory",
        "bracket",
    ];

    for (let i = 0; i < textList.length; i++) {
        let text = textList[i];

        // 文字列をハッシュ化
        let bg = hashBytes(text);

        // 反転色
        let fc = [
            ~bg[0] & 0xff,
            ~bg[1] & 0xff,
            ~bg[2] & 0xff,
        ];

        // 背景とフォントの明るさ計算
        let bgL = brightness(bg[0], bg[1], bg[2]);
        let fcL = brightness(fc[0], fc[1], fc[2]);

        // 背景とフォントの明度差が125未満なら、フォント色は白または黒にする。
        // (明度差が大きい方を採用)
        if (Math.abs(bgL - fcL) < 125) {
            fc[0] = fc[1] = fc[2] = ((0xff - bgL) > bgL) ? 0xff : 0x00;
        }

        // スタイル用の色コード文字列に成形。
        let bgCode = toColorCode(bg);
        let fcCode = toColorCode(fc);

        // pタグで描画
        document.writeln(`<p style="background:${bgCode}; color:${fcCode}">${text}</p>`);
    }

</script>
</body>
</html>

まとめ

  • JavaScriptにはハッシュ関数がないので、ライブラリを利用する。 
  • 4バイト長のハッシュを生成しくれる、CRC32というアルゴリズムを使う
  • 反転色は視認性がわるい場合があるので、明度差で判定する。

タイプや、カテゴリの色分けをしたい場合などに、
自分で色を考えなくても、ちゃんと色分けしてくれるので、
便利に使えるかと思います。


Java実装でも同じようなことをした記事もあるので、
こちらにリンクを貼っておきます。

文字列を適当な色に変換する。(Java)

コメントを残す