You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/// A method that draws a path with a given paint, outline color and stroke width
typedef DrawPathMethod = CustomPaint Function(
Path path, {
Paint? pathPaint,
Color outlineColor,
double outlineStrokeWidth,
});
/// 圆形图片裁剪
/// todo 超出边界回弹未实现 缩小超过初始大小0.5倍回放未实现
class MyCustomImageCrop extends StatefulWidget {
/// The image to crop
final ImageProvider image;
/// The controller that handles the cropping and
/// changing of the cropping area
final CustomImageCropController cropController;
/// The color behind the cropping area
final Color backgroundColor;
/// The color in front of the cropped area
final Color overlayColor;
/// A short text of guidance or advice, usually used to provide help or instructions
final Widget? promptText;
/// The shape of the cropping area.
/// Possible values:
/// - [CustomCropShape.Circle] Crop area will be circular.
/// - [CustomCropShape.Square] Crop area will be a square.
/// - [CustomCropShape.Ratio] Crop area will have a specified aspect ratio.
final CustomCropShape shape;
/// The shape of the mask area.
/// Possible values:
/// - [CustomCropShape.Circle] Mask area will be circular.
/// - [CustomCropShape.Square] Mask area will be a square.
/// - [CustomCropShape.Ratio] Mask area will have a specified aspect ratio.
final CustomCropShape? maskShape;
/// Ratio of the cropping area.
/// If [shape] is set to [CustomCropShape.Ratio], this property is required.
/// For example, to create a square crop area, use [Ratio(width: 1, height: 1)].
/// To create a rectangular crop area with a 16:9 aspect ratio, use [Ratio(width: 16, height: 9)].
final Ratio? ratio;
/// How to fit image inside visible space
final CustomImageFit imageFit;
/// The percentage of the available area that is
/// reserved for the cropping area
final double cropPercentage;
/// The path drawer of the border see [DottedCropPathPainter],
/// [SolidPathPainter] for more details or how to implement a
/// custom one
final DrawPathMethod drawPath;
/// Custom paint options for drawing the cropping border.
///
/// If [paint] is provided, it will be used for customizing the appearance
/// of the cropping border.
///
/// If [paint] is not provided, default values will be used:
/// - Color: [Colors.white]
/// - Stle [PaintingStyle.stroke]
/// - Stroke Join [StrokeJoin.round]
/// - Stroke Width: 4.0
final Paint? pathPaint;
/// The radius for rounded corners of the cropping area (only applicable to rounded rectangle shapes).
final double borderRadius;
/// Whether to allow the image to be rotated.
final bool canRotate;
/// Determines whether scaling gesture is disabled.
///
/// By default, scaling is enabled.
/// Set [canScale] to false to disable scaling.
final bool canScale;
/// Determines whether moving gesture overlay is disabled.
///
/// By default, moving is enabled.
/// Set [canMove] to false to disable move.
final bool canMove;
/// The paint used when drawing an image before cropping
final Paint imagePaintDuringCrop;
/// This widget is used to specify a custom progress indicator
final Widget? customProgressIndicator;
/// Whether to clip the area outside of the path when cropping
/// By default, the value is true
final bool clipShapeOnCrop;
/// Whether image area must cover clip path
/// By default, the value is false
/// If use CustomCropShape.circle, the cropped image may have white blank.
final bool forceInsideCropArea;
/// Sets the color of the outline of the crop selection area
/// This is provided to the [drawPath] method
/// Default is [Colors.white]
final Color outlineColor;
/// Sets the stroke width of the outline of the crop selection area
/// This is provided to the [drawPath] method
/// Default is 4.0
final double outlineStrokeWidth;
/// Adds a filter to overlay.
/// For example, consider using [ImageFilter.blur] to create a backdrop blur effect.
final ui.ImageFilter? imageFilter;
/// The blend mode of the image filter
/// Default is [BlendMode.srcOver]
final BlendMode imageFilterBlendMode;
/// A custom image cropper widget
///
/// Uses a CustomImageCropController to crop the image.
/// With the controller you can rotate, translate and/or
/// scale with buttons and sliders. This can also be
/// achieved with gestures
///
/// Use a shape with CustomCropShape.Circle or
/// CustomCropShape.Square
///
/// You can increase the cropping area using cropPercentage
///
/// Change the cropping border by changing drawPath,
/// we've provided two default painters as inspiration
/// DottedCropPathPainter.drawPath and
/// SolidCropPathPainter.drawPath
MyCustomImageCrop({
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.promptText,
this.maskShape,
this.imageFit = CustomImageFit.fitCropSpace,
this.cropPercentage = 0.8,
this.drawPath = DottedCropPathPainter.drawPath,
this.pathPaint,
this.canRotate = true,
this.canScale = true,
this.canMove = true,
this.clipShapeOnCrop = true,
this.customProgressIndicator,
this.ratio,
this.borderRadius = 0,
Paint? imagePaintDuringCrop,
this.forceInsideCropArea = false,
this.outlineColor = Colors.white,
this.outlineStrokeWidth = 4.0,
this.imageFilter,
this.imageFilterBlendMode = BlendMode.srcOver,
Key? key,
}) : this.imagePaintDuringCrop = imagePaintDuringCrop ??
(Paint()..filterQuality = FilterQuality.high),
assert(
!(shape == CustomCropShape.Ratio && ratio == null),
"If shape is set to Ratio, ratio should not be null.",
),
super(key: key);
class _MyCustomImageCropState extends State
with CustomImageCropListener {
CropImageData? _dataTransitionStart;
late Path _path;
late Path _maskPath;
late double _width, _height;
ui.Image? _imageAsUIImage;
ImageStream? _imageStream;
ImageStreamListener? _imageListener;
/// 适应裁剪框的缩放比例
var currentScale=1.0;
///裁剪框的大小
var cropSizeWidth;
var cropSizeHeight;
var currentAngle;
// Optionally remove magenta from image by evaluating every pixel
// See https://github.com/brendan-duncan/image/blob/master/lib/src/transform/copy_crop.dart
// final bytes = await compute(computeToByteData, <String, dynamic>{'pictureRecorder': pictureRecorder, 'cropWidth': cropWidth});
ui.Picture picture = pictureRecorder.endRecording();
ui.Image image = await picture.toImage(
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
final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
return bytes == null ? null : MemoryImage(bytes.buffer.asUint8List());
@OverRide
void setData(CropImageData newData) {
setState(() {
data = newData;
// The same check should happen (once available) as in addTransition
data.scale = data.scale.clamp(0.1, 10.0);
});
}
}
`
The text was updated successfully, but these errors were encountered:
`import 'dart:async';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:custom_image_crop/custom_image_crop.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';
import 'package:gesture_x_detector/gesture_x_detector.dart';
import 'package:vector_math/vector_math_64.dart' as vector_math;
/// A method that draws a path with a given paint, outline color and stroke width
typedef DrawPathMethod = CustomPaint Function(
Path path, {
Paint? pathPaint,
Color outlineColor,
double outlineStrokeWidth,
});
/// 圆形图片裁剪
/// todo 超出边界回弹未实现 缩小超过初始大小0.5倍回放未实现
class MyCustomImageCrop extends StatefulWidget {
/// The image to crop
final ImageProvider image;
/// The controller that handles the cropping and
/// changing of the cropping area
final CustomImageCropController cropController;
/// The color behind the cropping area
final Color backgroundColor;
/// The color in front of the cropped area
final Color overlayColor;
/// A short text of guidance or advice, usually used to provide help or instructions
final Widget? promptText;
/// The shape of the cropping area.
/// Possible values:
/// - [CustomCropShape.Circle] Crop area will be circular.
/// - [CustomCropShape.Square] Crop area will be a square.
/// - [CustomCropShape.Ratio] Crop area will have a specified aspect ratio.
final CustomCropShape shape;
/// The shape of the mask area.
/// Possible values:
/// - [CustomCropShape.Circle] Mask area will be circular.
/// - [CustomCropShape.Square] Mask area will be a square.
/// - [CustomCropShape.Ratio] Mask area will have a specified aspect ratio.
final CustomCropShape? maskShape;
/// Ratio of the cropping area.
/// If [shape] is set to [CustomCropShape.Ratio], this property is required.
/// For example, to create a square crop area, use [Ratio(width: 1, height: 1)].
/// To create a rectangular crop area with a 16:9 aspect ratio, use [Ratio(width: 16, height: 9)].
final Ratio? ratio;
/// How to fit image inside visible space
final CustomImageFit imageFit;
/// The percentage of the available area that is
/// reserved for the cropping area
final double cropPercentage;
/// The path drawer of the border see [DottedCropPathPainter],
/// [SolidPathPainter] for more details or how to implement a
/// custom one
final DrawPathMethod drawPath;
/// Custom paint options for drawing the cropping border.
///
/// If [paint] is provided, it will be used for customizing the appearance
/// of the cropping border.
///
/// If [paint] is not provided, default values will be used:
/// - Color: [Colors.white]
/// - Stle [PaintingStyle.stroke]
/// - Stroke Join [StrokeJoin.round]
/// - Stroke Width: 4.0
final Paint? pathPaint;
/// The radius for rounded corners of the cropping area (only applicable to rounded rectangle shapes).
final double borderRadius;
/// Whether to allow the image to be rotated.
final bool canRotate;
/// Determines whether scaling gesture is disabled.
///
/// By default, scaling is enabled.
/// Set [canScale] to
false
to disable scaling.final bool canScale;
/// Determines whether moving gesture overlay is disabled.
///
/// By default, moving is enabled.
/// Set [canMove] to
false
to disable move.final bool canMove;
/// The paint used when drawing an image before cropping
final Paint imagePaintDuringCrop;
/// This widget is used to specify a custom progress indicator
final Widget? customProgressIndicator;
/// Whether to clip the area outside of the path when cropping
/// By default, the value is
true
final bool clipShapeOnCrop;
/// Whether image area must cover clip path
/// By default, the value is
false
/// If use CustomCropShape.circle, the cropped image may have white blank.
final bool forceInsideCropArea;
/// Sets the color of the outline of the crop selection area
/// This is provided to the [drawPath] method
/// Default is [Colors.white]
final Color outlineColor;
/// Sets the stroke width of the outline of the crop selection area
/// This is provided to the [drawPath] method
/// Default is 4.0
final double outlineStrokeWidth;
/// Adds a filter to overlay.
/// For example, consider using [ImageFilter.blur] to create a backdrop blur effect.
final ui.ImageFilter? imageFilter;
/// The blend mode of the image filter
/// Default is [BlendMode.srcOver]
final BlendMode imageFilterBlendMode;
/// A custom image cropper widget
///
/// Uses a
CustomImageCropController
to crop the image./// With the controller you can rotate, translate and/or
/// scale with buttons and sliders. This can also be
/// achieved with gestures
///
/// Use a
shape
withCustomCropShape.Circle
or///
CustomCropShape.Square
///
/// You can increase the cropping area using
cropPercentage
///
/// Change the cropping border by changing
drawPath
,/// we've provided two default painters as inspiration
///
DottedCropPathPainter.drawPath
and///
SolidCropPathPainter.drawPath
MyCustomImageCrop({
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.promptText,
this.maskShape,
this.imageFit = CustomImageFit.fitCropSpace,
this.cropPercentage = 0.8,
this.drawPath = DottedCropPathPainter.drawPath,
this.pathPaint,
this.canRotate = true,
this.canScale = true,
this.canMove = true,
this.clipShapeOnCrop = true,
this.customProgressIndicator,
this.ratio,
this.borderRadius = 0,
Paint? imagePaintDuringCrop,
this.forceInsideCropArea = false,
this.outlineColor = Colors.white,
this.outlineStrokeWidth = 4.0,
this.imageFilter,
this.imageFilterBlendMode = BlendMode.srcOver,
Key? key,
}) : this.imagePaintDuringCrop = imagePaintDuringCrop ??
(Paint()..filterQuality = FilterQuality.high),
assert(
!(shape == CustomCropShape.Ratio && ratio == null),
"If shape is set to Ratio, ratio should not be null.",
),
super(key: key);
@OverRide
_MyCustomImageCropState createState() => _MyCustomImageCropState();
}
class _MyCustomImageCropState extends State
with CustomImageCropListener {
CropImageData? _dataTransitionStart;
late Path _path;
late Path _maskPath;
late double _width, _height;
ui.Image? _imageAsUIImage;
ImageStream? _imageStream;
ImageStreamListener? _imageListener;
/// 适应裁剪框的缩放比例
var currentScale=1.0;
///裁剪框的大小
var cropSizeWidth;
var cropSizeHeight;
var currentAngle;
@OverRide
void initState() {
super.initState();
widget.cropController.addListener(this);
WidgetsBinding.instance.addPostFrameCallback((_) {
// 在这里执行你想要的方法
scaleAdaptive();
});
}
@OverRide
void didChangeDependencies() {
super.didChangeDependencies();
_getImage();
}
@OverRide
void didUpdateWidget(MyCustomImageCrop oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.image != widget.image) _getImage();
}
void _getImage() {
final oldImageStream = _imageStream;
_imageStream = widget.image.resolve(createLocalImageConfiguration(context));
if (_imageStream?.key != oldImageStream?.key) {
if (_imageListener != null) {
oldImageStream?.removeListener(_imageListener!);
}
_imageListener = ImageStreamListener(_updateImage);
_imageStream?.addListener(_imageListener!);
}
}
void _updateImage(ImageInfo imageInfo, _) {
setState(() {
_imageAsUIImage = imageInfo.image;
});
}
@OverRide
void dispose() {
if (_imageListener != null) {
_imageStream?.removeListener(_imageListener!);
}
widget.cropController.removeListener(this);
super.dispose();
}
@OverRide
Widget build(BuildContext context) {
final image = _imageAsUIImage;
if (image == null) {
return Center(
child: widget.customProgressIndicator ?? CircularProgressIndicator(),
);
}
}
void onScaleStart(_) {
_dataTransitionStart = null; // Reset for update
}
///缩小到低于图片初始大小2倍,恢复至2倍
void onScaleEnd() {
if (data.scale < 1) {
scaleAdaptive();
}
moveSpringback();
}
///图片大小缩放自适应,必须比裁剪框大
void scaleAdaptive(){
final image = _imageAsUIImage;
if (image == null) {
return;
}
var w = image.width * currentScale * data.scale;
var h = image.height * currentScale * data.scale;
var scale = 1.0;
if (w < cropSizeWidth) {
scale = cropSizeWidth / w;
}
if (h < cropSizeHeight) {
var tempScale = cropSizeHeight / h;
if (tempScale > scale) {
scale = tempScale;
}
}
widget.cropController.addTransition(
CropImageData(scale: scale),
);
}
void onScaleUpdate(ScaleEvent event) {
final scale =
widget.canScale ? event.scale : (_dataTransitionStart?.scale ?? 1.0);
}
void onMoveStart(_) {
_dataTransitionStart = null; // Reset for update
}
void onMoveEnd(MoveEvent? event) {
if (!widget.canMove) return;
if (data.scale < currentScale) {
onScaleEnd();
}
}
///回弹
void moveSpringback(){
final image = _imageAsUIImage!;
final scale = data.scale * currentScale;
var scaledImageWidth = image.width * scale;
var scaledImageHeight = image.height * scale;
}
void onMoveUpdate(MoveEvent event) {
if (!widget.canMove) return;
}
Rect _getInitialImageRect() {
assert(_imageAsUIImage != null);
final image = _imageAsUIImage!;
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),
forceInsideCropArea: widget.forceInsideCropArea,
);
final initialWidth = image.width * cropFitParams.additionalScale;
final initialHeight = image.height * cropFitParams.additionalScale;
return Rect.fromLTWH(
(_width - initialWidth) / 2,
(_height - initialHeight) / 2,
initialWidth,
initialHeight,
);
}
void _correctTransition(CropImageData transition, VoidCallback callback) {
if (!widget.forceInsideCropArea || _imageAsUIImage == null) {
callback();
return;
}
final startX = data.x;
final startY = data.y;
callback();
final pathRect = _path.getBounds();
final initialImageRect = _getInitialImageRect();
bool isContainPath = _isContainPath(initialImageRect, pathRect, data.scale);
bool isRotated = data.angle != 0;
}
Rect _getImageRect(Rect initialImageRect, double currentScale) {
final diffScale = (1 - currentScale) / 2;
final left =
initialImageRect.left + diffScale * initialImageRect.width + data.x;
final top =
initialImageRect.top + diffScale * initialImageRect.height + data.y;
Rect imageRect = Rect.fromLTWH(
left,
top,
currentScale * initialImageRect.width,
currentScale * initialImageRect.height);
return imageRect;
}
double _getDistanceBetweenPointAndLine(
Offset point, Offset lineStart, Offset lineEnd) {
if (lineEnd.dy == lineStart.dy) {
return (point.dy - lineStart.dy).abs();
}
if (lineEnd.dx == lineStart.dx) {
return (point.dx - lineStart.dx).abs();
}
double line1Slop =
(lineEnd.dy - lineStart.dy) / (lineEnd.dx - lineStart.dx);
double line1Delta = lineEnd.dy - lineEnd.dx * line1Slop;
double line2Slop = -1 / line1Slop;
double line2Delta = point.dy - point.dx * line2Slop;
double crossPointX = (line2Delta - line1Delta) / (line1Slop - line2Slop);
double crossPointY = line1Slop * crossPointX + line1Delta;
return (Offset(crossPointX, crossPointY) - point).distance;
}
bool _isContainPath(
Rect initialImageRect, Rect pathRect, double currentScale) {
final imageRect = _getImageRect(initialImageRect, currentScale);
Offset topLeft, topRight, bottomLeft, bottomRight;
final rad = atan(imageRect.height / imageRect.width);
final len =
sqrt(pow(imageRect.width / 2, 2) + pow(imageRect.height / 2, 2));
bool isRotated = data.angle != 0;
}
double _calculateScaleAfterRotate(Rect pathRect, double startScale,
Rect initialImageRect, double minEdgeHalf) {
final imageCenter = initialImageRect.center.translate(data.x, data.y);
final topLeftDistance = (pathRect.topLeft - imageCenter).distance;
final topRightDistance = (pathRect.topRight - imageCenter).distance;
final bottomLeftDistance = (pathRect.bottomLeft - imageCenter).distance;
final bottomRightDistance = (pathRect.bottomRight - imageCenter).distance;
final maxDistance = max(
max(max(topLeftDistance, topRightDistance), bottomLeftDistance),
bottomRightDistance);
double endScale = maxDistance / minEdgeHalf;
}
Path _getPath({
required double cropWidth,
required double cropHeight,
required double width,
required double height,
required double borderRadius,
required CustomCropShape shape,
bool clipShape = true,
}) {
if (!clipShape) {
return Path()
..addRect(
Rect.fromCenter(
center: Offset(width / 2, height / 2),
width: cropWidth,
height: cropHeight,
),
);
}
}
@OverRide
Future<MemoryImage?> onCropImage() async {
if (_imageAsUIImage == null) {
return null;
}
final imageWidth = _imageAsUIImage!.width;
final imageHeight = _imageAsUIImage!.height;
final pictureRecorder = ui.PictureRecorder();
final canvas = Canvas(pictureRecorder);
final onCropParams = calculateOnCropParams(
cropPercentage: widget.cropPercentage,
imageFit: widget.imageFit,
imageHeight: imageHeight,
imageWidth: imageWidth,
screenHeight: _height,
screenWidth: _width,
dataScale: data.scale,
aspectRatio: (widget.ratio?.width ?? 1) / (widget.ratio?.height ?? 1),
forceInsideCropArea: widget.forceInsideCropArea,
);
final clipPath = Path.from(_getPath(
cropWidth: onCropParams.cropSizeWidth,
cropHeight: onCropParams.cropSizeHeight,
width: onCropParams.cropSizeWidth,
height: onCropParams.cropSizeHeight,
borderRadius: widget.borderRadius,
clipShape: widget.clipShapeOnCrop,
shape: widget.shape,
));
final matrix4Image = Matrix4.diagonal3(vector_math.Vector3.all(1))
..translate(
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.cropSizeWidth,
onCropParams.cropSizeHeight,
),
bgPaint,
);
canvas.save();
canvas.clipPath(clipPath);
canvas.transform(matrix4Image.storage);
canvas.drawImage(
_imageAsUIImage!,
Offset(-imageWidth / 2, -imageHeight / 2),
widget.imagePaintDuringCrop,
);
canvas.restore();
}
void _addTransitionInternal(CropImageData transition) {
setData(data + transition);
}
@OverRide
void addTransition(CropImageData transition) {
_correctTransition(transition, () {
_addTransitionInternal(transition);
});
}
@OverRide
void setData(CropImageData newData) {
setState(() {
data = newData;
// The same check should happen (once available) as in addTransition
data.scale = data.scale.clamp(0.1, 10.0);
});
}
}
`
The text was updated successfully, but these errors were encountered: