Added histogram and separated camera view builder

This commit is contained in:
Vadim 2023-08-06 11:52:10 +02:00
parent 1310b78a54
commit 680b9a1237
5 changed files with 203 additions and 21 deletions

View file

@ -14,10 +14,12 @@ class CameraView extends StatelessWidget {
valueListenable: controller, valueListenable: controller,
builder: (_, __, ___) => AspectRatio( builder: (_, __, ___) => AspectRatio(
aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio), aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
child: RotatedBox( child: value.isInitialized
quarterTurns: _getQuarterTurns(value), ? RotatedBox(
child: controller.buildPreview(), quarterTurns: _getQuarterTurns(value),
), child: controller.buildPreview(),
)
: const SizedBox.shrink(),
), ),
); );
} }

View file

@ -0,0 +1,132 @@
import 'dart:math';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.dart';
class CameraHistogram extends StatefulWidget {
final CameraController controller;
const CameraHistogram({required this.controller, super.key});
@override
_CameraHistogramState createState() => _CameraHistogramState();
}
class _CameraHistogramState extends State<CameraHistogram> {
List<int> histogramR = List.filled(256, 0);
List<int> histogramG = List.filled(256, 0);
List<int> histogramB = List.filled(256, 0);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_startImageStream();
});
}
@override
void dispose() {
widget.controller.stopImageStream();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
HistogramChannel(
color: Colors.red,
values: histogramR,
),
const SizedBox(height: Dimens.grid4),
HistogramChannel(
color: Colors.green,
values: histogramG,
),
const SizedBox(height: Dimens.grid4),
HistogramChannel(
color: Colors.blue,
values: histogramB,
),
],
);
}
void _startImageStream() {
widget.controller.startImageStream((CameraImage image) {
histogramR = List.filled(256, 0);
histogramG = List.filled(256, 0);
histogramB = List.filled(256, 0);
final int uvRowStride = image.planes[1].bytesPerRow;
final int uvPixelStride = image.planes[1].bytesPerPixel!;
for (int x = 0; x < image.width; x++) {
for (int y = 0; y < image.height; y++) {
final int uvIndex = uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
final int index = y * image.width + x;
final yp = image.planes[0].bytes[index];
final up = image.planes[1].bytes[uvIndex];
final vp = image.planes[2].bytes[uvIndex];
final r = yp + vp * 1436 / 1024 - 179;
final g = yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91;
final b = yp + up * 1814 / 1024 - 227;
histogramR[r.round().clamp(0, 255)]++;
histogramG[g.round().clamp(0, 255)]++;
histogramB[b.round().clamp(0, 255)]++;
}
}
if (mounted) setState(() {});
});
}
}
class HistogramChannel extends StatelessWidget {
final List<int> values;
final Color color;
final int _maxOccurences;
HistogramChannel({
required this.values,
required this.color,
super.key,
}) : _maxOccurences = values.reduce((value, element) => max(value, element));
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final pixelWidth = constraints.maxWidth / values.length;
return Column(
children: [
SizedBox(
height: Dimens.grid16,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: values
.map(
(e) => SizedBox(
height: _maxOccurences == 0 ? 0 : Dimens.grid16 * (e / _maxOccurences),
width: pixelWidth,
child: ColoredBox(color: color),
),
)
.toList(),
),
),
const Divider(),
],
);
},
);
}
}

View file

@ -0,0 +1,56 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart';
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
class CameraPreview extends StatefulWidget {
final CameraController? controller;
final CameraErrorType? error;
const CameraPreview({this.controller, this.error, super.key});
@override
State<CameraPreview> createState() => _CameraPreviewState();
}
class _CameraPreviewState extends State<CameraPreview> {
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: PlatformConfig.cameraPreviewAspectRatio,
child: Center(
child: Stack(
children: [
const CameraViewPlaceholder(error: null),
AnimatedSwitcher(
duration: Dimens.switchDuration,
child: widget.controller != null
? ValueListenableBuilder<CameraValue>(
valueListenable: widget.controller!,
builder: (_, __, ___) => Stack(
alignment: Alignment.bottomCenter,
children: [
if (widget.controller!.value.isInitialized)
CameraView(controller: widget.controller!),
if (widget.controller!.value.isInitialized)
Positioned(
left: 0,
right: 0,
bottom: Dimens.borderRadiusM,
child: CameraHistogram(controller: widget.controller!),
),
],
),
)
: CameraViewPlaceholder(error: widget.error),
),
],
),
),
);
}
}

View file

@ -10,8 +10,7 @@ import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls/widget_camera_controls.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls/widget_camera_controls.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls_placeholder/widget_placeholder_camera_controls.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls_placeholder/widget_placeholder_camera_controls.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_view/widget_camera_view.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_view_placeholder/widget_placeholder_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart'; import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
@ -107,20 +106,11 @@ class _CameraViewBuilder extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AspectRatio( return BlocBuilder<CameraContainerBloc, CameraContainerState>(
aspectRatio: PlatformConfig.cameraPreviewAspectRatio, buildWhen: (previous, current) => current is! CameraActiveState,
child: BlocBuilder<CameraContainerBloc, CameraContainerState>( builder: (context, state) => CameraPreview(
buildWhen: (previous, current) => current is! CameraActiveState, controller: state is CameraInitializedState ? state.controller : null,
builder: (context, state) => Center( error: state is CameraErrorState ? state.error : null,
child: AnimatedSwitcher(
duration: Dimens.durationM,
child: switch (state) {
CameraInitializedState() => CameraView(controller: state.controller),
CameraErrorState() => CameraViewPlaceholder(error: state.error),
_ => const CameraViewPlaceholder(error: null),
},
),
),
), ),
); );
} }
@ -161,7 +151,9 @@ class _CameraControlsBuilder extends StatelessWidget {
}, },
); );
} else { } else {
child = const Column(children: [Expanded(child: SizedBox.shrink())],); child = const Column(
children: [Expanded(child: SizedBox.shrink())],
);
} }
return AnimatedSwitcher( return AnimatedSwitcher(