How to take a screenshot of the CameraPreview in Flutter? - flutter

I need quick access to the CameraPreview data for my new flutter app.
If I take a picture with controller.takePicture(filePath) it takes a few seconds for the file to save to the disk, so that I can access it.
I don't need a high quality image, so getting the same resolution as the phone screen display resolution would be fine. I have tried this method, but it only captures the overlays and widgets that I drew myself, not the Camera preview data.
Here is a minimum working example of the problem when using this method:
https://www.youtube.com/watch?v=CWBLjCwH5c0
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:async';
import 'package:camera/camera.dart';
import 'dart:ui' as ui;
List<CameraDescription> cameras;
Future<Null> main() async {
debugPaintSizeEnabled = false;
debugPaintLayerBordersEnabled = false;
try {
cameras = await availableCameras();
} on CameraException catch (e) {
logError(e.code, e.description);
}
runApp(new MaterialApp(
home: new MyApp(),
));
}
void logError(String code, String message) =>
print('Error: $code\nError Message: $message');
class MyApp extends StatefulWidget {
#override
_State createState() => new _State();
}
class _State extends State<MyApp> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
static GlobalKey previewContainer = new GlobalKey();
CameraController controller;
ui.Image image;
Offset blueSquareOffset = new Offset(10.0, 10.0);
#override
void initState() {
super.initState();
controller = new CameraController(cameras[0], ResolutionPreset.low);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
void _getScreenShotImage() async {
_capturePng();
image = await _capturePng();
debugPrint("im height: ${image.height}, im width: ${image.width}");
setState(() {});
}
Future<ui.Image> _capturePng() async {
RenderRepaintBoundary boundary =
previewContainer.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
return image;
}
/// Display the preview from the camera (or a message if the preview is not available).
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text('Camera is initialising...');
} else {
return Center(
child: new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: RepaintBoundary(
//key: previewContainer,
child: new GestureDetector(
child: new CameraPreview(controller),
),
)),
);
}
}
void _moveBlueSquare(DragUpdateDetails details) {
setState(() {
_getScreenShotImage();
blueSquareOffset = blueSquareOffset + details.delta;
});
}
Widget _blueSquare() {
return new Positioned(
top: blueSquareOffset.dy,
left: blueSquareOffset.dx,
width: 50.0,
height: 50.0,
child: new GestureDetector(
onPanUpdate: _moveBlueSquare,
child: new Container(
color: Color.fromARGB(255, 10, 10, 255),
)));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('Render Boundry Screenshot Error Example'),
),
body: RepaintBoundary(
key: previewContainer,
child: new Container(
padding: new EdgeInsets.all(0.0),
margin: new EdgeInsets.all(0.0),
child: new RepaintBoundary(
//key: previewContainer,
child: new Stack(
fit: StackFit.expand,
overflow: Overflow.clip,
children: <Widget>[
new Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Expanded(
child: new Stack(children: <Widget>[
new RepaintBoundary(
child: new Container(child: _cameraPreviewWidget()),
),
_blueSquare(),
])),
new Expanded(
child: new Container(
//color: Color.fromARGB(50, 50, 50, 50),
child: new CustomPaint(
painter: new RectanglePainter(image),
)),
)
],
),
],
)))));
}
}
class RectanglePainter extends CustomPainter {
RectanglePainter(this.image);
ui.Image image;
#override
void paint(Canvas canvas, Size size) {
if (image == null) {
canvas.drawRect(
new Rect.fromLTRB(100.0, 50.0, 300.0, 200.0),
new Paint()
..color = Color.fromARGB(255, 50, 50, 255)
..style = PaintingStyle.stroke
..strokeWidth = 6.0);
} else {
canvas.drawImage(image, new Offset(0.0, 0.0), new Paint());
}
}
#override
bool shouldRepaint(RectanglePainter old) {
return true;
}
}
Any help would be greatly appreciated.

Update: July 2020
At the moment, the best way to get a screenshot of the CameraPreview on Flutter, is to use the native_screenshot package.
You can simply use,
Future<void> getScreenshot() async{
String path = await NativeScreenshot.takeScreenshot();
print(path);
}
to save the screenshot. Please refer to package page for additional permissions and settings. Performance-wise, it seems to be a bit slow (500ms-1s on my 2018 Xiaomi A1). I am currently looking at ways to improve the screen capture speed.

