Managed C++: Rubber-Banding and Cropping Images

My previous article illustrated how to load, display, and dynamically resize images. This article demonstrates how to allow a user to select an area of an image with their mouse (sometimes called rubber-banding) and crop the image to that selected region.

Step-by-Step Instructions

Define the form-level members

First, define the variables that will be used to control how and where the rectangle is drawn. The cropRect defines the exact coordinates of the cropped image (the x and y coordinates as well as the width and height). Use the cropping boolean value to determine when the user has the mouse button down so that the user doesn't draw a rectangle when he or she simply is moving the mouse across the image. Finally, use the cropOriginX and cropOriginY to determine the original starting position (You'll see why these values need to be stored separately from the cropRect member shortly.):

public __gc class Form1 : public System::Windows::Forms::Form
{
protected:
  Rectangle cropRect;
  bool cropping;
  int cropOriginX, cropOriginY;

...

Implement an event handle for the PictureBox control's MouseDown event

The user pressing the mouse button constitutes the start of a new rectangle. The first thing to verify is that an image file has been loaded. The PictureBox control has a member called Image that represents the image being displayed, so the code can easily check for its existence in a boolean statement. If an image is being displayed, the method first calls the PictureBox::Refresh method to remove the previously drawn rectangle. If you're familiar with the famous MFC Scribble example application, you'll recall that to erase the rectangle, it simply drew the rectangle using the client area background color. However, that obviously won't work here because the program has drawn on an image (instead of a simple blank background). Therefore, the quickest and easiest way to remove the previous rectangle is by refreshing the image.

Once the image has been refreshed, the method needs only to initialize the variables that define where the rectangle is drawn and set a boolean flag to indicate that the user is currently cropping the image:

private: System::Void picImage_MouseDown(System::Object * sender,
                                         System::Windows::Forms::
                                         MouseEventArgs *  e)
{
  if (picImage->Image)
  {
    picImage->Refresh();

    cropOriginX     = e->X;;
    cropRect.X      = e->X;
    cropRect.Height = 0;

    cropOriginY    = e->Y;
    cropRect.Y     = e->Y;
    cropRect.Width = 0;

    cropping = true;
  }
}

Implement an event handle for the PictureBox control's MouseMove event

As you might imagine, this is where most of the action occurs. The mousemove method first verifies that the user is cropping the image. If so, it refreshes the image (to erase any pre-existing rectangle), calls SetCroppingRectangle (to properly set up the rectangle to be drawn), and then calls the PictureBox::Invalidate method to cause the picture box to be repainted:

private: System::Void picImage_MouseMove(System::Object * sender,
                                         System::Windows::Forms::
                                         MouseEventArgs *  e)
{
  if (cropping)
  {
    picImage->Refresh();

    SetCroppingRectangle(e->X, e->Y);
    picImage->Invalidate();
  }
}

The SetCroppingRectangle receives the coordinates where the mouse is on the picture box. The first thing the method does is make sure that these coordinates are not outside of the image's dimensions. If either the x or the y coordinate is outside the image's dimensions, the value is modified so that it represents the extreme in that direction. In other words, if an image is 20 (width) x 40 (height) and the user attempts to move the mouse to pixel position 41 on the x axis, then the value representing the x coordinate is set to 40. Likewise, the code ensures that the user can't move to a value below 0. These numbers are then massaged to ensure that the full rectangle can be viewed within the picture box. (I used a hard-coded value of 6, which worked for me when using a pen size of 3. However, you might want to play around with them to ensure perfect accuracy for your application.)

You would think that when the user moves the mouse you need only increment the Width and Height members of your cropRect object. However, when you draw a rectangle in .NET, it is drawn relative to an upper-left hand corner. This causes a problem if the user moves the mouse from right to left and/or from bottom to top. As a result, you need code to handle that situation such that the rectangle properly reflects not the direction the user moves the mouse, but the coordinates that are necessary for drawing the rectangle.

As an example, suppose the user starts moving the mouse from 5,5 and to 15,15. This is easy. The x and y members are each equal to 5 and the width and height are each equal to 10. In this situation, you simply leave the x and y alone and change the width and height to the current position (15,15) minus the starting position (5,5), which gives you a width and height of 10,10. However, if the user moves left and/or up, you need to set the starting position of the rectangle to the point where the user is now and then set the width and height equal to that original starting position (this is why you needed to define the cropOriginX and cropOriginY members) minus the current positions:

private: System::Void SetCroppingRectangle(int moveToX, int moveToY)
{
  // Account for user cropping outside of image dimensions
  moveToX = Math::Max(0, Math::Min(moveToX,picImage->Image->Width));
  moveToY = Math::Max(0, Math::Min(moveToY,picImage->Image->Height));


  // Make sure that rectangle is viewable within the picturebox
  // (Note: If the form is scrollable and the picturebox is set to
  // autosize, the rectangle can be drawn beyond the user's view.
  if (moveToX == 0) moveToX = 6;
  if (moveToX >= picImage->Image->Width)
    moveToX -= 6;
  if (moveToY == 0) moveToY = 6;
  if (moveToY >= picImage->Image->Height)
    moveToY -= 6;

  if (moveToX >= cropRect.X)    // moving right
    cropRect.Width = moveToX - cropOriginX;
  else                          // moving left
  {
    cropRect.X = moveToX;
    cropRect.Width = cropOriginX - moveToX;
  }

  if (moveToY >= cropRect.Y)    // moving down
    cropRect.Height = moveToY - cropOriginY;
  else                          // moving up
  {
    cropRect.Y = moveToY;
    cropRect.Height = cropOriginY - moveToY;
  }
}

Implement an event handle for the PictureBox control's MouseUp event

At this point, you have the rectangle to draw. All you need to do is turn off the cropping boolean flag and, in the case of the demo, enable the button that allows the user to invoke the cropping of the image. As you can see, I enable the button based on whether or not a rectangle is present:

private: System::Void picImage_MouseUp(System::Object * sender,
                                       System::Windows::Forms::
                                       MouseEventArgs *  e)
{
  cropping = false;
  btnCropImage->Enabled = (cropRect.Width > 0 &&
                           cropRect.Height > 0);
}

Implement a means of the user invoking the crop function

In the article demo, the user first draws the rectangle and then clicks a button labeled "Crop Image". The method first disables the crop button, and then it adjusts the rectangle to make sure that if the user has moused to the edge of the image, the edge is also cropped.

Once that's done, you're finally finished with the rectangle drawing and can crop the image. Do this by creating a Bitmap object that is the width and height of the user's drawn rectangle. Then, obtain a Graphics object for drawing on the bitmap. Next, use the Graphics::DrawImage method. This method allows you to specify a source image, destination coordinates, and source coordinates. As you can see, the source image is the image in the PictureBox control. The destination coordinates are defined by the cropRect object that has been used to draw the rectangle on the image, and the source coordinates start at 0 and 0 (top left-hand corner of the new image) with a width and height equal to that of the drawn rectangle. The PictureBox control's Image property is set to the newly created image and the rectangle is initialized:

private: System::Void btnCropImage_Click(System::Object * sender,
                                         System::EventArgs * e)
{
  if (picImage->Image
  && cropRect.Width > 0
  && cropRect.Height > 0)
  {
    btnCropImage->Enabled = false;

    if (cropRect.X <= 6) cropRect.X = 0;
    if (cropRect.X + cropRect.Width >= picImage->Image->Width)
      cropRect.Width += 6;
    if (cropRect.Y <= 6) cropRect.Y = 0;
    if (cropRect.Y + cropRect.Height >=
        picImage->Image->Height)
      cropRect.Height += 6;

    Bitmap* croppedImage = new Bitmap(cropRect.Width,
                                      cropRect.Height);
    Graphics* g = Graphics::FromImage(croppedImage);
    g->DrawImage(picImage->Image,
                 Rectangle(0, 0,
                           cropRect.Width,
                           cropRect.Height),
                           cropRect,
                           GraphicsUnit::Pixel);

    picImage->Image = croppedImage;

    cropRect.X      = 0;
    cropRect.Width  = 0;
    cropRect.Y      = 0;
    cropRect.Height = 0;
  }
}

Implement a Paint method for the PictureBox control

This is done so that if the user switches away from the application and then back again, the rectangle (if drawn) is always present. As you can see, you simply check to see if an image and valid rectangle coordinates are present. If they are, you create a pen and draw the rectangle:

private: System::Void picImage_Paint(System::Object * sender,
                                     System::Windows::Forms::
                                     PaintEventArgs * e)
{
  if (picImage-<Image
  && cropRect.Width > 0
  && cropRect.Height > 0)
  {
    Pen* pen = new Pen(Brushes::Red, 3);
    Graphics* g = e->Graphics;
    g->DrawRectangle(pen, cropRect);
  }
}


About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Downloads

Comments

  • cijlcxvr

    Posted by zwuneglq on 04/22/2013 09:49pm

    ghmomulf http://handtaschenlouisvuittonbietenoff.de/ tnxfeowv http://hollistercoshippingoffr.co.uk/ rjtkqhnb http://thomassaboonlineshippingzaus.com/ fkltkkir http://louisvuittonsonnenbrilleverkauff.de/ ypcslxyy http://poloralphlaurengetoffs.fr/ elwuqzgc handtaschen louis vuitton spfrooyt hollister co oytltent thomas sabo online pqrundgr louis vuitton speedy 35 djmrqefo polo ralph lauren * The present affliction of this kidneys However , Halal Meat is for anyone. May be a Crash and burn Eating routine the right way to excess weight? HARDLY ANY : because limits food intake for only very little time and a second will don many of those forfeited pounds to come back on resumption in their common eating habits.

    Reply
  • qymhcflt

    Posted by rjfcfjwh on 04/22/2013 02:49am

    cprpisnd http://louisvuittontascheoffz.de/ jzvjfbvj http://ofrzabercrombieandfitchoutlet.co.uk/ kymyvaul http://louisvuittongeldborsejetztt.de/ chtorfix http://hollisterukitsale.co.uk/ pvlrsjgg http://nowthomassabocharmscheapzaus.com/ kknfryfr louis vuitton tasche rhhjmeyc abercrombie and fitch outlet wjoiiiow louis vuitton geldbörse kxviaduj hollister uk nqvsonvx thomas sabo charms Analysis into probiotics, the positive bacteria that will live in the intestinal tract, features released huge innovations within understanding of health. Although, exploit generates some sort of motion and also encourages give off of a lot more stomach acid, causing then acid reflux disease. They are definitely provided by using onions, lime butter, chilli peppers as well as a great curry.

    Reply
  • eobrivqb

    Posted by shbgumkb on 04/21/2013 07:37pm

    mpovojln http://oakleysunglassesaustraliao.co.uk/ lsefzdmg http://casquebeatspascherq.fr/ qtwixgjx http://louisvuittonwalletet.com/ mlyhhaln http://hollisteroutletlet.co.uk/ jgoueshv http://todsbagndrivingshoese.com/ ttugazpr oakley sunglasses australia upzmpevq casque beats pas cher hgklummm louis vuitton wallet fzaxlukw hollister outlet qttopetu tods shoes sale it must be tie in with to the environment the place you'll undertake your own halibut fishing. Usually there are some many people who wish to striper during the nighttime because of the individuality on the conditions may draw. Great things about Cameras Mango

    Reply
  • onxplfrj

    Posted by tgtyllpc on 04/21/2013 12:53am

    hkphybgn http://jetztlouisvuittons.de/ hjiuwuvn http://beatsbydrdreoffrsale.fr/ yzniskgq http://cheapchanelukhotsale.co.uk/ khelwyvu http://hotzpoloralphlaurenpaschers.fr/ btiamcma http://louisvuittonshoeshotoffr.com/ fpbxbrup louis vuitton pxemzfuj beats by dr dre rnqajfpv chanel uk ssmdtivl polo ralph lauren pas cher psxgirgl louis vuitton shoes Hardly any agreements Typically the keyless post apparatus contains a few resources offering typically the device, that is definitely built while in the family car and then the out of the way key element, that is definitely hooked up aided by the car vital. Better known as maintain adjuster, this is often a powerful insurance company staff member that's mainly responsible for investigating and even deciding all statements brought from claimants or insurance bags resulting from an individual casualty.

    Reply
  • buyfnvnh

    Posted by hmndyunp on 04/03/2013 12:24pm

    dnndfxha http://casquebeatsbydrec.fr/ qgwspina http://louisvuittonspeedy3035y.com/ xuvnfnvw http://hollistererruk.co.uk/ wnzalqkk http://hollisteruker.co.uk/ jqevktna http://todsshoesess.com/ yralvjtc casque beats by dre nhemjohd louis vuitton speedy 35 izolophy hollister uk pxecztqw hollister uk uvxvglqo tods shoes

    Reply
  • crops the area right of the selected rectangle

    Posted by sanjeet.uchil on 10/17/2008 06:16am

    i dnt knw why this is happening...on clicking crop button....image area right to the selected area gets cropped instead....please help...i have translated ur code into c#.

    • can you share your code?

      Posted by niketrk on 11/21/2008 02:11am

      Hi, Can you share your code here so other can try to help you? otherwise one good ready-made control, I found at http://futurefiesta.com/downloads.htm . Though it is not free but it has got many features related to crop control. It is in form of activex so it is easy to integrate in application. It is simple but effective. anyway please share your code, so I can try to fix it.

      Reply
    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: March 27, 2014 Teams need to deliver quality software faster and need integrated agile planning, task tracking, source control, auto deploy with continuous builds and a configurable process to adapt to the way you work. Rational Team Concert and DevOps Services (JazzHub) have everything you need to build great software, integrated seamlessly together right out of the box or available immediately in the cloud. And with the Rational Team Concert Client, you can connect your …

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds