Skip to content

Commit 5a86374

Browse files
committed
Add maskShape parameter
1 parent c08e8ec commit 5a86374

File tree

2 files changed

+59
-70
lines changed

2 files changed

+59
-70
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ The color behind the image. This color will also be used when there are gaps/emp
4242

4343
The shape of the cropping path.
4444

45+
### maskShape
46+
47+
The shape of the UI masking.
48+
4549
### cropPercentage
4650

4751
How big the crop should be in regards to the width and height available to the cropping widget.

lib/src/widgets/custom_image_crop_widget.dart

Lines changed: 55 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ class CustomImageCrop extends StatefulWidget {
3434
/// - [CustomCropShape.Ratio] Crop area will have a specified aspect ratio.
3535
final CustomCropShape shape;
3636

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+
3744
/// Ratio of the cropping area.
3845
/// If [shape] is set to [CustomCropShape.Ratio], this property is required.
3946
/// For example, to create a square crop area, use [Ratio(width: 1, height: 1)].
@@ -119,6 +126,7 @@ class CustomImageCrop extends StatefulWidget {
119126
this.overlayColor = const Color.fromRGBO(0, 0, 0, 0.5),
120127
this.backgroundColor = Colors.white,
121128
this.shape = CustomCropShape.Circle,
129+
this.maskShape,
122130
this.imageFit = CustomImageFit.fitCropSpace,
123131
this.cropPercentage = 0.8,
124132
this.drawPath = DottedCropPathPainter.drawPath,
@@ -133,8 +141,7 @@ class CustomImageCrop extends StatefulWidget {
133141
Paint? imagePaintDuringCrop,
134142
this.forceInsideCropArea = false,
135143
Key? key,
136-
}) : this.imagePaintDuringCrop = imagePaintDuringCrop ??
137-
(Paint()..filterQuality = FilterQuality.high),
144+
}) : this.imagePaintDuringCrop = imagePaintDuringCrop ?? (Paint()..filterQuality = FilterQuality.high),
138145
assert(
139146
!(shape == CustomCropShape.Ratio && ratio == null),
140147
"If shape is set to Ratio, ratio should not be null.",
@@ -145,10 +152,10 @@ class CustomImageCrop extends StatefulWidget {
145152
_CustomImageCropState createState() => _CustomImageCropState();
146153
}
147154

148-
class _CustomImageCropState extends State<CustomImageCrop>
149-
with CustomImageCropListener {
155+
class _CustomImageCropState extends State<CustomImageCrop> with CustomImageCropListener {
150156
CropImageData? _dataTransitionStart;
151157
late Path _path;
158+
late Path _maskPath;
152159
late double _width, _height;
153160
ui.Image? _imageAsUIImage;
154161
ImageStream? _imageStream;
@@ -227,7 +234,20 @@ class _CustomImageCropState extends State<CustomImageCrop>
227234
width: _width,
228235
height: _height,
229236
borderRadius: widget.borderRadius,
237+
shape: widget.shape,
230238
);
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+
231251
return XGestureDetector(
232252
onMoveStart: onMoveStart,
233253
onMoveUpdate: onMoveUpdate,
@@ -243,8 +263,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
243263
left: data.x + _width / 2,
244264
top: data.y + _height / 2,
245265
child: Transform(
246-
transform: Matrix4.diagonal3(
247-
vector_math.Vector3(scale, scale, scale))
266+
transform: Matrix4.diagonal3(vector_math.Vector3(scale, scale, scale))
248267
..rotateZ(data.angle)
249268
..translate(-image.width / 2, -image.height / 2),
250269
child: Image(
@@ -254,13 +273,13 @@ class _CustomImageCropState extends State<CustomImageCrop>
254273
),
255274
IgnorePointer(
256275
child: ClipPath(
257-
clipper: InvertedClipper(_path, _width, _height),
276+
clipper: InvertedClipper(_maskPath, _width, _height),
258277
child: Container(
259278
color: widget.overlayColor,
260279
),
261280
),
262281
),
263-
widget.drawPath(_path, pathPaint: widget.pathPaint),
282+
widget.drawPath(_maskPath, pathPaint: widget.pathPaint),
264283
],
265284
),
266285
),
@@ -274,8 +293,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
274293
}
275294

276295
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);
279297

280298
final angle = widget.canRotate ? event.rotationAngle : 0.0;
281299

@@ -301,8 +319,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
301319
void onMoveUpdate(MoveEvent event) {
302320
if (!widget.canMove) return;
303321

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));
306323
}
307324

308325
Rect _getInitialImageRect() {
@@ -318,8 +335,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
318335
aspectRatio: (widget.ratio?.width ?? 1) / (widget.ratio?.height ?? 1),
319336
);
320337
final initialWidth = _imageAsUIImage!.width * cropFitParams.additionalScale;
321-
final initialHeight =
322-
_imageAsUIImage!.height * cropFitParams.additionalScale;
338+
final initialHeight = _imageAsUIImage!.height * cropFitParams.additionalScale;
323339
return Rect.fromLTWH(
324340
(_width - initialWidth) / 2,
325341
(_height - initialHeight) / 2,
@@ -347,53 +363,38 @@ class _CustomImageCropState extends State<CustomImageCrop>
347363

348364
if (transition.x != 0 || transition.y != 0) {
349365
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));
352367
} else {
353368
final imageRect = _getImageRect(initialImageRect, data.scale);
354369
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;
358371
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;
362373
_addTransitionInternal(CropImageData(x: deltaX, y: deltaY));
363374
}
364375
return;
365376
}
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);
370379
_addTransitionInternal(CropImageData(scale: adaptScale / data.scale));
371380
}
372381

