computeVerdict static method

  1. @visibleForTesting
Brightness computeVerdict({
  1. required Uint8List rgba,
  2. required int width,
  3. required int height,
  4. required List<Rect> cellRects,
  5. required Brightness current,
  6. required double lightToDarkThreshold,
  7. required double darkToLightThreshold,
  8. required Color background,
})

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;
}