image_cropper_toolkit 0.0.1 copy "image_cropper_toolkit: ^0.0.1" to clipboard
image_cropper_toolkit: ^0.0.1 copied to clipboard

A reusable Flutter image cropper screen with rotate, flip, and repeat-crop support.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_cropper_toolkit/image_cropper_toolkit.dart';
import 'package:image_picker/image_picker.dart';

void main() {
  runApp(const CropperExampleApp());
}

/// Small demo app showing the full package usage flow.
class CropperExampleApp extends StatelessWidget {
  const CropperExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Image Cropper Toolkit',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF111827)),
        scaffoldBackgroundColor: Colors.white,
        useMaterial3: true,
      ),
      home: const CropperExampleHome(),
    );
  }
}

/// Home page that picks an image, crops it, previews it, and crops again.
class CropperExampleHome extends StatefulWidget {
  const CropperExampleHome({super.key});

  @override
  State<CropperExampleHome> createState() => _CropperExampleHomeState();
}

class _CropperExampleHomeState extends State<CropperExampleHome> {
  final ImagePicker _picker = ImagePicker();

  // Store both outputs: cropped preview for UI and full image for crop-again.
  XFile? _fullImage;
  XFile? _croppedImage;
  Rect? _cropRectInPixels;
  bool _isPicking = false;

  /// Picks an image from gallery and immediately opens the cropper.
  Future<void> _pickAndCropImage() async {
    if (_isPicking) {
      return;
    }

    setState(() => _isPicking = true);
    try {
      final XFile? pickedImage = await _picker.pickImage(
        source: ImageSource.gallery,
        imageQuality: 100,
      );
      if (!mounted || pickedImage == null) {
        return;
      }

      await _openCropper(
        CropImageItem.fromXFile(pickedImage),
        resetPreviousCrop: true,
      );
    } finally {
      if (mounted) {
        setState(() => _isPicking = false);
      }
    }
  }

  /// Reopens the cropper using the full image and the last selected crop rect.
  Future<void> _cropAgain() async {
    final XFile? fullImage = _fullImage;
    if (fullImage == null) {
      return;
    }

    await _openCropper(
      CropImageItem(
        fullImage: fullImage,
        previewImage: _croppedImage,
        cropRectInPixels: _cropRectInPixels,
      ),
    );
  }

  /// Opens the package crop screen and stores the result for preview/re-crop.
  Future<void> _openCropper(
    CropImageItem imageItem, {
    bool resetPreviousCrop = false,
  }) async {
    final CropImageResult? result = await ImageCropperToolkit.cropImage(
      context: context,
      imageItem: imageItem,
      config: const CropImageConfig(
        title: 'Crop Image',
        saveLabel: 'Save',
        primaryColor: Color.fromARGB(255, 221, 8, 8),
        canvasBackgroundColor: Colors.white,
        backgroundColor: Colors.white,
        saveButtonColor: Color(0xFF2563EB),
        cropLineColor: Color(0xFF2563EB),
        rotateLeftItem: CropToolbarItemConfig(
          child: Icon(Icons.rotate_left_rounded),
          tooltip: 'Rotate left',
        ),
        rotateRightItem: CropToolbarItemConfig(
          child: Icon(Icons.rotate_right_rounded),
          tooltip: 'Rotate right',
        ),
        flipItem: CropToolbarItemConfig(
          tooltip: 'Flip image',
          child: Icon(Icons.flip_rounded),
        ),
      ),
    );

    if (!mounted || result == null) {
      return;
    }

    setState(() {
      _fullImage = result.fullImage;
      _croppedImage = result.croppedImage;
      _cropRectInPixels = result.cropRectInPixels;

      if (resetPreviousCrop) {
        _cropRectInPixels = result.cropRectInPixels;
      }
    });
  }

