cropigo

pub package license: MIT

cropigo is a modern, unified image picker + cropper for Flutter. Pick an image from the gallery or camera and crop it with fully customizable output size, aspect ratio, shape, compression, and native crop-screen theming — all through one simple, null-safe API.

It wraps and coordinates two proven plugins (image_picker + image_cropper) behind a single, ergonomic surface, so you get one consistent API, one result type, and one cancellation model instead of juggling two packages.

final result = await Cropigo.pickAndCrop(source: CropigoSource.gallery);
if (result != null) {
  imageFile = result.file; // ready to display or upload
}

Table of contents


Features

  • 📷 One call, two sources — gallery or camera through the same method.
  • ✂️ Native crop UI — uses the platform's own crop screen (fast, familiar, accessible) instead of a hand-rolled widget.
  • 📐 Custom aspect ratios — lock to square, 4:3, 16:9, 3:4, any custom ratio, or leave it free-form.
  • ⭕ Rectangle or circular crop mask — perfect for avatars.
  • 🗜️ Custom output size & compression — set maxWidth/maxHeight, format (JPG/PNG/WebP-fallback), and quality.
  • 🎨 Themeable crop screen — toolbar color, grid visibility, locked ratios, and more, via CropigoTheme.
  • 🧩 Null-safe, cancellation-aware result — cancelling the picker or crop screen returns null instead of throwing, so happy-path code stays clean.
  • 📏 Decoded dimensions out of the box — CropigoResult.width/height and a human-readable file size ("482 KB"), no extra image-decoding code needed in your app.

Installation

Add it to your pubspec.yaml:

dependencies:
  cropigo: ^1.0.2

Then:

flutter pub get

Note on the name: verify cropigo is still unclaimed on pub.dev before publishing — package names are first-come, first-served and availability can change.

Platform setup

cropigo builds on image_picker and image_cropper, so both packages' native setup steps apply.

Android

In android/app/src/main/AndroidManifest.xml, make sure you request camera and storage permissions (gallery access on modern Android uses scoped storage and generally needs no extra permission, but camera does):

<uses-permission android:name="android.permission.CAMERA" />

In android/app/build.gradle, set a minimum SDK compatible with the crop UI:

android {
    defaultConfig {
        minSdkVersion 21
    }
}

If you use ProGuard/R8, no extra rules are typically required for the crop screen (UCrop), but if you hit a ClassNotFoundException in a release build, add:

-keep class com.yalantis.ucrop** { *; }
-keep interface com.yalantis.ucrop** { *; }

iOS

In ios/Runner/Info.plist, add usage descriptions for camera and photo library access — iOS will crash at runtime without these:

<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take a photo.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to pick a photo.</string>

Quick start

import 'package:cropigo/cropigo.dart';

Future<void> pickAvatar() async {
  final result = await Cropigo.pickAndCrop(
    source: CropigoSource.gallery,
    options: const CropigoOptions(
      aspectRatio: CropigoAspectRatio.square,
      shape: CropigoCropShape.oval,
      maxWidth: 512,
      maxHeight: 512,
      compressQuality: 85,
    ),
  );

  if (result == null) {
    // User cancelled the picker or the crop screen — nothing to do.
    return;
  }

  print('Picked ${result.width}x${result.height}, ${result.readableSize}');
  // Use result.file / result.path to display or upload the image.
}

Core concepts

CropigoSource

Where to pick the image from:

enum CropigoSource { gallery, camera }

CropigoOptions

The single configuration object passed to every Cropigo call. Every field has a default, so you only override what you need:

Field Default Description
aspectRatio null (free-form) Locks the crop box to a CropigoAspectRatio.
shape CropigoCropShape.rectangle rectangle or oval.
maxWidth/maxHeight 1080 / 1080 Maximum output dimensions in pixels.
compressFormat CropigoCompressFormat.jpg jpg, png, or webp (falls back to png where unsupported).
compressQuality 90 0–100 output quality.
enableCrop true Set false to skip the crop screen entirely.
lockAspectRatioList square/4:3/16:9/3:4 Quick-select ratio presets shown on the crop screen (Android).
theme CropigoTheme.modern Visual customization of the crop screen.
imageQuality 100 Quality applied by the OS picker itself, before cropping.
preferredCameraDevice CropigoCameraDevice.rear rear or front, used when picking from the camera.

CropigoAspectRatio

const CropigoAspectRatio(double width, double height)

