Custom image cropping in Windows Phone 7 - Part 1 of 2
Join the DZone community and get the full member experience.
Join For FreeIf you've tried using the default PhotoChooserTask to crop some images, you might've noticed already that it doesn't really work as expected - the application never receives the cropped stream. My solution to this? My own implementation of the cropping mechanism. In these two articles I am not going to develop a custom control, but rather show how the mechanism can be implemented in the context of an application page.
First of all, I created the basic page layout:
<phone:PhoneApplicationPage
x:Class="WP7Sandbox.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Image Height="309" Margin="0" Name="image1" Stretch="Fill" Width="320" />
</Grid>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.download.rest.png" Text="Resize" IsEnabled="False"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.upload.rest.png" Text="Move" IsEnabled="True"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.check.rest.png" Text="Accept" />
<shell:ApplicationBarIconButton IconUri="/Images/appbar.close.rest.png" Text="Cancel" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>
The most important part here is the Image control - it will basically define the cropping area. The application bar is here to switch between modes (e.g. between changing the size of the croping area and moving the cropping region). Note, that the icons I am using for the application bar icon buttons are the ones bundled with the SDK. If you want to add your own icons, you can do this by simply replacing the URL (make sure you are using the correct folder).
Notice that by default the Move button is disabled by default. This is because when the application launches (or when the user navigates to the page), the default mode is set to Resize - consider it a future toggle that will allow user to switch between these two modes.
So if you look at the page when the application runs, you can see that the complete UI is pretty modest, but is more than enough to perform the cropping:
The image that got there is in fact an actual image from the default Windows Phone 7 gallery. It is available in the emulator via the PhotoChooserTask. In fact, I launch this task just when the page loads:
// Constructor
public MainPage()
{
InitializeComponent();
PhotoChooserTask task = new PhotoChooserTask();
task.Show();
task.Completed += new EventHandler<PhotoResult>(task_Completed);
}
The task completion event is wired to task_Completed:
void task_Completed(object sender, PhotoResult e)
{
BitmapImage image = new BitmapImage();
image.SetSource(e.ChosenPhoto);
image1.Source = image;
SetPicture();
}
From this event handler, it becomes obvious that the returned image is in fact passed as a stream. One method that you probably know nothing about yet it SetPicture, and this is exactly where I am defining the cropping area.
void SetPicture()
{
Rectangle rect = new Rectangle();
rect.Opacity = .5;
rect.Fill = new SolidColorBrush(Colors.White);
rect.Height = image1.Height;
rect.MaxHeight = image1.Height;
rect.MaxWidth = image1.Width;
rect.Width = image1.Width;
rect.Stroke = new SolidColorBrush(Colors.Red);
rect.StrokeThickness = 2;
rect.Margin = image1.Margin;
rect.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(rect_ManipulationDelta);
LayoutRoot.Children.Add(rect);
}
The defined rectangle is semi-transparent and is placed exactly over the image that was selected.
Now you can see that I am wiring the ManipulationDelta event handler - this is the event handler that keeps track of the object state change, specifically its size and placement in the container. When the user touches the rectangle, you need to give him an option to resize it. So that's exactly what's done here:
void rect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
Rectangle r = (Rectangle)sender;
r.Width -= e.DeltaManipulation.Translation.X;
r.Height -= e.DeltaManipulation.Translation.Y;
}
The e.DeltaManipulation.Translation.X and e.DeltaManipulation.Translation.Y change depending on the speed the manipulation is performed and can be positive (if minimization is performed) and negative (if maximization is performed), therefore it makes sense to subtract the translation on a specific axis from the width or height of the rectangle.
If you run the application now, you can see that you can adjust the rectangle size when dragging the mouse:
Now it's time to actually implement method switching. In order to do this, first of all you need to modify the application bar to have the Resize button and the Move button wired to an event handler that will be triggered once those are clicked.
So here is what I came up with:
<shell:ApplicationBarIconButton IconUri="/Images/appbar.download.rest.png" Text="Resize" Click="btn_Click"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.upload.rest.png" Text="Move" Click="btn_Click" IsEnabled="False"/>
Notice that both buttons are wired to the same event handler - eventually, the state of the current button is determined by a common boolean variable. Notice that the buttons are not named - due to the nature of ApplicationBar content, you won't achieve a lot by calling the buttons in code-behind by their name - an index-based approach is needed.
So here is what I have for the event handler:
private void btn_Click(object sender, EventArgs e)
{
IApplicationBarIconButton button;
if (isMove)
{
button = (IApplicationBarIconButton)ApplicationBar.Buttons[1];
button.IsEnabled = false;
button = (IApplicationBarIconButton)ApplicationBar.Buttons[0];
button.IsEnabled = true;
isMove = false;
}
else
{
button = (IApplicationBarIconButton)ApplicationBar.Buttons[1];
button.IsEnabled = true;
button = (IApplicationBarIconButton)ApplicationBar.Buttons[0];
button.IsEnabled = false;
isMove = true;
}
}
Notice that I am getting buttons according to ther position in the application bar. Don't forget that counting starts with index zero (0). When the Resize button is pressed, the Move is enabled and Resize is disabled. The same happens the other way around - like a toggle switch.
Now you are ready to actually move the cropping rectangle and perform the cropping. In the next article (that will also be the last for this set) I will show how to do that.
Opinions expressed by DZone contributors are their own.
Comments