There is a way to take snapshot from layouts. try this:
Android get Image of Main Relativelayout from xml layout?
I hope this helps to you.

Related

how to achieve a functionality like linear loading bar which will load up as user move between various screens

I am using android studio and flutter. I want to build the screen as shown below in the image:screen Image
let's say I have 4 screens. on the first screen, the bar will load up to 25%. the user will move to next screen by clicking on continue, the linearbar will load up to 50% and so on. the user will get back to previous screens by clicking on the back button in the appbar.
I tried stepper but it doesn't serve my purpose.
You can use the widget LinearProgressIndicator(value: 0.25,) for the first screen and with value: 0.5 for the second screen etc.
If you want to change the bar value within a screen, just use StatefullWidget's setState(), or any state management approaches will do.
import 'package:flutter/material.dart';
class ProgressPage extends StatefulWidget {
const ProgressPage({super.key});
#override
State<ProgressPage> createState() => _ProgressPageState();
}
class _ProgressPageState extends State<ProgressPage> {
final _pageController = PageController();
final _pageCount = 3;
int? _currentPage;
double? _screenWidth;
double? _unit;
double? _progress;
#override
void initState() {
super.initState();
_pageController.addListener(() {
_currentPage = _pageController.page?.round();
setState(() {
_progress = (_currentPage! + 1) * _unit!;
});
});
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_screenWidth = MediaQuery.of(context).size.width;
_unit = _screenWidth! / _pageCount;
_progress ??= _unit;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HOZEROGOLD')),
body: Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.yellow,
height: 10,
width: _progress,
),
),
Expanded(
child: PageView(
controller: _pageController,
children: _createPage(),
),
),
],
),
);
}
List<Widget> _createPage() {
return List<Widget>.generate(
_pageCount,
(index) => Container(
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () => _moveNextPage(),
child: Text('NEXT $index'),
),
),
),
);
}
void _moveNextPage() {
if (_pageController.page!.round() == _pageCount-1) {
_pageController.jumpToPage(0);
} else {
_pageController.nextPage(
curve: Curves.bounceIn,
duration: const Duration(milliseconds: 100));
}
}
}
HAPPY CODING! I hope it will be of help.

How to zoom a GestureDetector with onPanUpdate call?

I'm working on a drawing app. I want to enable both drawing on the canvas and zooming the drawings. So far, I tried achieving it by wrapping GestureDetector in InteractiveViewer and using AbsorbPointer to turn the zoom mode on and off. See the minimum demo code below.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
home: new IssueExamplePage(),
debugShowCheckedModeBanner: false,
));
class IssueExamplePage extends StatefulWidget {
#override
_IssueExamplePageState createState() => _IssueExamplePageState();
}
class _IssueExamplePageState extends State<IssueExamplePage> {
bool drawingBlocked = false;
List<Offset> points = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
color: Colors.grey,
padding: EdgeInsets.all(5),
child: Container(
color: Colors.white,
child: InteractiveViewer(
child: AbsorbPointer(
absorbing: drawingBlocked,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onPanUpdate: (details) {
RenderBox renderBox = context.findRenderObject();
Offset cursorLocation = renderBox.globalToLocal(details.localPosition);
setState(() {
points = List.of(points)..add(cursorLocation);
});
},
onPanEnd: (details) {
setState(() {
points = List.of(points)..add(null);
});
},
child: CustomPaint(
painter: MyPainter(points),
size: Size.infinite
),
),
),
),
),
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
child: Icon(drawingBlocked? CupertinoIcons.hand_raised_fill : CupertinoIcons.hand_raised),
onPressed: () {
setState(() {
drawingBlocked = !drawingBlocked;
});
}
),
SizedBox(
height: 10
),
FloatingActionButton(
child: Icon(Icons.clear),
onPressed: () {
setState(() {
points = [];
});
}
),
SizedBox(
height: 20
),
],
),
);
}
}
class MyPainter extends CustomPainter {
MyPainter(this.points);
List<Offset> points;
Paint paintBrush = Paint()
..color = Colors.blue
..strokeWidth = 5
..strokeJoin = StrokeJoin.round
..strokeCap = StrokeCap.round;
#override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paintBrush);
}
}
}
#override
bool shouldRepaint(MyPainter oldDelegate) {
return points != oldDelegate.points;
}
}
However, with this realization OnPanUpdate is working with a delay (see Flutter onPanStart invokes late when the widget is wrapped inside InteractiveViewer). Is there any other way to achieve the expected result (both zooming and drawing) or is it possible to fix the OnPanUpdate delay?
UPDATE: I found some sort of solution. You can use Listener instead of GestureDetector (it has substitues for the most of the parameters: onPanStart -> onPointerDown, onPanUpdate -> onPointerMove, etc.). But it seems like there's no fix for GestureDetector.

