How to Crop and Compress Images in Dart/Flutter
Tags: flutter, dart
Published: 2022-04-03 03:04:57

---
title: How to Crop and Compress Images in Dart/Flutter
published: true
date: 2022-01-28 05:58:38 UTC
tags: Flutter,dart
canonical_url: https://sangams.com.np/how-to-crop-and-compress-images-in-dart-flutter/
---

Image cropping becomes necessary in apps when we want to save bandwidth by removing unnecessary image parts. For example we want to upload a thumbnail and we don’t want it to be very large is size and high quality image.

![meme](https://sangams.com.np/wp-content/uploads/2022/01/meme.jpeg)

So I started looking for plugins to crop and compress images in Flutter and there are pretty good plugins to accomplish this, but I preferred another way to crop my image. Here I will be talking about cropping images in the background without a UI. We don’t always ask user to crop their image, sometimes we need to do it ourselves without their interaction or intention.

We will use [image](https://pub.dev/packages/image), a dart package to manipulate images in a variety of different file formats. With this package we already have `copyCrop()`, `copyCropCircle()`, `copyResize()` and `copyResizeCropSquare()`but we may also need to center crop. So let’s do a center crop.

## Calculate the rectangle to crop

![Android Crop Center of Bitmap - Stack Overflow](https://i.stack.imgur.com/NBKOf.jpg)

First we need to calculate the rectangle to crop of desired aspect ratio. We want the crop rectangle to always be inside the original image. We are trying to get similar result as in Image widget with `fit: BoxFit.cover`.

* * *

```dart
/// Get Crop Rectangle from given sizes
Rectangle<int> getCropRect({
  required int sourceWidth,
  required int sourceHeight,
  required double aspectRatio,
}) {
  var left = 0;
  var top = 0;
  var width = sourceWidth;
  var height = sourceHeight;

  if (aspectRatio < sourceWidth / sourceHeight) {
    // crop horizontally, from width
    width = (sourceHeight * aspectRatio).toInt(); // set new cropped width
    left = (sourceWidth - width) ~/ 2;
  } else if (aspectRatio > sourceWidth / sourceHeight) {
    // crop vertically, from height
    height = sourceWidth ~/ aspectRatio; // set new cropped height
    top = (sourceHeight - height) ~/ 2;
  }
  // else source and destination have same aspect ratio

  return Rectangle<int>(left, top, width, height);
}
```

## Center crop

Now we can use this to center crop the image. Don’t forget to import `image` package.

```dart
import 'package:image/image.dart' as img;

/// Center crop the source image with given aspectRatio
img.Image centerCrop(img.Image source, double aspectRatio) {
  final rect = getCropRect(
      sourceWidth: source.width,
      sourceHeight: source.height,
      aspectRatio: aspectRatio);

  return img.copyCrop(source, rect.left, rect.top, rect.width, rect.height);
}
```

## Resize

That was easy, but we also want to resize the image and want smaller image don’t we?

```dart
/// Crops and resize the image with given aspectRatio and width
img.Image cropAndResize(img.Image src, double aspectRatio, int width) {
  final cropped = centerCrop(src, aspectRatio);
  final croppedResized = img.copyResize(
    cropped,
    width: width,
    interpolation: img.Interpolation.average,
  );
  return croppedResized;
}
```

What if your input image size is smaller than desired image size? It will upscale the input image. If you don’t want this you can always modify the code like this;

```dart
    cropped,
    width: width,
    interpolation: img.Interpolation.average,
  );
  if (cropped.width <= width) {
    // do not resize if cropped image is smaller than desired image size
    // to avoid upscaling
    return cropped;
  }
  return croppedResized;
}
```

Here you can change the `interpolation` that fits best for you. I have used `average` since it is good if you scale down image by large factor. You can check the GitHub issue here for more detail. [https://github.com/brendan-duncan/image/issues/23](https://github.com/brendan-duncan/image/issues/23)

## Combining all these methods

```dart
Future<File> cropAndResizeFile({
  required File file,
  required double aspectRatio,
  required int width,
  int quality = 100,
}) async {
  final tempDir = await getTemporaryDirectory();
  final destPath = path.join(
    tempDir.path,
    path.basenameWithoutExtension(file.path) + '_compressed.jpg',
  );

  return compute<_CropResizeArgs, File>(
      _cropAndResizeFile,
      _CropResizeArgs(
        sourcePath: file.path,
        destPath: destPath,
        aspectRatio: aspectRatio,
        width: width,
        quality: quality,
      ));
}

class _CropResizeArgs {
  final String sourcePath;
  final String destPath;
  final double aspectRatio;
  final int width;
  final int quality;
  _CropResizeArgs({
    required this.sourcePath,
    required this.destPath,
    required this.aspectRatio,
    required this.width,
    required this.quality,
  });
}

Future<File> _cropAndResizeFile(_CropResizeArgs args) async {
  final image =
      await img.decodeImage(await File(args.sourcePath).readAsBytes());

  if (image == null) throw Exception('Unable to decode image from file');

  final croppedResized = cropAndResize(image, args.aspectRatio, args.width);
  // Encoding image to jpeg to compress the image. 
  final jpegBytes = img.encodeJpg(croppedResized, quality: args.quality);

  final croppedImageFile = await File(args.destPath).writeAsBytes(jpegBytes);
  return croppedImageFile;
}
```

You may wonder why using `_CropResizeArgs`? It is used to pass data to `compute` function. And why using `compute` function? If you process resource intensive task on main isolate then app will jank. If you don’t know term jank then it is lag and unresponsiveness in simpler term. In the case of image picking jank will cause a black screen after you picked your image. It is because main isolate have to process all the image decoding, cropping, resizing and encoding after you picked the image. You can see more about them here, [https://dart.dev/guides/language/concurrency](https://dart.dev/guides/language/concurrency) , [https://docs.flutter.dev/cookbook/networking/background-parsing](https://docs.flutter.dev/cookbook/networking/background-parsing).

## Use it

```dart
TextButton(
      onPressed: () async {
        // resizing image with image picker decreases quality
        final _pickedFile = await picker.pickImage(
          source: ImageSource.gallery,
        );

        // if user cancels file picking process do nothing
        if (_pickedFile == null) return;

        final length = await _pickedFile!.length() / 1024;
        log('Picked File Size in KB $length');

        await cropAndResizeFile(
          file: File(_pickedFile!.path),
          aspectRatio: 1.6,
          width: 800,
          quality: 80,
        ).then((value) async {
          _croppedFile = value;

          final length = await value.length() / 1024;
          log('Compressed File Size in KB $length');
        });
      },
      child: const Text('Pick an image'),
    )
```

Changing aspect ratio, width and quality we can get center cropped image with given quality and compression. We can also resize (maintaining aspect ratio) and compress images with image picker plugin. I didn’t use it here because I got terribly bad quality image after resizing and compressing. Also each time we decode and re-encode jpeg some data will be lost and it cause degraded image quality. To avoid this we decode the original image once and do all the stuffs then re-encode it to final image.

[Source](https://sangams.com.np/how-to-crop-and-compress-images-in-dart-flutter/)