Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

我增加了滑动出裁剪框边界时的回弹效果,以及缩小图片不允许小于裁剪框的限制,并禁止了滑动旋转的操作 #63

Open
jzNccc opened this issue Feb 20, 2025 · 0 comments

Comments

@jzNccc
Copy link

jzNccc commented Feb 20, 2025

`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 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);

@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(),
);
}

if (currentAngle == null) {
  currentAngle = data.angle;
} else {
  if (currentAngle != data.angle) {
    currentAngle = data.angle;
    moveSpringback();
  }
}
print('----------aaaaaaaaaaaaa:${data.angle}');

return LayoutBuilder(
  builder: (context, constraints) {
    _width = constraints.maxWidth;
    _height = constraints.maxHeight;
    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,
    );
    cropSizeHeight = cropFitParams.cropSizeHeight;
    cropSizeWidth = cropFitParams.cropSizeWidth;
    currentScale = cropFitParams.additionalScale;
    print('currentScalecurrentScalecurrentScale:$currentScale');

    // final scale = data.scale * cropFitParams.additionalScale;
    final scale = data.scale * cropFitParams.additionalScale;
    _path = _getPath(
      cropWidth: cropFitParams.cropSizeWidth,
      cropHeight: cropFitParams.cropSizeHeight,
      width: _width,
      height: _height,
      borderRadius: widget.borderRadius,
      shape: widget.shape,
    );

    _maskPath = widget.maskShape == null
        ? _path
        : _getPath(
      cropWidth: cropFitParams.cropSizeWidth,
      cropHeight: cropFitParams.cropSizeHeight,
      width: _width,
      height: _height,
      borderRadius: widget.borderRadius,
      shape: widget.maskShape!,
    );

    Widget overlay = Container(
      color: widget.overlayColor,
    );
    final filter = widget.imageFilter;
    if (filter != null) {
      overlay = BackdropFilter(
        filter: filter,
        blendMode: widget.imageFilterBlendMode,
        child: overlay,
      );
    }
    overlay = IgnorePointer(
      child: ClipPath(
        clipper: InvertedClipper(_maskPath, _width, _height),
        child: overlay,
      ),
    );

    return XGestureDetector(
      onMoveStart: onMoveStart,
      onMoveUpdate: onMoveUpdate,
      onMoveEnd: onMoveEnd,
      onScaleStart: onScaleStart,
      onScaleUpdate: onScaleUpdate,
      onScaleEnd: onScaleEnd,
      child: Container(
        width: _width,
        height: _height,
        color: widget.backgroundColor,
        child: Stack(
          children: [
            Positioned(
              left: data.x + _width / 2,
              top: data.y + _height / 2,
              child: Transform(
                transform: Matrix4.diagonal3(
                    vector_math.Vector3(scale, scale, scale))
                  ..translate(-image.width / 2, -image.height / 2),
                child: Transform.rotate(
                  angle: data.angle,
                  child: Image(
                    image: widget.image,
                  ),
                ),
              ),
            ),
            overlay,
            widget.drawPath(
              _maskPath,
              pathPaint: widget.pathPaint,
              outlineColor: widget.outlineColor,
              outlineStrokeWidth: widget.outlineStrokeWidth,
            ),
        Positioned(
          top: _height / 2 + cropSizeHeight/ 2 + 10,
            left: 0,
            right: 0,
            child: Center(
              child: widget.promptText?? const SizedBox(),
            ),
        ),
          ],
        ),
      ),
    );
  },
);

}

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

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

if (_dataTransitionStart != null) {
  widget.cropController.addTransition(
    _dataTransitionStart! -
        CropImageData(
          scale: scale,
          angle: 0,//禁用旋转
        ),
  );
}
_dataTransitionStart = CropImageData(
  scale: scale,
  angle: 0, //禁用旋转
);

}

void onMoveStart(_) {
_dataTransitionStart = null; // Reset for update
}

void onMoveEnd(MoveEvent? event) {
if (!widget.canMove) return;
if (data.scale < currentScale) {
onScaleEnd();
}

moveSpringback();

}

///回弹
void moveSpringback(){
final image = _imageAsUIImage!;
final scale = data.scale * currentScale;
var scaledImageWidth = image.width * scale;
var scaledImageHeight = image.height * scale;

var cropWidth = cropSizeWidth;
var cropHeight = cropSizeHeight;
if (data.angle == (pi / 2)
|| data.angle == (pi + pi /2)) {
  cropWidth = cropSizeHeight;
  cropHeight = cropSizeWidth;
  scaledImageWidth = image.height * scale;
  scaledImageHeight = image.width * scale;
}

double minX, maxX, minY, maxY;
if (scaledImageWidth <= cropWidth) {
  // 图片宽度小于等于屏幕宽度,不允许水平滑动
  minX = 0;
  maxX = 0;
} else {
  minX = -scaledImageWidth / 2 + cropWidth / 2;
  maxX = scaledImageWidth / 2 - cropWidth / 2;
}

if (scaledImageHeight <= cropHeight) {
  // 图片高度小于等于屏幕高度,不允许垂直滑动
  minY = 0;
  maxY = 0;
} else {
  minY = -scaledImageHeight / 2 + cropHeight / 2;
  maxY = scaledImageHeight / 2 - cropHeight / 2;
}

// 处理浮点数精度问题
final safeMinX = num.parse(minX.toStringAsFixed(2)).toDouble();
final safeMaxX = num.parse(maxX.toStringAsFixed(2)).toDouble();
final safeMinY = num.parse(minY.toStringAsFixed(2)).toDouble();
final safeMaxY = num.parse(maxY.toStringAsFixed(2)).toDouble();

// 确保边界值逻辑正确
assert(safeMinX <= safeMaxX, 'minX should be less than or equal to maxX');
assert(safeMinY <= safeMaxY, 'minY should be less than or equal to maxY');

// 检查异常数据输入
if (data.x.isNaN || data.x.isInfinite || data.y.isNaN || data.y.isInfinite) {
  print('Invalid data: x=${data.x}, y=${data.y}');
  return;
}

// 计算回弹偏移量
final clampedX = data.x.clamp(safeMinX, safeMaxX);
final clampedY = data.y.clamp(safeMinY, safeMaxY);
final deltaX = clampedX - data.x;
final deltaY = clampedY - data.y;

if (deltaX != 0 || deltaY != 0) {
  widget.cropController
      .addTransition(CropImageData(x: deltaX, y: deltaY));
}

}

void onMoveUpdate(MoveEvent event) {
if (!widget.canMove) return;

widget.cropController
    .addTransition(CropImageData(x: event.delta.dx, y: event.delta.dy));

}

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;

if (isContainPath) {
  return;
}

if (transition.x != 0 || transition.y != 0) {
  if (isRotated) {
    _addTransitionInternal(
        CropImageData(x: startX - data.x, y: startY - data.y));
  } else {
    final imageRect = _getImageRect(initialImageRect, data.scale);
    double deltaX = min(pathRect.left - imageRect.left, 0);
    deltaX = pathRect.right > imageRect.right
        ? pathRect.right - imageRect.right
        : deltaX;
    double deltaY = min(pathRect.top - imageRect.top, 0);
    deltaY = pathRect.bottom > imageRect.bottom
        ? pathRect.bottom - imageRect.bottom
        : deltaY;
    _addTransitionInternal(CropImageData(x: deltaX, y: deltaY));
  }
  return;
}
double minEdgeHalf =
    min(initialImageRect.width, initialImageRect.height) / 2;
double adaptScale = _calculateScaleAfterRotate(
    pathRect, data.scale, initialImageRect, minEdgeHalf);
_addTransitionInternal(CropImageData(scale: adaptScale / data.scale));

}

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;

if (isRotated) {
  final clockAngle = rad + data.angle;
  final counterClockAngle = rad - data.angle;
  final cosClockValue = len * cos(clockAngle);
  final sinClockValue = len * sin(clockAngle);
  final cosCounterClockValue = len * cos(counterClockAngle);
  final sinCounterClockValue = len * sin(counterClockAngle);
  bottomRight = imageRect.center.translate(cosClockValue, sinClockValue);
  topRight = imageRect.center
      .translate(cosCounterClockValue, -sinCounterClockValue);
  topLeft = imageRect.center.translate(-cosClockValue, -sinClockValue);
  bottomLeft = imageRect.center
      .translate(-cosCounterClockValue, sinCounterClockValue);
} else {
  bottomRight = imageRect.bottomRight;
  topRight = imageRect.topRight;
  topLeft = imageRect.topLeft;
  bottomLeft = imageRect.bottomLeft;
}

if (widget.shape == CustomCropShape.Circle) {
  final anchor = max(pathRect.width, pathRect.height) / 2;
  final pathCenter = pathRect.center;
  return _getDistanceBetweenPointAndLine(pathCenter, topLeft, topRight) >=
      anchor &&
      _getDistanceBetweenPointAndLine(pathCenter, topRight, bottomRight) >=
          anchor &&
      _getDistanceBetweenPointAndLine(
          pathCenter, bottomLeft, bottomRight) >=
          anchor &&
      _getDistanceBetweenPointAndLine(pathCenter, topLeft, bottomLeft) >=
          anchor;
}

if (isRotated) {
  Path imagePath = Path()
    ..moveTo(topLeft.dx, topLeft.dy)
    ..lineTo(topRight.dx, topRight.dy)
    ..lineTo(bottomRight.dx, bottomRight.dy)
    ..lineTo(bottomLeft.dx, bottomLeft.dy)
    ..close();
  return imagePath.contains(pathRect.topLeft) &&
      imagePath.contains(pathRect.topRight) &&
      imagePath.contains(pathRect.bottomLeft) &&
      imagePath.contains(pathRect.bottomRight);
} else {
  return imageRect.contains(pathRect.topLeft) &&
      imageRect.contains(pathRect.topRight) &&
      imageRect.contains(pathRect.bottomLeft) &&
      imageRect.contains(pathRect.bottomRight);
}

}

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;

if (startScale >= endScale) {
  return endScale;
}

///use binary search to find best scale which just contain path.
///Also, we can use imageCenter、imageLine(longest one) and path vertex to calculate.
double step = 1 / minEdgeHalf;

while ((endScale - startScale).abs() > step) {
  double midScale = (endScale + startScale) / 2;

  if (_isContainPath(initialImageRect, pathRect, midScale)) {
    endScale = midScale;
  } else {
    startScale = midScale + step;
  }
}
return endScale;

}

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,
),
);
}

switch (shape) {
  case CustomCropShape.Circle:
    return Path()
      ..addOval(
        Rect.fromCircle(
          center: Offset(width / 2, height / 2),
          radius: cropWidth / 2,
        ),
      );
  case CustomCropShape.Ratio:
    return Path()
      ..addRRect(
        RRect.fromRectAndRadius(
          Rect.fromCenter(
            center: Offset(width / 2, height / 2),
            width: cropWidth,
            height: cropHeight,
          ),
          Radius.circular(borderRadius),
        ),
      );
  default:
    return Path()
      ..addRRect(
        RRect.fromRectAndRadius(
          Rect.fromCenter(
            center: Offset(width / 2, height / 2),
            width: cropWidth,
            height: cropHeight,
          ),
          Radius.circular(borderRadius),
        ),
      );
}

}

@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();

// 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());

}

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

`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant