Skip to content

Commit 7a0186c

Browse files
committed
optimize fillcropspace fit
1 parent f869627 commit 7a0186c

File tree

1 file changed

+109
-10
lines changed

1 file changed

+109
-10
lines changed

lib/src/widgets/custom_image_crop_widget.dart

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:math';
23
import 'dart:ui' as ui;
34

45
import 'package:custom_image_crop/custom_image_crop.dart';
@@ -273,7 +274,7 @@ class _CustomImageCropState extends State<CustomImageCrop>
273274
final angle = widget.canRotate ? event.rotationAngle : 0.0;
274275

275276
if (_dataTransitionStart != null) {
276-
addTransition(
277+
widget.cropController.addTransition(
277278
_dataTransitionStart! -
278279
CropImageData(
279280
scale: scale,
@@ -294,7 +295,107 @@ class _CustomImageCropState extends State<CustomImageCrop>
294295
void onMoveUpdate(MoveEvent event) {
295296
if (!widget.canMove) return;
296297

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+
}
298399
}
299400

300401
Path _getPath({
@@ -425,16 +526,14 @@ class _CustomImageCropState extends State<CustomImageCrop>
425526
return bytes == null ? null : MemoryImage(bytes.buffer.asUint8List());
426527
}
427528

529+
void _addTransitionInternal(CropImageData transition) {
530+
setData(data + transition);
531+
}
532+
428533
@override
429534
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);
438537
});
439538
}
440539

0 commit comments

Comments
 (0)