Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 11.15.2
- **FEAT**(crop-rotate-editor): Add new callback `onTransformUpdateEnd` that returns all transformation changes whenever a value in the crop-rotate editor is modified.

## 11.15.1
- **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).

Expand Down
16 changes: 16 additions & 0 deletions lib/core/models/editor_callbacks/crop_rotate_editor_callbacks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import 'package:flutter/widgets.dart';

// Project imports:
import '../complete_parameters.dart';
import 'editor_callbacks_typedef.dart';
import 'standalone_editor_callbacks.dart';

Expand All @@ -18,6 +19,7 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
this.onDoubleTap,
this.onResize,
this.onReset,
this.onTransformUpdateEnd,
super.onInit,
super.onAfterViewInit,
super.onUndo,
Expand Down Expand Up @@ -64,6 +66,18 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
/// A callback function that is triggered when a reset action is performed.
final Function()? onReset;

/// Callback that is triggered when a transformation update ends.
///
/// This callback is invoked when the user completes a gesture that modifies
/// the crop or rotation transformation (e.g., releasing a pinch gesture or
/// finishing a rotation gesture).
///
/// The [parameters] contain information about the completed transformation,
/// including the final state of the crop and rotation values.
///
/// **IMPORTANT:** The `imageBytes` will always be empty.
final Function(CompleteParameters parameters)? onTransformUpdateEnd;

/// Handles the rotate start event.
///
/// This method calls the [onRotateStart] callback with the provided [value]
Expand Down Expand Up @@ -159,6 +173,7 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
Function()? onRedo,
Function()? onUndo,
Function()? onCloseEditor,
Function(CompleteParameters parameters)? onTransformUpdateEnd,
}) {
return CropRotateEditorCallbacks(
onRotateStart: onRotateStart ?? this.onRotateStart,
Expand All @@ -177,6 +192,7 @@ class CropRotateEditorCallbacks extends StandaloneEditorCallbacks {
onRedo: onRedo ?? this.onRedo,
onUndo: onUndo ?? this.onUndo,
onCloseEditor: onCloseEditor ?? this.onCloseEditor,
onTransformUpdateEnd: onTransformUpdateEnd ?? this.onTransformUpdateEnd,
);
}
}
43 changes: 3 additions & 40 deletions lib/features/crop_rotate_editor/crop_rotate_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -833,46 +833,9 @@ class CropRotateEditorState extends State<CropRotateEditor>

/// Return complete parameters if requested
if (initConfigs.callbacks.onCompleteWithParameters != null) {
final isTransformed = transformC.isNotEmpty;

Size originalImageSize;
if (isVideoEditor) {
originalImageSize = videoController!.initialResolution;
} else {
var rawOriginalSize =
await widget.editorImage?.safeByteArray(context) ?? imageBytes;
var decodedImage = await decodeImageFromList(rawOriginalSize);
originalImageSize = Size(
decodedImage.width.toDouble(),
decodedImage.height.toDouble(),
);
}

Size? outputSize = transformC.getCropSize(originalImageSize);
Offset? outputOffset = transformC.getCropStartOffset(originalImageSize);

await callbacks.onCompleteWithParameters?.call(
CompleteParameters(
blur: appliedBlurFactor,
matrixFilterList: appliedFilters,
matrixTuneAdjustmentsList:
appliedTuneAdjustments.map((item) => item.matrix).toList(),
cropWidth: isTransformed ? outputSize.width.round() : null,
cropHeight: isTransformed ? outputSize.height.round() : null,
cropX: isTransformed ? outputOffset.dx.round() : null,
cropY: isTransformed ? outputOffset.dy.round() : null,
flipX:
transformC.is90DegRotated ? transformC.flipY : transformC.flipX,
flipY:
transformC.is90DegRotated ? transformC.flipX : transformC.flipY,
rotateTurns: transformC.angleToTurns(),
startTime: null,
endTime: null,
image: imageBytes,
isTransformed: isTransformed,
layers: layers ?? [],
),
);
final completeParams =
await getCompleteParameters(imageBytes: imageBytes);
await callbacks.onCompleteWithParameters?.call(completeParams);
}

LoadingDialog.instance.hide();
Expand Down
88 changes: 88 additions & 0 deletions lib/features/crop_rotate_editor/mixins/crop_area_history.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Dart imports:
import 'dart:math';
import 'dart:typed_data';

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

