Skip to content

Commit be51e7c

Browse files
authored
feat(crop-rotate-editor): add onTransformUpdateEnd callback to the cropRotateEditor (#722)
* feat(crop-rotate-editor): add onTransformUpdateEnd callback and complete parameters handling * Merge https://github.com/hm21/pro_image_editor into dev * chore(release): bump version to 11.15.2 and update changelog
1 parent 41ee461 commit be51e7c

File tree

5 files changed

+111
-41
lines changed

5 files changed

+111
-41
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 11.15.2
4+
- **FEAT**(crop-rotate-editor): Add new callback `onTransformUpdateEnd` that returns all transformation changes whenever a value in the crop-rotate editor is modified.
5+
36
## 11.15.1
47
- **FEAT**(text-editor): Add config `enableAutoWrapOnLayer` to the `TextEditorConfigs` which allows for deciding whether the layer applies the editor's auto wrapping or not. More details in PR [#720](https://github.com/hm21/pro_image_editor/pull/720).
58

lib/core/models/editor_callbacks/crop_rotate_editor_callbacks.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import 'package:flutter/widgets.dart';
33

44
// Project imports:
5+
import '../complete_parameters.dart';
56
import 'editor_callbacks_typedef.dart';
67
import 'standalone_editor_callbacks.dart';
78

@@ -18,6 +19,7 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
1819
this.onDoubleTap,
1920
this.onResize,
2021
this.onReset,
22+
this.onTransformUpdateEnd,
2123
super.onInit,
2224
super.onAfterViewInit,
2325
super.onUndo,
@@ -64,6 +66,18 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
6466
/// A callback function that is triggered when a reset action is performed.
6567
final Function()? onReset;
6668

69+
/// Callback that is triggered when a transformation update ends.
70+
///
71+
/// This callback is invoked when the user completes a gesture that modifies
72+
/// the crop or rotation transformation (e.g., releasing a pinch gesture or
73+
/// finishing a rotation gesture).
74+
///
75+
/// The [parameters] contain information about the completed transformation,
76+
/// including the final state of the crop and rotation values.
77+
///
78+
/// **IMPORTANT:** The `imageBytes` will always be empty.
79+
final Function(CompleteParameters parameters)? onTransformUpdateEnd;
80+
6781
/// Handles the rotate start event.
6882
///
6983
/// This method calls the [onRotateStart] callback with the provided [value]
@@ -159,6 +173,7 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
159173
Function()? onRedo,
160174
Function()? onUndo,
161175
Function()? onCloseEditor,
176+
Function(CompleteParameters parameters)? onTransformUpdateEnd,
162177
}) {
163178
return CropRotateEditorCallbacks(
164179
onRotateStart: onRotateStart ?? this.onRotateStart,
@@ -177,6 +192,7 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
177192
onRedo: onRedo ?? this.onRedo,
178193
onUndo: onUndo ?? this.onUndo,
179194
onCloseEditor: onCloseEditor ?? this.onCloseEditor,
195+
onTransformUpdateEnd: onTransformUpdateEnd ?? this.onTransformUpdateEnd,
180196
);
181197
}
182198
}

lib/features/crop_rotate_editor/crop_rotate_editor.dart

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -833,46 +833,9 @@ class CropRotateEditorState extends State<CropRotateEditor>
833833

834834
/// Return complete parameters if requested
835835
if (initConfigs.callbacks.onCompleteWithParameters != null) {
836-
final isTransformed = transformC.isNotEmpty;
837-
838-
Size originalImageSize;
839-
if (isVideoEditor) {
840-
originalImageSize = videoController!.initialResolution;
841-
} else {
842-
var rawOriginalSize =
843-
await widget.editorImage?.safeByteArray(context) ?? imageBytes;
844-
var decodedImage = await decodeImageFromList(rawOriginalSize);
845-
originalImageSize = Size(
846-
decodedImage.width.toDouble(),
847-
decodedImage.height.toDouble(),
848-
);
849-
}
850-
851-
Size? outputSize = transformC.getCropSize(originalImageSize);
852-
Offset? outputOffset = transformC.getCropStartOffset(originalImageSize);
853-
854-
await callbacks.onCompleteWithParameters?.call(
855-
CompleteParameters(
856-
blur: appliedBlurFactor,
857-
matrixFilterList: appliedFilters,
858-
matrixTuneAdjustmentsList:
859-
appliedTuneAdjustments.map((item) => item.matrix).toList(),
860-
cropWidth: isTransformed ? outputSize.width.round() : null,
861-
cropHeight: isTransformed ? outputSize.height.round() : null,
862-
cropX: isTransformed ? outputOffset.dx.round() : null,
863-
cropY: isTransformed ? outputOffset.dy.round() : null,
864-
flipX:
865-
transformC.is90DegRotated ? transformC.flipY : transformC.flipX,
866-
flipY:
867-
transformC.is90DegRotated ? transformC.flipX : transformC.flipY,
868-
rotateTurns: transformC.angleToTurns(),
869-
startTime: null,
870-
endTime: null,
871-
image: imageBytes,
872-
isTransformed: isTransformed,
873-
layers: layers ?? [],
874-
),
875-
);
836+
final completeParams =
837+
await getCompleteParameters(imageBytes: imageBytes);
838+
await callbacks.onCompleteWithParameters?.call(completeParams);
876839
}
877840

878841
LoadingDialog.instance.hide();

lib/features/crop_rotate_editor/mixins/crop_area_history.dart

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// Dart imports:
22
import 'dart:math';
3+
import 'dart:typed_data';
34

