Flutter & Firebase: Compression before upload image
Asked Answered
H

13

49

I want to send photo selected by user in my app to Firebase Storage. I have a simple class with property _imageFile which is set like this:

File _imageFile;

_getImage() async {
    var fileName = await ImagePicker.pickImage();
    setState(() {
        _imageFile = fileName;
    });
}

after that I send photo like with this code:

final String rand1 = "${new Random().nextInt(10000)}";
final String rand2 = "${new Random().nextInt(10000)}";
final String rand3 = "${new Random().nextInt(10000)}";
final StorageReference ref = FirebaseStorage.instance.ref().child('${rand1}_${rand2}_${rand3}.jpg');
final StorageUploadTask uploadTask = ref.put(_imageFile);
final Uri downloadUrl = (await uploadTask.future).downloadUrl;
print(downloadUrl);

The problem is that the photos are often very large. Is there any method in Flutter/Dart to compress and resize photo before upload? I am ok with loss of quality.

Hough answered 1/10, 2017 at 18:44 Comment(3)
You might want to give pub.dartlang.org/packages/image a try, I have not tried this, but when you read the documentation you will find that you can encode images into different formats and specify different compression levels.Innovate
You might want to check pub.dev/packages/uuid for uploading files on firebase because using random numbers may have collisions at some time.Precontract
If you are in for lutter web check out my answer. #60729372Yamashita
A
31

The image_picker plugin is currently very simple. It would be straightforward to add an option for specifying the desired size/quality of the picked image. If you do this, please send us a pull request!

Amerson answered 2/10, 2017 at 4:23 Comment(5)
What parameter to pass in the image picker library? Can you provide the link for itEthnomusicology
I did not find any params to resize all images above X and Y, did you found it? ThanksTrawler
use an imageQuality parameter in the image picker library.Spevek
SYNTAX: ImagePicker.pickImage(source.ImageSource.gallery, imageQuality: 85); But this will compress the image by 85% of its current quality, so if an image is already of less quality then it will just mess it up even more! ALTERNATE SOLUTION: ImagePicker.pickImage(source.ImageSource.gallery, maxHeight: 200); Using maxHeight will only compress the image if it greater than mentioned height!Marriott
Is there any solution for the web?Ops
M
84

June 05, 2020 - Update

The image_picker plugin now supports an imageQuality paramater. You can do something like

ImagePicker imagePicker = ImagePicker();
PickedFile compressedImage = await imagePicker.getImage(
  source: ImageSource.camera,
  imageQuality: 85,
);

Old Answer

or if you want to compress an image without using ImagePicker

I ran into this and was able to accomplish compression / resizing with the Dart image package along with path provider. You can look at dart image api and examples for other ways and more help.

Here's what I did:

import 'package:image/image.dart' as Im;
import 'package:path_provider/path_provider.dart';
import 'dart:math' as Math;

void compressImage() async {
  File imageFile = await ImagePicker.pickImage();
  final tempDir = await getTemporaryDirectory();
  final path = tempDir.path;
  int rand = new Math.Random().nextInt(10000);

  Im.Image image = Im.decodeImage(imageFile.readAsBytesSync());
  Im.Image smallerImage = Im.copyResize(image, 500); // choose the size here, it will maintain aspect ratio
  
  var compressedImage = new File('$path/img_$rand.jpg')..writeAsBytesSync(Im.encodeJpg(image, quality: 85));
}

Then I uploaded compressedImage to firebase storage. You can adjust the quality that the jpg is saved with using the quality property, in my case I chose 85 (out of 100).

Hope this helps! Let me know if you have any questions.

Monkhood answered 19/3, 2018 at 19:45 Comment(10)
I Am Also doing the same stuff but it takes long time in iOS (if the image is take from it)Sell
yes it's quite long and I cannot undersand why it freeze my UI even though the code is asyncBeachlamar
i have found that it does freeze my ui for a while in debug, it runs much better when the app is built for releaseMonkhood
@Beachlamar being async doesn't mean its parallelLarrigan
@Beachlamar it also doesn't mean multi threading, it all depends on the developers on how they implement their async functionalityLarrigan
@Beachlamar dart is single threaded. So it all runs on the UI thread. To achieve true multi threading, use an isolate.Dingdong
@Monkhood What is average image file size reduction?Decorate
Why is the smallerImage not used?Exclusion
but image picker doesn't support png compression what to do in that caseCosta
@mans my question is do I need to ask for permission in android for this code(like storage permission or something)Costa
A
31

The image_picker plugin is currently very simple. It would be straightforward to add an option for specifying the desired size/quality of the picked image. If you do this, please send us a pull request!

Amerson answered 2/10, 2017 at 4:23 Comment(5)
What parameter to pass in the image picker library? Can you provide the link for itEthnomusicology
I did not find any params to resize all images above X and Y, did you found it? ThanksTrawler
use an imageQuality parameter in the image picker library.Spevek
SYNTAX: ImagePicker.pickImage(source.ImageSource.gallery, imageQuality: 85); But this will compress the image by 85% of its current quality, so if an image is already of less quality then it will just mess it up even more! ALTERNATE SOLUTION: ImagePicker.pickImage(source.ImageSource.gallery, maxHeight: 200); Using maxHeight will only compress the image if it greater than mentioned height!Marriott
Is there any solution for the web?Ops
D
9

