cropigo
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
- Installation
- Platform setup
- Quick start
- Core concepts
- Recipes
- API reference summary
- FAQ
- Contributing
- License
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
nullinstead of throwing, so happy-path code stays clean. - 📏 Decoded dimensions out of the box —
CropigoResult.width/heightand 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
cropigois 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.