373382
Rect _getImageRect(Rect initialImageRect, double currentScale) {
374383
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);
384387
return imageRect;
385388
}
386389

387-
double _getDistanceBetweenPointAndLine(
388-
Offset point, Offset lineStart, Offset lineEnd) {
390+
double _getDistanceBetweenPointAndLine(Offset point, Offset lineStart, Offset lineEnd) {
389391
if (lineEnd.dy == lineStart.dy) {
390392
return (point.dy - lineStart.dy).abs();
391393
}
392394
if (lineEnd.dx == lineStart.dx) {
393395
return (point.dx - lineStart.dx).abs();
394396
}
395-
double line1Slop =
396-
(lineEnd.dy - lineStart.dy) / (lineEnd.dx - lineStart.dx);
397+
double line1Slop = (lineEnd.dy - lineStart.dy) / (lineEnd.dx - lineStart.dx);
397398
double line1Delta = lineEnd.dy - lineEnd.dx * line1Slop;
398399
double line2Slop = -1 / line1Slop;
399400
double line2Delta = point.dy - point.dx * line2Slop;
@@ -402,13 +403,11 @@ class _CustomImageCropState extends State<CustomImageCrop>
402403
return (Offset(crossPointX, crossPointY) - point).distance;
403404
}
404405

405-
bool _isContainPath(
406-
Rect initialImageRect, Rect pathRect, double currentScale) {
406+
bool _isContainPath(Rect initialImageRect, Rect pathRect, double currentScale) {
407407
final imageRect = _getImageRect(initialImageRect, currentScale);
408408
Offset topLeft, topRight, bottomLeft, bottomRight;
409409
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));
412411
bool isRotated = data.angle != 0;
413412

414413
if (isRotated) {
@@ -419,11 +418,9 @@ class _CustomImageCropState extends State<CustomImageCrop>
419418
final cosCounterClockValue = len * cos(counterClockAngle);
420419
final sinCounterClockValue = len * sin(counterClockAngle);
421420
bottomRight = imageRect.center.translate(cosClockValue, sinClockValue);
422-
topRight = imageRect.center
423-
.translate(cosCounterClockValue, -sinCounterClockValue);
421+
topRight = imageRect.center.translate(cosCounterClockValue, -sinCounterClockValue);
424422
topLeft = imageRect.center.translate(-cosClockValue, -sinClockValue);
425-
bottomLeft = imageRect.center
426-
.translate(-cosCounterClockValue, sinCounterClockValue);
423+
bottomLeft = imageRect.center.translate(-cosCounterClockValue, sinCounterClockValue);
427424
} else {
428425
bottomRight = imageRect.bottomRight;
429426
topRight = imageRect.topRight;
@@ -434,15 +431,10 @@ class _CustomImageCropState extends State<CustomImageCrop>
434431
if (widget.shape == CustomCropShape.Circle) {
435432
final anchor = max(pathRect.width, pathRect.height) / 2;
436433
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;
446438
}
447439

448440
if (isRotated) {
@@ -452,28 +444,19 @@ class _CustomImageCropState extends State<CustomImageCrop>
452444
..lineTo(bottomRight.dx, bottomRight.dy)
453445
..lineTo(bottomLeft.dx, bottomLeft.dy)
454446
..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);
459448
} 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);
464450
}
465451
}
466452

467-
double _calculateScaleAfterRotate(Rect pathRect, double startScale,
468-
Rect initialImageRect, double minEdgeHalf) {
453+
double _calculateScaleAfterRotate(Rect pathRect, double startScale, Rect initialImageRect, double minEdgeHalf) {
469454
final imageCenter = initialImageRect.center.translate(data.x, data.y);
470455
final topLeftDistance = (pathRect.topLeft - imageCenter).distance;
471456
final topRightDistance = (pathRect.topRight - imageCenter).distance;
472457
final bottomLeftDistance = (pathRect.bottomLeft - imageCenter).distance;
473458
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);
477460
double endScale = maxDistance / minEdgeHalf;
478461

479462
if (startScale >= endScale) {
@@ -502,6 +485,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
502485
required double width,
503486
required double height,
504487
required double borderRadius,
488+
required CustomCropShape shape,
505489
bool clipShape = true,
506490
}) {
507491
if (!clipShape) {
@@ -515,7 +499,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
515499
);
516500
}
517501

518-
switch (widget.shape) {
502+
switch (shape) {
519503
case CustomCropShape.Circle:
520504
return Path()
521505
..addOval(
@@ -577,6 +561,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
577561
height: onCropParams.cropSizeHeight,
578562
borderRadius: widget.borderRadius,
579563
clipShape: widget.clipShapeOnCrop,
564+
shape: widget.shape,
580565
));
581566
final matrix4Image = Matrix4.diagonal3(vector_math.Vector3.all(1))
582567
..translate(

0 commit comments

Comments
 (0)