Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 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,7 +69,7 @@ class MyHomePage extends StatefulWidget {
final String title;

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

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.0"
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();
}
48 changes: 25 additions & 23 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,7 +92,9 @@ 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();
}
Expand All @@ -105,7 +109,7 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
width = constraints.maxWidth;
height = constraints.maxHeight;
final cropWidth = min(width, height) * widget.cropPercentage;
final defaultScale = min(imageAsUIImage.width, imageAsUIImage.height) / cropWidth;
final defaultScale = min(imageAsUIImage!.width, imageAsUIImage!.height) / cropWidth;
final scale = data.scale * defaultScale;
path = _getPath(cropWidth, width, height);
return XGestureDetector(
Expand All @@ -125,7 +129,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(-imageAsUIImage!.width / 2, -imageAsUIImage!.height / 2),
child: Image(
image: widget.image,
),
Expand All @@ -139,9 +143,7 @@ class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropL
),
),
),
if (widget.drawPath != null) ...{
widget.drawPath(path),
},
widget.drawPath(path),
],
),
),
Expand All @@ -156,7 +158,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 +194,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 +216,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 +230,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