aves/lib/widgets/viewer/overlay/histogram.dart
2024-10-26 00:10:40 +02:00

155 lines
4.4 KiB
Dart

import 'dart:ui';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/widgets/viewer/overlay/top.dart';
import 'package:aves/widgets/viewer/view/controller.dart';
import 'package:aves/widgets/viewer/view/histogram.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
class ImageHistogram extends StatefulWidget {
final ViewStateController viewStateController;
final ImageProvider image;
const ImageHistogram({
super.key,
required this.viewStateController,
required this.image,
});
@override
State<ImageHistogram> createState() => _ImageHistogramState();
}
class _ImageHistogramState extends State<ImageHistogram> {
HistogramLevels _levels = {};
ImageStream? _imageStream;
late ImageStreamListener _imageListener;
ViewStateController get viewStateController => widget.viewStateController;
AvesEntry get entry => viewStateController.entry;
ImageProvider get imageProvider => widget.image;
@override
void initState() {
super.initState();
_registerWidget(widget);
}
@override
void didUpdateWidget(covariant ImageHistogram oldWidget) {
super.didUpdateWidget(oldWidget);
_unregisterWidget(oldWidget);
_registerWidget(widget);
}
@override
void dispose() {
_unregisterWidget(widget);
super.dispose();
}
void _registerWidget(ImageHistogram widget) {
_imageStream = imageProvider.resolve(ImageConfiguration.empty);
_imageListener = ImageStreamListener((image, synchronousCall) async {
// implementer is responsible for disposing the provided `ImageInfo`
await _updateLevels(image);
image.dispose();
});
_imageStream?.addListener(_imageListener);
}
void _unregisterWidget(ImageHistogram widget) {
_imageStream?.removeListener(_imageListener);
}
@override
Widget build(BuildContext context) {
return IgnorePointer(
child: CustomPaint(
painter: _HistogramPainter(
levels: _levels,
borderColor: ViewerTopOverlay.componentBorderColor,
),
size: const Size(ViewerTopOverlay.componentDimension, ViewerTopOverlay.componentDimension * .6),
),
);
}
Future<void> _updateLevels(ImageInfo info) async {
final targetEntry = entry;
final forceUpdate = targetEntry.isAnimated;
final newLevels = await viewStateController.getHistogramLevels(info, forceUpdate);
if (!mounted) return;
setState(() => _levels = targetEntry == entry ? newLevels : {});
}
}
class _HistogramPainter extends CustomPainter {
final HistogramLevels levels;
final Color borderColor;
late final Paint fill, borderStroke;
_HistogramPainter({
required this.levels,
this.borderColor = Colors.white,
}) {
fill = Paint()
..style = PaintingStyle.fill
..color = const Color(0x33000000);
borderStroke = Paint()
..style = PaintingStyle.stroke
..color = borderColor;
}
@override
void paint(Canvas canvas, Size size) {
if (levels.isEmpty) return;
final backgroundRect = Rect.fromPoints(Offset.zero, Offset(size.width, size.height));
canvas.drawRect(backgroundRect, fill);
levels.forEach((channel, values) {
final color = _getChannelColor(channel);
_drawLevels(canvas, size, color, values);
});
canvas.drawRect(backgroundRect, borderStroke);
}
void _drawLevels(Canvas canvas, Size size, Color color, List<double> values) {
if (values.length < 2) return;
final xFactor = size.width / (values.length - 1);
final yFactor = size.height;
final polyline = values.mapIndexed((i, v) => Offset(i * xFactor, size.height - v * yFactor)).toList();
canvas.drawPoints(
PointMode.polygon,
polyline,
Paint()
..style = PaintingStyle.stroke
..color = color);
polyline.add(Offset(size.width, size.height));
polyline.add(Offset(0, size.height));
canvas.drawPath(
Path()..addPolygon(polyline, true),
Paint()
..style = PaintingStyle.fill
..color = color.withAlpha((255.0 * .5).round()));
}
Color _getChannelColor(HistogramChannel channel) {
return switch (channel) {
HistogramChannel.red => Colors.red,
HistogramChannel.green => Colors.green,
HistogramChannel.blue => Colors.blue,
HistogramChannel.luminance => Colors.white,
};
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}