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

Last change on this file since 937 was 120, checked in by roman, 9 years ago
File size: 19.1 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        Dim MinimalSelectionSize = EffectiveMinimalSelectionSize
38        If SelectionRectangle.Width < MinimalSelectionSize.Width Then
39            SelectionRectangle.Width = MinimalSelectionSize.Width
40            If _Image IsNot Nothing AndAlso SelectionRectangle.Right > _Image.Width Then SelectionRectangle.X = Math.Max(0, _Image.Width - SelectionRectangle.Width)
41        End If
42        If SelectionRectangle.Height < MinimalSelectionSize.Height Then
43            SelectionRectangle.Height = MinimalSelectionSize.Height
44            If _Image IsNot Nothing AndAlso SelectionRectangle.Bottom > _Image.Height Then SelectionRectangle.Y = Math.Max(0, _Image.Height - SelectionRectangle.Height)
45        End If
46        ApplyMinimalSelection = SelectionRectangle
47    End Function
48    Private Function ApplyAspectRatio(ByVal SelectionRectangle As Rectangle, ByRef AdjustedSelectionRectangle As Rectangle) As Boolean
49        AdjustedSelectionRectangle = SelectionRectangle
50        ApplyAspectRatio = False
51        If Not _PreserveAspectRatio Then Exit Function
52        If _Image Is Nothing Then Exit Function
53        Debug.Assert(_Image.Height > 0 AndAlso _Image.Width > 0)
54        If SelectionRectangle.Width <= 0 Or SelectionRectangle.Height <= 0 Then Exit Function
55        Dim AdjustedHeight As Integer = Math.Round(SelectionRectangle.Width * _Image.Height / _Image.Width)
56        Dim AdjustedWidth As Integer = Math.Round(SelectionRectangle.Height * _Image.Width / _Image.Height)
57        Debug.Assert(AdjustedWidth >= SelectionRectangle.Width Or AdjustedHeight >= SelectionRectangle.Height)
58        If AdjustedWidth < SelectionRectangle.Width Then
59            AdjustedSelectionRectangle.Width = AdjustedWidth
60            ApplyAspectRatio = True
61        ElseIf AdjustedHeight < SelectionRectangle.Height Then
62            AdjustedSelectionRectangle.Height = AdjustedHeight
63            ApplyAspectRatio = True
64        End If
65    End Function
66    Private Function ApplyAspectRatioAndCenterRectangle(ByVal SelectionRectangle As Rectangle) As Rectangle
67        Dim AdjustedSelectionRectangle As Rectangle
68        If ApplyAspectRatio(SelectionRectangle, AdjustedSelectionRectangle) Then
69            AdjustedSelectionRectangle.Offset((SelectionRectangle.Width - AdjustedSelectionRectangle.Width) / 2, (SelectionRectangle.Height - AdjustedSelectionRectangle.Height) / 2)
70            ApplyAspectRatioAndCenterRectangle = AdjustedSelectionRectangle
71        Else
72            ApplyAspectRatioAndCenterRectangle = SelectionRectangle
73        End If
74    End Function
75
76    Public Property Image As Bitmap
77        Get
78            Image = _Image
79        End Get
80        Set(value As Bitmap)
81            _Image = value
82            If _Image IsNot Nothing Then
83                SelectionRectangle = New Rectangle(value.Width * 1 / 16, value.Height * 1 / 16, value.Width * 14 / 16, value.Height * 14 / 16)
84                'SelectionRectangle = New Rectangle(0, 0, value.Width, value.Height)
85                _FadeImage = _Image.Clone
86                Dim FadeImageGraphics As Graphics = Graphics.FromImage(_FadeImage)
87                Dim FadeBrush As HatchBrush = New HatchBrush(HatchStyle.Percent25, Color.FromArgb(0, Color.Black), Color.FromArgb(255, Color.Black))
88                FadeImageGraphics.FillRectangle(FadeBrush, 0, 0, _FadeImage.Width, _FadeImage.Height)
89                FadeImageGraphics.Save()
90            Else
91                _FadeImage = Nothing
92            End If
93            Update()
94        End Set
95    End Property
96    Public Property MinimalSelectionSize As Size
97        Get
98            MinimalSelectionSize = _MinimalSelectionSize
99        End Get
100        Set(value As Size)
101            Debug.Assert(value.Width >= 0 And value.Height >= 0)
102            _MinimalSelectionSize = value
103            ' TODO: Update SelectionRectangle
104        End Set
105    End Property
106    Private ReadOnly Property EffectiveMinimalSelectionSize As Size
107        Get
108            EffectiveMinimalSelectionSize = MinimalSelectionSize
109            If MinimalSelectionSize.Width > 0 And MinimalSelectionSize.Height > 0 And PreserveAspectRatio And Image IsNot Nothing Then
110                Dim Position = New Rectangle(0, 0, MinimalSelectionSize.Width, MinimalSelectionSize.Height)
111                Dim AdjustedPositon As Rectangle
112                If ApplyAspectRatio(Position, AdjustedPositon) Then EffectiveMinimalSelectionSize = AdjustedPositon.Size
113            End If
114        End Get
115    End Property
116    Public Property SelectionRectangle As Rectangle
117        Get
118            SelectionRectangle = _SelectionRectangle
119        End Get
120        Set(value As Rectangle)
121            Dim EffectiveSelectionRectangle As Rectangle = ApplyMinimalSelection(value)
122            If PreserveAspectRatio Then EffectiveSelectionRectangle = ApplyAspectRatioAndCenterRectangle(EffectiveSelectionRectangle)
123            Dim SelectionRectangleChanged As Boolean = _SelectionRectangle <> EffectiveSelectionRectangle
124            _SelectionRectangle = EffectiveSelectionRectangle
125            RaiseEvent SelectionRectangleChanged(Me, New System.EventArgs())
126            If SelectionRectangleChanged Then Invalidate()
127        End Set
128    End Property
129    Public Property PreserveAspectRatio As Boolean
130        Get
131            PreserveAspectRatio = _PreserveAspectRatio
132        End Get
133        Set(value As Boolean)
134            If _PreserveAspectRatio = value Then Exit Property
135            _PreserveAspectRatio = value
136            If _PreserveAspectRatio Then SelectionRectangle = ApplyAspectRatioAndCenterRectangle(_SelectionRectangle)
137        End Set
138    End Property
139
140    Private Sub ImageCropBox_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
141        BoundaryPenA = New Pen(Brushes.White)
142        BoundaryPenA.Width = 1
143        BoundaryPenA.DashPattern = New Single() {8.0F, 4.0F}
144        BoundaryPenB = New Pen(Brushes.Black)
145        BoundaryPenB.Width = 1
146        BoundaryPenB.DashPattern = New Single() {4.0F, 8.0F}
147        BoundaryPenB.DashOffset = 4
148        SpotBrush = Brushes.Black
149        SpotPen = New Pen(Brushes.White)
150        SpotPen.Width = 1
151        ResizeRedraw = True
152    End Sub
153    Private Sub DrawSpot(Graphics As Graphics, Position As Point)
154        Graphics.FillRectangle(SpotBrush, Position.X - CInt(SpotSize / 2) - 1, Position.Y - CInt(SpotSize / 2) - 1, SpotSize + 2, SpotSize + 2)
155        Graphics.DrawRectangle(SpotPen, Position.X - CInt(SpotSize / 2), Position.Y - CInt(SpotSize / 2), SpotSize, SpotSize)
156    End Sub
157    Private Sub DrawSpot(Graphics As Graphics, PositionX As Integer, PositionY As Integer)
158        DrawSpot(Graphics, New Point(PositionX, PositionY))
159    End Sub
160    Private Sub ImageCropBox_Paint(sender As System.Object, e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
161        If Image Is Nothing Then Exit Sub
162        Dim Extent = Me.Size
163        Extent.Width -= Spacing + SpotSize + Spacing
164        Extent.Height -= Spacing + SpotSize + Spacing
165        If Extent.Width <= 0 Or Extent.Height <= 0 Then Exit Sub
166        Debug.Assert(Image.Width > 0 AndAlso Image.Height > 0)
167        Dim Ratio As Double = Math.Max(Image.Width / Extent.Width, Image.Height / Extent.Height)
168        If Ratio > 1 And Ratio < 1.1 Then Ratio = 1.0 ' Snap to 100%
169        ClientImageExtent.Width = Math.Round(Image.Width / Ratio)
170        ClientImageExtent.Height = Math.Round(Image.Height / Ratio)
171        ClientImageOrigin.X = Spacing + SpotSize / 2 + (Extent.Width - ClientImageExtent.Width) / 2
172        ClientImageOrigin.Y = Spacing + SpotSize / 2 + (Extent.Height - ClientImageExtent.Height) / 2
173        e.Graphics.DrawImage(_FadeImage, ClientImageOrigin.X, ClientImageOrigin.Y, ClientImageExtent.Width, ClientImageExtent.Height)
174        ClientSelectionRectangle.X = Math.Round(_SelectionRectangle.Left / Ratio)
175        ClientSelectionRectangle.Y = Math.Round(_SelectionRectangle.Top / Ratio)
176        ClientSelectionRectangle.Width = Math.Round(_SelectionRectangle.Right / Ratio) - ClientSelectionRectangle.X
177        ClientSelectionRectangle.Height = Math.Round(_SelectionRectangle.Bottom / Ratio) - ClientSelectionRectangle.Y
178        ClientSelectionRectangle.Offset(ClientImageOrigin)
179        e.Graphics.SetClip(ClientSelectionRectangle)
180        e.Graphics.DrawImage(_Image, ClientImageOrigin.X, ClientImageOrigin.Y, ClientImageExtent.Width, ClientImageExtent.Height)
181        e.Graphics.ResetClip()
182        e.Graphics.DrawRectangle(BoundaryPenA, ClientSelectionRectangle)
183        e.Graphics.DrawRectangle(BoundaryPenB, ClientSelectionRectangle)
184        DrawSpot(e.Graphics, ClientSelectionRectangle.Left, ClientSelectionRectangle.Top)
185        DrawSpot(e.Graphics, ClientSelectionRectangle.Right, ClientSelectionRectangle.Top)
186        DrawSpot(e.Graphics, ClientSelectionRectangle.Left, ClientSelectionRectangle.Bottom)
187        DrawSpot(e.Graphics, ClientSelectionRectangle.Right, ClientSelectionRectangle.Bottom)
188    End Sub
189    Private Function SourcePointFromPoint(Position As Point) As Point
190        Dim SourcePosition As Point
191        SourcePosition.X = Math.Round((Position.X - ClientImageOrigin.X) * Image.Width / ClientImageExtent.Width)
192        SourcePosition.Y = Math.Round((Position.Y - ClientImageOrigin.Y) * Image.Height / ClientImageExtent.Height)
193        SourcePointFromPoint = SourcePosition
194    End Function
195    Private Function PointFromSourcePoint(SourcePosition As Point) As Point
196        Dim Position As Point
197        Position.X = ClientImageOrigin.X + Math.Round(SourcePosition.X * ClientImageExtent.Width / Image.Width)
198        Position.Y = ClientImageOrigin.Y + Math.Round(SourcePosition.Y * ClientImageExtent.Height / Image.Height)
199        PointFromSourcePoint = Position
200    End Function
201    Private Function IsSpotPosition(SpotPosition As Point, Position As Point) As Boolean
202        Dim SpotPositionEx = New Rectangle(SpotPosition.X - SpotSize / 2, SpotPosition.Y - SpotSize / 2, SpotSize, SpotSize)
203        IsSpotPosition = SpotPositionEx.Contains(Position)
204    End Function
205    Private Function IsSpotPosition(SpotPositionX As Integer, SpotPositionY As Integer, PositionX As Integer, PositionY As Integer) As Boolean
206        IsSpotPosition = IsSpotPosition(New Point(SpotPositionX, SpotPositionY), New Point(PositionX, PositionY))
207    End Function
208    Private Sub ApplyPointConstraint(ByRef Position As Point, P1 As Point, P2 As Point)
209        If Position.X > P2.X Then Position.X = P2.X
210        If Position.Y > P2.Y Then Position.Y = P2.Y
211        If Position.X < P1.X Then Position.X = P1.X
212        If Position.Y < P1.Y Then Position.Y = P1.Y
213    End Sub
214    Private Sub ImageCropBox_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
215        Dim Position As Point = e.Location, SourcePosition As Point
216        If Capture Then
217            Dim MinimalSelectionSize = EffectiveMinimalSelectionSize
218            Dim P1 As Point, P2 As Point
219            Dim NewSelectionRectangle As Rectangle
220            Select Case DragSpotIndex
221                Case 0 ' Left Top
222                    P1 = ClientImageOrigin
223                    P2 = PointFromSourcePoint(New Point(SelectionRectangle.Right - MinimalSelectionSize.Width, SelectionRectangle.Bottom - MinimalSelectionSize.Height))
224                    ApplyPointConstraint(Position, P1, P2)
225                    SourcePosition = SourcePointFromPoint(Position)
226                    NewSelectionRectangle = Rectangle.FromLTRB(SourcePosition.X, SourcePosition.Y, SelectionRectangle.Right, SelectionRectangle.Bottom)
227                    NewSelectionRectangle = ApplyMinimalSelection(ApplyAspectRatioAndCenterRectangle(NewSelectionRectangle))
228                    NewSelectionRectangle.Offset(SelectionRectangle.Right - NewSelectionRectangle.Right, SelectionRectangle.Bottom - NewSelectionRectangle.Bottom)
229                    SelectionRectangle = NewSelectionRectangle
230                Case 1 ' Right Top
231                    P1 = New Point(ClientImageOrigin.X + ClientImageExtent.Width, ClientImageOrigin.Y)
232                    P2 = PointFromSourcePoint(New Point(SelectionRectangle.Left + MinimalSelectionSize.Width, SelectionRectangle.Bottom - MinimalSelectionSize.Height))
233                    ApplyPointConstraint(Position, New Point(P2.X, P1.Y), New Point(P1.X, P2.Y))
234                    SourcePosition = SourcePointFromPoint(Position)
235                    NewSelectionRectangle = Rectangle.FromLTRB(SelectionRectangle.Left, SourcePosition.Y, SourcePosition.X, SelectionRectangle.Bottom)
236                    NewSelectionRectangle = ApplyMinimalSelection(ApplyAspectRatioAndCenterRectangle(NewSelectionRectangle))
237                    NewSelectionRectangle.Offset(SelectionRectangle.Left - NewSelectionRectangle.Left, SelectionRectangle.Bottom - NewSelectionRectangle.Bottom)
238                    SelectionRectangle = NewSelectionRectangle
239                Case 2 ' Left Bottom
240                    P1 = New Point(ClientImageOrigin.X, ClientImageOrigin.Y + ClientImageExtent.Height)
241                    P2 = PointFromSourcePoint(New Point(SelectionRectangle.Right - MinimalSelectionSize.Width, SelectionRectangle.Top + MinimalSelectionSize.Height))
242                    ApplyPointConstraint(Position, New Point(P1.X, P2.Y), New Point(P2.X, P1.Y))
243                    SourcePosition = SourcePointFromPoint(Position)
244                    NewSelectionRectangle = Rectangle.FromLTRB(SourcePosition.X, SelectionRectangle.Top, SelectionRectangle.Right, SourcePosition.Y)
245                    NewSelectionRectangle = ApplyMinimalSelection(ApplyAspectRatioAndCenterRectangle(NewSelectionRectangle))
246                    NewSelectionRectangle.Offset(SelectionRectangle.Right - NewSelectionRectangle.Right, SelectionRectangle.Top - NewSelectionRectangle.Top)
247                    SelectionRectangle = NewSelectionRectangle
248                Case 3 ' Right Bottom
249                    P1 = PointFromSourcePoint(New Point(SelectionRectangle.Left + MinimalSelectionSize.Width, SelectionRectangle.Top + MinimalSelectionSize.Height))
250                    P2 = ClientImageOrigin + ClientImageExtent
251                    ApplyPointConstraint(Position, P1, P2)
252                    SourcePosition = SourcePointFromPoint(Position)
253                    NewSelectionRectangle = Rectangle.FromLTRB(SelectionRectangle.Left, SelectionRectangle.Top, SourcePosition.X, SourcePosition.Y)
254                    NewSelectionRectangle = ApplyMinimalSelection(ApplyAspectRatioAndCenterRectangle(NewSelectionRectangle))
255                    NewSelectionRectangle.Offset(SelectionRectangle.Left - NewSelectionRectangle.Left, SelectionRectangle.Top - NewSelectionRectangle.Top)
256                    SelectionRectangle = NewSelectionRectangle
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.