45
// Flutter imports:
56
import 'package:flutter/material.dart';
67

78
// Project imports:
89
import '/core/mixins/standalone_editor.dart';
10+
import '/core/models/complete_parameters.dart';
911
import '/core/models/init_configs/crop_rotate_editor_init_configs.dart';
1012
import '/shared/widgets/extended/extended_custom_paint.dart';
1113
import '/shared/widgets/extended/extended_transform_scale.dart';
@@ -298,10 +300,21 @@ mixin CropAreaHistory
298300
),
299301
);
300302
screenshotHistoryPosition++;
303+
_handleTransformationUpdateEnd();
301304
setState(() {});
302305
takeScreenshot();
303306
}
304307

308+
void _handleTransformationUpdateEnd() async {
309+
final callback = cropRotateEditorCallbacks?.onTransformUpdateEnd;
310+
if (callback == null) return;
311+
312+
final completeParams =
313+
await getCompleteParameters(imageBytes: Uint8List(0));
314+
315+
callback(completeParams);
316+
}
317+
305318
/// Clears forward changes from the history.
306319
void cleanForwardChanges() {
307320
if (history.length > 1) {
@@ -325,6 +338,7 @@ mixin CropAreaHistory
325338
_setParametersFromHistory();
326339
}
327340
cropRotateEditorCallbacks?.handleUndo();
341+
_handleTransformationUpdateEnd();
328342
setState(() {});
329343
}
330344
}
@@ -335,6 +349,7 @@ mixin CropAreaHistory
335349
screenshotHistoryPosition++;
336350
_setParametersFromHistory();
337351
cropRotateEditorCallbacks?.handleRedo();
352+
_handleTransformationUpdateEnd();
338353
setState(() {});
339354
}
340355
}
@@ -446,4 +461,77 @@ mixin CropAreaHistory
446461
/// overridden to implement specific fitting logic.
447462
@protected
448463
void calcFitToScreen() {}
464+
465+
/// Generates complete parameters for the image transformation process.
466+
///
467+
/// This method calculates all the transformation parameters needed to export
468+
/// the edited image, including crop dimensions, rotation, flip operations,
469+
/// filters, and blur effects.
470+
///
471+
/// The method handles both image and video editing modes:
472+
/// - For video editing: uses the video controller's initial resolution
473+
/// - For image editing: decodes the original image to get its dimensions
474+
///
475+
/// Parameters:
476+
/// * [imageBytes] - The original image data as bytes
477+
///
478+
/// Returns a [CompleteParameters] object containing:
479+
/// * Crop dimensions (width, height) and offset (x, y) if transformed
480+
/// * Flip operations in x and y directions (adjusted for 90° rotations)
481+
/// * Rotation in turns
482+
/// * Applied blur factor
483+
/// * List of matrix filters
484+
/// * List of tune adjustment matrices
485+
/// * Layer data
486+
/// * Original image bytes
487+
/// * Transformation status flag
488+
///
489+
/// The crop dimensions and offsets are only included when [isTransformed]
490+
/// is true. Flip operations are automatically adjusted when the image
491+
/// is rotated by 90 degrees to maintain correct orientation.
492+
Future<CompleteParameters> getCompleteParameters({
493+
required Uint8List imageBytes,
494+
}) async {
495+
TransformConfigs transformC =
496+
!canRedo && !canUndo && initialTransformConfigs != null
497+
? initialTransformConfigs!
498+
: activeHistory;
499+
500+
final isTransformed = transformC.isNotEmpty;
501+
502+
Size originalImageSize;
503+
if (isVideoEditor) {
504+
originalImageSize = videoController!.initialResolution;
505+
} else {
506+
var rawOriginalSize =
507+
await widget.editorImage?.safeByteArray(context) ?? imageBytes;
508+
var decodedImage = await decodeImageFromList(rawOriginalSize);
509+
originalImageSize = Size(
510+
decodedImage.width.toDouble(),
511+
decodedImage.height.toDouble(),
512+
);
513+
}
514+
515+
Size? outputSize = transformC.getCropSize(originalImageSize);
516+
Offset? outputOffset = transformC.getCropStartOffset(originalImageSize);
517+
518+
return CompleteParameters(
519+
blur: appliedBlurFactor,
520+
matrixFilterList: appliedFilters,
521+
matrixTuneAdjustmentsList:
522+
appliedTuneAdjustments.map((item) => item.matrix).toList(),
523+
cropWidth: isTransformed ? outputSize.width.round() : null,
524+
cropHeight: isTransformed ? outputSize.height.round() : null,
525+
cropX: isTransformed ? outputOffset.dx.round() : null,
526+
cropY: isTransformed ? outputOffset.dy.round() : null,
527+
flipX: transformC.is90DegRotated ? transformC.flipY : transformC.flipX,
528+
flipY: transformC.is90DegRotated ? transformC.flipX : transformC.flipY,
529+
rotateTurns: transformC.angleToTurns(),
530+
startTime: null,
531+
endTime: null,
532+
image: imageBytes,
533+
isTransformed: isTransformed,
534+
layers: layers ?? [],
535+
);
536+
}
449537
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: pro_image_editor
22
description: "A Flutter image editor: Seamlessly enhance your images with user-friendly editing features."
3-
version: 11.15.1
3+
version: 11.15.2
44
homepage: https://github.com/hm21/pro_image_editor/
55
repository: https://github.com/hm21/pro_image_editor/
66
documentation: https://github.com/hm21/pro_image_editor/

0 commit comments

Comments
 (0)