ボタンやラベルなどの背景色を自由に変更できるアプリを作成したときに、見やすい文字色を選ぶのはけっこう大変です。
また、背景色と文字色をそれぞれ設定値に持たせるのも良いですが、自動で見やすい文字色を設定できれば楽ですよね。
見やすい文字色とは
背景色と文字色のコントラスト比が高いものが見やすい文字となります。
今回は、W3C が提唱する見やすいウェブサイトの規格 WCAG 2.0 を参考にしました。
WCAG 2.0 とは?
公式Webサイト ( https://waic.jp/docs/WCAG20/Overview.html ) から抜粋した内容です。
Web Content Accessibility Guidelines (WCAG) 2.0 は、ウェブコンテンツをよりアクセシブルにするための広範囲に及ぶ推奨事項を網羅している。 このガイドラインに従うことで、全盲又はロービジョン、ろう又は難聴、学習障害、認知障害、運動制限、発話困難、光過敏性発作及びこれらの組合せ等を含んだ、様々な障害のある人に対して、コンテンツをアクセシブルにすることができる。又、このガイドラインに従うと、多くの場合、ほとんどの利用者にとってウェブコンテンツがより使いやすくなる。
特に最後の行が重要です。
又、このガイドラインに従うと、多くの場合、ほとんどの利用者にとってウェブコンテンツがより使いやすくなる。
今回はWebではありませんが、『コントラストの達成基準』をこちらの規格で判定し、誰にでも見やすい文字色を目指してみます。
ざっくり仕様
- 文字色は黒文字か白文字のどちらかを選ぶケースがほとんどだと思います。なので、黒文字か白文字のどちらが見やすいか判定します。
- 背景色は単一色を想定してます。グラデーションのときは代表色として中間色あたりを選んでください。
- WCAG 2.0 の変換式を使い、背景色と黒文字・白文字でより高コントラストの方を採用します。
( https://waic.jp/docs/WCAG-TECHS/G17.html )
コード
using System.Drawing;
// RGB から相対輝度を算出(0.0 ~ 1.0)
public static double RelativeLuminance(byte R, byte G, byte B)
{
// RGB の各値を相対輝度算出用に変換
Func<byte, double> toRgb = (rgb) => {
double srgb = (double)rgb / 255;
return srgb <= 0.03928 ? srgb / 12.92 : Math.Pow((srgb + 0.055) / 1.055, 2.4);
};
return 0.2126 * toRgb(R) + 0.7152 * toRgb(G) + 0.0722 * toRgb(B);
}
// 2つの相対輝度値から、相対輝度比率を算出(0.0 ~ 21.0)
// 相対輝度比率が 7.0 以上の値だと見やすい
public static double RelativeLuminanceRatio(double relativeLuminance1, double relativeLuminance2)
{
// 相対輝度比率 = (大きい値 + 0.05) / (小さい値 + 0.05)
return (Math.Max(relativeLuminance1, relativeLuminance2) + 0.05) / (Math.Min(relativeLuminance1, relativeLuminance2) + 0.05);
}
// 背景色から白文字か黒文字を判定
public static Color chooseTextColor(byte R, byte G, byte B)
{
// 背景色の相対輝度
double background = RelativeLuminance(R, G, B);
const double white = 1.0D; // 白の相対輝度
const double black = 0.0D; // 黒の相対輝度
// 文字色と背景色のコントラスト比を計算
double whiteContrast = RelativeLuminanceRatio(white, background); // 文字色:白との比
double blackContrast = RelativeLuminanceRatio(black, background); // 文字色:黒との比
// コントラスト比が大きい文字色を採用
return whiteContrast < blackContrast ? Color.Black : Color.White;
}
使用方法
rgb(125, 40, 80)
であれば、白文字が選択されます。
// textColor = Color.White が選ばれる
Color textColor = chooseTextColor(125, 40, 80);
rgb(200, 160, 180)
であれば、黒文字が選択されます。
// textColor = Color.Black が選ばれる
Color textColor = chooseTextColor(200, 160, 180);
補足1
引数 Color のオーバーロードを追加すると、コントロールの背景色プロパティから設定ができて便利です。
public static Color chooseTextColor(Color color)
{
return chooseTextColor(color.R, color.G, color.B);
}
利用時は以下の通りです。
button1.ForeColor = chooseTextColor(button1.BackColor);
補足2
下記の部分はただのローカル関数なので、
// RGB の各値を相対輝度算出用に変換
Func<byte, double> toRgb = (rgb) => {
double srgb = (double)rgb / 255;
return srgb <= 0.03928 ? srgb / 12.92 : Math.Pow((srgb + 0.055) / 1.055, 2.4);
};
toRgb 関数として別の関数にしてもOKです。
ラムダ式やデリゲートがわかっていない人には、こちらのほうがわかりやすいかもしれません。
// RGB の各値を相対輝度算出用に変換
private static double toRgb(byte rgb)
{
double srgb = (double)rgb / 255;
return srgb <= 0.03928 ? srgb / 12.92 : Math.Pow((srgb + 0.055) / 1.055, 2.4);
}
// RGB から相対輝度を算出(0.0 ~ 1.0)
public static double RelativeLuminance(byte R, byte G, byte B)
{
return 0.2126 * toRgb(R) + 0.7152 * toRgb(G) + 0.0722 * toRgb(B);
}
ラムダ式使ってローカル関数化しているのは、ただの僕の好みです。
RelativeLuminance 関数でしか使用しない関数 toRgb を別の関数とにすると、改修などの際に「他の関数でも使用しているかもしれない」というチェックが必要になります。
RelativeLuminance 関数内でのみ使用する小さな関数は、ローカル関数にしておいたほうが RelativeLuminance 関数の独立性が高く、メンテナンスもしやすくなります。
toRgb 関数を他の関数と共有する状態になったときに、初めて別関数へ切り出せば良いと思ってます。
会社ではラムダ式が浸透していない&他の制約で使えないですが(悲)
補足3
別記事で、chooseTextColor 関数を改良しています。
【C#】背景色から黒文字か白文字の見やすい方を自動判定 の数学的改良
https://qiita.com/mainy/items/b96717c4f51ef6af512f
最後に
この方法であれば、誰が設定しても見やすい文字になるかと思います。
一部見にくい背景色と文字色の組み合わせがあると思います。
文字色が黒文字か白文字という条件なので、相対輝度比率が 7.0 未満の背景色がどうしても出てきてしまいます。
例えば、赤 rgb(255,0,0)
は黒文字も白文字も相対輝度比率が 7.0 未満です。
そのときは背景色の選定が間違っていると考えてください。(逃げ)
参考
W3C - WCAG 2.0 - G17: テキスト (及び文字画像) とその背景の間に、少なくとも 7:1 のコントラスト比を確保する
https://waic.jp/docs/WCAG-TECHS/G17.html
Web 猫 - 任意の背景色に対して読みやすい文字色を選択する方法
https://katashin.info/2018/12/18/247
意識の高い時に雑記 - JavascriptでWC3のコントラスト計算式を書いてみる
https://lifehackdev.xsrv.jp/ZakkiBlog/articles/detail/web15