Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port ImageCropper #97

Merged
merged 17 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Sample updates
  • Loading branch information
niels9001 committed Jun 19, 2023
commit 2a27a3fd02c7f3a9530cd6a3c7e54a44c3b00169
95 changes: 81 additions & 14 deletions components/ImageCropper/samples/ImageCropper.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: ImageCropper
author: githubaccount
description: TODO: Your experiment's description here
author: HHChaos
description: Control to crop rectangular and circular images.
keywords: ImageCropper, Control, Layout
dev_langs:
- csharp
Expand All @@ -12,21 +12,88 @@ issue-id: 0
icon: assets/icon.png
---

<!-- To know about all the available Markdown syntax, Check out https://docs.microsoft.com/contribute/markdown-reference -->
<!-- Ensure you remove all comments before submission, to ensure that there are no formatting issues when displaying this page. -->
<!-- It is recommended to check how the Documentation will look in the sample app, before Merging a PR -->
<!-- **Note:** All links to other docs.microsoft.com pages should be relative without locale, i.e. for the one above would be /contribute/markdown-reference -->
<!-- Included images should be optimized for size and not include any Intellectual Property references. -->
# ImageCropper

<!-- Be sure to update the discussion/issue numbers above with your Labs discussion/issue id numbers in order for UI links to them from the sample app to work. -->
The [ImageCropper Control](/dotnet/api/microsoft.toolkit.uwp.ui.controls.imagecropper) allows user to freely crop an image.

# ImageCropper
> [!Sample ImageCropperSample]

## Syntax

```xaml
<Page ...
xmlns:controls="using:CommunityToolkit.WinUI.Controls"/>
<controls:ImageCropper x:Name="ImageCropper" />
</Page>
```



## Properties
Arlodotexe marked this conversation as resolved.
Show resolved Hide resolved

| Property | Type | Description |
| -- | -- | -- |
| MinCroppedPixelLength | double | Gets or sets the minimum cropped length(in pixel). |
| MinSelectedLength | double | Gets or sets the minimum selectable length. |
| CroppedRegion | Rect | Gets the current cropped region. |
| Source | WriteableBitmap | Gets or sets the source of the cropped image. |
| AspectRatio | double? | Gets or sets the aspect ratio of the cropped image, the default value is null. |
| CropShape | CropShape | Gets or sets the shape to use when cropping. |
| Mask | Brush | Gets or sets the mask on the cropped image. |
| PrimaryThumbStyle | Style | Gets or sets a value for the style to use for the primary thumbs of the ImageCropper. |
| SecondaryThumbStyle | Style | Gets or sets a value for the style to use for the secondary thumbs of the ImageCropper. |
| ThumbPlacement | ThumbPlacement | Gets or sets a value for thumb placement. |

## Methods

| Methods | Return Type | Description |
| -- | -- | -- |
| LoadImageFromFile(StorageFile) | Task | Load an image from a file. |
| SaveAsync(IRandomAccessStream,BitmapFileFormat,bool) | Task | Saves the cropped image to a stream with the specified format. Setting the boolean argument to True will save pixel values to the extent of the cropped area regardless of the crop shape, otherwise transparent or black pixels will fill the uncropped area depending on file format. |
| Reset() | void | Reset the cropped area. |
| TrySetCroppedRegion(Rect rect) | bool | Tries to set a new value for the cropped region, returns true if it succeeded, false if the region is invalid |

## Examples

### Use ImageCropper

You can set the cropped image source by using the `LoadImageFromFile(StorageFile)` method or setting the `Source` property.

```csharp
//Load an image.
await ImageCropper.LoadImageFromFile(file);

//Another way to load an image.
ImageCropper.Source = writeableBitmap;

//Saves the cropped image to a stream.
using (var fileStream = await someFile.OpenAsync(FileAccessMode.ReadWrite, StorageOpenOptions.None))
{
await _imageCropper.SaveAsync(fileStream, BitmapFileFormat.Png);
}
```


### Use Circular ImageCropper

You can set `CropShape` property to use the circular ImageCropper.

```csharp
ImageCropper.CropShape = CropShape.Circular;
```


### Change Aspect Ratio

You can set `AspectRatio` property to change the aspect ratio of the cropped image.

TODO: Fill in information about this experiment and how to get started here...
```csharp
ImageCropper.AspectRatio = 16d / 9d;
```

## Custom Control

You can inherit from an existing component as well, like `Panel`, this example shows a control without a
XAML Style that will be more light-weight to consume by an app developer:
Or you can crop image without aspect ratio.

> [!Sample ImageCropperCustomSample]
```csharp
ImageCropper.AspectRatio = null;
```
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- 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. -->
<Page x:Class="ImageCropperExperiment.Samples.ImageCropperCustomSample"
<Page x:Class="ImageCropperExperiment.Samples.ImageCropperSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ namespace ImageCropperExperiment.Samples;
[ToolkitSampleMultiChoiceOption("CropShapeSetting", "Rectangular", "Circular", Title = "Crop Shape")]
[ToolkitSampleMultiChoiceOption("AspectRatioSetting", "Custom", "Square", "Landscape(16:9)", "Portrait(9:16)", "4:3", "3:2", Title = "Aspect Ratio")]

