smithvoice.com
 Y'herd thisun? 

“What are linux Run Levels? How do you configure startups of linux services? Here ya go...”

from this page by smith

Your own image control and App part 15

TaggedCoding, VB, Imaging

Originally published December 2002 on DoItIn.net using VB7.0/2002. Updated for VB7.1 February 2005

Links for compiled demo versions, all required resources and source code are included at the end of this article.

Plus, get the complete eBook in Adobe Acrobat 7 format ... all here.


15) Feature 4: Cropping

I was watching Emeril Live last night (don't laugh, there's a connection between everything) and though it was cool when he said: "Cooking is recipes, Baking is formulas. You can adjust anything in recipes, but formulas y'gotta stick to." Cropping is baking, there's a formula to it.

The ingredients are simple: 1 source image, 4 mouse coordinates. We mix them together step by step with a MouseDown, MoveMove, MouseUp and a call to DrawImage.

Unlike a TV chef, we'll take care of the last step first, put the following in your ImageUtilities module:

Friend Function CropImage(ByVal srcImage As Image, _
ByVal srcX As Single, ByVal srcY As Single, _
ByVal srcWidth As Single, ByVal srcHeight As Single) _
As Image
 
'destination rect values are limited to Ints
Dim destX As Integer
Dim destY As Integer
Dim destWidth As Integer
Dim destHeight As Integer
Dim g As Graphics
Dim bmpNew As System.Drawing.Bitmap
Dim croppedImage As Image
 
Try
destX = 0
destY = 0
destWidth = CInt(srcWidth)
destHeight = CInt(srcHeight)
bmpNew = New System.Drawing.Bitmap( _
destWidth, _
destHeight, _
System.Drawing.Imaging.PixelFormat.Format24bppRgb _
)
 
g = Graphics.FromImage(bmpNew)
g.DrawImage(srcImage, _
New Rectangle(destX, destY, destWidth, destHeight), _
srcX, srcY, srcWidth, srcHeight, _
GraphicsUnit.Pixel)
croppedImage = DirectCast(bmpNew.Clone, Image)
bmpNew.Dispose()
g.Dispose()
 
Return croppedImage
Catch ex As Exception
Throw ex
'todo: remove after testing
End Try
End Function

DrawImage only lets you specify a destination area values with Integers but thankfully it's overloaded to let you use Singles for the source area values. We need that precision because of our calculating the actual coordinates based on mouse events over zoomed images and rounding the results to Ints would throw the ratios off too far.

Now for those Mouse events. We'll need some module level private variables to keep track of the ever-changing coordinates and a new "Private Methods" sub-region named "Mouse Handling" for the down, move and up events.

 

#Region "Declarations"
 
#Region "Private Variables"
 
...
 
#Region "Crop Variables"
 
'cropping support
Private m_NowCropping As Boolean = False
Private m_CropStartX As Integer = 0
Private m_CropStartY As Integer = 0
Private m_CropEndX As Integer = 0
Private m_CropEndY As Integer = 0
 
'ants:
Private m_CropAntsPosition1 As Point
Private m_CropAntsPosition2 As Point
#End Region
 
...
 
#End Region
 
#Region "Private Methods"
 
#Region "Mouse Handling"
 
Private Sub pbDisplay_MouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles pbDisplay.MouseDown
 
If Not pbDisplay.Image Is Nothing Then
m_NowCropping = True
 
'trap the mouse in the picturebox
'so if its displayed small, the user can't crop
'outside of the true image
'(which will save a big black block of worthless data
Cursor.Clip = pbDisplay.RectangleToScreen(pbDisplay.ClientRectangle)
 
m_CropStartX = e.X
m_CropStartY = e.Y
 
'ants:
' Save the current mouse position.
m_CropAntsPosition1 = Control.MousePosition
m_CropAntsPosition2 = m_CropAntsPosition1
'end ants
 
End If
 
End Sub
 
