@@ -34,6 +34,13 @@ class CustomImageCrop extends StatefulWidget {
34
34
/// - [CustomCropShape.Ratio] Crop area will have a specified aspect ratio.
35
35
final CustomCropShape shape;
36
36
37
+ /// The shape of the mask area.
38
+ /// Possible values:
39
+ /// - [CustomCropShape.Circle] Mask area will be circular.
40
+ /// - [CustomCropShape.Square] Mask area will be a square.
41
+ /// - [CustomCropShape.Ratio] Mask area will have a specified aspect ratio.
42
+ final CustomCropShape ? maskShape;
43
+
37
44
/// Ratio of the cropping area.
38
45
/// If [shape] is set to [CustomCropShape.Ratio] , this property is required.
39
46
/// For example, to create a square crop area, use [Ratio(width: 1, height: 1)] .
@@ -119,6 +126,7 @@ class CustomImageCrop extends StatefulWidget {
119
126
this .overlayColor = const Color .fromRGBO (0 , 0 , 0 , 0.5 ),
120
127
this .backgroundColor = Colors .white,
121
128
this .shape = CustomCropShape .Circle ,
129
+ this .maskShape,
122
130
this .imageFit = CustomImageFit .fitCropSpace,
123
131
this .cropPercentage = 0.8 ,
124
132
this .drawPath = DottedCropPathPainter .drawPath,
@@ -133,8 +141,7 @@ class CustomImageCrop extends StatefulWidget {
133
141
Paint ? imagePaintDuringCrop,
134
142
this .forceInsideCropArea = false ,
135
143
Key ? key,
136
- }) : this .imagePaintDuringCrop = imagePaintDuringCrop ??
137
- (Paint ()..filterQuality = FilterQuality .high),
144
+ }) : this .imagePaintDuringCrop = imagePaintDuringCrop ?? (Paint ()..filterQuality = FilterQuality .high),
138
145
assert (
139
146
! (shape == CustomCropShape .Ratio && ratio == null ),
140
147
"If shape is set to Ratio, ratio should not be null." ,
@@ -145,10 +152,10 @@ class CustomImageCrop extends StatefulWidget {
145
152
_CustomImageCropState createState () => _CustomImageCropState ();
146
153
}
147
154
148
- class _CustomImageCropState extends State <CustomImageCrop >
149
- with CustomImageCropListener {
155
+ class _CustomImageCropState extends State <CustomImageCrop > with CustomImageCropListener {
150
156
CropImageData ? _dataTransitionStart;
151
157
late Path _path;
158
+ late Path _maskPath;
152
159
late double _width, _height;
153
160
ui.Image ? _imageAsUIImage;
154
161
ImageStream ? _imageStream;
@@ -227,7 +234,20 @@ class _CustomImageCropState extends State<CustomImageCrop>
227
234
width: _width,
228
235
height: _height,
229
236
borderRadius: widget.borderRadius,
237
+ shape: widget.shape,
230
238
);
239
+
240
+ _maskPath = widget.maskShape == null
241
+ ? _path
242
+ : _getPath (
243
+ cropWidth: cropFitParams.cropSizeWidth,
244
+ cropHeight: cropFitParams.cropSizeHeight,
245
+ width: _width,
246
+ height: _height,
247
+ borderRadius: widget.borderRadius,
248
+ shape: widget.maskShape! ,
249
+ );
250
+
231
251
return XGestureDetector (
232
252
onMoveStart: onMoveStart,
233
253
onMoveUpdate: onMoveUpdate,
@@ -243,8 +263,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
243
263
left: data.x + _width / 2 ,
244
264
top: data.y + _height / 2 ,
245
265
child: Transform (
246
- transform: Matrix4 .diagonal3 (
247
- vector_math.Vector3 (scale, scale, scale))
266
+ transform: Matrix4 .diagonal3 (vector_math.Vector3 (scale, scale, scale))
248
267
..rotateZ (data.angle)
249
268
..translate (- image.width / 2 , - image.height / 2 ),
250
269
child: Image (
@@ -254,13 +273,13 @@ class _CustomImageCropState extends State<CustomImageCrop>
254
273
),
255
274
IgnorePointer (
256
275
child: ClipPath (
257
- clipper: InvertedClipper (_path , _width, _height),
276
+ clipper: InvertedClipper (_maskPath , _width, _height),
258
277
child: Container (
259
278
color: widget.overlayColor,
260
279
),
261
280
),
262
281
),
263
- widget.drawPath (_path , pathPaint: widget.pathPaint),
282
+ widget.drawPath (_maskPath , pathPaint: widget.pathPaint),
264
283
],
265
284
),
266
285
),
@@ -274,8 +293,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
274
293
}
275
294
276
295
void onScaleUpdate (ScaleEvent event) {
277
- final scale =
278
- widget.canScale ? event.scale : (_dataTransitionStart? .scale ?? 1.0 );
296
+ final scale = widget.canScale ? event.scale : (_dataTransitionStart? .scale ?? 1.0 );
279
297
280
298
final angle = widget.canRotate ? event.rotationAngle : 0.0 ;
281
299
@@ -301,8 +319,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
301
319
void onMoveUpdate (MoveEvent event) {
302
320
if (! widget.canMove) return ;
303
321
304
- widget.cropController
305
- .addTransition (CropImageData (x: event.delta.dx, y: event.delta.dy));
322
+ widget.cropController.addTransition (CropImageData (x: event.delta.dx, y: event.delta.dy));
306
323
}
307
324
308
325
Rect _getInitialImageRect () {
@@ -318,8 +335,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
318
335
aspectRatio: (widget.ratio? .width ?? 1 ) / (widget.ratio? .height ?? 1 ),
319
336
);
320
337
final initialWidth = _imageAsUIImage! .width * cropFitParams.additionalScale;
321
- final initialHeight =
322
- _imageAsUIImage! .height * cropFitParams.additionalScale;
338
+ final initialHeight = _imageAsUIImage! .height * cropFitParams.additionalScale;
323
339
return Rect .fromLTWH (
324
340
(_width - initialWidth) / 2 ,
325
341
(_height - initialHeight) / 2 ,
@@ -347,53 +363,38 @@ class _CustomImageCropState extends State<CustomImageCrop>
347
363
348
364
if (transition.x != 0 || transition.y != 0 ) {
349
365
if (isRotated) {
350
- _addTransitionInternal (
351
- CropImageData (x: startX - data.x, y: startY - data.y));
366
+ _addTransitionInternal (CropImageData (x: startX - data.x, y: startY - data.y));
352
367
} else {
353
368
final imageRect = _getImageRect (initialImageRect, data.scale);
354
369
double deltaX = min (pathRect.left - imageRect.left, 0 );
355
- deltaX = pathRect.right > imageRect.right
356
- ? pathRect.right - imageRect.right
357
- : deltaX;
370
+ deltaX = pathRect.right > imageRect.right ? pathRect.right - imageRect.right : deltaX;
358
371
double deltaY = min (pathRect.top - imageRect.top, 0 );
359
- deltaY = pathRect.bottom > imageRect.bottom
360
- ? pathRect.bottom - imageRect.bottom
361
- : deltaY;
372
+ deltaY = pathRect.bottom > imageRect.bottom ? pathRect.bottom - imageRect.bottom : deltaY;
362
373
_addTransitionInternal (CropImageData (x: deltaX, y: deltaY));
363
374
}
364
375
return ;
365
376
}
366
- double minEdgeHalf =
367
- min (initialImageRect.width, initialImageRect.height) / 2 ;
368
- double adaptScale = _calculateScaleAfterRotate (
369
- pathRect, data.scale, initialImageRect, minEdgeHalf);
377
+ double minEdgeHalf = min (initialImageRect.width, initialImageRect.height) / 2 ;
378
+ double adaptScale = _calculateScaleAfterRotate (pathRect, data.scale, initialImageRect, minEdgeHalf);
370
379
_addTransitionInternal (CropImageData (scale: adaptScale / data.scale));
371
380
}
372
381
373
382
Rect _getImageRect (Rect initialImageRect, double currentScale) {
374
383
final diffScale = (1 - currentScale) / 2 ;
375
- final left =
376
- initialImageRect.left + diffScale * initialImageRect.width + data.x;
377
- final top =
378
- initialImageRect.top + diffScale * initialImageRect.height + data.y;
379
- Rect imageRect = Rect .fromLTWH (
380
- left,
381
- top,
382
- currentScale * initialImageRect.width,
383
- currentScale * initialImageRect.height);
384
+ final left = initialImageRect.left + diffScale * initialImageRect.width + data.x;
385
+ final top = initialImageRect.top + diffScale * initialImageRect.height + data.y;
386
+ Rect imageRect = Rect .fromLTWH (left, top, currentScale * initialImageRect.width, currentScale * initialImageRect.height);
384
387
return imageRect;
385
388
}
386
389
387
- double _getDistanceBetweenPointAndLine (
388
- Offset point, Offset lineStart, Offset lineEnd) {
390
+ double _getDistanceBetweenPointAndLine (Offset point, Offset lineStart, Offset lineEnd) {
389
391
if (lineEnd.dy == lineStart.dy) {
390
392
return (point.dy - lineStart.dy).abs ();
391
393
}
392
394
if (lineEnd.dx == lineStart.dx) {
393
395
return (point.dx - lineStart.dx).abs ();
394
396
}
395
- double line1Slop =
396
- (lineEnd.dy - lineStart.dy) / (lineEnd.dx - lineStart.dx);
397
+ double line1Slop = (lineEnd.dy - lineStart.dy) / (lineEnd.dx - lineStart.dx);
397
398
double line1Delta = lineEnd.dy - lineEnd.dx * line1Slop;
398
399
double line2Slop = - 1 / line1Slop;
399
400
double line2Delta = point.dy - point.dx * line2Slop;
@@ -402,13 +403,11 @@ class _CustomImageCropState extends State<CustomImageCrop>
402
403
return (Offset (crossPointX, crossPointY) - point).distance;
403
404
}
404
405
405
- bool _isContainPath (
406
- Rect initialImageRect, Rect pathRect, double currentScale) {
406
+ bool _isContainPath (Rect initialImageRect, Rect pathRect, double currentScale) {
407
407
final imageRect = _getImageRect (initialImageRect, currentScale);
408
408
Offset topLeft, topRight, bottomLeft, bottomRight;
409
409
final rad = atan (imageRect.height / imageRect.width);
410
- final len =
411
- sqrt (pow (imageRect.width / 2 , 2 ) + pow (imageRect.height / 2 , 2 ));
410
+ final len = sqrt (pow (imageRect.width / 2 , 2 ) + pow (imageRect.height / 2 , 2 ));
412
411
bool isRotated = data.angle != 0 ;
413
412
414
413
if (isRotated) {
@@ -419,11 +418,9 @@ class _CustomImageCropState extends State<CustomImageCrop>
419
418
final cosCounterClockValue = len * cos (counterClockAngle);
420
419
final sinCounterClockValue = len * sin (counterClockAngle);
421
420
bottomRight = imageRect.center.translate (cosClockValue, sinClockValue);
422
- topRight = imageRect.center
423
- .translate (cosCounterClockValue, - sinCounterClockValue);
421
+ topRight = imageRect.center.translate (cosCounterClockValue, - sinCounterClockValue);
424
422
topLeft = imageRect.center.translate (- cosClockValue, - sinClockValue);
425
- bottomLeft = imageRect.center
426
- .translate (- cosCounterClockValue, sinCounterClockValue);
423
+ bottomLeft = imageRect.center.translate (- cosCounterClockValue, sinCounterClockValue);
427
424
} else {
428
425
bottomRight = imageRect.bottomRight;
429
426
topRight = imageRect.topRight;
@@ -434,15 +431,10 @@ class _CustomImageCropState extends State<CustomImageCrop>
434
431
if (widget.shape == CustomCropShape .Circle ) {
435
432
final anchor = max (pathRect.width, pathRect.height) / 2 ;
436
433
final pathCenter = pathRect.center;
437
- return _getDistanceBetweenPointAndLine (pathCenter, topLeft, topRight) >=
438
- anchor &&
439
- _getDistanceBetweenPointAndLine (pathCenter, topRight, bottomRight) >=
440
- anchor &&
441
- _getDistanceBetweenPointAndLine (
442
- pathCenter, bottomLeft, bottomRight) >=
443
- anchor &&
444
- _getDistanceBetweenPointAndLine (pathCenter, topLeft, bottomLeft) >=
445
- anchor;
434
+ return _getDistanceBetweenPointAndLine (pathCenter, topLeft, topRight) >= anchor &&
435
+ _getDistanceBetweenPointAndLine (pathCenter, topRight, bottomRight) >= anchor &&
436
+ _getDistanceBetweenPointAndLine (pathCenter, bottomLeft, bottomRight) >= anchor &&
437
+ _getDistanceBetweenPointAndLine (pathCenter, topLeft, bottomLeft) >= anchor;
446
438
}
447
439
448
440
if (isRotated) {
@@ -452,28 +444,19 @@ class _CustomImageCropState extends State<CustomImageCrop>
452
444
..lineTo (bottomRight.dx, bottomRight.dy)
453
445
..lineTo (bottomLeft.dx, bottomLeft.dy)
454
446
..close ();
455
- return imagePath.contains (pathRect.topLeft) &&
456
- imagePath.contains (pathRect.topRight) &&
457
- imagePath.contains (pathRect.bottomLeft) &&
458
- imagePath.contains (pathRect.bottomRight);
447
+ return imagePath.contains (pathRect.topLeft) && imagePath.contains (pathRect.topRight) && imagePath.contains (pathRect.bottomLeft) && imagePath.contains (pathRect.bottomRight);
459
448
} else {
460
- return imageRect.contains (pathRect.topLeft) &&
461
- imageRect.contains (pathRect.topRight) &&
462
- imageRect.contains (pathRect.bottomLeft) &&
463
- imageRect.contains (pathRect.bottomRight);
449
+ return imageRect.contains (pathRect.topLeft) && imageRect.contains (pathRect.topRight) && imageRect.contains (pathRect.bottomLeft) && imageRect.contains (pathRect.bottomRight);
464
450
}
465
451
}
466
452
467
- double _calculateScaleAfterRotate (Rect pathRect, double startScale,
468
- Rect initialImageRect, double minEdgeHalf) {
453
+ double _calculateScaleAfterRotate (Rect pathRect, double startScale, Rect initialImageRect, double minEdgeHalf) {
469
454
final imageCenter = initialImageRect.center.translate (data.x, data.y);
470
455
final topLeftDistance = (pathRect.topLeft - imageCenter).distance;
471
456
final topRightDistance = (pathRect.topRight - imageCenter).distance;
472
457
final bottomLeftDistance = (pathRect.bottomLeft - imageCenter).distance;
473
458
final bottomRightDistance = (pathRect.bottomRight - imageCenter).distance;
474
- final maxDistance = max (
475
- max (max (topLeftDistance, topRightDistance), bottomLeftDistance),
476
- bottomRightDistance);
459
+ final maxDistance = max (max (max (topLeftDistance, topRightDistance), bottomLeftDistance), bottomRightDistance);
477
460
double endScale = maxDistance / minEdgeHalf;
478
461
479
462
if (startScale >= endScale) {
@@ -502,6 +485,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
502
485
required double width,
503
486
required double height,
504
487
required double borderRadius,
488
+ required CustomCropShape shape,
505
489
bool clipShape = true ,
506
490
}) {
507
491
if (! clipShape) {
@@ -515,7 +499,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
515
499
);
516
500
}
517
501
518
- switch (widget. shape) {
502
+ switch (shape) {
519
503
case CustomCropShape .Circle :
520
504
return Path ()
521
505
..addOval (
@@ -577,6 +561,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
577
561
height: onCropParams.cropSizeHeight,
578
562
borderRadius: widget.borderRadius,
579
563
clipShape: widget.clipShapeOnCrop,
564
+ shape: widget.shape,
580
565
));
581
566
final matrix4Image = Matrix4 .diagonal3 (vector_math.Vector3 .all (1 ))
582
567
..translate (
0 commit comments