computeVerdict static method
Decides a control's Brightness from a raw RGBA capture.
Exposed for tests; production code goes through the sampling pipeline.
cellRects are the control's grid cells in pixel coordinates of the
width×height image. Each cell votes via WCAG contrast (light
variant's dark glyphs vs dark variant's light glyphs over the cell's
average color, with background substituted for transparent pixels);
the verdict applies sticky ties and the dual-threshold hysteresis
described on GlassContentAwareScope.
Implementation
@visibleForTesting
static Brightness computeVerdict({
required Uint8List rgba,
required int width,
required int height,
required List<Rect> cellRects,
required Brightness current,
required double lightToDarkThreshold,
required double darkToLightThreshold,
required Color background,
}) {
if (width <= 0 || height <= 0) return current;
var votesLight = 0;
var votesDark = 0;
for (final r in cellRects) {
final x0 = r.left.floor().clamp(0, width - 1);
final x1 = r.right.ceil().clamp(x0 + 1, width);
final y0 = r.top.floor().clamp(0, height - 1);
final y1 = r.bottom.ceil().clamp(y0 + 1, height);
var sumR = 0.0, sumG = 0.0, sumB = 0.0;
var n = 0;
for (var y = y0; y < y1; y++) {
for (var x = x0; x < x1; x++) {
final i = (y * width + x) * 4;
final a = rgba[i + 3] / 255.0;
if (a < 0.02) {
// Unpainted pixel — evaluate as the page background.
sumR += background.r;
sumG += background.g;
sumB += background.b;
} else {
sumR += rgba[i] / 255.0;
sumG += rgba[i + 1] / 255.0;
sumB += rgba[i + 2] / 255.0;
}
n++;
}
}
if (n == 0) continue;
final cellLuma = _wcagLuma(sumR / n, sumG / n, sumB / n);
// Which glyph reads better over this cell? The light variant uses dark
// glyphs, the dark variant uses light glyphs. Dark glyphs hold
// contrast over light AND medium content; light glyphs only win once
// the content is genuinely dark — so the vote keeps controls light
// through the medium range and flips late, like the native bars.
final lightVariantContrast = _contrast(_lightVariantGlyphLuma, cellLuma);
final darkVariantContrast = _contrast(_darkVariantGlyphLuma, cellLuma);
if (lightVariantContrast >= darkVariantContrast) {
votesLight++;
} else {
votesDark++;
}
}
final total = votesLight + votesDark;
if (total == 0) return current;
// Dual-threshold hysteresis on top of sticky ties: each direction needs
// a supermajority, so boundary content keeps the current appearance.
if (current == Brightness.light) {
return votesDark / total >= lightToDarkThreshold
? Brightness.dark
: Brightness.light;
}
return votesLight / total >= darkToLightThreshold
? Brightness.light
: Brightness.dark;
}