I'm taking a photo with the newest camera plugin version and I'm using code from flutter example. This is how I pick a camera:
final cameras = await availableCameras();
final firstCamera = cameras.first;
This is inside init:
_cameraController = CameraController(
widget.camera,
ResolutionPreset.medium,
enableAudio: false,
);
This is the rest of the relevant code:
Future _takePhoto(BuildContext context) async {
try {
await _initializeControllerFuture;
final path = join(
(await getTemporaryDirectory()).path,
'${DateTime.now()}.png',
);
await _cameraController.takePicture(path);
setState(() {
_imagePath = path;
});
} catch (e) {
print(e);
}
}
Afterwards, I show the photo to the user with Image.file(File(_imagePath)) and later I send the photo to API.
The problem is that the photo is sometimes captured with a wrong orientation. (I'm sure about this because the photo is also rotated in the database.) For example, on 3 years old Xiaomi phone, it works flawlessly, but on a certain new Samsung phone, the photo is always rotated.
How to make sure that the rotation is always correct? (Even on ios devices)
This worked for me:
import 'package:image/image.dart' as img;
...
final img.Image capturedImage = img.decodeImage(await File(path).readAsBytes());
final img.Image orientedImage = img.bakeOrientation(capturedImage);
await File(path).writeAsBytes(img.encodeJpg(orientedImage));
This is my solution that works cross-platform and doesn't use plugins.
import 'dart:io';
import 'package:exif/exif.dart';
import 'package:image/image.dart' as img;
Future<File> fixExifRotation(String imagePath) async {
final originalFile = File(imagePath);
List<int> imageBytes = await originalFile.readAsBytes();
final originalImage = img.decodeImage(imageBytes);
final height = originalImage.height;
final width = originalImage.width;
// Let's check for the image size
// This will be true also for upside-down photos but it's ok for me
if (height >= width) {
// I'm interested in portrait photos so
// I'll just return here
return originalFile;
}
// We'll use the exif package to read exif data
// This is map of several exif properties
// Let's check 'Image Orientation'
final exifData = await readExifFromBytes(imageBytes);
img.Image fixedImage;
if (height < width) {
logger.logInfo('Rotating image necessary');
// rotate
if (exifData['Image Orientation'].printable.contains('Horizontal')) {
fixedImage = img.copyRotate(originalImage, 90);
} else if (exifData['Image Orientation'].printable.contains('180')) {
fixedImage = img.copyRotate(originalImage, -90);
} else if (exifData['Image Orientation'].printable.contains('CCW')) {
fixedImage = img.copyRotate(originalImage, 180);
} else {
fixedImage = img.copyRotate(originalImage, 0);
}
}
// Here you can select whether you'd like to save it as png
// or jpg with some compression
// I choose jpg with 100% quality
final fixedFile =
await originalFile.writeAsBytes(img.encodeJpg(fixedImage));
return fixedFile;
}
Source
You can use package https://pub.dev/packages/flutter_exif_rotation
Support iOS and Android
In some devices the exif data shows picture in landscape mode when they're actually in portrait.
This plugin fixes the orientation for pictures taken with those devices.
For Android
Add this in your AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
code snippet
image = await FlutterExifRotation.rotateImage(path: image.path);
//Note : iOS not implemented
image = await FlutterExifRotation.rotateAndSaveImage(path: image.path);
if you want to rotate image, you can use https://pub.dev/packages/image to manipulate images:
import 'package:image/image.dart';
If you use package "camera", you can't rotate image (remove mirror effect) using bakeOrientation because don't have exif data.
this works for me using "image_picker" or "camera".
File originalFile = File.fromUri(Uri(path: file.path));
List<int> imageBytes = await originalFile.readAsBytes();
final Image originalImage = decodeImage(imageBytes)!;
final Image orientedImage = flipHorizontal(originalImage);
List<int> imageBytesOrientated = encodeJpg(orientedImage);
For Write in same path:
await File(path).writeAsBytes(imageBytesOrientated);
You can use this package flutter_image_compress to resolve the camera image orientation problem. Note that this approach is faster than using the image package approach.
Use it this way:
import 'package:flutter_image_compress/flutter_image_compress.dart';
........
final fixedImageBytes = await FlutterImageCompress.compressWithFile(
image.path,
rotate: 0,
quality: 100,
keepExif: false,
autoCorrectionAngle: true,
format: CompressFormat.jpeg,
);
Note:
Make sure to set autoCorrectionAngle to true, keepExif to false and rotate to 0
I know this is late, but I just wanna to share how I fix my issue, you can call this function after it's initialized or every time before you take a photo, and here is the code:
await _camCtrl.lockCaptureOrientation(DeviceOrientation.portraitUp);
This fix my issue, somebody says it won't work on iOS, but I haven't test that it, so you can test it out and see if it is compatible with iOS or not.
A bit late to the party. It is really bad idea to ask Flutter to rotate images or similar operations because mobile apps are not designed for these purposes. The only reasonable use case is to ask Flutter to resize the image because you don't want to send a high resolution image to your server over the mobile connection. My solution is send your full size (if you prefer and resize in the back end) or resized images from Flutter to the back end where you can do auto orientation. I use MagickNET lib which is cross platform to do the job and it works perfectly. By doing this, your Flutter code is much cleaner and tidier.
--- VERY SIMPLE SOLUTION ---
import 'package:image/image.dart' as img;
XFile xfile = await _cameraController.takePicture();
List<int> imageBytes = await xfile.readAsBytes();
img.Image? originalImage = img.decodeImage(imageBytes);
img.Image fixedImage = img.flipVertical(originalImage!);
File file = File(xfile.path);
File fixedFile = await file.writeAsBytes(
img.encodeJpg(fixedImage),
flush: true,
);
Related
I'm working on a screen recording app in Flutter. In order to keep the quality of the captured screen image, I have to scale the image as shown below. However, it makes the image data very large. How can I reduce the data size (scale back to the original size) before saving it to a file? For performance reasons, I want to work with raw RGBA instead of encoding it PNG.
double dpr = ui.window.devicePixelRatio;
final boundary =
key.currentContext!.findRenderObject() as RenderRepaintBoundary;
return getBufferSync(key);
}
ui.Image image = boundary.toImageSync(pixelRatio: dpr);
var rawBytes = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
Uint8List uData = rawBytes!.buffer.asUint8List();
So I've been trying really hard to crop an image according to my needs in flutter.
Problem statement:
I have a screen and in that screen I show a frame while the device camera is run on the background. Now, what I want is that whenever the user clicks a photo, only the area of that image inside the frame should be kept and rest should be cropped.
What I have done so far?
Added a package image 3.1.3
Wrote code to fetch x,y coordinates of my frame.
Using the calculated x,y coordinates and copyCrop method from the Image package to crop the clicked image.
Now the problem is that I do not know how copyCrop works and the code right now does not give me the expected results.
final GlobalKey _key = GlobalKey();
void _getOffset(GlobalKey key) {
RenderBox? box = key.currentContext?.findRenderObject() as RenderBox?;
Offset? position = box?.localToGlobal(Offset.zero);
if (position != null) {
setState(() {
_x = position.dx;
_y = position.dy;
});
}
}
I assign this _key to my Image.file(srcToFrameImage) and the function above yields 10, 289.125
Here 10 is the offset from x and 289.125 is the offset from y. I used this tutorial for the same.
Code to crop my image using the Image package:
var bytes = await File(pictureFile!.path).readAsBytes();
img.Image src = img.decodeImage(bytes)!;
img.Image destImage = img.copyCrop(
src, _x!.toInt(), _y!.toInt(), src.width, src.height);
var jpg = img.encodeJpg(destImage);
await File(pictureFile!.path).writeAsBytes(jpg);
bloc.addFrontImage(File(pictureFile!.path));
Now, can anyone tell me how i can do this effectively? Right now, it does crop my image but not as I want it to be. It would be really great if someone could tell me how does copyCrop work and what is the meaning of all these different parameters that we pass into it.
Any help would be appreciated.
Edit:
Now, as you can see, i only want the image between this frame to be kept after being captured and rest to be cropped off.
I have a home-view and in this homeview I have placed my homepage pic, start and settings buttons.
When I run my game in the first opening, homepage pic is shown smaller and the buttons are not shown.
But if i click on restart in the emulator everything looks normal and well-sized. In a real android device homepage pic always smaller and the buttons are not shown.
Util flameUtil = Util();
await flameUtil.fullScreen();
await flameUtil.setLandscapeLeftOnly();
I want to prefer landscape left only.
void resize(Size size) {
screenSize = size;
tileSize = screenSize.width / 9;}
this is my resize method.
titleRect = Rect.fromLTWH(0,
0,
game.tileSize*10,
game.tileSize*5);
titleSprite = Sprite('uipics/homepage.png');
my homepage pic
rect = Rect.fromLTWH(game.tileSize*5,
(game.screenSize.height) - (game.tileSize*1.5),
game.tileSize*0.9,
game.tileSize*0.8);
sprite = Sprite('uipics/playbutton.png');
my start(play) button.
rect = Rect.fromLTWH(game.tileSize*7.2,
game.screenSize.height - (game.tileSize *1.5),
game.tileSize*0.9,
game.tileSize*0.8);
my settings button.
as I mentioned, if i click on restart in my IDE(vsc) everthing is automatically fixed and I can play my game without any problem. but in the first running and in an android device it is problematic. I have tried changing the sizes and the positions but it never worked successfully. Thank you for your help in advance.
You have to run the await flameUtil.fullScreen(); and setLandscapeLeftOnly(); before you do runApp, something like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences storage = await SharedPreferences.getInstance();
await Flame.util.fullScreen();
await Flame.util.setLandscapeLeftOnly();
TheGame game = TheGame(storage);
runApp(game.widget);
}
I have taken a widget screenshot and I want to save it as .jpg file. I was able to save it as .png file below but I don't know how to save it as .jpg file. Here is my code:
RenderRepaintBoundary boundary = _repainkey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
final directory = (await getExternalStorageDirectory());
print(directory.path);
File imgFile = new File('${directory.path}/flutter2.png');
await imgFile.writeAsBytes(pngBytes);
You could use the jpeg_encode library. It would allow you to convert your ui.Image object to a jpeg without the intermediary step of converting to a png, and is thus likely to be faster.
You have to do a file type conversion. You can use the image package to help you do this. This answer is very similar to what you're looking for, just doing the conversion reversed from what you want.
Example from the linked answer:
import 'dart:io';
import 'package:image/image.dart';
void main() {
// Read a jpeg image from file.
Image image = decodeImage(new File('test.jpg').readAsBytesSync());
// Resize the image to a 120x? thumbnail (maintaining the aspect ratio).
Image thumbnail = copyResize(image, 120);
// Save the thumbnail as a PNG.
new File('out/thumbnail-test.png')
..writeAsBytesSync(encodePng(thumbnail));
}
import 'dart:io';
import 'package:image/image.dart';
void main() {
// Read a jpeg image from file.
Image image = decodeImage(new File('test.png').readAsBytesSync());
// Resize the image to a 120x? thumbnail (maintaining the aspect ratio).
Image thumbnail = copyResize(image, 120);
// Save the thumbnail as a PNG.
new File('out/thumbnail-test.jpg')
..writeAsBytesSync(encodeJpg(thumbnail));
}
On my iOS device, I'm accessing the camera through a CameraController (provided by the camera package) with controller.startImageStream((CameraImage img) {...}
The data coming out of the camera is in a bgra8888 format on my phone, but I've read that it's in yuv420 on android devices. To convert the image stream data to a usable, consistent format, I'm using:
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:image/image.dart' as im;
Uint8List concatenatePlanes(List<Plane> planes) {
final WriteBuffer allBytes = WriteBuffer();
planes.forEach((Plane plane) => allBytes.putUint8List(plane.bytes));
return allBytes.done().buffer.asUint8List();
}
List<CameraDescription> cameras = await availableCameras();
CameraController controller = CameraController(cameras[0], ResolutionPreset.medium);
controller.startImageStream((CameraImage img) {
print(img.format.group); // returns ImageFormatGroup.bgra8888
List<int> imgData = concatenatePlanes(img.planes);
im.Image image = im.decodeImage(imgData);
});
The imgData variable is full of data streaming off the camera, but the converted image returned from decodeImage is null.
I had read on other posts that the image package would be up to the task of decoding bgra8888/yuv420 images (https://stackoverflow.com/a/57635827/479947) but I'm not seeing support in its formats.dart source (https://github.com/brendan-duncan/image/blob/master/lib/src/formats/formats.dart).
The target Image format is defined as:
An image buffer where pixels are encoded into 32-bit unsigned ints
(Uint32). Pixels are stored in 32-bit unsigned integers in #AARRGGBB
format. This is to be consistent with the Flutter image data.
How would I get my image stream image in bgra8888/yuv420 converted into the desired Image format?
Have you check this? convertImage
If you convert from bgra8888 to Image, it's kinda fast but yuv420 to Image take much more time, so if you have problem about performance, I can help a little bit with my experience. By the way, if you have performance problem and trying using Isolate, u will meet the memory issue.