如何检测背景模糊的容器的亮度以改变文本的颜色

How to detect luminance of a container with background blur to change the colour of the text

模糊容器的亮度根据背景中的图像而变化。有没有办法检测图像的亮度或模糊容器以更改文本颜色?最好不处理图片。

我做了一个小部件作为一个有趣的练习和一个 proof-of-concept,叫做 PixelDataOverlay。它有一个 background 生成器和一个 overlay 生成器。

使用时,它会先调用background构建器来构建底层的小部件(图片或其他)。然后它将从构建的内容中提取像素数据。然后它将调用 overlay 构建器并将像素数据传回给您,因此您可以根据收到的信息决定如何构建 overlay

密钥用法:

PixelDataOverlay(
  background: (BuildContext context) {
    return ImageFiltered( /* ... */ );
  },
  overlay: (BuildContext context, Uint8List? bytes) {
    final score = PixelDataOverlay.getBrightnessFromBytes(bytes);
    return Text('Background is ${score > 0 ? 'bright' : 'dark'}');
  },
)

演示:

完整来源:

import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Demo(),
        ),
      ),
    );
  }
}

class Demo extends StatefulWidget {
  const Demo({Key? key}) : super(key: key);

  @override
  _DemoState createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  final List<double> _sliderValues = [0.8, 0.8, 0.2, 1.0];

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        PixelDataOverlay(
          background: (BuildContext context) {
            return ClipRect(
              child: ImageFiltered(
                imageFilter: ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0),
                child: Container(
                  color: Color.fromARGB(
                    (_sliderValues[3] * 255).clamp(0, 255).round(),
                    (_sliderValues[0] * 255).clamp(0, 255).round(),
                    (_sliderValues[1] * 255).clamp(0, 255).round(),
                    (_sliderValues[2] * 255).clamp(0, 255).round(),
                  ),
                  child: FlutterLogo(size: 100),
                ),
              ),
            );
          },
          overlay: (BuildContext context, Uint8List bytes) {
            final score = PixelDataOverlay.getBrightnessFromBytes(bytes);
            return Center(
              child: Text(
                'Brightness: \n${score.toStringAsFixed(2)}',
                style: TextStyle(
                  color: score > 0 ? Colors.black : Colors.white,
                ),
              ),
            );
          },
        ),
        const SizedBox(height: 48),
        _buildControls(),
      ],
    );
  }

  _buildControls() {
    return Column(
      children: [
        Text('Adjust the sliders to see the effect of the blur filter.\n'
            'The sliders are: Red, Green, Blue, Alpha.'),
        for (int i = 0; i < 4; i++)
          Slider(
            value: _sliderValues[i],
            onChanged: (v) => setState(() => _sliderValues[i] = v),
          ),
      ],
    );
  }
}

class PixelDataOverlay extends StatefulWidget {
  final WidgetBuilder background;
  final Widget Function(BuildContext context, Uint8List bytes) overlay;

  const PixelDataOverlay(
      {Key? key, required this.background, required this.overlay})
      : super(key: key);

  @override
  _PixelDataOverlayState createState() => _PixelDataOverlayState();

  /// Returns the brightness score for the given [bytes] containing raw image
  /// data. A positive score indicates that the image is (on average) bright,
  /// while a negative score indicates that the image is dark.
  static double getBrightnessFromBytes(Uint8List bytes) {
    // Keep track of total brightness of the image.
    // For each pixel, assign positive value if it's bright.
    // For example: +1 for #FFFFFF, -1 for #000000.
    // So for neutral grey, its score will be close to 0.
    // However, if alpha is not FF, the score is discounted accordingly.
    // For example: `Colors.black.withOpacity(0.5)` has a score of `-0.5`.
    double totalScore = 0.0;

    for (int i = 0; i < bytes.length; i += 4) {
      final r = bytes[i];
      final g = bytes[i + 1];
      final b = bytes[i + 2];
      final a = bytes[i + 3];
      final brightness = (0.2126 * r + 0.7152 * g + 0.0722 * b); // 0 to 255
      final normalized = (brightness / 127.5 - 1) * (a / 255); // -1 to 1
      totalScore += normalized;
    }
    return totalScore;
  }
}

class _PixelDataOverlayState extends State<PixelDataOverlay> {
  final _globalKey = GlobalKey();
  Uint8List? _bytes;

  @override
  void initState() {
    super.initState();
  }

  @override
  void didUpdateWidget(covariant PixelDataOverlay oldWidget) {
    super.didUpdateWidget(oldWidget);
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      _capture();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        RepaintBoundary(
          key: _globalKey,
          child: widget.background(context),
        ),
        if (_bytes != null)
          Positioned(
            top: 0,
            left: 0,
            bottom: 0,
            right: 0,
            child: widget.overlay(context, _bytes!),
          ),
      ],
    );
  }

  void _capture() async {
    final render = (_globalKey.currentContext!.findRenderObject()
        as RenderRepaintBoundary);
    final imageBytes = (await (await render.toImage())
            .toByteData(format: ImageByteFormat.rawStraightRgba))!
        .buffer
        .asUint8List();
    setState(() => _bytes = imageBytes);
  }
}