// Project imports:
import '/core/mixins/standalone_editor.dart';
import '/core/models/complete_parameters.dart';
import '/core/models/init_configs/crop_rotate_editor_init_configs.dart';
import '/shared/widgets/extended/extended_custom_paint.dart';
import '/shared/widgets/extended/extended_transform_scale.dart';
Expand Down Expand Up @@ -298,10 +300,21 @@ mixin CropAreaHistory
),
);
screenshotHistoryPosition++;
_handleTransformationUpdateEnd();
setState(() {});
takeScreenshot();
}

void _handleTransformationUpdateEnd() async {
final callback = cropRotateEditorCallbacks?.onTransformUpdateEnd;
if (callback == null) return;

final completeParams =
await getCompleteParameters(imageBytes: Uint8List(0));

callback(completeParams);
}

/// Clears forward changes from the history.
void cleanForwardChanges() {
if (history.length > 1) {
Expand All @@ -325,6 +338,7 @@ mixin CropAreaHistory
_setParametersFromHistory();
}
cropRotateEditorCallbacks?.handleUndo();
_handleTransformationUpdateEnd();
setState(() {});
}
}
Expand All @@ -335,6 +349,7 @@ mixin CropAreaHistory
screenshotHistoryPosition++;
_setParametersFromHistory();
cropRotateEditorCallbacks?.handleRedo();
_handleTransformationUpdateEnd();
setState(() {});
}
}
Expand Down Expand Up @@ -446,4 +461,77 @@ mixin CropAreaHistory
/// overridden to implement specific fitting logic.
@protected
void calcFitToScreen() {}

/// Generates complete parameters for the image transformation process.
///
/// This method calculates all the transformation parameters needed to export
/// the edited image, including crop dimensions, rotation, flip operations,
/// filters, and blur effects.
///
/// The method handles both image and video editing modes:
/// - For video editing: uses the video controller's initial resolution
/// - For image editing: decodes the original image to get its dimensions
///
/// Parameters:
/// * [imageBytes] - The original image data as bytes
///
/// Returns a [CompleteParameters] object containing:
/// * Crop dimensions (width, height) and offset (x, y) if transformed
/// * Flip operations in x and y directions (adjusted for 90° rotations)
/// * Rotation in turns
/// * Applied blur factor
/// * List of matrix filters
/// * List of tune adjustment matrices
/// * Layer data
/// * Original image bytes
/// * Transformation status flag
///
/// The crop dimensions and offsets are only included when [isTransformed]
/// is true. Flip operations are automatically adjusted when the image
/// is rotated by 90 degrees to maintain correct orientation.
Future<CompleteParameters> getCompleteParameters({
required Uint8List imageBytes,
}) async {
TransformConfigs transformC =
!canRedo && !canUndo && initialTransformConfigs != null
? initialTransformConfigs!
: activeHistory;

final isTransformed = transformC.isNotEmpty;

Size originalImageSize;
if (isVideoEditor) {
originalImageSize = videoController!.initialResolution;
} else {
var rawOriginalSize =
await widget.editorImage?.safeByteArray(context) ?? imageBytes;
var decodedImage = await decodeImageFromList(rawOriginalSize);
originalImageSize = Size(
decodedImage.width.toDouble(),
decodedImage.height.toDouble(),
);
}

Size? outputSize = transformC.getCropSize(originalImageSize);
Offset? outputOffset = transformC.getCropStartOffset(originalImageSize);

return CompleteParameters(
blur: appliedBlurFactor,
matrixFilterList: appliedFilters,
matrixTuneAdjustmentsList:
appliedTuneAdjustments.map((item) => item.matrix).toList(),
cropWidth: isTransformed ? outputSize.width.round() : null,
cropHeight: isTransformed ? outputSize.height.round() : null,
cropX: isTransformed ? outputOffset.dx.round() : null,
cropY: isTransformed ? outputOffset.dy.round() : null,
flipX: transformC.is90DegRotated ? transformC.flipY : transformC.flipX,
flipY: transformC.is90DegRotated ? transformC.flipX : transformC.flipY,
rotateTurns: transformC.angleToTurns(),
startTime: null,
endTime: null,
image: imageBytes,
isTransformed: isTransformed,
layers: layers ?? [],
);
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: pro_image_editor
description: "A Flutter image editor: Seamlessly enhance your images with user-friendly editing features."
version: 11.15.1
version: 11.15.2
homepage: https://github.com/hm21/pro_image_editor/
repository: https://github.com/hm21/pro_image_editor/
documentation: https://github.com/hm21/pro_image_editor/
Expand Down