  /// Clears the current demo state.
  void _clearImage() {
    setState(() {
      _fullImage = null;
      _croppedImage = null;
      _cropRectInPixels = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    final bool hasResult = _croppedImage != null;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Image Cropper Toolkit'),
        actions: [
          if (hasResult)
            IconButton(
              tooltip: 'Clear',
              onPressed: _clearImage,
              icon: const Icon(Icons.delete_outline_rounded),
            ),
        ],
      ),
      body: SafeArea(
        child: ListView(
          padding: const EdgeInsets.all(24),
          children: [
            _PreviewCard(croppedImage: _croppedImage),
            const SizedBox(height: 24),
            FilledButton.icon(
              onPressed: _isPicking ? null : _pickAndCropImage,
              icon: _isPicking
                  ? const SizedBox(
                      width: 18,
                      height: 18,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : const Icon(Icons.photo_library_outlined),
              label: Text(_isPicking ? 'Opening gallery...' : 'Pick image'),
            ),
            const SizedBox(height: 12),
            OutlinedButton.icon(
              onPressed: hasResult ? _cropAgain : null,
              icon: const Icon(Icons.crop_rounded),
              label: const Text('Crop again'),
            ),
            const SizedBox(height: 24),
            _UsageNotes(
              hasResult: hasResult,
              cropRectInPixels: _cropRectInPixels,
            ),
          ],
        ),
      ),
    );
  }
}

/// Square preview area for the last cropped image.
class _PreviewCard extends StatelessWidget {
  const _PreviewCard({required this.croppedImage});

  final XFile? croppedImage;

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 1,
      child: DecoratedBox(
        decoration: BoxDecoration(
          color: const Color(0xFFF3F4F6),
          borderRadius: BorderRadius.circular(16),
          border: Border.all(color: const Color(0xFFE5E7EB)),
        ),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(16),
          child: croppedImage == null
              ? const _EmptyPreview()
              : Image.file(File(croppedImage!.path), fit: BoxFit.contain),
        ),
      ),
    );
  }
}

/// Placeholder shown before the user picks an image.
class _EmptyPreview extends StatelessWidget {
  const _EmptyPreview();

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.image_outlined, size: 56, color: Color(0xFF9CA3AF)),
          SizedBox(height: 12),
          Text(
            'Pick an image to crop',
            style: TextStyle(
              color: Color(0xFF6B7280),
              fontWeight: FontWeight.w600,
            ),
          ),
        ],
      ),
    );
  }
}

/// Explains which result fields are used by the example.
class _UsageNotes extends StatelessWidget {
  const _UsageNotes({required this.hasResult, required this.cropRectInPixels});

  final bool hasResult;
  final Rect? cropRectInPixels;

  @override
  Widget build(BuildContext context) {
    final Rect? rect = cropRectInPixels;

    return DecoratedBox(
      decoration: BoxDecoration(
        color: const Color(0xFFF9FAFB),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFFE5E7EB)),
      ),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Example flow',
              style: Theme.of(
                context,
              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
            ),
            const SizedBox(height: 8),
            const Text(
              '1. Pick an image from gallery.\n'
              '2. Crop, rotate, or flip it.\n'
              '3. The home screen shows croppedImage.\n'
              '4. Crop again uses fullImage and cropRectInPixels, so the full image is still available.',
            ),
            if (hasResult && rect != null) ...[
              const SizedBox(height: 12),
              Text(
                'Last crop rect: '
                'left ${rect.left.toStringAsFixed(0)}, '
                'top ${rect.top.toStringAsFixed(0)}, '
                'width ${rect.width.toStringAsFixed(0)}, '
                'height ${rect.height.toStringAsFixed(0)}',
                style: const TextStyle(
                  color: Color(0xFF4B5563),
                  fontWeight: FontWeight.w600,
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}
6
likes
160
points
8
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

A reusable Flutter image cropper screen with rotate, flip, and repeat-crop support.

Repository (GitHub)
View/report issues

Topics

#image-cropper #crop #image #flutter

License

MIT (license)

Dependencies

cross_file, flutter

More

Packages that depend on image_cropper_toolkit