feat: color a site from its address, and offer a failed web page to the system browser
This commit is contained in:
parent
56db479df0
commit
9d9f09ebc7
24 changed files with 597 additions and 25 deletions
90
app/lib/presentation/reader/oklch.dart
Normal file
90
app/lib/presentation/reader/oklch.dart
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// OkLCH color math. Lightness and chroma are perceptual here, so a palette
|
||||
/// can be moved to another hue without the per-hue lightness corrections that
|
||||
/// rotating an HSL hue would need.
|
||||
|
||||
/// Builds a color from lightness (0..1), chroma and hue (degrees). Chroma is
|
||||
/// reduced until the color fits in sRGB, so a hue that can't hold the asked
|
||||
/// chroma comes back muted rather than clipped to a wrong hue.
|
||||
Color oklch(double lightness, double chroma, double hue) {
|
||||
var low = 0.0;
|
||||
var high = chroma;
|
||||
if (_fitsSrgb(_linearSrgb(lightness, chroma, hue))) {
|
||||
return _color(_linearSrgb(lightness, chroma, hue));
|
||||
}
|
||||
for (var i = 0; i < 16; i++) {
|
||||
final mid = (low + high) / 2;
|
||||
if (_fitsSrgb(_linearSrgb(lightness, mid, hue))) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
return _color(_linearSrgb(lightness, low, hue));
|
||||
}
|
||||
|
||||
/// The WCAG contrast ratio, from 1 (identical) to 21 (black on white).
|
||||
double contrastRatio(Color a, Color b) {
|
||||
final la = _relativeLuminance(a);
|
||||
final lb = _relativeLuminance(b);
|
||||
final lighter = math.max(la, lb);
|
||||
final darker = math.min(la, lb);
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
}
|
||||
|
||||
/// How far a color sits from gray, in the same units [oklch] takes: the
|
||||
/// bigger it is, the more vivid the color looks.
|
||||
double chromaOf(Color color) {
|
||||
double linear(double value) =>
|
||||
value <= 0.04045 ? value / 12.92 : math.pow((value + 0.055) / 1.055, 2.4) as double;
|
||||
final r = linear(color.r);
|
||||
final g = linear(color.g);
|
||||
final b = linear(color.b);
|
||||
final l = math.pow(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b, 1 / 3) as double;
|
||||
final m = math.pow(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b, 1 / 3) as double;
|
||||
final s = math.pow(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b, 1 / 3) as double;
|
||||
final a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s;
|
||||
final bb = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s;
|
||||
return math.sqrt(a * a + bb * bb);
|
||||
}
|
||||
|
||||
List<double> _linearSrgb(double lightness, double chroma, double hue) {
|
||||
final radians = hue * math.pi / 180;
|
||||
final a = chroma * math.cos(radians);
|
||||
final b = chroma * math.sin(radians);
|
||||
final l = math.pow(lightness + 0.3963377774 * a + 0.2158037573 * b, 3) as double;
|
||||
final m = math.pow(lightness - 0.1055613458 * a - 0.0638541728 * b, 3) as double;
|
||||
final s = math.pow(lightness - 0.0894841775 * a - 1.2914855480 * b, 3) as double;
|
||||
return [
|
||||
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
||||
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
||||
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
|
||||
];
|
||||
}
|
||||
|
||||
bool _fitsSrgb(List<double> rgb) =>
|
||||
rgb.every((channel) => channel >= -0.0001 && channel <= 1.0001);
|
||||
|
||||
Color _color(List<double> rgb) => Color.fromARGB(
|
||||
255,
|
||||
_encode(rgb[0]),
|
||||
_encode(rgb[1]),
|
||||
_encode(rgb[2]),
|
||||
);
|
||||
|
||||
int _encode(double channel) {
|
||||
final clamped = channel.clamp(0.0, 1.0);
|
||||
final encoded = clamped <= 0.0031308
|
||||
? 12.92 * clamped
|
||||
: 1.055 * math.pow(clamped, 1 / 2.4) - 0.055;
|
||||
return (encoded * 255).round().clamp(0, 255);
|
||||
}
|
||||
|
||||
double _relativeLuminance(Color color) {
|
||||
double channel(double value) =>
|
||||
value <= 0.04045 ? value / 12.92 : math.pow((value + 0.055) / 1.055, 2.4) as double;
|
||||
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue