Tuesday, January 06, 2009
Home
.Net
Return of the Repeater
Complete image control & app
Part 2 - Starting the control
Part 3 - Loading images
Part 4 - Custom exceptions
Part 5 - Fax/Multipage tifs
Part 6 - Custom events
Part 7 - Selecting fax pages
Part 8 - Rotating displays
Part 9 - The power of GDI+
Part 10 - Zooming
Part 11 - .Net's fatal exception
Part 12 - Fix the squishies
Part 13 - Zoom to fit
Part 14 - You beat the pros
Part 15 - Cropping
Part 16 - Bonus: StickyMouse
Part 17 - Finishing touches?
Part 18 - Make the application
Part 19 - Adding viewports
Part 20 - A better toolbar
Part 21 - Connect the toolbar
Part 22 - Adding ImageEditors
Part 23 - Toolbar ZoomCombo
Part 24 - VB3 style ease
Part 25 - Saving images to files
Part 26 - Integer-only textBox
Part 27 - Passing save settings
Part 28 - The last exception
Part 29 - Menus offer more
Part 30 - Book, app & source
Couple of CS Snippets
XML processing quickies
File extension extensions
McAfee.Not
cs IntBox
Floating Holidays
Code snippets
Flexible sorts
Converted deserializations
Autodeploy not found
Autodeploy just stops
VB must be killed
Media file attributes
Fastest file searches
Webservicing custom objects
Aspect correct resize
Funky thumbnails
Wide interval timing
VB2005 or bust?
Recurring events
Single instances
Proper proper casing
Simple address formating
Combo filling reminder
Easy gradient forms
Grrrr on interops?
Winform memory usage
Windows service your ISP
Pretty up VS code printing
Remote configs with no BS
GDI+ printing cd cases
Your own flat combo
Where's the splitter?
Full autoemail
ReessppoonncceeWwrriittee
Kill the back cache
Color to hex
Source to web
Recode without recompile
Prop snippet for VS2008
Database tricks

Your own image control

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


Print  

pagecomment
  Add Comment



Submit Comment
  View Ratings
50.00%0
40.00%0
30.00%0
20.00%0
10.00%0

Number of Comments 0 , Average of Ratings
  View Comments
No comment.


Privacy Statement  |  Terms Of Use
Copyright 2008 by Robert C. Smith