Memory Image Cropper

A Flutter plugin for cropping images entirely in memory without creating files on disk. Designed for compliance-sensitive applications where writing image data to storage is not permitted.

Features

No files created on disk - All operations are performed in memory
Pure Dart implementation - Cropping uses Flutter's Canvas API
Android & iOS support
Multiple crop shapes - Rectangle, Circle, Square, Custom Ratio
Customizable UI - Colors, grid, borders, toolbar
Gesture support - Pan, zoom, resize crop area
Rotation support - 90° left/right rotation
Returns Uint8List - Ready for upload or display

Installation

Add to your pubspec.yaml:

dependencies:
  memory_image_cropper:
    path: ./memory_image_cropper  # or publish to pub.dev

Usage

Basic Usage (Dialog)

import 'package:memory_image_cropper/memory_image_cropper.dart';

// Your image bytes (from camera, gallery, network, etc.)
Uint8List imageBytes = await getImageBytes();

// Show crop dialog
CropResult? result = await MemoryImageCropperPlugin.cropImage(
  context,
  imageBytes: imageBytes,
  shape: CropShape.rectangle,
  title: 'Crop Image',
  toolbarColor: Color(0xFF97144D),
);

if (result != null) {
  // Use cropped bytes directly - NO FILE CREATED!
  Uint8List croppedBytes = result.bytes;
  
  // Display
  Image.memory(croppedBytes);
  
  // Or upload directly
  await uploadToServer(croppedBytes);
}

Full Screen Cropper

CropResult? result = await MemoryImageCropperPlugin.cropImageFullScreen(
  context,
  imageBytes: imageBytes,
  shape: CropShape.circle,
  aspectRatio: CropAspectRatio.square,
  maxWidth: 1024,
  maxHeight: 1024,
);

Using the Widget Directly

final controller = CropperController();

// Load image
await controller.loadImage(imageBytes);

// In your widget tree
MemoryImageCropper(
  imageBytes: imageBytes,
  controller: controller,
  shape: CropShape.rectangle,
  aspectRatio: CropAspectRatio.ratio16x9,
  showGrid: true,
  borderColor: Colors.white,
  overlayColor: Colors.black54,
)

// Crop when ready
CropResult? result = await controller.cropImage(
  maxWidth: 1024,
  maxHeight: 1024,
);

Controller Methods

final controller = CropperController();

// Load image
await controller.loadImage(imageBytes);

// Rotation
controller.rotateLeft();   // Rotate 90° counter-clockwise
controller.rotateRight();  // Rotate 90° clockwise

// Zoom
controller.zoomIn();
controller.zoomOut();

// Reset all transformations
controller.reset();

// Get cropped image
CropResult? result = await controller.cropImage();

Crop Shapes

CropShape.rectangle  // Free form rectangle
CropShape.circle     // Circular crop (1:1 aspect)
CropShape.square     // Square crop (1:1 aspect)
CropShape.ratio      // Custom aspect ratio

Aspect Ratios

CropAspectRatio.square     // 1:1
CropAspectRatio.ratio16x9  // 16:9
CropAspectRatio.ratio9x16  // 9:16
CropAspectRatio.ratio4x3   // 4:3
CropAspectRatio.ratio3x4   // 3:4
CropAspectRatio.ratio3x2   // 3:2
CropAspectRatio.ratio2x3   // 2:3

// Custom
CropAspectRatio(width: 5, height: 4)

CropResult

class CropResult {
  final Uint8List bytes;   // Cropped image bytes (PNG format)
  final int width;         // Width in pixels
  final int height;        // Height in pixels
  
  bool get isValid;        // Check if result is valid
  int get sizeInBytes;     // Size in bytes
  double get sizeInKB;     // Size in KB
  double get sizeInMB;     // Size in MB
}

Customization Options

MemoryImageCropperPlugin.cropImage(
  context,
  imageBytes: imageBytes,
  
  // Shape
  shape: CropShape.rectangle,
  aspectRatio: CropAspectRatio.ratio16x9,
  
  // UI Colors
  toolbarColor: Color(0xFF97144D),
  toolbarWidgetColor: Colors.white,
  backgroundColor: Colors.black,
  overlayColor: Color(0x80000000),
  
  // Grid
  showGrid: true,
  
  // Output size limits
  maxWidth: 1024,
  maxHeight: 1024,
  
  // Dialog behavior
  barrierDismissible: false,
  title: 'Crop Image',
);

Integration with Your Existing Code

Replace your UCrop implementation:

// OLD (UCrop - creates files)
File? croppedFile = await cropImageFile(sourceFile);
Uint8List bytes = await croppedFile.readAsBytes();

// NEW (MemoryImageCropper - no files)
Uint8List sourceBytes = await sourceFile.readAsBytes();
CropResult? result = await MemoryImageCropperPlugin.cropImage(
  context,
  imageBytes: sourceBytes,
);
Uint8List croppedBytes = result!.bytes;

For Your Gold Loan App

// In your captureOrnamentPhoto method:

Future<Uint8List?> captureAndCropOrnament() async {
  // Capture image (returns bytes, not file)
  final XFile? photo = await picker.pickImage(source: ImageSource.camera);
  if (photo == null) return null;
  
  final Uint8List imageBytes = await photo.readAsBytes();
  
  // Crop in memory (NO FILE CREATED)
  final CropResult? result = await MemoryImageCropperPlugin.cropImage(
    context,
    imageBytes: imageBytes,
    shape: CropShape.rectangle,
    toolbarColor: const Color(0xFF97144D),
    maxWidth: 1024,
    maxHeight: 1024,
  );
  
  if (result == null) return null;
  
  // Return bytes directly for upload
  return result.bytes;
}

Compliance Benefits

Feature UCrop MemoryImageCropper
Creates temp files ✅ Yes ❌ No
Writes to disk ✅ Yes ❌ No
Data in memory only ❌ No ✅ Yes
Work profile safe ❌ Issues ✅ Safe
Compliance friendly ❌ No ✅ Yes

License

MIT License

Author

Created for compliance-sensitive Flutter applications.