source: trunk/Snippets/CropImage/ImageCropBox.vb @ 119

Last change on this file since 119 was 119, checked in by roman, 10 years ago

Added PreserveAspectRatio? property

File size: 18.8 KB
Line 
1Imports System.Diagnostics
2Imports System.Drawing
3Imports System.Drawing.Imaging
4Imports System.Drawing.Drawing2D
5
6Public Class ImageCropBox
7    Private Const Spacing As Integer = 2
8    Private Const SpotSize As Integer = 7
9    Private _Image As Bitmap
10    Private _FadeImage As Bitmap
11    Private _MinimalSelectionSize As Size
12    Private _SelectionRectangle As Rectangle
13    Private _PreserveAspectRatio As Boolean = False
14    Private ClientImageOrigin As Point
15    Private ClientImageExtent As Size
16    Private ClientSelectionRectangle As Rectangle
17    Private BoundaryPenA As Pen
18    Private BoundaryPenB As Pen
19    Private SpotBrush As Brush
20    Private SpotPen As Pen
21    Private DragSpotIndex As Integer ' 0..3 Corner Resize, 4 Body Move
22    Private MouseDownSelection As Rectangle
23    Private MouseDownPosition As Point
24
25    Public Event SelectionRectangleChanged As EventHandler
26    Public Event SelectionDoubleClick As EventHandler
27
28    Public Sub New()
29        ' This call is required by the designer.
30        InitializeComponent()
31        ' Add any initialization after the InitializeComponent() call.
32        _MinimalSelectionSize = New Size(0, 0)
33        _PreserveAspectRatio = False
34    End Sub
35
36    Private Function ApplyMinimalSelection(ByVal SelectionRectangle As Rectangle) As Rectangle
37        If SelectionRectangle.Width < MinimalSelectionSize.Width Then
38            SelectionRectangle.Width = MinimalSelectionSize.Width
39            If _Image IsNot Nothing AndAlso SelectionRectangle.Right > _Image.Width Then SelectionRectangle.X = Math.Max(0, _Image.Width - SelectionRectangle.Width)
40        End If
41        If SelectionRectangle.Height < MinimalSelectionSize.Height Then
42            SelectionRectangle.Height = MinimalSelectionSize.Height
43            If _Image IsNot Nothing AndAlso SelectionRectangle.Bottom > _Image.Height Then SelectionRectangle.Y = Math.Max(0, _Image.Height - SelectionRectangle.Height)
44        End If
45        ApplyMinimalSelection = SelectionRectangle
46    End Function
47    Private Function ApplyAspectRatio(ByVal SelectionRectangle As Rectangle, ByRef AdjustedSelectionRectangle As Rectangle) As Boolean
48        AdjustedSelectionRectangle = SelectionRectangle
49        ApplyAspectRatio = False
50        If Not _PreserveAspectRatio Then Exit Function
51        If _Image Is Nothing Then Exit Function
52        Debug.Assert(_Image.Height > 0 AndAlso _Image.Width > 0)
53        If SelectionRectangle.Width <= 0 Or SelectionRectangle.Height <= 0 Then Exit Function
54        Dim AdjustedHeight As Integer = Math.Round(SelectionRectangle.Width * _Image.Height / _Image.Width)
55        Dim AdjustedWidth As Integer = Math.Round(SelectionRectangle.Height * _Image.Width / _Image.Height)
56        Debug.Assert(AdjustedWidth >= SelectionRectangle.Width Or AdjustedHeight >= SelectionRectangle.Height)
57        If AdjustedWidth < SelectionRectangle.Width Then
58            AdjustedSelectionRectangle.Width = AdjustedWidth
59            ApplyAspectRatio = True
60        ElseIf AdjustedHeight < SelectionRectangle.Height Then
61            AdjustedSelectionRectangle.Height = AdjustedHeight
62            ApplyAspectRatio = True
63        End If
64    End Function
65    Private Function ApplyAspectRatioAndCenterRectangle(ByVal SelectionRectangle As Rectangle) As Rectangle
66        Dim AdjustedSelectionRectangle As Rectangle
67        If ApplyAspectRatio(SelectionRectangle, AdjustedSelectionRectangle) Then
68            AdjustedSelectionRectangle.Offset((SelectionRectangle.Width - AdjustedSelectionRectangle.Width) / 2, (SelectionRectangle.Height - AdjustedSelectionRectangle.Height) / 2)
69            ApplyAspectRatioAndCenterRectangle = AdjustedSelectionRectangle
70        Else
71            ApplyAspectRatioAndCenterRectangle = SelectionRectangle
72        End If
73    End Function
74
75    Public Property Image As Bitmap
76        Get
77            Image = _Image
78        End Get
79        Set(value As Bitmap)
80            _Image = value
81            If _Image IsNot Nothing Then
82                SelectionRectangle = New Rectangle(value.Width * 1 / 16, value.Height * 1 / 16, value.Width * 14 / 16, value.Height * 14 / 160)
83                'SelectionRectangle = New Rectangle(0, 0, value.Width, value.Height)
84                _FadeImage = _Image.Clone
85                Dim FadeImageGraphics As Graphics = Graphics.FromImage(_FadeImage)
86                Dim FadeBrush As HatchBrush = New HatchBrush(HatchStyle.Percent25, Color.FromArgb(0, Color.Black), Color.FromArgb(255, Color.Black))
87                FadeImageGraphics.FillRectangle(FadeBrush, 0, 0, _FadeImage.Width, _FadeImage.Height)
88                FadeImageGraphics.Save()
89            Else
90                _FadeImage = Nothing
91            End If
92            Update()
93        End Set
94    End Property
95    Public Property MinimalSelectionSize As Size
96        Get
97            MinimalSelectionSize = _MinimalSelectionSize
98        End Get
99        Set(value As Size)
100            Debug.Assert(value.Width >= 0 And value.Height >= 0)
101            _MinimalSelectionSize = value
102            ' TODO: Update SelectionRectangle
103        End Set
104    End Property
105    Public Property SelectionRectangle As Rectangle
106        Get
107            SelectionRectangle = _SelectionRectangle
108        End Get
109        Set(value As Rectangle)
110            Dim EffectiveSelectionRectangle As Rectangle = ApplyMinimalSelection(value)
111            If PreserveAspectRatio Then EffectiveSelectionRectangle = ApplyAspectRatioAndCenterRectangle(EffectiveSelectionRectangle)
112            Dim SelectionRectangleChanged As Boolean = _SelectionRectangle <> EffectiveSelectionRectangle
113            _SelectionRectangle = EffectiveSelectionRectangle
114            RaiseEvent SelectionRectangleChanged(Me, New System.EventArgs())
115            If SelectionRectangleChanged Then Invalidate()
116        End Set
117    End Property
118    Public Property PreserveAspectRatio As Boolean
119        Get
120            PreserveAspectRatio = _PreserveAspectRatio
121        End Get
122        Set(value As Boolean)
123            If _PreserveAspectRatio = value Then Exit Property
124            _PreserveAspectRatio = value
125            If _PreserveAspectRatio Then SelectionRectangle = ApplyAspectRatioAndCenterRectangle(_SelectionRectangle)
126        End Set
127    End Property
128
129    Private Sub ImageCropBox_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
130        BoundaryPenA = New Pen(Brushes.White)
131        BoundaryPenA.Width = 1
132        BoundaryPenA.DashPattern = New Single() {8.0F, 4.0F}
133        BoundaryPenB = New Pen(Brushes.Black)
134        BoundaryPenB.Width = 1
135        BoundaryPenB.DashPattern = New Single() {4.0F, 8.0F}
136        BoundaryPenB.DashOffset = 4
137        SpotBrush = Brushes.Black
138        SpotPen = New Pen(Brushes.White)
139        SpotPen.Width = 1
140        ResizeRedraw = True
141    End Sub
142    Private Sub DrawSpot(Graphics As Graphics, Position As Point)
143        Graphics.FillRectangle(SpotBrush, Position.X - CInt(SpotSize / 2) - 1, Position.Y - CInt(SpotSize / 2) - 1, SpotSize + 2, SpotSize + 2)
144        Graphics.DrawRectangle(SpotPen, Position.X - CInt(SpotSize / 2), Position.Y - CInt(SpotSize / 2), SpotSize, SpotSize)
145    End Sub
146    Private Sub DrawSpot(Graphics As Graphics, PositionX As Integer, PositionY As Integer)
147        DrawSpot(Graphics, New Point(PositionX, PositionY))
148    End Sub
149    Private Sub ImageCropBox_Paint(sender As System.Object, e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
150        If Image Is Nothing Then Exit Sub
151        Dim Extent = Me.Size
152        Extent.Width -= Spacing + SpotSize + Spacing
153        Extent.Height -= Spacing + SpotSize + Spacing
154        If Extent.Width <= 0 Or Extent.Height <= 0 Then Exit Sub
155        Debug.Assert(Image.Width > 0 AndAlso Image.Height > 0)
156        Dim Ratio As Double = Math.Max(Image.Width / Extent.Width, Image.Height / Extent.Height)
157        If Ratio > 1 And Ratio < 1.1 Then Ratio = 1.0 ' Snap to 100%
158        ClientImageExtent.Width = Math.Round(Image.Width / Ratio)
159        ClientImageExtent.Height = Math.Round(Image.Height / Ratio)
160        ClientImageOrigin.X = Spacing + SpotSize / 2 + (Extent.Width - ClientImageExtent.Width) / 2
161        ClientImageOrigin.Y = Spacing + SpotSize / 2 + (Extent.Height - ClientImageExtent.Height) / 2
162        e.Graphics.DrawImage(_FadeImage, ClientImageOrigin.X, ClientImageOrigin.Y, ClientImageExtent.Width, ClientImageExtent.Height)
163        ClientSelectionRectangle.X = Math.Round(_SelectionRectangle.Left / Ratio)
164        ClientSelectionRectangle.Y = Math.Round(_SelectionRectangle.Top / Ratio)
165        ClientSelectionRectangle.Width = Math.Round(_SelectionRectangle.Right / Ratio) - ClientSelectionRectangle.X
166        ClientSelectionRectangle.Height = Math.Round(_SelectionRectangle.Bottom / Ratio) - ClientSelectionRectangle.Y
167        ClientSelectionRectangle.Offset(ClientImageOrigin)
168        e.Graphics.SetClip(ClientSelectionRectangle)
169        e.Graphics.DrawImage(_Image, ClientImageOrigin.X, ClientImageOrigin.Y, ClientImageExtent.Width, ClientImageExtent.Height)
170        e.Graphics.ResetClip()
171        e.Graphics.DrawRectangle(BoundaryPenA, ClientSelectionRectangle)
172        e.Graphics.DrawRectangle(BoundaryPenB, ClientSelectionRectangle)
173        DrawSpot(e.Graphics, ClientSelectionRectangle.Left, ClientSelectionRectangle.Top)
174        DrawSpot(e.Graphics, ClientSelectionRectangle.Right, ClientSelectionRectangle.Top)
175        DrawSpot(e.Graphics, ClientSelectionRectangle.Left, ClientSelectionRectangle.Bottom)
176        DrawSpot(e.Graphics, ClientSelectionRectangle.Right, ClientSelectionRectangle.Bottom)
177    End Sub
178    Private Function SourcePointFromPoint(Position As Point) As Point
179        Dim SourcePosition As Point
180        SourcePosition.X = Math.Round((Position.X - ClientImageOrigin.X) * Image.Width / ClientImageExtent.Width)
181        SourcePosition.Y = Math.Round((Position.Y - ClientImageOrigin.Y) * Image.Height / ClientImageExtent.Height)
182        SourcePointFromPoint = SourcePosition
183    End Function
184    Private Function PointFromSourcePoint(SourcePosition As Point) As Point
185        Dim Position As Point
186        Position.X = ClientImageOrigin.X + Math.Round(SourcePosition.X * ClientImageExtent.Width / Image.Width)
187        Position.Y = ClientImageOrigin.Y + Math.Round(SourcePosition.Y * ClientImageExtent.Height / Image.Height)
188        PointFromSourcePoint = Position
189    End Function
190    Private Function IsSpotPosition(SpotPosition As Point, Position As Point) As Boolean
191        Dim SpotPositionEx = New Rectangle(SpotPosition.X - SpotSize / 2, SpotPosition.Y - SpotSize / 2, SpotSize, SpotSize)
192        IsSpotPosition = SpotPositionEx.Contains(Position)
193    End Function
194    Private Function IsSpotPosition(SpotPositionX As Integer, SpotPositionY As Integer, PositionX As Integer, PositionY As Integer) As Boolean
195        IsSpotPosition = IsSpotPosition(New Point(SpotPositionX, SpotPositionY), New Point(PositionX, PositionY))
196    End Function
197    Private Sub ApplyPointConstraint(ByRef Position As Point, P1 As Point, P2 As Point)
198        If Position.X > P2.X Then Position.X = P2.X
199        If Position.Y > P2.Y Then Position.Y = P2.Y
200        If Position.X < P1.X Then Position.X = P1.X
201        If Position.Y < P1.Y Then Position.Y = P1.Y
202    End Sub
203    Private Sub ImageCropBox_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
204        Dim Position As Point = e.Location, SourcePosition As Point
205        If Capture Then
206            Dim P1 As Point, P2 As Point
207            Dim NewSelectionRectangle As Rectangle, NewAdjustedSelectionRectangle As Rectangle
208            Select Case DragSpotIndex
209                Case 0 ' Left Top
210                    P1 = ClientImageOrigin
211                    P2 = PointFromSourcePoint(New Point(SelectionRectangle.Right - MinimalSelectionSize.Width, SelectionRectangle.Bottom - MinimalSelectionSize.Height))
212                    ApplyPointConstraint(Position, P1, P2)
213                    SourcePosition = SourcePointFromPoint(Position)
214                    NewSelectionRectangle = Rectangle.FromLTRB(SourcePosition.X, SourcePosition.Y, SelectionRectangle.Right, SelectionRectangle.Bottom)
215                    If ApplyAspectRatio(NewSelectionRectangle, NewAdjustedSelectionRectangle) Then
216                        NewAdjustedSelectionRectangle.Offset(NewSelectionRectangle.Width - NewAdjustedSelectionRectangle.Width, NewSelectionRectangle.Height - NewAdjustedSelectionRectangle.Height)
217                        SelectionRectangle = NewAdjustedSelectionRectangle
218                    Else
219                        SelectionRectangle = NewSelectionRectangle
220                    End If
221                Case 1 ' Right Top
222                    P1 = New Point(ClientImageOrigin.X + ClientImageExtent.Width, ClientImageOrigin.Y)
223                    P2 = PointFromSourcePoint(New Point(SelectionRectangle.Left + MinimalSelectionSize.Width, SelectionRectangle.Bottom - MinimalSelectionSize.Height))
224                    ApplyPointConstraint(Position, New Point(P2.X, P1.Y), New Point(P1.X, P2.Y))
225                    SourcePosition = SourcePointFromPoint(Position)
226                    NewSelectionRectangle = Rectangle.FromLTRB(SelectionRectangle.Left, SourcePosition.Y, SourcePosition.X, SelectionRectangle.Bottom)
227                    If ApplyAspectRatio(NewSelectionRectangle, NewAdjustedSelectionRectangle) Then
228                        NewAdjustedSelectionRectangle.Offset(0, NewSelectionRectangle.Height - NewAdjustedSelectionRectangle.Height)
229                        SelectionRectangle = NewAdjustedSelectionRectangle
230                    Else
231                        SelectionRectangle = NewSelectionRectangle
232                    End If
233                Case 2 ' Left Bottom
234                    P1 = New Point(ClientImageOrigin.X, ClientImageOrigin.Y + ClientImageExtent.Height)
235                    P2 = PointFromSourcePoint(New Point(SelectionRectangle.Right - MinimalSelectionSize.Width, SelectionRectangle.Top + MinimalSelectionSize.Height))
236                    ApplyPointConstraint(Position, New Point(P1.X, P2.Y), New Point(P2.X, P1.Y))
237                    SourcePosition = SourcePointFromPoint(Position)
238                    NewSelectionRectangle = Rectangle.FromLTRB(SourcePosition.X, SelectionRectangle.Top, SelectionRectangle.Right, SourcePosition.Y)
239                    If ApplyAspectRatio(NewSelectionRectangle, NewAdjustedSelectionRectangle) Then
240                        NewAdjustedSelectionRectangle.Offset(NewSelectionRectangle.Width - NewAdjustedSelectionRectangle.Width, 0)
241                        SelectionRectangle = NewAdjustedSelectionRectangle
242                    Else
243                        SelectionRectangle = NewSelectionRectangle
244                    End If
245                Case 3 ' Right Bottom
246                    P1 = PointFromSourcePoint(New Point(SelectionRectangle.Left + MinimalSelectionSize.Width, SelectionRectangle.Top + MinimalSelectionSize.Height))
247                    P2 = ClientImageOrigin + ClientImageExtent
248                    ApplyPointConstraint(Position, P1, P2)
249                    SourcePosition = SourcePointFromPoint(Position)
250                    NewSelectionRectangle = Rectangle.FromLTRB(SelectionRectangle.Left, SelectionRectangle.Top, SourcePosition.X, SourcePosition.Y)
251                    If ApplyAspectRatio(NewSelectionRectangle, NewAdjustedSelectionRectangle) Then
252                        'NewAdjustedSelectionRectangle.Offset(0, 0)
253                        SelectionRectangle = NewAdjustedSelectionRectangle
254                    Else
255                        SelectionRectangle = NewSelectionRectangle
256                    End If
257                Case 4 ' Move
258                    Dim Move As Size = SourcePointFromPoint(e.Location) - SourcePointFromPoint(MouseDownPosition)
259                    Dim PreSelection As Rectangle = MouseDownSelection
260                    PreSelection.Offset(Move)
261                    PreSelection.Offset(Math.Max(0, -PreSelection.Left), Math.Max(0, -PreSelection.Top))
262                    PreSelection.Offset(-Math.Max(0, PreSelection.Right - Image.Width), -Math.Max(0, PreSelection.Bottom - Image.Height))
263                    SelectionRectangle = PreSelection
264            End Select
265            Exit Sub
266        Else
267            If IsSpotPosition(ClientSelectionRectangle.Left, ClientSelectionRectangle.Top, e.X, e.Y) Then
268                Cursor = Cursors.SizeNWSE
269                Exit Sub
270            End If
271            If IsSpotPosition(ClientSelectionRectangle.Right, ClientSelectionRectangle.Top, e.X, e.Y) Then
272                Cursor = Cursors.SizeNESW
273                Exit Sub
274            End If
275            If IsSpotPosition(ClientSelectionRectangle.Left, ClientSelectionRectangle.Bottom, e.X, e.Y) Then
276                Cursor = Cursors.SizeNESW
277                Exit Sub
278            End If
279            If IsSpotPosition(ClientSelectionRectangle.Right, ClientSelectionRectangle.Bottom, e.X, e.Y) Then
280                Cursor = Cursors.SizeNWSE
281                Exit Sub
282            End If
283            If ClientSelectionRectangle.Contains(e.Location) Then
284                Cursor = Cursors.SizeAll
285                Exit Sub
286            End If
287        End If
288        Cursor = Cursors.Default
289    End Sub
290    Private Sub ImageCropBox_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
291        DragSpotIndex = -1
292        MouseDownSelection = SelectionRectangle
293        MouseDownPosition = e.Location
294        If IsSpotPosition(ClientSelectionRectangle.Left, ClientSelectionRectangle.Top, e.X, e.Y) Then
295            DragSpotIndex = 0
296            Capture = True
297            Exit Sub
298        End If
299        If IsSpotPosition(ClientSelectionRectangle.Right, ClientSelectionRectangle.Top, e.X, e.Y) Then
300            DragSpotIndex = 1
301            Capture = True
302            Exit Sub
303        End If
304        If IsSpotPosition(ClientSelectionRectangle.Left, ClientSelectionRectangle.Bottom, e.X, e.Y) Then
305            DragSpotIndex = 2
306            Capture = True
307            Exit Sub
308        End If
309        If IsSpotPosition(ClientSelectionRectangle.Right, ClientSelectionRectangle.Bottom, e.X, e.Y) Then
310            DragSpotIndex = 3
311            Capture = True
312            Exit Sub
313        End If
314        If ClientSelectionRectangle.Contains(e.Location) Then
315            DragSpotIndex = 4
316            Capture = True
317            Exit Sub
318        End If
319    End Sub
320    Private Sub ImageCropBox_MouseUp(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
321        Capture = False
322    End Sub
323    Private Sub ImageCropBox_MouseDoubleClick(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDoubleClick
324        If ClientSelectionRectangle.Contains(e.Location) Then
325            RaiseEvent SelectionDoubleClick(Me, New System.EventArgs)
326            Exit Sub
327        End If
328    End Sub
329End Class
Note: See TracBrowser for help on using the repository browser.