8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

文字を読みやすく! コントラスト比を計算して文字色を自動選択

Posted at

コントラスト比

文字の読みやすさは、文字色と背景色のコントラスト比 (contrast ratio) に大きく左右されます。

コントラスト比が小さいと、下の文のように読みにくくなってしまいます。
コントラスト比が小さいと、上の文のように読みにくくなってしまいます。

コントラスト比は、ざっくり言うと色の明るさの比です。
コントラスト比が小さい状態は、明るい背景に明るい文字を置いたり、暗い背景に暗い文字を置いたりしている状態であり、文字は読みにくいです。
文字を読みやすくするため、コントラスト比はある程度大きくあるべきです(隠したい要素でない限り)。

コントラスト比は 1 以上 21 以下の値であり、大きいほど明暗の差がはっきりしています。
だいたいの基準として、文字を読むには 4.5 以上はあったほうがよいです。
4.5 未満だと Chrome の開発者ツールに怒られます。

Chrome の開発者ツールの表示。コントラスト比が 3.23 であるので警告が出ている。

コントラスト比の具体的な計算方法は Web Content Accessibility Guidelines (WCAG) 2.2 に掲載されています。

contrast ratio
(L1 + 0.05) / (L2 + 0.05), where

https://www.w3.org/TR/WCAG22/#dfn-contrast-ratio

ここに出てくる相対輝度 (relative luminance) は、最も暗い黒を 0 、最も明るい白を 1 に正規化した、色空間内での相対的な明るさのことのようです。
ある色をリニアRGBで表したときの各要素を0から1に正規化した値をそれぞれ R, G, B とするとき、その相対輝度 L は
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
で求まります。

Siv3D では、相対輝度、コントラスト比を得る関数は次のように書けます。

[[nodiscard]]
double GetRelativeLuminance(const ColorF &color) noexcept
{
	const auto [r, g, b, a] = color.removeSRGBCurve();
	return (0.2126 * r + 0.7152 * g + 0.0722 * b);
}

[[nodiscard]]
double GetContrastRatio(const ColorF& color1, const ColorF& color2) noexcept
{
	const auto l1 = GetRelativeLuminance(color1);
	const auto l2 = GetRelativeLuminance(color2);
	return ((Max(l1, l2) + 0.05) / (Min(l1, l2) + 0.05));
}

背景色に合わせた文字色の自動選択

文字のある画面でユーザが背景色を変更できるとき、文字色もそれに合わせて読みやすい色に変更する必要があります。
ライトテーマとダークテーマを用意するぐらいであればそれぞれに適切な文字色を設定しておけばよいですが、何十通りにも増えると面倒だし、完全に自由に設定できる場合は不可能です。

そこで、コントラスト比を用いて文字色を選択します。
じつは、文字色を #000000#FFFFFF の2色から選ぶだけでも、背景色とのコントラスト比が 4.5 を下回らないようにできます。

おまけ:コントラスト比が 4.5 を下回らないようにできることの証明

相対輝度が $L$ である色を考える。
その色と #000000#FFFFFF のコントラスト比をそれぞれ $C_\text{Black},C_\text{White}$ とすると、それらはそれぞれ次のようになる。

\begin{align*}
C_\text{Black} &= \frac{L+0.05}{0.05}, & C_\text{White} &= \frac{1.05}{L+0.05}
\end{align*}

ここで、コントラスト比が大きいほうを背景色として選択するようにする。
このとき、コントラスト比 $C$ は $C=\max(C_\text{Black},C_\text{White})$ である。

$C$ が最小値 $C_\text{min}$ をとるとき、 $C_\text{Black}=C_\text{White}$ が成り立つから、

\begin{align*}
C_\text{min} &= \sqrt{C_\text{Black}C_\text{White}}
= \sqrt{\frac{1.05}{0.05}}
= \sqrt{21}
\end{align*}

$C_\text{min} = \sqrt{21} > \sqrt{20.25} = 4.5$ である。(証明終わり)

# include <Siv3D.hpp> // Siv3D v0.6.15

// `color` の相対輝度を返します。
// https://www.w3.org/TR/WCAG22/#dfn-relative-luminance
[[nodiscard]]
double GetRelativeLuminance(const ColorF &color) noexcept
{
	const auto [r, g, b, a] = color.removeSRGBCurve();
	return (0.2126 * r + 0.7152 * g + 0.0722 * b);
}

// `color1` と `color2` のコントラスト比を返します。
// https://www.w3.org/TR/WCAG22/#dfn-contrast-ratio
[[nodiscard]]
double GetContrastRatio(const ColorF& color1, const ColorF& color2) noexcept
{
	const auto l1 = GetRelativeLuminance(color1);
	const auto l2 = GetRelativeLuminance(color2);
	return ((Max(l1, l2) + 0.05) / (Min(l1, l2) + 0.05));
}

void Main()
{
	const Font fontLight{ FontMethod::MSDF, 48, Typeface::Light };
	const Font fontBold{ FontMethod::MSDF, 48, Typeface::Bold };

	HSV backgroundColor{ 210, 0.2, 1.0 };
	Scene::SetBackground(backgroundColor);
	
	while (System::Update())
	{
		SimpleGUI::Headline(U"Background", Vec2{ 20, 20 }, 160);
		if (SimpleGUI::ColorPicker(backgroundColor, Vec2{ 20, 60 }))
		{
			Scene::SetBackground(backgroundColor);
		}

		// コントラスト比を比較して白か黒か選択する
		const auto contrastBlack = GetContrastRatio(backgroundColor, Palette::Black);
		const auto contrastWhite = GetContrastRatio(backgroundColor, Palette::White);
		const auto foregroundColor = ((contrastBlack < contrastWhite) ? Palette::White : Palette::Black);

		fontBold(U"Hello, Siv3D!").drawAt(96, Scene::CenterF(), foregroundColor);

		const auto contrast = Max(contrastBlack, contrastWhite);
		const auto luminanceForeground = GetRelativeLuminance(foregroundColor);
		const auto luminanceBackground = GetRelativeLuminance(backgroundColor);
		const auto debugText =
			U"Contrast Ratio: {:.3f}\n"
			U"Luminance (fore): {}\n"
			U"Luminance (back): {:.3f}\n"_fmt(contrast, luminanceForeground, luminanceBackground);
		fontLight(debugText).drawAt(24, Scene::CenterF().movedBy(0, 150), foregroundColor);
	}
}

背景色によって文字色が自動的に切り替わり、常に見やすい状態を維持します。

読みやすくする別の方法:枠をつける

元も子もないですが、文字にコントラスト比の大きい枠を付ければ、文字色を変えずに読みやすさを保てます。

# include <Siv3D.hpp> // Siv3D v0.6.15

void Main()
{
	const Font fontBold{ FontMethod::MSDF, 48, Typeface::Bold };
	fontBold.setBufferThickness(3);

	HSV backgroundColor{ 210, 0.2, 1.0 };
	Scene::SetBackground(backgroundColor);
	
	while (System::Update())
	{
		SimpleGUI::Headline(U"Background", Vec2{ 20, 20 }, 160);
		if (SimpleGUI::ColorPicker(backgroundColor, Vec2{ 20, 60 }))
		{
			Scene::SetBackground(backgroundColor);
		}

		fontBold(U"Hello, Siv3D!").drawAt(TextStyle::Outline(0.2, Palette::Black), 96, Scene::CenterF(), Palette::White);
	}
}

こっちの方が簡単でいいですね。

参考サイト

8
7
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
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?