crop_view 0.1.0
crop_view: ^0.1.0 copied to clipboard
An aspect-locked image cropper for Flutter. The crop window stays fixed while the image pans, zooms and rotates behind it, so the crop is always fully covered. Crops to JPEG (with a quality factor) or [...]
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:crop_view/crop_view.dart';
import 'package:flutter/material.dart';
void main() => runApp(const CropViewExampleApp());
/// A tiny app that generates an image, crops it, and shows the result.
class CropViewExampleApp extends StatelessWidget {
/// Creates the example app.
const CropViewExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'crop_view example',
theme: ThemeData.dark(),
home: const CropPage(),
);
}
}
/// Crops a generated gradient and shows the result underneath.
class CropPage extends StatefulWidget {
/// Creates the crop page.
const CropPage({super.key});
@override
State<CropPage> createState() => _CropPageState();
}
class _CropPageState extends State<CropPage> {
final CropController _controller = CropController(aspectRatio: 1);
Future<Uint8List>? _source;
Uint8List? _result;
@override
void initState() {
super.initState();
_source = _generateImage();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
/// A colourful placeholder so the example needs no asset.
Future<Uint8List> _generateImage() async {
const size = 600;
const full = Rect.fromLTWH(0, 0, 600, 600);
final recorder = ui.PictureRecorder();
final canvas = ui.Canvas(recorder);
canvas.drawRect(
full,
ui.Paint()
..shader = const LinearGradient(
colors: [Colors.indigo, Colors.pink, Colors.orange],
).createShader(full),
);
for (var i = 0; i < 6; i++) {
canvas.drawCircle(
Offset(size * (i + 1) / 7, size / 2),
24,
ui.Paint()..color = Colors.white.withValues(alpha: 0.7),
);
}
final image = await recorder.endRecording().toImage(size, size);
final data = await image.toByteData(format: ui.ImageByteFormat.png);
image.dispose();
return data!.buffer.asUint8List();
}
Future<void> _crop() async {
final bytes = await _controller.crop(
maxEdge: 512,
format: const CropFormat.jpeg(quality: 85),
);
if (mounted) setState(() => _result = bytes);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('crop_view')),
body: FutureBuilder<Uint8List>(
future: _source,
builder: (context, snapshot) {
final bytes = snapshot.data;
if (bytes == null) {
return const Center(child: CircularProgressIndicator());
}
return Column(
children: [
Expanded(
child: CropView(imageBytes: bytes, controller: _controller),
),
_Toolbar(controller: _controller, onCrop: _crop),
if (_result != null)
Padding(
padding: const EdgeInsets.all(12),
child: Image.memory(_result!, height: 120),
),
],
);
},
),
);
}
}
class _Toolbar extends StatelessWidget {
const _Toolbar({required this.controller, required this.onCrop});
final CropController controller;
final VoidCallback onCrop;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: controller.rotateLeft,
icon: const Icon(Icons.rotate_left),
),
IconButton(
onPressed: controller.rotateRight,
icon: const Icon(Icons.rotate_right),
),
IconButton(
onPressed: controller.zoomIn,
icon: const Icon(Icons.zoom_in),
),
IconButton(
onPressed: controller.zoomOut,
icon: const Icon(Icons.zoom_out),
),
IconButton(
onPressed: controller.reset,
icon: const Icon(Icons.restore),
),
FilledButton(onPressed: onCrop, child: const Text('Crop')),
],
),
);
}
}