I need to take a picture, convert the file to an image to crop, and then convert the image back to a file to then run into a tflite model (currently just displaying an image on another screen).
As it stands I am using a simple camera app (https://flutter.dev/docs/cookbook/plugins/picture-using-camera?source=post_page---------------------------) and stacking a container on the preview screen to use as a viewfinder. I Use the rect_getter package to get the container coordinates for the copyCrop() function from the Image package.
Attempting to convert my file to an image (so the copyCrop() function can be run) and then back to a file (cropSaveFile.path) to later be used in a tflite model is resulting in an error: The following FileSystemException was thrown resolving an image codec: ��GFD�oom����������������� etc.
final image = await _controller.takePicture();
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop =
img.copyCrop(newImage!, _proX, _proY, _proW, _proH);
print('Crop: $crop');
final newBytes = crop.getBytes();
final File cropSaveFile = File.fromRawPath(newBytes);
I'm not sure what kind of file I'm really getting back. It is unreadable. Any Ideas? The full code to run is below:
import 'dart:async';
import 'dart:io';
import 'package:universal_io/io.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rect_getter/rect_getter.dart';
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
const TakePictureScreen({
Key? key,
required this.camera,
}) : super(key: key);
final CameraDescription camera;
@override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
var ContainerKey = RectGetter.createGlobalKey();
// Coordinates for rectangle
late int _proX;
late int _proY;
late int _proW;
late int _proH;
@override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
@override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Take a picture')),
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return Column(
children: [
AspectRatio(
aspectRatio: 1 / _controller.value.aspectRatio,
child: Stack(
children: [
CameraPreview(_controller),
Padding(
padding: const EdgeInsets.fromLTRB(
50.0,
8.0,
16.0,
8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
key: ContainerKey,
height: 175,
width: 175,
decoration: BoxDecoration(
border: Border.all(
width: 10,
color: Colors.yellow,
),
borderRadius: BorderRadius.circular(10.0),
),
),
Text(
'Place Image in Box Above',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
);
} else {
// Otherwise, display a loading indicator.
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
_controller.setFlashMode(FlashMode.off);
///////////////////////////////////////////
Rect? imageRect = RectGetter.getRectFromKey(ContainerKey);
setState(() {
_proX = imageRect!.left.toInt();
_proY = imageRect.top.toInt();
_proW = imageRect.right.toInt();
_proH = imageRect.bottom.toInt();
});
print(_proX);
print(_proY);
print(_proW);
print(_proH);
///////////////////////////////////////////
// Attempt to take a picture and get the file `image`
// where it was saved.
final image = await _controller.takePicture();
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop =
img.copyCrop(newImage!, _proX, _proY, _proW, _proH);
print('Crop: $crop');
final newBytes = crop.getBytes();
final File cropSaveFile = File.fromRawPath(newBytes);
// If the picture was taken, display it on a new screen.
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(
// Pass the automatically generated path to
// the DisplayPictureScreen widget.
imagePath: cropSaveFile.path,
),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
child: const Icon(Icons.camera_alt),
),
);
}
}
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key? key, required this.imagePath})
: super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Display the Picture')),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Image.file(File(imagePath)),
);
}
}
Edit: Future me found the following package much better than the code below... https://pub.dev/packages/mask_for_camera_view
This works for me.
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop = img.copyCrop(newImage!, _proY, _proY, 175, 175);
final jpg = img.encodeJpg(crop);
File cropSaveFile = File(xpath);
cropSaveFile.writeAsBytes(jpg);
If anybody is interested in full code to crop an image from the camera based on a viewfinder container it is below.
import 'dart:io';
import 'package:universal_io/io.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:rect_getter/rect_getter.dart';
import 'package:image/image.dart' as img;
Future<void> main() async {
// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
const TakePictureScreen({
Key? key,
required this.camera,
}) : super(key: key);
final CameraDescription camera;
@override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
var ContainerKey = RectGetter.createGlobalKey();
// Coordinates for rectangle
late int _proY;
@override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
@override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
Future<File> writeImageWidgetToFile(
img.Image crop, String croppedImagePath) async {
final imgByteData = await crop.getBytes();
final buffer = imgByteData.buffer;
return File(croppedImagePath).writeAsBytes(buffer.asUint8List(
imgByteData.offsetInBytes, imgByteData.lengthInBytes));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Take a picture')),
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return Column(
children: [
AspectRatio(
aspectRatio: 1 / _controller.value.aspectRatio,
child: Stack(
children: [
CameraPreview(_controller),
Padding(
padding: const EdgeInsets.fromLTRB(
16.0,
8.0,
16.0,
8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
key: ContainerKey,
height: 175,
width: 175,
decoration: BoxDecoration(
border: Border.all(
width: 10,
color: Colors.yellow,
),
borderRadius: BorderRadius.circular(10.0),
),
),
Text(
'Place Image in Box Above',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
);
} else {
// Otherwise, display a loading indicator.
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
_controller.setFlashMode(FlashMode.off);
///////////////////////////////////////////
Rect? imageRect = RectGetter.getRectFromKey(ContainerKey);
setState(() {
_proY = imageRect!.top.toInt();
});
print('Top Left Corner of Rect: $_proY');
///////////////////////////////////////////
// Attempt to take a picture and get the file `image`
// where it was saved.
final image = await _controller.takePicture();
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop = img.copyCrop(newImage!, _proY, _proY, 175, 175);
final jpg = img.encodeJpg(crop);
File cropSaveFile = File(xpath);
cropSaveFile.writeAsBytes(jpg);
// If the picture was taken, display it on a new screen.
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(
// Pass the automatically generated path to
// the DisplayPictureScreen widget.
imagePath: cropSaveFile.path,
),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
child: const Icon(Icons.camera_alt),
),
);
}
}
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key? key, required this.imagePath})
: super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Display the Picture')),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Image.file(File(imagePath)),
);
}
}