flutter web :How to find iframeElement to replace it's src

I have a flutter project that i added web support to it. Now in my page i have 2 button.I want to when user clicked on each button, a web page shown.So in order to i create custom widget :
#override
Widget build(BuildContext context) {
print("url is ------> ${url}");
isAddedElement();
_iframeElement.height = double.maxFinite.toString();
_iframeElement.width = double.maxFinite.toString();
_iframeElement.src = url;
_iframeElement.style.border = 'none';
_iframeElement.id = 'iframe';
ui.platformViewRegistry.registerViewFactory(
'iframeElement',
(int viewId) => _iframeElement,
);
_iframeWidget = HtmlElementView(
// key: UniqueKey(),
viewType: 'iframeElement',
);
return SizedBox(
height: 600,
width: 600,
child: _iframeWidget,
);
}
inisAddedElement method i want to check iframeElement.id = 'iframe' is exist or not?If exist i want to change src with the new web page:
bool isAddedElement() {
IFrameElement frame = document.querySelector('iframe');
if (frame != null) frame.src = url;
}
but document.querySelector('iframe') always return null?
The query returns null, most likely because flutter adds the HtmlElementView widget inside a shadowDom and these elements are not available directly in the document scope.
In order to access the element you need to go via a shadowroot which is usually placed under custome tag called flutter-platform-view.
So the following approaches may work.
Accessing via flt-platform-view
Try to access the iframe element as shown below.
document.getElementsByTagName('flutter-platform-view')[0].shadowRoot.getElementById('iframe');
Accessing the IframeElement instance
You already have the reference of IframeElement instance and can check what you need with it as shown below.
bool isAddedElement() {
//IFrameElement frame = document.querySelector('iframe');
if (_iframeElement!= null && _iframeElement.src == null) _iframeElement.src = url;
}
However I don't understand why in your code you are calling the isAddedElement() even before setting up the IframeElement.
You can find more details about the why its put behind a shadow dom in this issue in github.
Here is full working demo using the first approach. The live version is available as a codepen demo
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:html';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: IframeDemo(),
),
),
);
}
}
class IframeDemo extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyWidget();
}
}
class MyWidget extends State<IframeDemo> {
String _url;
IFrameElement _iframeElement;
#override
initState() {
super.initState();
_url = 'https://en.wikipedia.org/wiki/Twitter';
_iframeElement = IFrameElement()
..src = _url
..id = 'iframe'
..style.border = 'none';
ui.platformViewRegistry.registerViewFactory(
'iframeElement',
(int viewId) => _iframeElement,
);
}
void updateUrl(String page) {
setState(() {
// building a new url
_url = 'https://en.wikipedia.org/wiki/$page';
// Setting the url to the src field of the iframe element.
HtmlElement elem = document.getElementsByTagName('flt-platform-view')[0];
IFrameElement ifrelem = elem.shadowRoot.getElementById('iframe');
ifrelem..src = _url;
});
}
#override
Widget build(BuildContext context) {
print('url is $_url');
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
color: Colors.blueAccent,
child: Text('Show Twitter wiki'),
onPressed: () {
updateUrl('Twitter');
},
),
SizedBox(
width: 50,
),
MaterialButton(
color: Colors.orangeAccent,
child: Text('Show Facebook Wiki'),
onPressed: () {
updateUrl('Facebook');
},
),
],
),
SizedBox(
height: 100,
),
SizedBox(
height: 600,
width: 600,
child: HtmlElementView(
// key: UniqueKey(),
viewType: 'iframeElement',
),
),
],
);
}
}

Using video_player package with Flutter Hooks to play a background fullscreen video

I have a Home Screen Widget, that plays a fullscreen background video using the video_player package.
This code works fine for me:
class HomeScreen extends StatefulWidget {
HomeScreen({Key key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
VideoPlayerController _controller;
void initState() {
super.initState();
// Pointing the video controller to mylocal asset.
_controller = VideoPlayerController.asset("assets/waterfall.mp4");
_controller.initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
_controller.play();
_controller.setLooping(true);
// Ensure the first frame is shown after the video is initialized.
setState(() {});
});
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Stack(
children: <Widget>[
SizedBox.expand(
child: FittedBox(
// If your background video doesn't look right, try changing the BoxFit property.
// BoxFit.fill created the look I was going for.
fit: BoxFit.fill,
child: SizedBox(
width: _controller.value.size?.width ?? 0,
height: _controller.value.size?.height ?? 0,
child: VideoPlayer(_controller),
),
),
),
Container(
child: Center(
child: Text('Hello!'),
),
),
],
),
),
);
}
}
The question is, how can I implement this using flutter Hooks? I understand that I have to use useEffect() to implement the functionality of initState() and dispose(), useFuture() and maybe useMemoized() to handle asynchronous _controller.initialize() call and what possibly else? But, I cannot glue them to get the desired result. Can anyone indicate to me the "using Hooks" implementation of the above code?
I was looking for the answer to how to convert a VideoPlayer demo from StatefulWidget to HookWidget when I came across this question. I've come up with something that works so I'll post it here since there is nothing elsewhere that I could find and some others are hitting this page looking for an answer.
I used a viewmodel. The video controller is a property of the viewmodel. This code will not compile since some of the controls are not included. But it will demonstrate the structure and incorporation of the viewmodel.
Here's the widget file:
import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:video_player/video_player.dart';
import 'intro_viewmodel.dart';
class IntroPage extends HookWidget {
Future<void> saveAndGetStarted(BuildContext context) async {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
await introViewModel.completeIntro();
}
Future<void> onNext(BuildContext context) async {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
await introViewModel.incrementIntro();
}
final List<SliderModel> slides = [
SliderModel(
description: 'A word with you before you get started.\n',
title: 'Why This App?',
localImageSrc: 'media/Screen1-Movingforward-pana.svg',
backgroundColor: Colors.lightGray),
SliderModel(
description: 'This information will help the app be more accurate\n',
title: 'Personal Profile',
localImageSrc: 'media/Screen2-Teaching-cuate.svg',
backgroundColor: Colors.lightGray)
];
#override
Widget build(BuildContext context) {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
children: [
Text(
slides[introViewModel.index].description,
style: Theme.of(context).textTheme.headline5,
textAlign: TextAlign.center,
),
Expanded(
child: FractionallySizedBox(
widthFactor: .98,
heightFactor: .5,
child: VideoPlayer(introViewModel.videoController),
)),
Align(
alignment: Alignment.bottomCenter,
child: CustomRaisedButton(
onPressed: () {
if (introViewModel.index == slides.length - 1) {
saveAndGetStarted(context);
} else {
onNext(context);
}
},
color: Theme.of(context).accentColor,
borderRadius: 15,
height: 50,
child: Text(
introViewModel.index == 0
? 'Continue'
: 'Save and Get Started',
style: Theme.of(context)
.textTheme
.headline5
.copyWith(color: Colors.white),
),
),
),
],
),
),
));
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IterableProperty<SliderModel>('slides', slides));
}
}
And here is the viewmodel code
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:video_player/video_player.dart';
import '../top_level_providers.dart';
final introViewModelProvider = ChangeNotifierProvider<IntroViewModel>((ref) {
//this singleton class provides global access to selected variables
final SharedPreferencesService localSharedPreferencesService =
ref.watch(sharedPreferencesService);
return IntroViewModel(localSharedPreferencesService);
});
class IntroViewModel extends ChangeNotifier {
IntroViewModel(this.localSharedPreferencesService) : super() {
state = localSharedPreferencesService?.isIntroComplete();
// Pointing the video controller to my local asset.
videoController = VideoPlayerController.asset('media/test_search.mp4');
videoController.initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
// not autoplaying yet
// videoController.play();
// videoController.setLooping(true);
});
}
final SharedPreferencesService localSharedPreferencesService;
VideoPlayerController videoController;
bool state = false;
int index = 0;
Future<void> completeIntro() async {
await localSharedPreferencesService.setIntroComplete();
state = true;
notifyListeners();
}
Future<void> incrementIntro() async {
++index;
notifyListeners();
}
bool get isIntroComplete => state;
}

