diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ef83a..19caef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,50 +1,55 @@ +## [0.0.12] - 2023-09-08 + +- Fixed issues with Ratio and CustomImageCrop +- Added fillCropSpace as CustomImageFit + ## [0.0.11] - 2023-09-01 -* Added clipShapeOnCrop to prevent clipping the image to the crop shape +- Added clipShapeOnCrop to prevent clipping the image to the crop shape ## [0.0.10] - 2023-08-17 -* Added didupdateWidget check to fix issues with updated images +- Added didupdateWidget check to fix issues with updated images ## [0.0.9] - 2023-08-10 -* Added borderRadius +- Added borderRadius ## [0.0.8] - 2023-08-10 -* Added pathPaint to customize the crop border style +- Added pathPaint to customize the crop border style ## [0.0.7] - 2023-08-09 -* Added Ratio as new shape and arguments +- Added Ratio as new shape and arguments ## [0.0.6] -* Added new param to CustomImageCrop for new image fit types +- Added new param to CustomImageCrop for new image fit types ## [0.0.5] -* Added canRotate -* Added customProgressIndicator -* Added canScale -* Added canMove +- Added canRotate +- Added customProgressIndicator +- Added canScale +- Added canMove ## [0.0.4] -* Added documentation +- Added documentation ## [0.0.3] -* Fixed issue where cropped image's size depends on screen size used -* Fixed issue where cropped image's quality is worse than original image -* Updated to flutter 2.8.0 +- Fixed issue where cropped image's size depends on screen size used +- Fixed issue where cropped image's quality is worse than original image +- Updated to flutter 2.8.0 ## [0.0.2] -* Updated docs +- Updated docs ## [0.0.1] -* Added custom crop -* Added Cicrle and Square crop shapes -* Added Solid and Dotted painters for crop border +- Added custom crop +- Added Cicrle and Square crop shapes +- Added Solid and Dotted painters for crop border diff --git a/example/assets/test2.png b/example/assets/test2.png new file mode 100644 index 0000000..65642f5 Binary files /dev/null and b/example/assets/test2.png differ diff --git a/example/lib/main.dart b/example/lib/main.dart index f6ae7d3..462719d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -41,6 +41,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { late CustomImageCropController controller; CustomCropShape _currentShape = CustomCropShape.Circle; + CustomImageFit _imageFit = CustomImageFit.fillCropSpace; final TextEditingController _widthController = TextEditingController(); final TextEditingController _heightController = TextEditingController(); final TextEditingController _radiusController = TextEditingController(); @@ -67,6 +68,12 @@ class _MyHomePageState extends State { }); } + void _changeImageFit(CustomImageFit imageFit) { + setState(() { + _imageFit = imageFit; + }); + } + void _updateRatio() { setState(() { if (_widthController.text.isNotEmpty) { @@ -102,17 +109,17 @@ class _MyHomePageState extends State { ? Ratio(width: _width, height: _height) : null, canRotate: true, - canMove: false, - canScale: false, + canMove: true, + canScale: true, borderRadius: _currentShape == CustomCropShape.Ratio ? _radius : 0, customProgressIndicator: const CupertinoActivityIndicator(), - // use custom paint if needed - // pathPaint: Paint() - // ..color = Colors.red - // ..strokeWidth = 4.0 - // ..style = PaintingStyle.stroke - // ..strokeJoin = StrokeJoin.round, + imageFit: _imageFit, + pathPaint: Paint() + ..color = Colors.red + ..strokeWidth = 4.0 + ..style = PaintingStyle.stroke + ..strokeJoin = StrokeJoin.round, ), ), Row( @@ -136,24 +143,13 @@ class _MyHomePageState extends State { icon: const Icon(Icons.rotate_right), onPressed: () => controller.addTransition(CropImageData(angle: pi / 4))), - IconButton( - icon: const Icon(Icons.crop), - onPressed: () async { - final image = await controller.onCropImage(); - if (image != null) { - Navigator.of(context).push(MaterialPageRoute( - builder: (BuildContext context) => - ResultScreen(image: image))); - } - }, - ), - PopupMenuButton( + PopupMenuButton( icon: const Icon(Icons.crop_original), onSelected: _changeCropShape, itemBuilder: (BuildContext context) { return CustomCropShape.values.map( (shape) { - return PopupMenuItem( + return PopupMenuItem( value: shape, child: getShapeIcon(shape), ); @@ -161,9 +157,37 @@ class _MyHomePageState extends State { ).toList(); }, ), + PopupMenuButton( + icon: const Icon(Icons.fit_screen), + onSelected: _changeImageFit, + itemBuilder: (BuildContext context) { + return CustomImageFit.values.map( + (imageFit) { + return PopupMenuItem( + value: imageFit, + child: Text(imageFit.label), + ); + }, + ).toList(); + }, + ), + IconButton( + icon: const Icon( + Icons.crop, + color: Colors.green, + ), + onPressed: () async { + final image = await controller.onCropImage(); + if (image != null) { + Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => + ResultScreen(image: image))); + } + }, + ), ], ), - if (_currentShape == CustomCropShape.Ratio) + if (_currentShape == CustomCropShape.Ratio) ...[ SizedBox( child: Row( children: [ @@ -198,6 +222,7 @@ class _MyHomePageState extends State { ], ), ), + ], SizedBox(height: MediaQuery.of(context).padding.bottom), ], ), @@ -215,3 +240,26 @@ class _MyHomePageState extends State { } } } + +extension CustomImageFitExtension on CustomImageFit { + String get label { + switch (this) { + case CustomImageFit.fillCropSpace: + return 'Fill crop space'; + case CustomImageFit.fitCropSpace: + return 'Fit crop space'; + case CustomImageFit.fillCropHeight: + return 'Fill crop height'; + case CustomImageFit.fillCropWidth: + return 'Fill crop width'; + case CustomImageFit.fillVisibleSpace: + return 'Fill visible space'; + case CustomImageFit.fitVisibleSpace: + return 'Fit visible space'; + case CustomImageFit.fillVisibleHeight: + return 'Fill visible height'; + case CustomImageFit.fillVisibleWidth: + return 'Fill visible width'; + } + } +} diff --git a/example/lib/result_screen.dart b/example/lib/result_screen.dart index 1dfeadc..e0291d1 100644 --- a/example/lib/result_screen.dart +++ b/example/lib/result_screen.dart @@ -16,20 +16,36 @@ class ResultScreen extends StatelessWidget { title: const Text('Result'), systemOverlayStyle: SystemUiOverlayStyle.dark, ), - body: Center( - child: Column( - children: [ - const Spacer(), - Image( + body: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.blueGrey.shade400, + Colors.blueGrey.shade600, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + )), + ), + Center( + child: Image( image: image, ), - ElevatedButton( + ), + Positioned( + bottom: 16, + left: 16, + right: 16, + child: ElevatedButton( child: const Text('Back'), onPressed: () => Navigator.of(context).pop(), ), - const Spacer(), - ], - ), + ), + ], ), ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index e4769ed..00fc57f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -49,7 +49,7 @@ packages: path: ".." relative: true source: path - version: "0.0.9" + version: "0.0.12" fake_async: dependency: transitive description: diff --git a/lib/src/calculators/calculate_crop_fit_params.dart b/lib/src/calculators/calculate_crop_fit_params.dart new file mode 100644 index 0000000..a245b05 --- /dev/null +++ b/lib/src/calculators/calculate_crop_fit_params.dart @@ -0,0 +1,126 @@ +import 'dart:math'; + +import 'package:custom_image_crop/src/models/params_model.dart'; +import 'package:custom_image_crop/src/widgets/custom_image_crop_widget.dart'; + +/// Returns params to use for displaying crop screen. +CropFitParams calculateCropFitParams({ + required double screenWidth, + required double screenHeight, + required double cropPercentage, + required int imageWidth, + required int imageHeight, + required CustomImageFit imageFit, + required double aspectRatio, +}) { + /// the width of the area to crop + final double cropSizeWidth; + + /// the height of the area to crop + final double cropSizeHeight; + + /// used to adjust image scale + final double defaultScale; + + switch (imageFit) { + case CustomImageFit.fillCropSpace: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + defaultScale = cropSizeWidth / imageWidth; + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + defaultScale = cropSizeHeight / imageHeight; + } + break; + + case CustomImageFit.fitCropSpace: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + defaultScale = + cropSizeWidth / max(imageWidth, imageHeight * aspectRatio); + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + defaultScale = + cropSizeHeight / max(imageHeight, imageWidth / aspectRatio); + } + break; + + case CustomImageFit.fillCropWidth: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + } + defaultScale = cropSizeWidth / imageWidth; + break; + + case CustomImageFit.fillCropHeight: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + } + defaultScale = cropSizeHeight / imageHeight; + break; + + case CustomImageFit.fitVisibleSpace: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + defaultScale = screenWidth / imageWidth; + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + defaultScale = screenHeight / imageHeight; + } + break; + + case CustomImageFit.fillVisibleSpace: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + defaultScale = screenHeight / imageHeight; + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + defaultScale = screenWidth / imageWidth; + } + break; + + case CustomImageFit.fillVisibleHeight: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + } + defaultScale = screenHeight / imageHeight; + break; + + case CustomImageFit.fillVisibleWidth: + if (screenWidth <= screenHeight * aspectRatio) { + cropSizeWidth = screenWidth * cropPercentage; + cropSizeHeight = cropSizeWidth / aspectRatio; + } else { + cropSizeHeight = screenHeight * cropPercentage; + cropSizeWidth = cropSizeHeight * aspectRatio; + } + defaultScale = screenWidth / imageWidth; + break; + } + + return CropFitParams( + cropSizeWidth: cropSizeWidth, + cropSizeHeight: cropSizeHeight, + additionalScale: defaultScale, + ); +} diff --git a/lib/src/calculators/calculate_crop_params.dart b/lib/src/calculators/calculate_crop_params.dart deleted file mode 100644 index bbe174a..0000000 --- a/lib/src/calculators/calculate_crop_params.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'dart:math'; - -import 'package:custom_image_crop/src/models/params_model.dart'; -import 'package:custom_image_crop/src/widgets/custom_image_crop_widget.dart'; - -/// Returns params to use for displaing crop screen. -CropFitParams calculateCropParams({ - required double screenWidth, - required double screenHeight, - required double cropPercentage, - required int imageWidth, - required int imageHeight, - required CustomImageFit imageFit, -}) { - /// 'cropSize' is the size of the full crop area - /// 'cropSizeToPaint' is the size of the crop area highlighted in ui - /// 'defaultScale' is used to adjust image scale - - switch (imageFit) { - case CustomImageFit.fitCropSpace: - final cropSize = min(screenWidth, screenHeight) * cropPercentage; - final cropSizeToPaint = cropSize; - final defaultScale = cropSize / max(imageWidth, imageHeight); - - return CropFitParams(cropSize, cropSizeToPaint, defaultScale); - - case CustomImageFit.fillCropWidth: - final cropSize = screenWidth * cropPercentage; - final cropSizeToPaint = cropSize; - final defaultScale = cropSize / imageWidth; - - return CropFitParams(cropSize, cropSizeToPaint, defaultScale); - case CustomImageFit.fillCropHeight: - final cropSize = screenHeight * cropPercentage; - final cropSizeToPaint = cropSize; - final defaultScale = cropSize / imageHeight; - - return CropFitParams(cropSize, cropSizeToPaint, defaultScale); - case CustomImageFit.fitVisibleSpace: - late final double cropSize; - late final double cropSizeToPaint; - late final double defaultScale; - cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage; - - if (screenHeight < screenWidth) { - cropSize = screenHeight; - defaultScale = screenHeight / imageHeight; - } else { - cropSize = screenWidth; - defaultScale = screenWidth / imageWidth; - } - - return CropFitParams(cropSize, cropSizeToPaint, defaultScale); - case CustomImageFit.fillVisibleSpace: - late final double cropSize; - late final double cropSizeToPaint; - late final double defaultScale; - cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage; - - if (screenHeight > screenWidth) { - cropSize = screenHeight; - defaultScale = screenHeight / imageHeight; - } else { - cropSize = screenWidth; - defaultScale = screenWidth / imageWidth; - } - - return CropFitParams(cropSize, cropSizeToPaint, defaultScale); - case CustomImageFit.fillVisibleHeight: - final cropSize = screenHeight; - final cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage; - final defaultScale = screenHeight / imageHeight; - - return CropFitParams(cropSize, cropSizeToPaint, defaultScale); - case CustomImageFit.fillVisiblelWidth: - final cropSize = screenWidth; - final cropSizeToPaint = min(screenWidth, screenHeight) * cropPercentage; - final defaultScale = screenWidth / imageWidth; - - return CropFitParams(cropSize, cropSizeToPaint, defaultScale); - } -} diff --git a/lib/src/calculators/calculate_on_crop_params.dart b/lib/src/calculators/calculate_on_crop_params.dart index 5e459b0..b94e6be 100644 --- a/lib/src/calculators/calculate_on_crop_params.dart +++ b/lib/src/calculators/calculate_on_crop_params.dart @@ -9,108 +9,135 @@ OnCropParams caclulateOnCropParams({ required double screenHeight, required double cropPercentage, required double dataScale, + required double aspectRatio, required int imageWidth, required int imageHeight, required CustomImageFit imageFit, }) { - /// 'uiSize' is the size of the ui screen - /// 'cropSize' is the size of the area to crop - /// 'translateScale' is the scale used to adjust x and y coodinates of the crop area - /// 'scale' is used to adjust image scale + /// the size of the area to crop (width and/or height depending on the aspect ratio) + final double cropSizeMax; + + /// the scale used to adjust x and y coodinates of the crop area + final double translateScale; + + /// used to adjust image scale + final double scale; + + /// Temp variable used to calculate the translateScale + final double uiSize; switch (imageFit) { - case CustomImageFit.fitCropSpace: - final uiSize = min(screenWidth, screenHeight); - final cropSize = max(imageWidth, imageHeight).toDouble(); - final translateScale = cropSize / (uiSize * cropPercentage); - final scale = dataScale; + case CustomImageFit.fillCropSpace: + if (screenWidth > screenHeight * aspectRatio) { + uiSize = screenHeight; + cropSizeMax = imageHeight.toDouble(); + } else { + uiSize = screenWidth; + cropSizeMax = imageWidth.toDouble(); + } + translateScale = cropSizeMax / (uiSize * cropPercentage); + scale = dataScale; + break; - return OnCropParams(cropSize, translateScale, scale); + case CustomImageFit.fitCropSpace: + if (screenWidth > screenHeight * aspectRatio) { + uiSize = screenHeight; + cropSizeMax = imageWidth.toDouble() / aspectRatio; + } else { + uiSize = screenWidth; + cropSizeMax = imageHeight.toDouble() * aspectRatio; + } + translateScale = cropSizeMax / (uiSize * cropPercentage); + scale = dataScale; + break; case CustomImageFit.fillCropWidth: - final uiSize = screenWidth; - final cropSize = imageWidth.toDouble(); - final translateScale = cropSize / (uiSize * cropPercentage); - final scale = dataScale; + uiSize = screenWidth; + cropSizeMax = imageWidth / min(1, aspectRatio); + translateScale = cropSizeMax / (uiSize * cropPercentage); + scale = dataScale; + break; - return OnCropParams(cropSize, translateScale, scale); case CustomImageFit.fillCropHeight: - final uiSize = screenHeight; - final cropSize = imageHeight.toDouble(); - final translateScale = cropSize / (uiSize * cropPercentage); - final scale = dataScale; + uiSize = screenHeight; + cropSizeMax = imageHeight * max(1, aspectRatio); + translateScale = cropSizeMax / (uiSize * cropPercentage); + scale = dataScale; + break; - return OnCropParams(cropSize, translateScale, scale); case CustomImageFit.fitVisibleSpace: - late final double uiSize; - late final double cropSize; - late final double translateScale; - late final double scale; - - if (screenHeight < screenWidth) { + final double uiSize; + if (screenHeight * aspectRatio < screenWidth) { uiSize = screenHeight; - cropSize = imageHeight.toDouble(); + cropSizeMax = imageHeight.toDouble(); } else { uiSize = screenWidth; - cropSize = imageWidth.toDouble(); + cropSizeMax = imageWidth.toDouble(); } - scale = dataScale / cropPercentage; - translateScale = cropSize / uiSize / cropPercentage; + translateScale = cropSizeMax / uiSize / cropPercentage; + break; - return OnCropParams(cropSize, translateScale, scale); case CustomImageFit.fillVisibleSpace: final heightToWidthRatio = (screenHeight / screenWidth); - late final double uiSize; - late final double cropSize; - late final double translateScale; - late final double scale; - if (screenHeight > screenWidth) { + if (screenHeight * aspectRatio > screenWidth) { uiSize = screenHeight; - cropSize = imageHeight.toDouble(); + cropSizeMax = imageHeight.toDouble(); translateScale = - cropSize / uiSize / cropPercentage * heightToWidthRatio; + cropSizeMax / uiSize / cropPercentage * heightToWidthRatio; scale = dataScale / cropPercentage * heightToWidthRatio; } else { uiSize = screenWidth; - cropSize = imageWidth.toDouble(); + cropSizeMax = imageWidth.toDouble(); translateScale = - cropSize / uiSize / cropPercentage / heightToWidthRatio; + cropSizeMax / uiSize / cropPercentage / heightToWidthRatio; scale = dataScale / cropPercentage / heightToWidthRatio; } + break; - return OnCropParams(cropSize, translateScale, scale); case CustomImageFit.fillVisibleHeight: final heightToWidthRatio = (screenHeight / screenWidth); - final uiSize = screenHeight; - final cropSize = imageHeight.toDouble(); - late final double translateScale; - late final double scale; - if (screenWidth > screenHeight) { - translateScale = cropSize / uiSize / cropPercentage; + uiSize = screenHeight; + cropSizeMax = imageHeight.toDouble(); + if (screenWidth > screenHeight * aspectRatio) { + translateScale = cropSizeMax / uiSize / cropPercentage; scale = dataScale / cropPercentage; } else { translateScale = - cropSize / uiSize / cropPercentage * heightToWidthRatio; + cropSizeMax / uiSize / cropPercentage * heightToWidthRatio; scale = dataScale / cropPercentage * heightToWidthRatio; } - return OnCropParams(cropSize, translateScale, scale); - case CustomImageFit.fillVisiblelWidth: + break; + + case CustomImageFit.fillVisibleWidth: final heightToWidthRatio = (screenHeight / screenWidth); - final uiSize = screenWidth; - final cropSize = imageWidth.toDouble(); - late final double translateScale; - late final double scale; - if (screenWidth > screenHeight) { + uiSize = screenWidth; + cropSizeMax = imageWidth.toDouble(); + if (screenWidth > screenHeight * aspectRatio) { translateScale = - cropSize / uiSize / cropPercentage / heightToWidthRatio; + cropSizeMax / uiSize / cropPercentage / heightToWidthRatio; scale = dataScale / cropPercentage / heightToWidthRatio; } else { - translateScale = cropSize / uiSize / cropPercentage; + translateScale = cropSizeMax / uiSize / cropPercentage; scale = dataScale / cropPercentage; } + break; + } - return OnCropParams(cropSize, translateScale, scale); + final double cropSizeWidth; + final double cropSizeHeight; + if (aspectRatio > 1) { + cropSizeWidth = cropSizeMax; + cropSizeHeight = cropSizeWidth / aspectRatio; + } else { + cropSizeHeight = cropSizeMax; + cropSizeWidth = cropSizeHeight * aspectRatio; } + return OnCropParams( + cropSizeHeight: cropSizeHeight, + cropSizeWidth: cropSizeWidth, + translateScale: translateScale, + scale: scale, + ); } diff --git a/lib/src/models/params_model.dart b/lib/src/models/params_model.dart index 6753c8c..289976a 100644 --- a/lib/src/models/params_model.dart +++ b/lib/src/models/params_model.dart @@ -1,21 +1,28 @@ /// Params used to display crop screen. class CropFitParams { - /// The size of actual crop area. - final double cropSize; + /// The width of displayed crop area. + final double cropSizeWidth; - /// The size of crop area to show on screen. - final double cropSizeToPaint; + /// The height of displayed crop area. + final double cropSizeHeight; /// The scale used to adjust display of the image based on 'CustomImageFit' type. final double additionalScale; - CropFitParams(this.cropSize, this.cropSizeToPaint, this.additionalScale); + CropFitParams({ + required this.cropSizeWidth, + required this.cropSizeHeight, + required this.additionalScale, + }); } /// Params used to crop image. class OnCropParams { - /// The size of actual crop area. - final double cropSize; + /// The width of actual crop area. + final double cropSizeWidth; + + /// The height of actual crop area. + final double cropSizeHeight; /// The translate scale used to crop the image based on 'CustomImageFit' type. final double translateScale; @@ -23,5 +30,10 @@ class OnCropParams { /// Is used to crop the image based on 'CustomImageFit' type. final double scale; - OnCropParams(this.cropSize, this.translateScale, this.scale); + OnCropParams({ + required this.cropSizeWidth, + required this.cropSizeHeight, + required this.translateScale, + required this.scale, + }); } diff --git a/lib/src/widgets/custom_image_crop_widget.dart b/lib/src/widgets/custom_image_crop_widget.dart index f1961d4..f3e9caa 100644 --- a/lib/src/widgets/custom_image_crop_widget.dart +++ b/lib/src/widgets/custom_image_crop_widget.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:custom_image_crop/custom_image_crop.dart'; -import 'package:custom_image_crop/src/calculators/calculate_crop_params.dart'; +import 'package:custom_image_crop/src/calculators/calculate_crop_fit_params.dart'; import 'package:custom_image_crop/src/calculators/calculate_on_crop_params.dart'; import 'package:custom_image_crop/src/clippers/inverted_clipper.dart'; import 'package:flutter/material.dart'; @@ -204,17 +204,23 @@ class _CustomImageCropState extends State builder: (context, constraints) { _width = constraints.maxWidth; _height = constraints.maxHeight; - final cropParams = calculateCropParams( + final cropFitParams = calculateCropFitParams( cropPercentage: widget.cropPercentage, imageFit: widget.imageFit, imageHeight: image.height, imageWidth: image.width, screenHeight: _height, screenWidth: _width, + aspectRatio: (widget.ratio?.width ?? 1) / (widget.ratio?.height ?? 1), + ); + final scale = data.scale * cropFitParams.additionalScale; + _path = _getPath( + cropWidth: cropFitParams.cropSizeWidth, + cropHeight: cropFitParams.cropSizeHeight, + width: _width, + height: _height, + borderRadius: widget.borderRadius, ); - final scale = data.scale * cropParams.additionalScale; - _path = _getPath(cropParams.cropSizeToPaint, _width, _height, - widget.borderRadius, true); return XGestureDetector( onMoveStart: onMoveStart, onMoveUpdate: onMoveUpdate, @@ -291,15 +297,21 @@ class _CustomImageCropState extends State addTransition(CropImageData(x: event.delta.dx, y: event.delta.dy)); } - Path _getPath(double cropWidth, double width, double height, - double borderRadius, bool clip) { - if (!clip) { + Path _getPath({ + required double cropWidth, + required double cropHeight, + required double width, + required double height, + required double borderRadius, + bool clipShape = true, + }) { + if (!clipShape) { return Path() ..addRect( Rect.fromCenter( center: Offset(width / 2, height / 2), width: cropWidth, - height: cropWidth, + height: cropHeight, ), ); } @@ -320,7 +332,7 @@ class _CustomImageCropState extends State Rect.fromCenter( center: Offset(width / 2, height / 2), width: cropWidth, - height: cropWidth * widget.ratio!.height / widget.ratio!.width, + height: cropHeight, ), Radius.circular(borderRadius), ), @@ -332,7 +344,7 @@ class _CustomImageCropState extends State Rect.fromCenter( center: Offset(width / 2, height / 2), width: cropWidth, - height: cropWidth, + height: cropHeight, ), Radius.circular(borderRadius), ), @@ -357,31 +369,43 @@ class _CustomImageCropState extends State screenHeight: _height, screenWidth: _width, dataScale: data.scale, + aspectRatio: (widget.ratio?.width ?? 1) / (widget.ratio?.height ?? 1), ); final clipPath = Path.from(_getPath( - onCropParams.cropSize, - onCropParams.cropSize, - onCropParams.cropSize, - widget.borderRadius, - widget.clipShapeOnCrop, + cropWidth: onCropParams.cropSizeWidth, + cropHeight: onCropParams.cropSizeHeight, + width: onCropParams.cropSizeWidth, + height: onCropParams.cropSizeHeight, + borderRadius: widget.borderRadius, + clipShape: widget.clipShapeOnCrop, )); final matrix4Image = Matrix4.diagonal3(vector_math.Vector3.all(1)) ..translate( - onCropParams.translateScale * data.x + onCropParams.cropSize / 2, - onCropParams.translateScale * data.y + onCropParams.cropSize / 2) + onCropParams.translateScale * data.x + onCropParams.cropSizeWidth / 2, + onCropParams.translateScale * data.y + onCropParams.cropSizeHeight / 2, + ) ..scale(onCropParams.scale) ..rotateZ(data.angle); final bgPaint = Paint() ..color = widget.backgroundColor ..style = PaintingStyle.fill; canvas.drawRect( - Rect.fromLTWH(0, 0, onCropParams.cropSize, onCropParams.cropSize), - bgPaint); + Rect.fromLTWH( + 0, + 0, + onCropParams.cropSizeWidth, + onCropParams.cropSizeHeight, + ), + bgPaint, + ); canvas.save(); canvas.clipPath(clipPath); canvas.transform(matrix4Image.storage); - canvas.drawImage(_imageAsUIImage!, - Offset(-imageWidth / 2, -imageHeight / 2), widget.imagePaintDuringCrop); + canvas.drawImage( + _imageAsUIImage!, + Offset(-imageWidth / 2, -imageHeight / 2), + widget.imagePaintDuringCrop, + ); canvas.restore(); // Optionally remove magenta from image by evaluating every pixel @@ -391,7 +415,9 @@ class _CustomImageCropState extends State ui.Picture picture = pictureRecorder.endRecording(); ui.Image image = await picture.toImage( - onCropParams.cropSize.floor(), onCropParams.cropSize.floor()); + onCropParams.cropSizeWidth.floor(), + onCropParams.cropSizeHeight.floor(), + ); // Adding compute would be preferrable. Unfortunately we cannot pass an ui image to this. // A workaround would be to save the image and load it inside of the isolate @@ -429,11 +455,12 @@ enum CustomCropShape { } enum CustomImageFit { + fillCropSpace, fitCropSpace, fillCropWidth, fillCropHeight, fitVisibleSpace, fillVisibleSpace, fillVisibleHeight, - fillVisiblelWidth, + fillVisibleWidth, } diff --git a/pubspec.yaml b/pubspec.yaml index 83cc4c0..5ee76e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: custom_image_crop description: An image cropper that is customizable. You can rotate, scale and translate either through gestures or a controller -version: 0.0.11 +version: 0.0.12 homepage: https://github.com/icapps/flutter-custom-image-crop environment: