1
1
import 'dart:async' ;
2
+ import 'dart:math' ;
2
3
import 'dart:ui' as ui;
3
4
4
5
import 'package:custom_image_crop/custom_image_crop.dart' ;
@@ -273,7 +274,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
273
274
final angle = widget.canRotate ? event.rotationAngle : 0.0 ;
274
275
275
276
if (_dataTransitionStart != null ) {
276
- addTransition (
277
+ widget.cropController. addTransition (
277
278
_dataTransitionStart! -
278
279
CropImageData (
279
280
scale: scale,
@@ -294,7 +295,107 @@ class _CustomImageCropState extends State<CustomImageCrop>
294
295
void onMoveUpdate (MoveEvent event) {
295
296
if (! widget.canMove) return ;
296
297
297
- addTransition (CropImageData (x: event.delta.dx, y: event.delta.dy));
298
+ widget.cropController.addTransition (CropImageData (x: event.delta.dx, y: event.delta.dy));
299
+ }
300
+
301
+ Rect _getInitialImageRect () {
302
+ assert (_imageAsUIImage != null );
303
+ final image = _imageAsUIImage! ;
304
+ final cropFitParams = calculateCropFitParams (
305
+ cropPercentage: widget.cropPercentage,
306
+ imageFit: widget.imageFit,
307
+ imageHeight: image.height,
308
+ imageWidth: image.width,
309
+ screenHeight: _height,
310
+ screenWidth: _width,
311
+ aspectRatio: (widget.ratio? .width ?? 1 ) / (widget.ratio? .height ?? 1 ),
312
+ );
313
+ final initialWidth = _imageAsUIImage! .width * cropFitParams.additionalScale;
314
+ final initialHeight = _imageAsUIImage! .height * cropFitParams.additionalScale;
315
+ return Rect .fromLTWH (
316
+ (_width - initialWidth) / 2 ,
317
+ (_height - initialHeight) / 2 ,
318
+ initialWidth,
319
+ initialHeight,
320
+ );
321
+ }
322
+
323
+ // just support 'pi/2 * n' angle for now
324
+ bool _isAngleSupported () {
325
+ final angle = data.angle;
326
+ return angle % (pi/ 2 ) == 0 ;
327
+ }
328
+
329
+ void _correctTransition (CropImageData transition, VoidCallback callback) {
330
+ if (_imageAsUIImage == null ||
331
+ widget.imageFit != CustomImageFit .fillCropSpace ||
332
+ ! _isAngleSupported ()) {
333
+ callback ();
334
+ return ;
335
+ }
336
+ final startData = CropImageData (
337
+ scale: data.scale,
338
+ angle: data.angle,
339
+ x: data.x,
340
+ y: data.y,
341
+ );
342
+ callback ();
343
+ final initialImageRect = _getInitialImageRect ();
344
+ final diffScale = (1 - data.scale) / 2 ;
345
+ final left = initialImageRect.left + diffScale * initialImageRect.width + data.x;
346
+ final top = initialImageRect.top + diffScale * initialImageRect.height + data.y;
347
+ Rect imageRect = Rect .fromLTWH (left, top, data.scale * initialImageRect.width, data.scale * initialImageRect.height);
348
+ Offset topLeft, topRight, bottomLeft, bottomRight;
349
+ final rad = atan (imageRect.height / imageRect.width);
350
+ final len = sqrt (pow (imageRect.width / 2 , 2 ) + pow (imageRect.height / 2 , 2 ));
351
+
352
+ if (data.angle != 0 ) {
353
+ final clockAngle = rad + data.angle;
354
+ final counterClockAngle = rad - data.angle;
355
+ final cosClockValue = len * cos (clockAngle);
356
+ final sinClockValue = len * sin (clockAngle);
357
+ final cosCounterClockValue = len * cos (counterClockAngle);
358
+ final sinCounterClockValue = len * sin (counterClockAngle);
359
+ bottomRight =
360
+ imageRect.center.translate (cosClockValue, sinClockValue);
361
+ topRight =
362
+ imageRect.center.translate (cosCounterClockValue, - sinCounterClockValue);
363
+ topLeft =
364
+ imageRect.center.translate (- cosClockValue, - sinClockValue);
365
+ bottomLeft =
366
+ imageRect.center.translate (- cosCounterClockValue, sinCounterClockValue);
367
+
368
+ Path imagePath = Path ()
369
+ ..moveTo (topLeft.dx, topLeft.dy)
370
+ ..lineTo (topRight.dx, topRight.dy)
371
+ ..lineTo (bottomRight.dx, bottomRight.dy)
372
+ ..lineTo (bottomLeft.dx, bottomLeft.dy)
373
+ ..close ();
374
+ imageRect = imagePath.getBounds ();
375
+ }
376
+ final pathRect = _path.getBounds ();
377
+
378
+ double toPrecision (num n) => double .parse (n.toStringAsFixed (1 ));
379
+
380
+ num compareTwoDecimal (double a, double b) => toPrecision (a) - toPrecision (b);
381
+
382
+ if (compareTwoDecimal (imageRect.left, pathRect.left) <= 0 &&
383
+ compareTwoDecimal (imageRect.top, pathRect.top) <= 0 &&
384
+ compareTwoDecimal (imageRect.right, pathRect.right) >= 0 &&
385
+ compareTwoDecimal (imageRect.bottom, pathRect.bottom) >= 0 ) {
386
+ return ;
387
+ }
388
+ if (transition.x != 0 || transition.y != 0 || transition.angle != 0 ) {
389
+ double deltaX = min (pathRect.left - imageRect.left, 0 );
390
+ deltaX = pathRect.right > imageRect.right ? pathRect.right - imageRect.right : deltaX;
391
+ double deltaY = min (pathRect.top - imageRect.top, 0 );
392
+ deltaY = pathRect.bottom > imageRect.bottom ? pathRect.bottom - imageRect.bottom : deltaY;
393
+ _addTransitionInternal (CropImageData (x: deltaX, y: deltaY));
394
+ return ;
395
+ }
396
+ if (transition.scale != 1.0 ) {
397
+ _addTransitionInternal (CropImageData (scale: startData.scale / data.scale));
398
+ }
298
399
}
299
400
300
401
Path _getPath ({
@@ -425,16 +526,14 @@ class _CustomImageCropState extends State<CustomImageCrop>
425
526
return bytes == null ? null : MemoryImage (bytes.buffer.asUint8List ());
426
527
}
427
528
529
+ void _addTransitionInternal (CropImageData transition) {
530
+ setData (data + transition);
531
+ }
532
+
428
533
@override
429
534
void addTransition (CropImageData transition) {
430
- setState (() {
431
- data += transition;
432
- // For now, this will do. The idea is that we create
433
- // a path from the data and check if when we combine
434
- // that with the crop path that the resulting path
435
- // overlap the hole (crop). So we check if all pixels
436
- // from the crop contain pixels from the original image
437
- data.scale = data.scale.clamp (0.1 , 10.0 );
535
+ _correctTransition (transition, () {
536
+ _addTransitionInternal (transition);
438
537
});
439
538
}
440
539
0 commit comments