Flutter default image not loading

New to flutter. Working on a personal project. Stuck with a small issue related to show images. Here is my widget code which I'm using for showing images.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cached_network_image/cached_network_image.dart';
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => new UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Map userDetails = {};
String profileImgPath;
#override
void initState() {
super.initState();
getUserDetails();
}
Future<Null> getUserDetails() async {
try {
final SharedPreferences prefs = await _prefs;
this.userDetails = json.decode(prefs.getString('user'));
if (prefs.getString('user') != null) {
if (this.userDetails['isLoggedIn']) {
setState(() {
this.profileImgPath = this.userDetails['profileImg'];
print('Shared preference userDetailsss : ${this.userDetails}');
});
}
} else {
print('Shared preference has no data');
}
} catch (e) {
print('Exception caught at getUserDetails method');
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
Widget profileImage = new Container(
margin: const EdgeInsets.only(top: 20.0),
child: new Row(
children: <Widget>[
new Expanded(
child: new Column(
children: <Widget>[
new CircleAvatar(
backgroundImage: (this.profileImgPath == null) ? new AssetImage('images/user-avatar.png') : new CachedNetworkImageProvider(this.profileImgPath),
radius:50.0,
)
],
)
)
],
)
);
return new Scaffold(
appBar: new AppBar(title: new Text("Profile"), backgroundColor: const Color(0xFF009688)),
body: new ListView(
children: <Widget>[
profileImage,
],
),
);
}
}
What I'm trying to do is, show the default user-avatar.png image as long as CachedNetworkImageProvider don't get original image. But, it's bit behaving differently.
Whenever I'm opening the page - I'm getting a blank blue box then suddenly the original image from CachedNetworkImageProvider comes up.
Can't able to understand what's happening.
#Jonah Williams for your reference -
CachedNetworkImage can't be used for backgroundImage property because it does not extends ImageProvider. You can create a custom CircleAvatar like described below to use the CachedNetworkImage package:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class CustomCircleAvatar extends StatelessWidget {
final int animationDuration;
final double radius;
final String imagePath;
const CustomCircleAvatar({
Key key,
this.animationDuration,
this.radius,
this.imagePath
}) : super(key: key);
#override
Widget build(BuildContext context) {
return new AnimatedContainer(
duration: new Duration(
milliseconds: animationDuration,
),
constraints: new BoxConstraints(
minHeight: radius,
maxHeight: radius,
minWidth: radius,
maxWidth: radius,
),
child: new ClipOval(
child: new CachedNetworkImage(
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
imageUrl: imagePath,
placeholder: (context, url) => CircularProgressIndicator(),
),
),
);
}
}
And how to use:
body: new Center(
child: new CustomCircleAvatar(
animationDuration: 300,
radius: 100.0,
imagePath: 'https://avatars-01.gitter.im/g/u/mi6friend4all_twitter?s=128',
),
),
Maybe it is not the better way. But, it works!
(I'm assuming that CachedNetworkImageProvider is actually CachedNetworkImage from this package).
This line of code will always display the second image.
(this.profileImgPath == null)
? new AssetImage('images/user-avatar.png')
: new CachedNetworkImageProvider(this.profileImgPath)
Since profileImagePath is not null, the AssetImage is never created. Even if it was, as soon as it's not the cached network image will replace it before it has loaded. Instead, use the placeholder parameter of the network image. This will display your asset image until the network image loads.
new CachedNetworkImage(
placeholder: new AssetImage('images/user-avatar.png'),
imageUrl: profileImgPath,
)