Commit 49b183c5 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Estimate the brightness of the primary color (#10071)

If the caller doesn't explicitly give the brightness of the primary
color, we now estimate it using an algorithm from the Web Content
Accessibility Guidelines.

Also, this patch contains a function that converts RGB colors to
HSVColors. I was originally going to use that, but the WCAG algorithm
ended up seeming like a better choice. The patch still includes this
function because it's generally useful.

Fixes #5718
parent c5cf8e01
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' show Color, hashValues;
import 'package:flutter/foundation.dart';
......@@ -42,6 +43,32 @@ const Color _kLightThemeSplashColor = const Color(0x66C8C8C8);
const Color _kDarkThemeHighlightColor = const Color(0x40CCCCCC);
const Color _kDarkThemeSplashColor = const Color(0x40CCCCCC);
// See <https://www.w3.org/TR/WCAG20/#relativeluminancedef>
double _linearizeColorComponent(double component) {
if (component <= 0.03928)
return component / 12.92;
return math.pow((component + 0.055) / 1.055, 2.4);
}
Brightness _estimateBrightnessForColor(Color color) {
// See <https://www.w3.org/TR/WCAG20/#relativeluminancedef>
final double R = _linearizeColorComponent(color.red / 0xFF);
final double G = _linearizeColorComponent(color.green / 0xFF);
final double B = _linearizeColorComponent(color.blue / 0xFF);
final double L = 0.2126 * R + 0.7152 * G + 0.0722 * B;
// See <https://www.w3.org/TR/WCAG20/#contrast-ratiodef>
// The spec says to use kThreshold=0.0525, but Material Design appears to bias
// more towards using light text than WCAG20 recommends. Material Design spec
// doesn't say what value to use, but 0.15 seemed close to what the Material
// Design spec shows for its color palette on
// <https://material.io/guidelines/style/color.html#color-color-palette>.
const double kThreshold = 0.15;
if ((L + 0.05) * (L + 0.05) > kThreshold )
return Brightness.light;
return Brightness.dark;
}
/// Holds the color and typography values for a material design theme.
///
/// Use this class to configure a [Theme] widget.
......@@ -107,10 +134,10 @@ class ThemeData {
final bool isDark = brightness == Brightness.dark;
primarySwatch ??= Colors.blue;
primaryColor ??= isDark ? Colors.grey[900] : primarySwatch[500];
primaryColorBrightness ??= Brightness.dark;
primaryColorBrightness ??= _estimateBrightnessForColor(primaryColor);
final bool primaryIsDark = primaryColorBrightness == Brightness.dark;
accentColor ??= isDark ? Colors.tealAccent[200] : primarySwatch[500];
accentColorBrightness ??= isDark ? Brightness.light : Brightness.dark;
accentColorBrightness ??= _estimateBrightnessForColor(accentColor);
final bool accentIsDark = accentColorBrightness == Brightness.dark;
canvasColor ??= isDark ? Colors.grey[850] : Colors.grey[50];
scaffoldBackgroundColor ??= canvasColor;
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' show Color, lerpDouble, hashValues;
import 'package:flutter/foundation.dart';
......@@ -23,6 +24,37 @@ class HSVColor {
assert(saturation != null),
assert(value != null);
/// Creates an [HSVColor] from an RGB [Color].
///
/// This constructor does not necessarily round-trip with [toColor] because
/// of floating point imprecision.
factory HSVColor.fromColor(Color color) {
final double alpha = color.alpha / 0xFF;
final double red = color.red / 0xFF;
final double green = color.green / 0xFF;
final double blue = color.blue / 0xFF;
final double max = math.max(red, math.max(green, blue));
final double min = math.min(red, math.min(green, blue));
final double delta = max - min;
double hue = 0.0;
if (max == 0.0) {
hue = 0.0;
} else if (max == red) {
hue = 60.0 * (((green - blue) / delta) % 6);
} else if (max == green) {
hue = 60.0 * (((blue - red) / delta) + 2);
} else if (max == blue) {
hue = 60.0 * (((red - green) / delta) + 4);
}
final double saturation = max == 0.0 ? 0.0 : delta / max;
return new HSVColor.fromAHSV(alpha, hue, saturation, max);
}
/// Alpha, from 0.0 to 1.0.
final double alpha;
......
......@@ -89,4 +89,17 @@ void main() {
expect(themeData.primaryTextTheme.title.fontFamily, equals('Ahem'));
expect(themeData.accentTextTheme.display4.fontFamily, equals('Ahem'));
});
test('Can estimate brightness', () {
expect(new ThemeData(primaryColor: Colors.white).primaryColorBrightness, equals(Brightness.light));
expect(new ThemeData(primaryColor: Colors.black).primaryColorBrightness, equals(Brightness.dark));
expect(new ThemeData(primaryColor: Colors.blue).primaryColorBrightness, equals(Brightness.dark));
expect(new ThemeData(primaryColor: Colors.yellow).primaryColorBrightness, equals(Brightness.light));
expect(new ThemeData(primaryColor: Colors.deepOrange).primaryColorBrightness, equals(Brightness.dark));
expect(new ThemeData(primaryColor: Colors.orange).primaryColorBrightness, equals(Brightness.light));
expect(new ThemeData(primaryColor: Colors.lime).primaryColorBrightness, equals(Brightness.light));
expect(new ThemeData(primaryColor: Colors.grey).primaryColorBrightness, equals(Brightness.light));
expect(new ThemeData(primaryColor: Colors.teal).primaryColorBrightness, equals(Brightness.dark));
expect(new ThemeData(primaryColor: Colors.indigo).primaryColorBrightness, equals(Brightness.dark));
});
}
......@@ -26,4 +26,16 @@ void main() {
expect(result.saturation, lessThan(0.4001));
expect(result.value, 0.5);
});
test('HSVColor.fromColor test', () {
final HSVColor black = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0x00, 0x00, 0x00));
final HSVColor red = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00));
final HSVColor green = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0x00, 0xFF, 0x00));
final HSVColor blue = new HSVColor.fromColor(const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF));
expect(black.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0x00, 0x00)));
expect(red.toColor(), equals(const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00)));
expect(green.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0xFF, 0x00)));
expect(blue.toColor(), equals(const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF)));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment