DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

Custom image cropping in Windows Phone 7 - Part 2 of 2

Denzel D. user avatar by
Denzel D.
·
Nov. 14, 10 · Interview
Like (0)
Save
Tweet
Share
17.30K Views

Join the DZone community and get the full member experience.

Join For Free

In my previous article I set the ground for a custom cropping mechanism. To be specific, I set the main UI layout and showed how it is possible to open an image to be cropped. But that's not it yet - now you actually have to perform the cropping. And this process comes with specific interesting aspects that should be considered.

For now, we have the ManipulationDelta handler that allows us to resize the cropping area. It is time to make it move now. For this purpose, I introduced a Boolean variable in the main class that will show whether the cropping box is in move mode or not.

bool isMove = false;

Now, when the translation is performed, the translation values on X and Y axis should be temporarily stored to be manipulated. Therefore, in the class header I am adding two fields:

int trX = 0;
int trY = 0;

Also, I will need to keep track of the rectangle that defines the cropping region, so there is a Rectangle instance defined:

Rectangle r;

So before going any further, I need to adjust the size of LayoutRoot - the main grid where the image is placed, so that I can reference the Rectangle inside the Grid, therefore using the same coordinates on top of the image. To do this, in the existing SetPicture method add two lines at the end:

LayoutRoot.Height = image1.Height;
LayoutRoot.Width = image1.Width;

Let's now proceed to the easiest part - button switching. Obviously, when the user clicks on Move, then the Resize mode is disabled and vice versa. Here is what I have for this:

private void btn_Click(object sender, EventArgs e)
{
IApplicationBarIconButton button;
if (isMove)
{
button = (IApplicationBarIconButton)ApplicationBar.Buttons[1];
button.IsEnabled = true;
button = (IApplicationBarIconButton)ApplicationBar.Buttons[0];
button.IsEnabled = false;
isMove = false;
}
else
{
button = (IApplicationBarIconButton)ApplicationBar.Buttons[1];
button.IsEnabled = false;
button = (IApplicationBarIconButton)ApplicationBar.Buttons[0];
button.IsEnabled = true;
isMove = true;
}
}

Look at the XAML I have for the application buttons (since that is what I am using as the "switch panel"):

<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.download.rest.png" Text="Resize" x:Name="btnResize" Click="btn_Click" IsEnabled="False"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.upload.rest.png" Text="Move" x:Name="btnMove" Click="btn_Click" IsEnabled="True"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.check.rest.png" Text="Accept" Click="Accept_Click" />
<shell:ApplicationBarIconButton IconUri="/Images/appbar.close.rest.png" Text="Cancel" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

Notice one interesting fact - I am sharing the Click event handler between the two mode buttons. In Windows Phone 7 application buttons aren't referenced on a name basis, but rather on an index one. Therefore, in the event handler itself I have to detect what button was clicked by checking the isMove field, which is only set by these buttons. Confusing a bit? Don't worry about that - you'll get used to it.

There is nothing special about the event handler itself - all that is done there is enabling and disabling buttons at the same time setting the value of isMove (as you remember, it shows whether the Move or Resize mode is on).

The ManipulationDelta event handler went through a small overhaul here. Here it is:

void rect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
    GeneralTransform gt = ((Rectangle)sender).TransformToVisual(LayoutRoot);
    Point p = gt.Transform(new Point(0, 0));

    int intermediateValueY = (int)((LayoutRoot.Height - ((Rectangle)sender).Height));
    int intermediateValueX = (int)((LayoutRoot.Width - ((Rectangle)sender).Width));
    Rectangle croppingRectangle = (Rectangle)sender;

    if (isMove)
    {
        TranslateTransform tr = new TranslateTransform();
        trX += (int)e.DeltaManipulation.Translation.X;
        trY += (int)e.DeltaManipulation.Translation.Y;

        if (trY < (-intermediateValueY /2))
        {
            trY = (-intermediateValueY /2);
        }
        else if (trY > (intermediateValueY/2))
        {
            trY = (intermediateValueY/2);
        }

        if (trX < (-intermediateValueX/2))
        {
            trX = (-intermediateValueX/2);
        }
        else if (trX > (intermediateValueX/2))
        {
            trX = (intermediateValueX/2);
        }

        tr.X = trX;
        tr.Y = trY;

        croppingRectangle.RenderTransform = tr;
    }
    else
    {
        if (p.X >= 0)
        {
            if (p.X <= intermediateValueX)
            {
                croppingRectangle.Width -= (int)e.DeltaManipulation.Translation.X;
            }
            else
            {
                croppingRectangle.Width -= (p.X - intermediateValueX);
            }
        }
        else
        {
            croppingRectangle.Width -= Math.Abs(p.X);
        }

        if (p.Y >= 0)
        {
            if (p.Y <= intermediateValueY)
            {
                croppingRectangle.Height -= (int)e.DeltaManipulation.Translation.Y;
            }
            else
            {
                croppingRectangle.Height -= (p.Y - intermediateValueY);
            }
        }
        else
        {
            croppingRectangle.Height -= Math.Abs(p.Y);
        }
    }
}

That's a lot of code to start with. But let me break it down, since the pieces are really quite simple.

First of all, I need to find the cropping rectangle position inside the Grid (in my case, LayoutRoot). Unlike a WinForms application, a WP7 application doesn't really let you find the control coordinates inside another control. Or, at least, it takes a bit more effort to do this. And this snippet does it:

GeneralTransform gt = ((Rectangle)sender).TransformToVisual(LayoutRoot);
Point p = gt.Transform(new Point(0, 0));

Most of the location and size processing is done with the help of an intermediate value.

int intermediateValueY = (int)((LayoutRoot.Height - ((Rectangle)sender).Height));
int intermediateValueX = (int)((LayoutRoot.Width - ((Rectangle)sender).Width));

Both for height (intermediateValueY) and width (intermediateValueX), the intermediate value is the difference between the height/width of the main grid (and therefore, the image) and the height/width of the cropping rectangle.

Depending on the current mode, I am using translation and rectangle resizing.

if (isMove)
{
TranslateTransform tr = new TranslateTransform();
trX += (int)e.DeltaManipulation.Translation.X;
trY += (int)e.DeltaManipulation.Translation.Y;

if (trY < (-intermediateValueY /2))
{
trY = (-intermediateValueY /2);
}
else if (trY > (intermediateValueY/2))
{
trY = (intermediateValueY/2);
}

if (trX < (-intermediateValueX/2))
{
trX = (-intermediateValueX/2);
}
else if (trX > (intermediateValueX/2))
{
trX = (intermediateValueX/2);
}

tr.X = trX;
tr.Y = trY;

croppingRectangle.RenderTransform = tr;
}
else
{
if (p.X >= 0)
{
if (p.X <= intermediateValueX)
{
croppingRectangle.Width -= (int)e.DeltaManipulation.Translation.X;
}
else
{
croppingRectangle.Width -= (p.X - intermediateValueX);
}
}
else
{
croppingRectangle.Width -= Math.Abs(p.X);
}

if (p.Y >= 0)
{
if (p.Y <= intermediateValueY)
{
croppingRectangle.Height -= (int)e.DeltaManipulation.Translation.Y;
}
else
{
croppingRectangle.Height -= (p.Y - intermediateValueY);
}
}
else
{
croppingRectangle.Height -= Math.Abs(p.Y);
}
}