Private Sub pbDisplay_MouseMove(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles pbDisplay.MouseMove
 
If Not pbDisplay.Image Is Nothing Then
'ants:
' Do nothing if we're not cropping.
If Not m_NowCropping Then Exit Sub
' Erase the previous line.
ControlPaint.DrawReversibleFrame( _
New Rectangle( _
m_CropAntsPosition1.X, _
m_CropAntsPosition1.Y, _
-(m_CropAntsPosition1.X - m_CropAntsPosition2.X), _
-(m_CropAntsPosition1.Y - m_CropAntsPosition2.Y) _
), _
Me.BackColor, _
FrameStyle.Dashed)
 
' Save the new point.
m_CropAntsPosition2 = Control.MousePosition
' Draw the new line.
ControlPaint.DrawReversibleFrame( _
New Rectangle( _
m_CropAntsPosition1.X, _
m_CropAntsPosition1.Y, _
-(m_CropAntsPosition1.X - m_CropAntsPosition2.X), _
-(m_CropAntsPosition1.Y - m_CropAntsPosition2.Y) _
), _
Me.BackColor, _
FrameStyle.Dashed)
'end ants
 
End If
 
End Sub
 
Private Sub pbDisplay_MouseUp(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles pbDisplay.MouseUp
 
'source coordinate/size values need high enough
'precision to avoid rounding issues
'during zoom calculations
Dim currentRatio As Single
Dim srcX As Single
Dim srcY As Single
Dim srcWidth As Single
Dim srcHeight As Single
If Not pbDisplay.Image Is Nothing Then
Try
If m_NowCropping Then
'clear the clip so mouse can exit the control
Cursor.Clip = Nothing
'a quick click with no position change
'shouldn't be calculated
If m_CropStartX = e.X Or m_CropStartY = e.Y Then
m_NowCropping = False
Exit Sub
End If
 
'account for dragging from any corner
If m_CropStartX > e.X Then
m_CropEndX = m_CropStartX
m_CropStartX = e.X
Else
m_CropEndX = e.X
End If
If m_CropStartY > e.Y Then
m_CropEndY = m_CropStartY
m_CropStartY = e.Y
Else
m_CropEndY = e.Y
End If
 
'account for clipping the mouse to the picturebox affecting
'right or bottom end points
'the values in this case will round to 3 pixels
'less than the actual edges
'yep, this is a hack but it's the lesser evil
If m_CropEndX >= pbDisplay.Width - 3 Then
m_CropEndX = pbDisplay.Width
End If
If m_CropEndY >= pbDisplay.Height - 3 Then
m_CropEndY = pbDisplay.Height
End If
 
'currentRatio = pbDisplay.Width / objImage.Width
'same as:
currentRatio = _
CSng(m_ImagePages(m_CurrentPageNumber).CurrentZoom / 100)
 
'watch the type of division
'"\" gives a rounded int, "/" gives the precision you need
If currentRatio <> 0 Then
srcX = m_CropStartX / currentRatio
srcY = m_CropStartY / currentRatio
srcWidth = (m_CropEndX - m_CropStartX) / currentRatio
srcHeight = (m_CropEndY - m_CropStartY) / currentRatio
Else
srcX = m_CropStartX
srcY = m_CropStartY
srcWidth = m_CropEndX - m_CropStartX
srcHeight = m_CropEndY - m_CropStartY
End If
 
'ants:
' Erase the previous line.
ControlPaint.DrawReversibleFrame( _
New Rectangle( _
m_CropAntsPosition1.X, _
m_CropAntsPosition1.Y, _
-(m_CropAntsPosition1.X - m_CropAntsPosition2.X), _
-(m_CropAntsPosition1.Y - m_CropAntsPosition2.Y) _
), _
Me.BackColor, _
FrameStyle.Dashed _
)
'end ants
Dim croppedImage As Image = CropImage(pbDisplay.Image, _
srcX, srcY, srcWidth, srcHeight)
 
End If
 
Catch ex As Exception
Throw ex
'todo: remove after testing
Finally
m_NowCropping = False
End Try
 
End If
  
End Sub
 
#End Region
 
...

 

If you've poked around the web for crop examples, you might notice that mine's just a tiny bit more involved than most even without the zoom ratio calculations. For instance, you'd think it's obvious but lots of tipsters only show how to deal with selections starting at an upper left and moving to a lower right and I've never seen any users stick to right/down diagonal selecting so my boilerplate accounts for all mouse directions from the git go. Also, I've never had a client request cropping without a dynamic selection rectangle so the DrawReversableFrame is always embedded in the mouse code. It's a formula; to bake correctly it needs all of its parts.

We're not quite ready to test yet, while we are now getting a cropped image, we're not passing it up to the dev-user. For that we need a new event and a new eventargs called svImageCroppedEventArgs. Open the svEventArguments.vb file and add this in:

Public Class svImageCroppedEventArgs
Inherits EventArgs
  
Private m_CroppedImage As Image
  
Public Sub New(ByVal croppedImage As Image)
MyBase.New()
m_CroppedImage = croppedImage
  
End Sub
  
Public ReadOnly Property CroppedImage() As Image
Get
 Return m_CroppedImage
End Get
  
End Property
  
End Class

Now declare the event in the UC's Declarations>Events   sub-region:

Public Event ZoomChanged(ByVal sender As Object, _
ByVal e As svImageZoomChangeEventArgs)
  
Public Event ImageCropped(ByVal sender As Object, _
ByVal e As svImageCroppedEventArgs)
  
#End Region

And wrap it up with a raise at the end of the pbDisplay_MouseUp:

 

...
'end ants
 
Dim croppedImage As Image = CropImage(pbDisplay.Image, _
srcX, srcY, srcWidth, srcHeight)
 
RaiseEvent ImageCropped(Me, New svImageCroppedEventArgs(croppedImage))
  
End If
Catch ex As Exception
...

Rebuild and Save the solution. Now add a generic picturebox named "pbCropResult" (or even a second instance of the control itself) to the testHarness form and put code in the svImageEditor's ImageCropped event:

 

Private Sub SvImageEditor1_ImageCropped(ByVal sender As Object, _
ByVal e As Smithvoice.svImageCroppedEventArgs) _
Handles SvImageEditor1.ImageCropped
  
pbCropResult.Image = e.CroppedImage
  
End Sub

F5 and Test.

In the words of Chef Emeril, "BAM!" :) We've hit all four features. Just a little bit of cleanup and we're ready to use the control in a pretty app GUI and in any other imaging projects that may ever come down the pike.

But as long as we're here so early, we'll continue to quote: "Let's take it up another notch!"

Next: Bonus Feature: Sticky Mouse

Robert Smith
Kirkland, WA

added to smithvoice march 2005


 

jump to:

  • 1) The spec
  • 2) Setting up the workspace
  • 3) Feature 1: Loading an image
  • 4) Custom Exceptions
  • 5) "Fax images" and Multipage TIFFs
  • 6) Custom events
  • 7) Selecting specific fax pages
  • 8) Feature 2: Rotating image displays
  • 9) The most useful tool in GDI+: DrawImage
  • 10) Feature 3: Zooming
  • 11) Handling the unhandleable exception
  • 12) Fixing the squish
  • 13) Zooming to fit the control
  • 14) You're already beating the Pros
  • 15) Feature 4: Cropping
  • 16) Bonus Feature: StickyMouse
  • 17a) Final Cleanup
  • 17b) Passing the current display image
  • 18) Making the application
  • 19) Source and result viewports
  • 20) A better toolbar
  • 21) Hooking the toolbar to the project
  • 22) Adding ImageEditors
  • 23) The toolbar ZoomCombo
  • 24) The final solution
  • 25) Saving to image files
  • 26) An integer-only textbox
  • 27) Passing save options between forms
  • 28) Dealing with that last exception
  • 29) Offer more options with menus
  • 30 The downloads and ebook


  • home     who is smith    contact smith     rss feed π
    Since 1997 a place for my stuff, and it if helps you too then all the better smithvoice.com