From 928513ee0c5bbd566fbfbc73128cc8010b1fe914 Mon Sep 17 00:00:00 2001 From: hm21 Date: Thu, 5 Mar 2026 08:58:02 +0100 Subject: [PATCH 1/3] feat(crop-rotate-editor): add helperLineWidth to customize grid line thickness --- CHANGELOG.md | 3 +++ .../styles/crop_rotate_editor_style.dart | 9 +++++++ .../widgets/crop_corner_painter.dart | 25 ++++++++----------- pubspec.yaml | 2 +- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ad7c6b..aa2ac389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 12.0.3 +- **FEAT**(crop-rotate-editor): Add `helperLineWidth` to `CropRotateEditorStyle`, allowing the grid line thickness to be customized or hidden entirely by setting it to `0`. + ## 12.0.2 - **FIX**(main-editor): Resolve crash when `setState` is called after widget disposal, preventing "Cannot add new events after calling close" error. diff --git a/lib/core/models/styles/crop_rotate_editor_style.dart b/lib/core/models/styles/crop_rotate_editor_style.dart index eb081aa6..d2338f7f 100644 --- a/lib/core/models/styles/crop_rotate_editor_style.dart +++ b/lib/core/models/styles/crop_rotate_editor_style.dart @@ -56,6 +56,7 @@ class CropRotateEditorStyle { this.appBarBackground = kImageEditorAppBarBackground, this.appBarColor = kImageEditorAppBarColor, this.helperLineColor = const Color(0xFF000000), + this.helperLineWidth = 0.5, this.background = kImageEditorBackground, this.cropCornerColor = kImageEditorPrimaryColor, this.cropOverlayColor = const Color(0xFF000000), @@ -109,6 +110,12 @@ class CropRotateEditorStyle { /// Color from the helper lines when moving the image. final Color helperLineColor; + /// The width (thickness) of the helper lines drawn inside the crop area. + /// + /// Set to `0` to hide the helper lines entirely. + /// Defaults to `0.5`. + final double helperLineWidth; + /// This refers to the overlay area atop the image when the cropping area is /// smaller than the image. /// @@ -158,6 +165,7 @@ class CropRotateEditorStyle { Color? background, Color? cropCornerColor, Color? helperLineColor, + double? helperLineWidth, Color? cropOverlayColor, double? cropCornerLength, double? cropCornerThickness, @@ -179,6 +187,7 @@ class CropRotateEditorStyle { background: background ?? this.background, cropCornerColor: cropCornerColor ?? this.cropCornerColor, helperLineColor: helperLineColor ?? this.helperLineColor, + helperLineWidth: helperLineWidth ?? this.helperLineWidth, cropOverlayColor: cropOverlayColor ?? this.cropOverlayColor, cropCornerLength: cropCornerLength ?? this.cropCornerLength, cropCornerThickness: cropCornerThickness ?? this.cropCornerThickness, diff --git a/lib/features/crop_rotate_editor/widgets/crop_corner_painter.dart b/lib/features/crop_rotate_editor/widgets/crop_corner_painter.dart index 575cd0a0..335cd398 100644 --- a/lib/features/crop_rotate_editor/widgets/crop_corner_painter.dart +++ b/lib/features/crop_rotate_editor/widgets/crop_corner_painter.dart @@ -98,12 +98,6 @@ class CropCornerPainter extends CustomPainter { /// such as highlighting or accentuating elements during user actions. final double interactionOpacity; - /// The width of the helper lines. - /// - /// This double value specifies the thickness of auxiliary lines drawn to - /// assist with cropping, providing additional visual guides. - double helperLineWidth = 0.5; - /// The scale factor for resizing elements. /// /// This double value determines how much the elements are scaled, allowing @@ -347,6 +341,9 @@ class CropCornerPainter extends CustomPainter { } void _drawHelperAreas({required Canvas canvas, required Size size}) { + final lineWidth = style.helperLineWidth; + if (lineWidth <= 0) return; + Path path = Path(); double cropWidth = _cropOffsetRight - _cropOffsetLeft; @@ -356,15 +353,15 @@ class CropCornerPainter extends CustomPainter { double cropAreaSpaceH = cropHeight / 3; /// Calculation is important for the round-cropper - double lineWidth = !drawCircle + double drawWidth = !drawCircle ? cropWidth : sqrt(pow(cropWidth, 2) - pow(cropAreaSpaceW, 2)); - double lineHeight = !drawCircle + double drawHeight = !drawCircle ? cropHeight : sqrt(pow(cropHeight, 2) - pow(cropAreaSpaceH, 2)); - double gapW = (cropWidth - lineWidth) / 2; - double gapH = (cropHeight - lineHeight) / 2; + double gapW = (cropWidth - drawWidth) / 2; + double gapH = (cropHeight - drawHeight) / 2; for (var i = 1; i < 3; i++) { path @@ -372,23 +369,23 @@ class CropCornerPainter extends CustomPainter { Rect.fromLTWH( cropAreaSpaceW * i + _cropOffsetLeft, gapH + _cropOffsetTop, - helperLineWidth, - lineHeight, + lineWidth, + drawHeight, ), ) ..addRect( Rect.fromLTWH( gapW + _cropOffsetLeft, cropAreaSpaceH * i + _cropOffsetTop, + drawWidth, lineWidth, - helperLineWidth, ), ); } final cornerPaint = Paint() ..color = style.helperLineColor.withValues( - alpha: fadeInOpacity * interactionOpacity, + alpha: style.helperLineColor.a * fadeInOpacity * interactionOpacity, ) ..style = PaintingStyle.fill; canvas.drawPath(path, cornerPaint); diff --git a/pubspec.yaml b/pubspec.yaml index c8f0689e..30d79f46 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pro_image_editor description: "A Flutter image editor: Seamlessly enhance your images with user-friendly editing features." -version: 12.0.2 +version: 12.0.3 homepage: https://github.com/hm21/pro_image_editor/ repository: https://github.com/hm21/pro_image_editor/ documentation: https://github.com/hm21/pro_image_editor/ From 962eedddf8bd5535ae434b0382fdc9c95366f998 Mon Sep 17 00:00:00 2001 From: hm21 Date: Thu, 5 Mar 2026 09:09:48 +0100 Subject: [PATCH 2/3] feat(crop-rotate-editor): add exportOvalMask to control oval cropping in exports --- CHANGELOG.md | 1 + .../editor_configs/crop_rotate_editor_configs.dart | 11 +++++++++++ .../transform/transformed_content_generator.dart | 10 ++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2ac389..bcacd097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## 12.0.3 +- **FEAT**(crop-rotate-editor): Add `exportOvalMask` to `CropRotateEditorConfigs` (default `true`). When set to `false`, the exported image uses a plain rectangular crop even if `CropMode.oval` is active, while the oval UI remains visible inside the crop editor. - **FEAT**(crop-rotate-editor): Add `helperLineWidth` to `CropRotateEditorStyle`, allowing the grid line thickness to be customized or hidden entirely by setting it to `0`. ## 12.0.2 diff --git a/lib/core/models/editor_configs/crop_rotate_editor_configs.dart b/lib/core/models/editor_configs/crop_rotate_editor_configs.dart index 908884d9..b7e940f0 100644 --- a/lib/core/models/editor_configs/crop_rotate_editor_configs.dart +++ b/lib/core/models/editor_configs/crop_rotate_editor_configs.dart @@ -51,6 +51,7 @@ class CropRotateEditorConfigs implements BaseSubEditorConfigs { this.invertMouseScroll = false, this.invertDragDirection = false, this.initialCropMode = CropMode.rectangular, + this.exportOvalMask = true, this.enableTransformLayers = true, this.enableProvideImageInfos = false, this.enableDoubleTap = true, @@ -132,6 +133,14 @@ class CropRotateEditorConfigs implements BaseSubEditorConfigs { /// presented to the user before any manual adjustments are made. final CropMode initialCropMode; + /// Controls whether the oval mask is applied to the exported image when + /// [initialCropMode] is set to [CropMode.oval]. + /// + /// When `true` (default), the exported image is clipped to an oval/circle + /// shape. When `false`, the raw rectangular crop is exported without any + /// oval masking, while the oval UI is still shown inside the crop editor. + final bool exportOvalMask; + /// Defines which crop-rotate tools are available in the editor. /// /// The order of the tools in this list determines the order in the UI. @@ -259,6 +268,7 @@ class CropRotateEditorConfigs implements BaseSubEditorConfigs { bool? invertMouseScroll, bool? invertDragDirection, CropMode? initialCropMode, + bool? exportOvalMask, List? tools, bool? enableProvideImageInfos, double? initAspectRatio, @@ -294,6 +304,7 @@ class CropRotateEditorConfigs implements BaseSubEditorConfigs { invertMouseScroll: invertMouseScroll ?? this.invertMouseScroll, invertDragDirection: invertDragDirection ?? this.invertDragDirection, initialCropMode: initialCropMode ?? this.initialCropMode, + exportOvalMask: exportOvalMask ?? this.exportOvalMask, tools: tools ?? this.tools, enableProvideImageInfos: enableProvideImageInfos ?? this.enableProvideImageInfos, diff --git a/lib/shared/widgets/transform/transformed_content_generator.dart b/lib/shared/widgets/transform/transformed_content_generator.dart index 22477804..ff6630bd 100644 --- a/lib/shared/widgets/transform/transformed_content_generator.dart +++ b/lib/shared/widgets/transform/transformed_content_generator.dart @@ -143,12 +143,18 @@ class TransformedContentGenerator extends StatelessWidget { CropMode cropMode = _transformConfigs.cropMode; + final effectiveCropMode = + cropMode == CropMode.oval && + !configs.cropRotateEditor.exportOvalMask + ? CropMode.rectangular + : cropMode; + final clipper = CutOutsideArea( configs: _transformConfigs, - cropMode: cropMode, + cropMode: effectiveCropMode, ); - if (cropMode == CropMode.oval) { + if (effectiveCropMode == CropMode.oval) { return ClipOval(clipper: clipper, child: child); } else { return ClipRect(clipper: clipper, child: child); From 433bb6cc89a87fcd102b1bccf1c08aabd41b2b94 Mon Sep 17 00:00:00 2001 From: hm21 Date: Thu, 5 Mar 2026 09:10:45 +0100 Subject: [PATCH 3/3] refactor(transformed-content-generator): simplify effectiveCropMode assignment --- .../widgets/transform/transformed_content_generator.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/shared/widgets/transform/transformed_content_generator.dart b/lib/shared/widgets/transform/transformed_content_generator.dart index ff6630bd..c3046e11 100644 --- a/lib/shared/widgets/transform/transformed_content_generator.dart +++ b/lib/shared/widgets/transform/transformed_content_generator.dart @@ -144,10 +144,9 @@ class TransformedContentGenerator extends StatelessWidget { CropMode cropMode = _transformConfigs.cropMode; final effectiveCropMode = - cropMode == CropMode.oval && - !configs.cropRotateEditor.exportOvalMask - ? CropMode.rectangular - : cropMode; + cropMode == CropMode.oval && !configs.cropRotateEditor.exportOvalMask + ? CropMode.rectangular + : cropMode; final clipper = CutOutsideArea( configs: _transformConfigs,