0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

matplotlib: 背景色に合わせた文字色選定(白か黒か?)(2025.09.05)

Last updated at Posted at 2025-09-05

はじめに

最近、matplotlibのtableの使い方を覚えて、色々試していたのだが、昔投稿した以下の記事について、気になることがあった。

背景色に応じた文字色の選定が今ひとつしっくりこない。このため文字色を白にするか黒にするかについて、すっきりさせることにした。
参考にさせてもらったのは、以下の記事。

【WCAG2.2】コントラスト比の計算方法を学ぶ

コントラスト比というものを用いれば、背景色に合わせた文字色をうまく選定できそうである。

コントラスト比の計算

コントラスト比

2つの色のコントラスト比は、相対輝度を用いて、以下の式により求めることができる。

\begin{equation}
\text{contrast ratio} \quad C=\cfrac{L_1+0.05}{L_2+0.05}
\end{equation}
\begin{align}
&L_1 & &\text{relative luminance of the lighter of the colors (明るい方の色の相対輝度)}\\
&L_2 & &\text{relative luminance of the darker of the colors (暗い方の色の相対輝度)}
\end{align}

相対輝度

コントラスト比を求めるために必要な、相対輝度は、以下の式により求めることができる。

\begin{equation}
\text{relative luminance} \quad L = 0.2126\times R + 0.7152\times G + 0.0722\times B
\end{equation}

ここで評価したい色の rgb を $(r,g,b)$とすれば、相対輝度計算には、以下の $(R,G,B)$ を用いる。

\begin{align}
&v_{i=1,2,3}=r,g,b & (0 \leqq r,g,b \leqq 1)\\
&V_{i=1,2,3}=R,G,B & (0 \leqq R,G,B \leqq 1)
\end{align}
\begin{equation}
V_i=
\begin{cases}
\cfrac{v_i}{12.92} & (v_i \leqq 0.04045) \\
\left(\cfrac{v_i+0.055}{1.055}\right)^{2.4} & (v_i > 0.04045)
\end{cases}
\end{equation}

コントラスト比の目標値と文字色選定

WCAGによれば、レベルAAAでは$7:1$以上、レベルAAでは$4.5:1$のコントラスト比が必要とされている。

コントラスト比を求める式を見た場合、白文字では白の相対輝度が$L_1$、黒文字では黒の相対輝度が$L_2$となるため、これらを考慮して文字色選定の条件式を示すと、以下の通りとなる。

\begin{equation}
C < \cfrac{L_1+0.05}{L_2+0.05}
\end{equation}
\begin{align}
&\text{白文字選定条件} & &L_2 < (L_1+0.05) / C-0.05 = 1.05/C-0.05 \quad (L_1=1)\\
&\text{黒文字選定条件} & &L_1 > C \cdot(L_2+0.05)-0.05 = 0.05\cdot C-0.05 \quad (L_2=0)
\end{align}

以上より、コントラスト比の目標値を$C=7$、背景色の相対輝度を$L_{bg}$とすれば、文字色の選定基準は、

\begin{align}
&\text{白文字選定条件} & &L_{bg} < 1.05/C-0.05 = 0.1 \quad (C=7.0)\\
&\text{黒文字選定条件} & &L_{bg} > 0.05\cdot C-0.05 =0.3 \quad (C=7.0)
\end{align}

また、コントラスト比の目標値を$C=4.5$とすれば、上記と同様に、

\begin{align}
&\text{白文字選定条件} & &L_{bg} < 1.05/C-0.05 = 0.183 \quad (C=4.5)\\
&\text{黒文字選定条件} & &L_{bg} > 0.05\cdot C-0.05 =0.175 \quad (C=4.5)
\end{align}

以上をまとめると、背景色の相対輝度$L_{bg}$に対し、以下が文字色選定の基準となる。

目標コントラスト比 白文字選定 黒文字選定
$C=7.0$ $L_{bg} < 0.1$ $0.3 < L_{bg}$
$C=4.5$ $L_{bg} < 0.18$ $0.18 < L_{bg}$

上表より、目標コントラスト比を$C=4.5$とした場合、白文字と黒文字選択の境界となる背景色相対輝度は、$L_{bg}\doteqdot 0.18$と一つの数値で定めることができる。

しかし目標コントラスト比を$C=7.0$とした場合、$0.1 < L_{bg} < 0.3$の範囲が選定領域から外れる。
この場合は、白文字を優先したいか黒文字を優先したいかで、境界値を 0.1 にするか 0.3 にするか、あるいは境界値を 0.18 にするかの選択が必要となる。

作成画像事例

fig_col_mpl.jpg

プログラム

色一覧画像を作成するプログラムを以下に示す。
背景色の選定は、pencという関数で行っている。関数に16進の色文字列(#含む)を入力するとその色の相対輝度を計算し、テキストの文字色を決定してくれる。ここでは、背景色の相対輝度が0.3以上なら黒文字、それ以下なら白文字を選定するようにしている。

import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np


def penc(hval):
    #https://zenn.dev/yend724/articles/20240202-85u5hfjvgp5irtjd
    rr=0.3 # white<rr<black
    def cal(rgb):
        for i in range(len(rgb)):
            if rgb[i] <= 0.04045: rgb[i] = rgb[i] / 12.92
            else: rgb[i] = ((rgb[i]+0.055)/1.055)**2.4
        lum=0.2126*rgb[0]+0.7152*rgb[1]+0.0722*rgb[2]
        return lum

    r=int(hval[1:3],16)/255
    g=int(hval[3:5],16)/255
    b=int(hval[6:8],16)/255
    rgb = [r,g,b]
    lum=cal(rgb)    
    col='#000000'
    if lum<rr: col='#ffffff'
    return col


def main():
    cdic=mcolors.CSS4_COLORS
    lkey=[]
    lval=[]
    for key, val in zip(cdic.keys(), cdic.values()):
        lkey.append(key)
        lval.append(val)
    key=np.array(lkey,dtype=object)
    val=np.array(lval,dtype=object)
    #ii=np.argsort(val)
    #key=key[ii]
    #val=val[ii]
    m=4
    n=len(key)//m
    sdata=np.empty((n,m),dtype=object)
    cdata=np.empty((n,m),dtype=object)
    for k in range(len(key)):
        i,j=divmod(k,m)
        sdata[i,j]=f'{key[k]}\n{val[k]}'
        cdata[i,j]=val[k]

    tw,th=1,1
    col=np.array([1]*m)
    colw=col/np.sum(col)*tw

    fsz=8 # fontsize
    xmin,xmax=0,1
    ymin,ymax=0,1
    plt.figure(figsize=(8,14),facecolor='w')
    plt.rcParams['font.family']='monospace'
    plt.xlim([xmin,xmax])
    plt.ylim([ymax,ymin])
    plt.axis('off')

    tbl=plt.table(
        cellText=sdata,
        colWidths=colw,
        cellLoc='center',
        edges='open',
        bbox=[xmin,ymin,tw,th]
    )
    # fontsize change
    tbl.auto_set_font_size(False)
    tbl.set_fontsize(fsz)

    for i in range(n):
        for j in range(m):
            col_fa=cdata[i,j]
            col_tx=penc(col_fa)
            tbl[i,j].visible_edges='closed'
            tbl[i,j].set_linewidth(0)
            tbl[i,j].set_text_props(color=col_tx)
            tbl[i,j].set_facecolor(col_fa)
    fnameF='fig_col_mpl.jpg'
    plt.savefig(fnameF, dpi=300, bbox_inches="tight", pad_inches=0)


#==============
# Execution
#==============
if __name__ == '__main__': main()

以 上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?