When I am moving it, all I have to do is make sure that the rectangle is still in the bounds of the grid. Speaking of which, another way to move the rectangle is by using matrices (like it was done in one of Charles Petzold's samples) but TranslateTransform does pretty much the same thing here, so there is no need in a direct transformation layer.

It is a bit more complicated when it comes to resizing the rectangle. Its maximum size is pre-set to be the size of the image, but the bounds aren't defined and that is exactly why I have to check the X and Y coordinates when the rectangle is resized. Those can be negative (automatically means that it is out of bounds), bigger than the intermediate value (also means that it is out of bounds) or in between (means that the rectangle is in "good standing").

Once the rectangle is correctly positioned and has the right size, you have to actually crop it. To do this, I am using the Accept button. The event handler tied to it looks like this:

private void Accept_Click(object sender, EventArgs e)
{
ClipImage();

WriteBitmap(LayoutRoot);

var image = new BitmapImage();
using (var local = new IsolatedStorageFileStream("myImage.jpg", FileMode.Open, IsolatedStorageFile.GetUserStoreForApplication()))
{
image.SetSource(local);
}

WriteDummyImage(image);
}

Here, ClipImage will actually select the needed region (defined by the rectangle) and will transform the image so that the selected part is moved to the left topmost corner of the grid:

void ClipImage()
{
RectangleGeometry geo = new RectangleGeometry();

r = (Rectangle)(from c in LayoutRoot.Children where c.Opacity == .5 select c).First();
GeneralTransform gt = r.TransformToVisual(LayoutRoot);
Point p = gt.Transform(new Point(0, 0));
geo.Rect = new Rect(p.X, p.Y, r.Width, r.Height);
image1.Clip = geo;
r.Visibility = System.Windows.Visibility.Collapsed;

TranslateTransform t = new TranslateTransform();
t.X = -p.X;
t.Y = -p.Y;
image1.RenderTransform = t;
}

Once the selected region is positioned, I am writing the "screenshot" of the grid to a file. I can also try and get just the Image control representation, but this comes with a few strings attached. The image isn't actually cropped inside the Image control - there is a mask applied that leaves the selected region visible. The image remains the same though and the control size doesn't change, so getting the graphical image of the control itself won't be a good thing since I will have to handle lots of whitespace.

Also, due to security restrictions I cannot directly extract the image from an Image control, so I have to have this workaround.

WriteBitmap will store the "screenshot" in the isolated storage for further processing:

void WriteBitmap(FrameworkElement element)
{
WriteableBitmap wBitmap = new WriteableBitmap(element, null);

using (MemoryStream stream = new MemoryStream())
{
wBitmap.SaveJpeg(stream, (int)element.Width, (int)element.Height, 0, 100);

using (var local = new IsolatedStorageFileStream("myImage.jpg", FileMode.Create, IsolatedStorageFile.GetUserStoreForApplication()))
{
local.Write(stream.GetBuffer(), 0, stream.GetBuffer().Length);
}
}
}

You can change the name, obviously and if you plan to implement a custom control on top of this mechanism, you shouldn't be using any pre-set file names in the first place, since those can be conflicting with user content. But that's a separate discussion.

Once the grid representation is saved, I am loading it again and now I am calling WriteDummyImage with the opened image passed as a parameter:

private void WriteDummyImage(BitmapImage image)
{
Image imageC = new Image();
imageC.Stretch = Stretch.None;
imageC.Source = image;
imageC.Height = r.Height;
imageC.Width = r.Width;

WriteBitmap(imageC);
}

I am creating a new image that will allow me to select only the needed part - now that it is a JPEG and has no whitespace around it. I am adjusting the horizontal and vertical margins simply by changing the size of the image to the size of the cropping rectangle, and then once again I am writing the image contents to a file in the isolated storage.

The cropped image is now there and you are able to access it anytime!

On a side note, I did not put restrictions on the minimum size of the cropping rectangle due to the fact that some regions require very small sections to be selected, therefore you need to set those by yourself if you have a minimum crropping region size limit. This can be done in ManipulationDelta. Also, the cropped image is dependent on the actual size of the Image control, not the image itself. Play around with the Stretch mode if you need different cropping sizes.

You can download the sample project I was working on here.

Windows Phone

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Key Elements of Site Reliability Engineering (SRE)
  • gRPC on the Client Side
  • Required Knowledge To Pass AWS Certified Solutions Architect — Professional Exam
  • Front-End Troubleshooting Using OpenTelemetry

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: