我制作了一个小部件作为一个有趣的练习和概念验证,称为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'}');
},
)
Demo:
完整来源:
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);
}
}