Built-in presets: CropigoAspectRatio.square, .classic (4:3), .widescreen (16:9), .portrait (3:4). Pass any custom ratio, e.g. CropigoAspectRatio(2, 1), or leave CropigoOptions.aspectRatio as null for a fully free-form crop.

CropigoTheme

Customizes the native crop screen's look:

const CropigoTheme({
  String toolbarTitle = 'Crop Image',
  Color? toolbarColor,
  Color? toolbarWidgetColor,
  Color? statusBarColor,
  Color? backgroundColor,
  Color? activeControlsWidgetColor,
  Color? cropFrameColor,
  Color? cropGridColor,
  bool showCropGrid = true,
  bool lockAspectRatio = false,
  bool hideBottomControls = false,
});

Use CropigoTheme.modern as a sensible starting point, or copyWith(...) to tweak individual fields.

CropigoResult

Returned on success:

class CropigoResult {
  final File file;
  final int width;
  final int height;
  final int sizeInBytes;
  final String path;
  String get readableSize; // e.g. "482 KB"
}

Error handling

cropigo treats user cancellation as a normal outcome, not an error: if the user backs out of the gallery/camera picker or the crop screen, every Cropigo method simply returns null. You don't need a try/catch for the common "changed their mind" path.

Genuine failures — most commonly a denied camera/photo-library permission, or an unexpected I/O/platform error — throw a CropigoException:

try {
  final result = await Cropigo.pickAndCrop(source: CropigoSource.camera);
  if (result == null) return; // cancelled, not an error
  // use result...
} on CropigoException catch (e) {
  switch (e.reason) {
    case CropigoFailureReason.permissionDenied:
      // Prompt the user to enable camera/photos permission in Settings.
      break;
    case CropigoFailureReason.unknown:
    default:
      // Log / show a generic error message.
      break;
  }
}

Recipes

Circular profile photo

final result = await Cropigo.pickAndCrop(
  source: CropigoSource.gallery,
  options: const CropigoOptions(
    aspectRatio: CropigoAspectRatio.square,
    shape: CropigoCropShape.oval,
    maxWidth: 512,
    maxHeight: 512,
  ),
);

Free-form crop, no aspect lock

final result = await Cropigo.pickAndCrop(
  source: CropigoSource.gallery,
  options: const CropigoOptions(aspectRatio: null),
);

Pick without cropping

final result = await Cropigo.pickOnly(source: CropigoSource.camera);

Crop an image you already have

Useful when the image didn't come from the picker (e.g. downloaded, or produced by a previous step):

final result = await Cropigo.cropExisting(
  imagePath: existingFile.path,
  options: const CropigoOptions(aspectRatio: CropigoAspectRatio.widescreen),
);

Custom themed crop screen

final result = await Cropigo.pickAndCrop(
  source: CropigoSource.gallery,
  options: CropigoOptions(
    theme: CropigoTheme.modern.copyWith(
      toolbarTitle: 'Adjust your photo',
      toolbarColor: Colors.indigo,
      toolbarWidgetColor: Colors.white,
      activeControlsWidgetColor: Colors.indigoAccent,
      showCropGrid: true,
      lockAspectRatio: true,
    ),
  ),
);

API reference summary

Method Description
Cropigo.pickAndCrop({source, options}) Picks an image and (by default) opens the crop screen.
Cropigo.pickOnly({source, options}) Picks an image, applying size/quality constraints, no crop screen.
Cropigo.cropExisting({imagePath, options}) Opens the crop screen for an already-existing file.

All three return Future<CropigoResult?> and throw CropigoException on genuine failure (see Error handling).

FAQ

Does this replace image_picker or image_cropper? No — it composes them behind one API. You can still depend on either directly for advanced use cases (e.g. multi-image picking) that fall outside cropigo's scope.

Does WebP output actually produce a .webp file? The underlying native crop libraries only support JPG/PNG encoders, so CropigoCompressFormat.webp currently falls back to PNG. This is called out explicitly so it's never a silent surprise; convert to WebP afterward with a dedicated image-encoding package if you need the file itself in that format.

Does it work on web/desktop? image_picker supports web; the native crop screen (image_cropper) is mobile-first (Android/iOS). On unsupported platforms, use Cropigo.pickOnly() to pick without the crop step.

Contributing

Issues and pull requests are welcome. Please include tests for any new behavior and run flutter analyze + flutter test before submitting.

License

MIT — see LICENSE.

Libraries

cropigo
Cropigo — a modern, unified image picker + cropper for Flutter.