There are many solutions :

Use image_picker package:

You can use the built-in imageQuality property of ImagePicker to compress the image. This property takes a value between 0 and 100 and represents a percentage of the quality of the original image.

First, add image_picker as a dependency in your pubspec.yaml file.

Usage

  File _image;

  Future getImage() async {
    var image = await ImagePicker.pickImage(
        source: ImageSource.gallery,  
                imageQuality: 25,
    );

    setState(() {
      _image = image;
    });
  }

The advantage of this approach is that it is embedded in the image_picker package and is therefore incredibly easy to use.

You can also adjust the quality from the Image widget.

Use filterQuality to set the FilterQuality of the image.

Example :

Image.asset('assets/kab1.png', filterQuality: FilterQuality.high,), 

These properties are present in AssetImage , NetworkImage , FileImage and MemoryImage.

You can also simply resize the image (Resizing an image can compress it). To do so, try the ResizeImage widget

Another solution is to use flutter_image_compress package

Drachm answered 7/2, 2021 at 17:42 Comment(0)
H
8

Use image_picker plugin and call pick image function as

Future<File> imageFile = ImagePicker.pickImage(source: ImageSource.gallery , maxHeight: 200 , maxWidth: 200 );

change maxHeight and maxWidth to whatever size of image you need.

Handkerchief answered 26/3, 2019 at 0:32 Comment(3)
This worked for me! I used 400 instead of 200 to get a better quality. Generally, a 200x200 is less than 20kb, and the 400x400 is less than 40kbTruant
Great, this is something I needed. i don't want to use another library for compress.Wray
2020 : 'pickImage' is deprecated and shouldn't be used. Use imagePicker.getImage() method instead..Arterialize
L
5

Besides mentioning this native library: https://pub.dartlang.org/packages/flutter_image_compress

This is a fully dart based compressor with isolates, which might make the compression parallel to UI thread in multi core CPUs.

You might want to use compute function which makes using isolates simpler: https://docs.flutter.io/flutter/foundation/compute.html https://flutter.io/cookbook/networking/background-parsing/

import 'package:image/image.dart' as ImageLib;
import 'package:path_provider/path_provider.dart';

Future<void> getCompressedImage(SendPort sendPort) async {
  ReceivePort receivePort = ReceivePort();

  sendPort.send(receivePort.sendPort);
  List msg = (await receivePort.first) as List;

  String srcPath = msg[0];
  String name = msg[1];
  String destDirPath = msg[2];
  SendPort replyPort = msg[3];

  ImageLib.Image image =
      ImageLib.decodeImage(await new File(srcPath).readAsBytes());

  if (image.width > 500 || image.height > 500) {
    image = ImageLib.copyResize(image, 500);
  }

  File destFile = new File(destDirPath + '/' + name);
  await destFile.writeAsBytes(ImageLib.encodeJpg(image, quality: 60));

  replyPort.send(destFile.path);
}

Future<File> compressImage(File f) async {
  ReceivePort receivePort = ReceivePort();

  await Isolate.spawn(getCompressedImage, receivePort.sendPort);
  SendPort sendPort = await receivePort.first;

  ReceivePort receivePort2 = ReceivePort();

  sendPort.send([
    f.path,
    f.uri.pathSegments.last,
    (await getTemporaryDirectory()).path,
    receivePort2.sendPort,
  ]);

  var msg = await receivePort2.first;

  return new File(msg);
}

if (false ==
    await SimplePermissions.checkPermission(
        Permission.ReadExternalStorage)) {
  await SimplePermissions.requestPermission(
    Permission.ReadExternalStorage);
}

File img = await ImagePicker.pickImage(
    source: ImageSource.gallery);
