Skip to content

Commit f05ca93

Browse files
Merge pull request #25 from JolimJostar/feature/image-fill-screen-dimensions
Added new param to CustomImageCrop for new image fit types.
2 parents 8164120 + 2a3b3ce commit f05ca93

File tree

4 files changed

+271
-16
lines changed

4 files changed

+271
-16
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import 'dart:math';
2+
3+
import 'package:custom_image_crop/src/models/params_model.dart';
4+
import 'package:custom_image_crop/src/widgets/custom_image_crop_widget.dart';
5+
6+
/// Returns params to use for displaing crop screen.
7+
CropFitParams calculateCropParams({
8+
required double screenWidth,
9+
required double screenHeight,
10+
required double cropPercentage,
11+
required int imageWidth,
12+
required int imageHeight,
13+
required CustomImageFit imageFit,
14+
}) {
15+
/// 'cropSize' is the size of the full crop area
16+
/// 'cropSizeToPaint' is the size of the crop area highlighted in ui
17+
/// 'defaultScale' is used to adjust image scale
18+
19+
switch (imageFit) {
20+
case CustomImageFit.fitCropSpace:
21+
final cropSize = min(screenWidth, screenHeight) * cropPercentage;
22+
final cropSizeToPaint = cropSize;
23+
final defaultScale = cropSize / max(imageWidth, imageHeight);
24+
25+
return CropFitParams(cropSize, cropSizeToPaint, defaultScale);
26+
27+
case CustomImageFit.fillCropWidth:
28+
final cropSize = screenWidth * cropPercentage;
29+
final cropSizeToPaint = cropSize;
30+
final defaultScale = cropSize / imageWidth;
31+
32+
return CropFitParams(cropSize, cropSizeToPaint, defaultScale);
33+
case CustomImageFit.fillCropHeight:
34+
final cropSize = screenHeight * cropPercentage;
35+
final cropSizeToPaint = cropSize;
36+
final defaultScale = cropSize / imageHeight;
37+
38+
return CropFitParams(cropSize, cropSizeToPaint, defaultScale);
39+
case CustomImageFit.fitVisibleSpace:
40+
late final double cropSize;
41+
late final double cropSizeToPaint;
42+
late final double defaultScale;
43+
cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage;
44+
45+
if (screenHeight < screenWidth) {
46+
cropSize = screenHeight;
47+
defaultScale = screenHeight / imageHeight;
48+
} else {
49+
cropSize = screenWidth;
50+
defaultScale = screenWidth / imageWidth;
51+
}
52+
53+
return CropFitParams(cropSize, cropSizeToPaint, defaultScale);
54+
case CustomImageFit.fillVisibleSpace:
55+
late final double cropSize;
56+
late final double cropSizeToPaint;
57+
late final double defaultScale;
58+
cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage;
59+
60+
if (screenHeight > screenWidth) {
61+
cropSize = screenHeight;
62+
defaultScale = screenHeight / imageHeight;
63+
} else {
64+
cropSize = screenWidth;
65+
defaultScale = screenWidth / imageWidth;
66+
}
67+
68+
return CropFitParams(cropSize, cropSizeToPaint, defaultScale);
69+
case CustomImageFit.fillVisibleHeight:
70+
final cropSize = screenHeight;
71+
final cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage;
72+
final defaultScale = screenHeight / imageHeight;
73+
74+
return CropFitParams(cropSize, cropSizeToPaint, defaultScale);
75+
case CustomImageFit.fillVisiblelWidth:
76+
final cropSize = screenWidth;
77+
final cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage;
78+
final defaultScale = screenWidth / imageWidth;
79+
80+
return CropFitParams(cropSize, cropSizeToPaint, defaultScale);
81+
}
82+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import 'dart:math';
2+
3+
import 'package:custom_image_crop/src/models/params_model.dart';
4+
import 'package:custom_image_crop/src/widgets/custom_image_crop_widget.dart';
5+
6+
/// Returns params to use for cropping image.
7+
OnCropParams caclulateOnCropParams({
8+
required double screenWidth,
9+
required double screenHeight,
10+
required double cropPercentage,
11+
required double dataScale,
12+
required int imageWidth,
13+
required int imageHeight,
14+
required CustomImageFit imageFit,
15+
}) {
16+
/// 'uiSize' is the size of the ui screen
17+
/// 'cropSize' is the size of the area to crop
18+
/// 'translateScale' is the scale used to adjust x and y coodinates of the crop area
19+
/// 'scale' is used to adjust image scale
20+
21+
switch (imageFit) {
22+
case CustomImageFit.fitCropSpace:
23+
final uiSize = min(screenWidth, screenHeight);
24+
final cropSize = max(imageWidth, imageHeight).toDouble();
25+
final translateScale = cropSize / (uiSize * cropPercentage);
26+
final scale = dataScale;
27+
28+
return OnCropParams(cropSize, translateScale, scale);
29+
30+
case CustomImageFit.fillCropWidth:
31+
final uiSize = screenWidth;
32+
final cropSize = imageWidth.toDouble();
33+
final translateScale = cropSize / (uiSize * cropPercentage);
34+
final scale = dataScale;
35+
36+
return OnCropParams(cropSize, translateScale, scale);
37+
case CustomImageFit.fillCropHeight:
38+
final uiSize = screenHeight;
39+
final cropSize = imageHeight.toDouble();
40+
final translateScale = cropSize / (uiSize * cropPercentage);
41+
final scale = dataScale;
42+
43+
return OnCropParams(cropSize, translateScale, scale);
44+
case CustomImageFit.fitVisibleSpace:
45+
late final double uiSize;
46+
late final double cropSize;
47+
late final double translateScale;
48+
late final double scale;
49+
50+
if (screenHeight < screenWidth) {
51+
uiSize = screenHeight;
52+
cropSize = imageHeight.toDouble();
53+
} else {
54+
uiSize = screenWidth;
55+
cropSize = imageWidth.toDouble();
56+
}
57+
58+
scale = dataScale / cropPercentage;
59+
translateScale = cropSize / uiSize / cropPercentage;
60+
61+
return OnCropParams(cropSize, translateScale, scale);
62+
case CustomImageFit.fillVisibleSpace:
63+
final heightToWidthRatio = (screenHeight / screenWidth);
64+
late final double uiSize;
65+
late final double cropSize;
66+
late final double translateScale;
67+
late final double scale;
68+
69+
if (screenHeight > screenWidth) {
70+
uiSize = screenHeight;
71+
cropSize = imageHeight.toDouble();
72+
translateScale =
73+
cropSize / uiSize / cropPercentage * heightToWidthRatio;
74+
scale = dataScale / cropPercentage * heightToWidthRatio;
75+
} else {
76+
uiSize = screenWidth;
77+
cropSize = imageWidth.toDouble();
78+
translateScale =
79+
cropSize / uiSize / cropPercentage / heightToWidthRatio;
80+
scale = dataScale / cropPercentage / heightToWidthRatio;
81+
}
82+
83+
return OnCropParams(cropSize, translateScale, scale);
84+
case CustomImageFit.fillVisibleHeight:
85+
final heightToWidthRatio = (screenHeight / screenWidth);
86+
final uiSize = screenHeight;
87+
final cropSize = imageHeight.toDouble();
88+
late final double translateScale;
89+
late final double scale;
90+
if (screenWidth > screenHeight) {
91+
translateScale = cropSize / uiSize / cropPercentage;
92+
scale = dataScale / cropPercentage;
93+
} else {
94+
translateScale =
95+
cropSize / uiSize / cropPercentage * heightToWidthRatio;
96+
scale = dataScale / cropPercentage * heightToWidthRatio;
97+
}
98+
return OnCropParams(cropSize, translateScale, scale);
99+
case CustomImageFit.fillVisiblelWidth:
100+
final heightToWidthRatio = (screenHeight / screenWidth);
101+
final uiSize = screenWidth;
102+
final cropSize = imageWidth.toDouble();
103+
late final double translateScale;
104+
late final double scale;
105+
if (screenWidth > screenHeight) {
106+
translateScale =
107+
cropSize / uiSize / cropPercentage / heightToWidthRatio;
108+
scale = dataScale / cropPercentage / heightToWidthRatio;
109+
} else {
110+
translateScale = cropSize / uiSize / cropPercentage;
111+
scale = dataScale / cropPercentage;
112+
}
113+
114+
return OnCropParams(cropSize, translateScale, scale);
115+
}
116+
}

lib/src/models/params_model.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// Params used to display crop screen.
2+
class CropFitParams {
3+
/// The size of actual crop area.
4+
final double cropSize;
5+
6+
/// The size of crop area to show on screen.
7+
final double cropSizeToPaint;
8+
9+
/// The scale used to adjust display of the image based on 'CustomImageFit' type.
10+
final double additionalScale;
11+
12+
CropFitParams(this.cropSize, this.cropSizeToPaint, this.additionalScale);
13+
}
14+
15+
/// Params used to crop image.
16+
class OnCropParams {
17+
/// The size of actual crop area.
18+
final double cropSize;
19+
20+
/// The translate scale used to crop the image based on 'CustomImageFit' type.
21+
final double translateScale;
22+
23+
/// Is used to crop the image based on 'CustomImageFit' type.
24+
final double scale;
25+
26+
OnCropParams(this.cropSize, this.translateScale, this.scale);
27+
}

lib/src/widgets/custom_image_crop_widget.dart

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'dart:async';
2-
import 'dart:math';
32
import 'dart:ui' as ui;
43

54
import 'package:flutter/material.dart';
@@ -10,6 +9,8 @@ import 'package:custom_image_crop/src/controllers/controller.dart';
109
import 'package:custom_image_crop/src/painters/dotted_path_painter.dart';
1110
import 'package:custom_image_crop/src/clippers/inverted_clipper.dart';
1211
import 'package:custom_image_crop/src/models/model.dart';
12+
import 'package:custom_image_crop/src/calculators/calculate_crop_params.dart';
13+
import 'package:custom_image_crop/src/calculators/calculate_on_crop_params.dart';
1314

1415
/// An image cropper that is customizable.
1516
/// You can rotate, scale and translate either
@@ -31,6 +32,9 @@ class CustomImageCrop extends StatefulWidget {
3132
/// The shape of the cropping area
3233
final CustomCropShape shape;
3334

35+
/// How to fit image inside visible space
36+
final CustomImageFit imageFit;
37+
3438
/// The percentage of the available area that is
3539
/// reserved for the cropping area
3640
final double cropPercentage;
@@ -83,6 +87,7 @@ class CustomImageCrop extends StatefulWidget {
8387
this.overlayColor = const Color.fromRGBO(0, 0, 0, 0.5),
8488
this.backgroundColor = Colors.white,
8589
this.shape = CustomCropShape.Circle,
90+
this.imageFit = CustomImageFit.fitCropSpace,
8691
this.cropPercentage = 0.8,
8792
this.drawPath = DottedCropPathPainter.drawPath,
8893
this.canRotate = true,
@@ -159,10 +164,16 @@ class _CustomImageCropState extends State<CustomImageCrop>
159164
builder: (context, constraints) {
160165
_width = constraints.maxWidth;
161166
_height = constraints.maxHeight;
162-
final cropWidth = min(_width, _height) * widget.cropPercentage;
163-
final defaultScale = cropWidth / max(image.width, image.height);
164-
final scale = data.scale * defaultScale;
165-
_path = _getPath(cropWidth, _width, _height);
167+
final cropParams = calculateCropParams(
168+
cropPercentage: widget.cropPercentage,
169+
imageFit: widget.imageFit,
170+
imageHeight: image.height,
171+
imageWidth: image.width,
172+
screenHeight: _height,
173+
screenWidth: _width,
174+
);
175+
final scale = data.scale * cropParams.additionalScale;
176+
_path = _getPath(cropParams.cropSizeToPaint, _width, _height);
166177
return XGestureDetector(
167178
onMoveStart: onMoveStart,
168179
onMoveUpdate: onMoveUpdate,
@@ -270,20 +281,29 @@ class _CustomImageCropState extends State<CustomImageCrop>
270281
final imageHeight = _imageAsUIImage!.height;
271282
final pictureRecorder = ui.PictureRecorder();
272283
final canvas = Canvas(pictureRecorder);
273-
final uiWidth = min(_width, _height) * widget.cropPercentage;
274-
final cropWidth = max(imageWidth, imageHeight).toDouble();
275-
final translateScale = cropWidth / uiWidth;
276-
final scale = data.scale;
277-
final clipPath = Path.from(_getPath(cropWidth, cropWidth, cropWidth));
284+
final onCropParams = caclulateOnCropParams(
285+
cropPercentage: widget.cropPercentage,
286+
imageFit: widget.imageFit,
287+
imageHeight: imageHeight,
288+
imageWidth: imageWidth,
289+
screenHeight: _height,
290+
screenWidth: _width,
291+
dataScale: data.scale,
292+
);
293+
final clipPath = Path.from(_getPath(
294+
onCropParams.cropSize, onCropParams.cropSize, onCropParams.cropSize));
278295
final matrix4Image = Matrix4.diagonal3(vector_math.Vector3.all(1))
279-
..translate(translateScale * data.x + cropWidth / 2,
280-
translateScale * data.y + cropWidth / 2)
281-
..scale(scale)
296+
..translate(
297+
onCropParams.translateScale * data.x + onCropParams.cropSize / 2,
298+
onCropParams.translateScale * data.y + onCropParams.cropSize / 2)
299+
..scale(onCropParams.scale)
282300
..rotateZ(data.angle);
283301
final bgPaint = Paint()
284302
..color = widget.backgroundColor
285303
..style = PaintingStyle.fill;
286-
canvas.drawRect(Rect.fromLTWH(0, 0, cropWidth, cropWidth), bgPaint);
304+
canvas.drawRect(
305+
Rect.fromLTWH(0, 0, onCropParams.cropSize, onCropParams.cropSize),
306+
bgPaint);
287307
canvas.save();
288308
canvas.clipPath(clipPath);
289309
canvas.transform(matrix4Image.storage);
@@ -297,8 +317,8 @@ class _CustomImageCropState extends State<CustomImageCrop>
297317
// final bytes = await compute(computeToByteData, <String, dynamic>{'pictureRecorder': pictureRecorder, 'cropWidth': cropWidth});
298318

299319
ui.Picture picture = pictureRecorder.endRecording();
300-
ui.Image image =
301-
await picture.toImage(cropWidth.floor(), cropWidth.floor());
320+
ui.Image image = await picture.toImage(
321+
onCropParams.cropSize.floor(), onCropParams.cropSize.floor());
302322

303323
// Adding compute would be preferrable. Unfortunately we cannot pass an ui image to this.
304324
// A workaround would be to save the image and load it inside of the isolate
@@ -333,3 +353,13 @@ enum CustomCropShape {
333353
Circle,
334354
Square,
335355
}
356+
357+
enum CustomImageFit {
358+
fitCropSpace,
359+
fillCropWidth,
360+
fillCropHeight,
361+
fitVisibleSpace,
362+
fillVisibleSpace,
363+
fillVisibleHeight,
364+
fillVisiblelWidth,
365+
}

0 commit comments

Comments
 (0)