Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ You can provide the image using any Imageprovider.

## Parameters

### @required image
### required image
The image that needs to be cropped

### cropController
Expand Down Expand Up @@ -69,16 +69,16 @@ class MyHomePage extends StatefulWidget {
final String title;

MyHomePage({
@required this.title,
Key key,
required this.title,
Key? key,
}) : super(key: key);

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
CustomImageCropController controller;
late CustomImageCropController controller;

@override
void initState() {
Expand Down Expand Up @@ -118,7 +118,9 @@ class _MyHomePageState extends State<MyHomePage> {
icon: const Icon(Icons.crop),
onPressed: () async {
final image = await controller.onCropImage();
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image)));
if (image != null) {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image)));
}
},
),
],
Expand Down
10 changes: 6 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ class MyHomePage extends StatefulWidget {
final String title;

MyHomePage({
@required this.title,
Key key,
required this.title,
Key? key,
}) : super(key: key);

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
CustomImageCropController controller;
late CustomImageCropController controller;

@override
void initState() {
Expand Down Expand Up @@ -75,7 +75,9 @@ class _MyHomePageState extends State<MyHomePage> {
icon: const Icon(Icons.crop),
onPressed: () async {
final image = await controller.onCropImage();
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image)));
if (image != null) {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image)));
}
},
),
],
Expand Down
4 changes: 2 additions & 2 deletions example/lib/resultScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ class ResultScreen extends StatelessWidget {
final MemoryImage image;

const ResultScreen({
@required this.image,
Key key,
required this.image,
Key? key,
}) : super(key: key);

@override
Expand Down
6 changes: 3 additions & 3 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ packages:
name: gesture_x_detector
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.5"
version: "1.0.0"
matcher:
dependency: transitive
description:
Expand Down Expand Up @@ -164,5 +164,5 @@ packages:
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.17.0"
dart: ">=2.12.0 <3.0.0"
flutter: ">=2.0.2"
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ publish_to: 'none'
version: 1.0.0+1

environment:
sdk: ">=2.7.0 <3.0.0"
sdk: '>=2.12.0 <3.0.0'

dependencies:
flutter:
Expand Down
12 changes: 3 additions & 9 deletions lib/src/controllers/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@ import 'package:flutter/material.dart';
class CustomImageCropController {
final listeners = <CustomImageCropListener>[];

Future<MemoryImage> onCropImage() => listeners.map((e) => e.onCropImage()).firstWhere(
(element) => element != null,
orElse: () => null,
);
Future<MemoryImage?> onCropImage() => listeners.map((e) => e.onCropImage()).first;

CropImageData get cropImageData => listeners.map((e) => e.data).firstWhere(
(element) => element != null,
orElse: () => null,
);
CropImageData? get cropImageData => listeners.map((e) => e.data).first;

void addListener(CustomImageCropListener listener) => listeners.add(listener);

Expand All @@ -36,5 +30,5 @@ mixin CustomImageCropListener {

void setData(CropImageData transition);

Future<MemoryImage> onCropImage();
Future<MemoryImage?> onCropImage();
}
51 changes: 27 additions & 24 deletions lib/src/widgets/custom_image_crop_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,27 @@ class CustomImageCrop extends StatefulWidget {
/// `DottedCropPathPainter.drawPath` and
/// `SolidCropPathPainter.drawPath`
const CustomImageCrop({
@required this.image,
@required this.cropController,
Key key,
required this.image,
required this.cropController,
this.overlayColor = const Color.fromRGBO(0, 0, 0, 0.5),
this.backgroundColor = Colors.white,
this.shape = CustomCropShape.Circle,
this.cropPercentage = 0.8,
this.drawPath = DottedCropPathPainter.drawPath,
Key? key,
}) : super(key: key);

@override
_CustomImageCropState createState() => _CustomImageCropState();
}

class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropListener {
CropImageData dataTransitionStart;
Path path;
double width, height;
ui.Image imageAsUIImage;
ImageStream _imageStream;
ImageStreamListener _imageListener;
CropImageData? dataTransitionStart;
late Path path;
late double width, height;
ui.Image? imageAsUIImage;
ImageStream? _imageStream;
ImageStreamListener? _imageListener;

@override
void initState() {
Expand All @@ -75,10 +75,12 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
void _getImage() {
final oldImageStream = _imageStream;
_imageStream = widget.image.resolve(createLocalImageConfiguration(context));
if (_imageStream.key != oldImageStream?.key) {
oldImageStream?.removeListener(_imageListener);
if (_imageStream?.key != oldImageStream?.key) {
if (_imageListener != null) {
oldImageStream?.removeListener(_imageListener!);
}
_imageListener = ImageStreamListener(_updateImage);
_imageStream.addListener(_imageListener);
_imageStream?.addListener(_imageListener!);
}
}

Expand All @@ -90,22 +92,25 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL

@override
void dispose() {
_imageStream?.removeListener(_imageListener);
if (_imageListener != null) {
_imageStream?.removeListener(_imageListener!);
}
widget.cropController.removeListener(this);
super.dispose();
}

@override
Widget build(BuildContext context) {
if (imageAsUIImage == null) {
final image = imageAsUIImage;
if (image == null) {
return const Center(child: CircularProgressIndicator());
}
return LayoutBuilder(
builder: (context, constraints) {
width = constraints.maxWidth;
height = constraints.maxHeight;
final cropWidth = min(width, height) * widget.cropPercentage;
final defaultScale = min(imageAsUIImage.width, imageAsUIImage.height) / cropWidth;
final defaultScale = min(image.width, image.height) / cropWidth;
final scale = data.scale * defaultScale;
path = _getPath(cropWidth, width, height);
return XGestureDetector(
Expand All @@ -125,7 +130,7 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
child: Transform(
transform: Matrix4.diagonal3(vector_math.Vector3(scale, scale, 0))
..rotateZ(data.angle)
..translate(-imageAsUIImage.width / 2, -imageAsUIImage.height / 2),
..translate(-image.width / 2, -image.height / 2),
child: Image(
image: widget.image,
),
Expand All @@ -139,9 +144,7 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
),
),
),
if (widget.drawPath != null) ...{
widget.drawPath(path),
},
widget.drawPath(path),
],
),
),
Expand All @@ -156,7 +159,7 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL

void onScaleUpdate(ScaleEvent event) {
if (dataTransitionStart != null) {
addTransition(dataTransitionStart - CropImageData(scale: event.scale, angle: event.rotationAngle));
addTransition(dataTransitionStart! - CropImageData(scale: event.scale, angle: event.rotationAngle));
}
dataTransitionStart = CropImageData(scale: event.scale, angle: event.rotationAngle);
}
Expand Down Expand Up @@ -192,14 +195,14 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
}

@override
Future<MemoryImage> onCropImage() async {
Future<MemoryImage?> onCropImage() async {
if (imageAsUIImage == null) {
return null;
}
final cropWidth = min(width, height) * widget.cropPercentage;
final pictureRecorder = ui.PictureRecorder();
final canvas = Canvas(pictureRecorder);
final defaultScale = min(imageAsUIImage.width, imageAsUIImage.height) / cropWidth;
final defaultScale = min(imageAsUIImage!.width, imageAsUIImage!.height) / cropWidth;
final scale = data.scale * defaultScale;
final clipPath = Path.from(_getPath(cropWidth, cropWidth, cropWidth));
final matrix4Image = Matrix4.diagonal3(vector_math.Vector3(1, 1, 0))
Expand All @@ -214,7 +217,7 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
canvas.save();
canvas.clipPath(clipPath);
canvas.transform(matrix4Image.storage);
canvas.drawImage(imageAsUIImage, Offset(-imageAsUIImage.width / 2, -imageAsUIImage.height / 2), imagePaint);
canvas.drawImage(imageAsUIImage!, Offset(-imageAsUIImage!.width / 2, -imageAsUIImage!.height / 2), imagePaint);
canvas.restore();

// Optionally remove magenta from image by evaluating every pixel
Expand All @@ -228,7 +231,7 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
// A workaround would be to save the image and load it inside of the isolate
final bytes = await image.toByteData(format: ui.ImageByteFormat.png);

return MemoryImage(bytes.buffer.asUint8List());
return bytes == null ? null : MemoryImage(bytes.buffer.asUint8List());
}

@override
Expand Down
Loading