はじめに
最近、matplotlibのtableの使い方を覚えて、色々試していたのだが、昔投稿した以下の記事について、気になることがあった。
背景色に応じた文字色の選定が今ひとつしっくりこない。このため文字色を白にするか黒にするかについて、すっきりさせることにした。
参考にさせてもらったのは、以下の記事。
コントラスト比というものを用いれば、背景色に合わせた文字色をうまく選定できそうである。
コントラスト比の計算
コントラスト比
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 にするかの選択が必要となる。
作成画像事例
プログラム
色一覧画像を作成するプログラムを以下に示す。
背景色の選定は、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()
以 上