diff --git a/components/ImageCropper/samples/ImageCropper.md b/components/ImageCropper/samples/ImageCropper.md index c7d210e4..74030a9b 100644 --- a/components/ImageCropper/samples/ImageCropper.md +++ b/components/ImageCropper/samples/ImageCropper.md @@ -16,7 +16,7 @@ The `ImageCropper Control` allows user to freely crop an image. > [!Sample ImageCropperSample] -## Syntax +### Syntax ```xaml ``` -## Examples - -### Use ImageCropper +### Basic usage You can set the cropped image source by using the `LoadImageFromFile(StorageFile)` method or setting the `Source` property. @@ -66,3 +64,9 @@ Or you can crop image without aspect ratio. ```csharp ImageCropper.AspectRatio = null; ``` + +### With an Overlay + +The crop area can be overlaid with a brush. Here we use a `RadialGradientBrush`, but you can use any brush; backdrop Media brushes, Geometry brushes, bitmap and image brushes, and so on. + +> [!Sample ImageCropperOverlaySample] diff --git a/components/ImageCropper/samples/ImageCropperOverlaySample.xaml b/components/ImageCropper/samples/ImageCropperOverlaySample.xaml new file mode 100644 index 00000000..894d45b6 --- /dev/null +++ b/components/ImageCropper/samples/ImageCropperOverlaySample.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/components/ImageCropper/samples/ImageCropperOverlaySample.xaml.cs b/components/ImageCropper/samples/ImageCropperOverlaySample.xaml.cs new file mode 100644 index 00000000..00f617fe --- /dev/null +++ b/components/ImageCropper/samples/ImageCropperOverlaySample.xaml.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.Storage; + +namespace ImageCropperExperiment.Samples; + +[ToolkitSampleMultiChoiceOption("ThumbPlacementSetting", "All", "Corners", Title = "Thumb Placement")] +[ToolkitSampleMultiChoiceOption("CropShapeSetting", "Circular", "Rectangular", Title = "Crop Shape")] +[ToolkitSampleMultiChoiceOption("AspectRatioSetting", "Custom", "Square", "Landscape(16:9)", "Portrait(9:16)", "4:3", "3:2", Title = "Aspect Ratio")] + +[ToolkitSample(id: nameof(ImageCropperOverlaySample), "ImageCropper Overlay", description: $"A sample for showing how to use the overlay feature of {nameof(ImageCropper)}.")] +public sealed partial class ImageCropperOverlaySample : Page +{ + public ImageCropperOverlaySample() + { + this.InitializeComponent(); + + _ = Load(); + } + + private async Task Load() + { + var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Owl.jpg")); + await ImageCropper.LoadImageFromFile(file); + } +} diff --git a/components/ImageCropper/src/ImageCropper.Constants.cs b/components/ImageCropper/src/ImageCropper.Constants.cs index 9844a29f..4e070915 100644 --- a/components/ImageCropper/src/ImageCropper.Constants.cs +++ b/components/ImageCropper/src/ImageCropper.Constants.cs @@ -29,6 +29,11 @@ public partial class ImageCropper /// private const string MaskAreaPathPartName = "PART_MaskAreaPath"; + /// + /// Key of the overlay layer. + /// + private const string OverlayAreaPathPartName = "PART_OverlayAreaPath"; + /// /// Key of the ImageCropperThumb that on the top. /// diff --git a/components/ImageCropper/src/ImageCropper.Logic.cs b/components/ImageCropper/src/ImageCropper.Logic.cs index 90099266..dc661876 100644 --- a/components/ImageCropper/src/ImageCropper.Logic.cs +++ b/components/ImageCropper/src/ImageCropper.Logic.cs @@ -448,14 +448,20 @@ private void UpdateCropShape() { case CropShape.Rectangular: _innerGeometry = new RectangleGeometry(); + _overlayGeometry = new RectangleGeometry(); break; case CropShape.Circular: _innerGeometry = new EllipseGeometry(); + _overlayGeometry = new EllipseGeometry(); break; } _maskAreaGeometryGroup.Children.Add(_outerGeometry); _maskAreaGeometryGroup.Children.Add(_innerGeometry); + if (_overlayAreaPath != null) + { + _overlayAreaPath.Data = _overlayGeometry; + } } /// @@ -473,7 +479,7 @@ private void UpdateMaskArea(bool animate = false) switch (CropShape) { case CropShape.Rectangular: - if (_innerGeometry is RectangleGeometry rectangleGeometry) + var updateRectangleGeometry = (RectangleGeometry rectangleGeometry) => { var to = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); if (animate) @@ -486,11 +492,19 @@ private void UpdateMaskArea(bool animate = false) { rectangleGeometry.Rect = to; } + }; + if (_innerGeometry is RectangleGeometry innerRectangleGeometry) + { + updateRectangleGeometry(innerRectangleGeometry); + } + if (_overlayGeometry is RectangleGeometry overlayRectangleGeometry) + { + updateRectangleGeometry(overlayRectangleGeometry); } break; case CropShape.Circular: - if (_innerGeometry is EllipseGeometry ellipseGeometry) + var updateEllipseGeometry = (EllipseGeometry ellipseGeometry) => { var center = new Point(((_endX - _startX) / 2) + _startX, ((_endY - _startY) / 2) + _startY); var radiusX = (_endX - _startX) / 2; @@ -509,6 +523,14 @@ private void UpdateMaskArea(bool animate = false) ellipseGeometry.RadiusX = radiusX; ellipseGeometry.RadiusY = radiusY; } + }; + if (_innerGeometry is EllipseGeometry innerEllipseGeometry) + { + updateEllipseGeometry(innerEllipseGeometry); + } + if (_overlayGeometry is EllipseGeometry overlayEllipseGeometry) + { + updateEllipseGeometry(overlayEllipseGeometry); } break; diff --git a/components/ImageCropper/src/ImageCropper.Properties.cs b/components/ImageCropper/src/ImageCropper.Properties.cs index 26cdf0c7..7926a4d0 100644 --- a/components/ImageCropper/src/ImageCropper.Properties.cs +++ b/components/ImageCropper/src/ImageCropper.Properties.cs @@ -124,6 +124,15 @@ public Brush Mask set { SetValue(MaskProperty, value); } } + /// + /// Gets or sets the overlay on the cropped image. + /// + public Brush Overlay + { + get { return (Brush)GetValue(OverlayProperty); } + set { SetValue(OverlayProperty, value); } + } + /// /// Gets or sets a value for the style to use for the primary thumbs of the ImageCropper. /// @@ -175,6 +184,12 @@ public ThumbPlacement ThumbPlacement public static readonly DependencyProperty MaskProperty = DependencyProperty.Register(nameof(Mask), typeof(Brush), typeof(ImageCropper), new PropertyMetadata(default(Brush))); + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty OverlayProperty = + DependencyProperty.Register(nameof(Overlay), typeof(Brush), typeof(ImageCropper), new PropertyMetadata(default(Brush))); + /// /// Identifies the dependency property. /// diff --git a/components/ImageCropper/src/ImageCropper.cs b/components/ImageCropper/src/ImageCropper.cs index c1a4c4a6..a430ebd8 100644 --- a/components/ImageCropper/src/ImageCropper.cs +++ b/components/ImageCropper/src/ImageCropper.cs @@ -21,6 +21,7 @@ namespace CommunityToolkit.WinUI.Controls; [TemplatePart(Name = ImageCanvasPartName, Type = typeof(Canvas))] [TemplatePart(Name = SourceImagePartName, Type = typeof(Image))] [TemplatePart(Name = MaskAreaPathPartName, Type = typeof(Path))] +[TemplatePart(Name = OverlayAreaPathPartName, Type = typeof(Path))] [TemplatePart(Name = TopThumbPartName, Type = typeof(ImageCropperThumb))] [TemplatePart(Name = BottomThumbPartName, Type = typeof(ImageCropperThumb))] [TemplatePart(Name = LeftThumbPartName, Type = typeof(ImageCropperThumb))] @@ -39,6 +40,7 @@ public partial class ImageCropper : Control private Canvas? _imageCanvas; private Image? _sourceImage; private Path? _maskAreaPath; + private Path? _overlayAreaPath; private ImageCropperThumb? _topThumb; private ImageCropperThumb? _bottomThumb; private ImageCropperThumb? _leftThumb; @@ -59,6 +61,7 @@ public partial class ImageCropper : Control private Rect _restrictedSelectRect = Rect.Empty; private RectangleGeometry _outerGeometry; private Geometry _innerGeometry; + private Geometry _overlayGeometry; private TimeSpan _animationDuration = TimeSpan.FromSeconds(0.3); /// @@ -165,6 +168,7 @@ protected override void OnApplyTemplate() _imageCanvas = GetTemplateChild(ImageCanvasPartName) as Canvas; _sourceImage = GetTemplateChild(SourceImagePartName) as Image; _maskAreaPath = GetTemplateChild(MaskAreaPathPartName) as Path; + _overlayAreaPath = GetTemplateChild(OverlayAreaPathPartName) as Path; _topThumb = GetTemplateChild(TopThumbPartName) as ImageCropperThumb; _bottomThumb = GetTemplateChild(BottomThumbPartName) as ImageCropperThumb; _leftThumb = GetTemplateChild(LeftThumbPartName) as ImageCropperThumb; @@ -195,6 +199,11 @@ private void HookUpEvents() _maskAreaPath.Data = _maskAreaGeometryGroup; } + if (_overlayAreaPath != null) + { + _overlayAreaPath.Data = _overlayGeometry; + } + if (_topThumb != null) { _topThumb.Position = ThumbPosition.Top; diff --git a/components/ImageCropper/src/ImageCropper.xaml b/components/ImageCropper/src/ImageCropper.xaml index d678c75a..877bbc82 100644 --- a/components/ImageCropper/src/ImageCropper.xaml +++ b/components/ImageCropper/src/ImageCropper.xaml @@ -31,6 +31,9 @@ Source="{TemplateBinding Source}" /> + diff --git a/tooling b/tooling index 229d8fa7..a3ec76e7 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 229d8fa7b5e908fc64533f23c8e431031935b96f +Subproject commit a3ec76e7d3d04ff6c9e200d574d241db52a3d883