[ToolkitSample(id: nameof(ImageCropperCustomSample), "ImageCropper", description: $"A sample for showing how to create and use a {nameof(ImageCropper)}.")]
public sealed partial class ImageCropperCustomSample : Page
[ToolkitSample(id: nameof(ImageCropperSample), "ImageCropper", description: $"A sample for showing how to create and use a {nameof(ImageCropper)}.")]
public sealed partial class ImageCropperSample : Page
{
public ImageCropperCustomSample()
public ImageCropperSample()
{
this.InitializeComponent();
Load();
Expand Down
13 changes: 9 additions & 4 deletions components/ImageCropper/src/ImageCropper.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundAltHighBrush}" />
<Setter Property="Mask" Value="{ThemeResource SystemControlBackgroundAltMediumHighBrush}" />
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="Mask" Value="{ThemeResource SmokeFillColorDefaultBrush}" />
<Setter Property="Padding" Value="80" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ImageCropper">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid x:Name="PART_LayoutGrid"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
VerticalAlignment="{TemplateBinding VerticalAlignment}"
CornerRadius="{TemplateBinding CornerRadius}">
<Canvas x:Name="PART_ImageCanvas">
<Image x:Name="PART_SourceImage"
Source="{TemplateBinding Source}" />
Expand Down
101 changes: 77 additions & 24 deletions components/ImageCropper/src/ImageCropperThumb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,30 @@ namespace CommunityToolkit.WinUI.Controls;
/// </summary>
public class ImageCropperThumb : Control
{
private readonly TranslateTransform _layoutTransform = new TranslateTransform();
private readonly TranslateTransform _layoutTransform = new();
internal const string NormalState = "Normal";
internal const string PointerOverState = "PointerOver";
internal const string PressedState = "Pressed";
internal const string DisabledState = "Disabled";
internal ThumbPosition Position { get; set; }

/// <summary>
/// Gets or sets the X coordinate of the ImageCropperThumb.
/// </summary>
public double X
{
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}

/// <summary>
/// Gets or sets the Y coordinate of the ImageCropperThumb.
/// </summary>
public double Y
{
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}

/// <summary>
/// Initializes a new instance of the <see cref="ImageCropperThumb"/> class.
Expand All @@ -20,15 +43,27 @@ public ImageCropperThumb()
RenderTransform = _layoutTransform;
ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;
SizeChanged += ImageCropperThumb_SizeChanged;

}

protected override void OnApplyTemplate()
{
PointerEntered -= Control_PointerEntered;
PointerExited -= Control_PointerExited;
PointerCaptureLost -= Control_PointerCaptureLost;
PointerCanceled -= Control_PointerCanceled;

PointerEntered += Control_PointerEntered;
PointerExited += Control_PointerExited;
PointerCaptureLost += Control_PointerCaptureLost;
PointerCanceled += Control_PointerCanceled;
}

private void ImageCropperThumb_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdatePosition();
}

internal ThumbPosition Position { get; set; }

private void UpdatePosition()
{
if (_layoutTransform != null)
Expand All @@ -38,33 +73,15 @@ private void UpdatePosition()
}
}

/// <summary>
/// Gets or sets the X coordinate of the ImageCropperThumb.
/// </summary>
public double X
{
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}

private static void OnXChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
private static void OnXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (ImageCropperThumb)d;
target.UpdatePosition();
}

/// <summary>
/// Gets or sets the Y coordinate of the ImageCropperThumb.
/// </summary>
public double Y
{
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}

private static void OnYChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)

private static void OnYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (ImageCropperThumb)d;
target.UpdatePosition();
Expand All @@ -81,4 +98,40 @@ private static void OnYChanged(
/// </summary>
public static readonly DependencyProperty YProperty =
DependencyProperty.Register(nameof(Y), typeof(double), typeof(ImageCropperThumb), new PropertyMetadata(0d, OnYChanged));

public void Control_PointerEntered(object sender, PointerRoutedEventArgs e)
{
base.OnPointerEntered(e);
VisualStateManager.GoToState(this, PointerOverState, true);
}

public void Control_PointerExited(object sender, PointerRoutedEventArgs e)
{
base.OnPointerExited(e);
VisualStateManager.GoToState(this, NormalState, true);
}

private void Control_PointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
base.OnPointerCaptureLost(e);
VisualStateManager.GoToState(this, NormalState, true);
}

private void Control_PointerCanceled(object sender, PointerRoutedEventArgs e)
{
base.OnPointerCanceled(e);
VisualStateManager.GoToState(this, NormalState, true);
}

protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
base.OnPointerPressed(e);
VisualStateManager.GoToState(this, PressedState, true);
}

protected override void OnPointerReleased(PointerRoutedEventArgs e)
{
base.OnPointerReleased(e);
VisualStateManager.GoToState(this, NormalState, true);
}
}
53 changes: 45 additions & 8 deletions components/ImageCropper/src/ImageCropperThumb.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,55 @@
<Style TargetType="controls:ImageCropperThumb">
<Setter Property="IsTabStop" Value="True" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeWhiteBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundChromeHighBrush}" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="Background" Value="{ThemeResource ControlOnImageFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource ControlStrokeColorForStrongFillWhenOnImageBrush}" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ImageCropperThumb">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10" />
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Thumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource ControlOnImageFillColorSecondaryBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Thumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource ControlOnImageFillColorTertiaryBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Thumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource ControlOnImageFillColorDisabledBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="PART_Thumb"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" />
</Grid>

</ControlTemplate>
</Setter.Value>
</Setter>
Expand Down
Loading
Loading