if (null != img) {
  img = await compressImage(img);
}
Larrigan answered 20/10, 2018 at 16:18 Comment(2)
Hangs up sometimes :( Without exceptionCeres
Forgot to import 'dart:isolate';Soule
S
3

The following code is what I use to take an image with the camera and then compress it:

import 'dart:async' show Future;
import 'dart:io' show File;
import 'package:flutter/foundation.dart' show compute;
import 'package:flutter/material.dart' show BuildContext;
import 'package:image/image.dart' as Im;
import 'dart:math' as Math;
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;

Future<File> takeCompressedPicture(BuildContext context) async {
  var _imageFile = await ImagePicker.pickImage(source: ImageSource.camera);
  if (_imageFile == null) {
    return null;
  }

  // You can have a loading dialog here but don't forget to pop before return file;

  final tempDir = await getTemporaryDirectory();
  final rand = Math.Random().nextInt(10000);
  _CompressObject compressObject =
      _CompressObject(_imageFile, tempDir.path, rand);
  String filePath = await _compressImage(compressObject);
  print('new path: ' + filePath);
  File file = File(filePath);

  // Pop loading

  return file;
}

Future<String> _compressImage(_CompressObject object) async {
  return compute(_decodeImage, object);
}

String _decodeImage(_CompressObject object) {
  Im.Image image = Im.decodeImage(object.imageFile.readAsBytesSync());
  Im.Image smallerImage = Im.copyResize(
      image, 1024); // choose the size here, it will maintain aspect ratio
  var decodedImageFile = File(object.path + '/img_${object.rand}.jpg');
  decodedImageFile.writeAsBytesSync(Im.encodeJpg(smallerImage, quality: 85));
  return decodedImageFile.path;
}

class _CompressObject {
  File imageFile;
  String path;
  int rand;

  _CompressObject(this.imageFile, this.path, this.rand);
}

You can call this very easy with this:

import 'path/to/compress_image.dart' as CompressImage;
// ...
File file = await CompressImage.takeCompressedPicture(context);
Stripling answered 23/6, 2018 at 7:12 Comment(1)
gives this error for return compute(_decodeImage, object); like this 'Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '_decodeImage@38216194':.)'Subsume
P
0

You can do as follows,

//Compressing Image
    File compressedImg = await FlutterNativeImage.compressImage(
      _image.path,
      quality: 70,
    );
//Compressing Image
Pigtail answered 1/10, 2017 at 18:45 Comment(0)
G
0

While i am using

package:image/image.dart

facing below issues

  • showing blank page while converting image ( may be taking so much process while compressing image)
  • After compression image was stretched and not looking good

Then used below plugin, same working fine without any issue, even faster and what i expected

https://github.com/btastic/flutter_native_image.git

steps and method available in above link.

Grimes answered 20/5, 2019 at 11:47 Comment(0)
M
0

You can use a plugin called flutter_image_compress

// Active image file
File _imageFile;

// Select an image via gallery or camera
Future<void> _pickImage(ImageSource source) async {
  File selected = await ImagePicker.pickImage(source: source);

// Compress plugin
  File compressedImage = await FlutterImageCompress.compressAndGetFile(
    selected.path,
    selected.path,
    quality: 50,
  );

  setState(() {
    _imageFile = compressedImage;
    print('compressedimagesize: ${_imageFile.lengthSync()}');
  });
}

Voila! Compressed file image

Mend answered 4/11, 2019 at 4:37 Comment(1)
its going to say that the target path cannot be the same as the file path.Lowdown
S
0

Since you are using Firebase, one option would be using the Extension - Resize Image. It gives you the option to keep or delete the original image and it is very easy to install and use.

Seraph answered 24/1, 2020 at 9:11 Comment(1)
From Review: This post does not seem to provide a quality answer to the question. Please either edit your answer, or just post it as a comment to the question.Jampack
Y
0

Well
1. If you are in mobile you could use flutter_image_compress: ^1.0.0 will do the work

Eg. Pass youe Uint8List and quality, you will get compressed image in no time.

Future<Uint8List> testComporessList(Uint8List uint8List) async {
    var result = await FlutterImageCompress.compressWithList(
      uint8List,
      quality: 50,
    );
    return result;
}

2. But If you are in flutter web then you won't get any other option than image picker etc etc.
I end up using javascript. You can find answer here.
Flutter Web: How Do You Compress an Image/File?

Yamashita answered 13/7, 2021 at 7:52 Comment(0)
B
0

ImagePicker has now a default option to reduce imageQuality.

 final returnedImage = await ImagePicker()
    .pickImage(source: ImageSource.gallery, imageQuality: 50);

Here

imageQuality: 50

means that the image will be reduced to 50% of it's actual quality.

Barth answered 18/9, 2023 at 15:20 Comment(0)
A
-1

Update 2020 'pickImage' is deprecated and shouldn't be used. Use imagePicker.getImage() method instead**

  ImagePicker picker = ImagePicker();
   PickedFile compressedImage = await imagePicker.getImage(
  source: ImageSource.camera,
  imageQuality: 80,
);

Doc for ImageQuality :

Returns a PickedFile object wrapping the image that was picked. The returned PickedFile is intended to be used within a single APP session. Do not save the file path and use it across sessions. The source argument controls where the image comes from. This can be either ImageSource.camera or ImageSource.gallery. Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used in addition to a size modification, of which the usage is explained below. If specified, the image will be at most maxWidth wide and maxHeight tall. Otherwise the image will be returned at it's original width and height. The imageQuality argument modifies the quality of the image, ranging from 0-100 where 100 is the original/max quality. If imageQuality is null, the image with the original quality will be returned. Compression is only supported for certain image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked, a warning message will be logged. Use preferredCameraDevice to specify the camera to use when the source is ImageSource.camera. The preferredCameraDevice is ignored when source is ImageSource.gallery. It is also ignored if the chosen camera is not supported on the device. Defaults to CameraDevice.rear. Note that Android has no documented parameter for an intent to specify if the front or rear camera should be opened, this function is not guaranteed to work on an Android device. In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost in this call. You can then call getLostData when your app relaunches to retrieve the lost data.

Arterialize answered 6/12, 2020 at 22:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.