1 #Region "Microsoft.VisualBasic::7b82c5e4590aaa400a62e098ecc636f8, Microsoft.VisualBasic.Core\Extensions\Image\Bitmap\Effects.vb"
2
3     ' Author:
4     
5     '       asuka (amethyst.asuka@gcmodeller.org)
6     '       xie (genetics@smrucc.org)
7     '       xieguigang (xie.guigang@live.com)
8     
9     ' Copyright (c) 2018 GPL3 Licensed
10     
11     
12     ' GNU GENERAL PUBLIC LICENSE (GPL3)
13     
14     
15     ' This program is free software: you can redistribute it and/or modify
16     ' it under the terms of the GNU General Public License as published by
17     ' the Free Software Foundation, either version 3 of the License, or
18     ' (at your option) any later version.
19     
20     ' This program is distributed in the hope that it will be useful,
21     ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22     ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     ' GNU General Public License for more details.
24     
25     ' You should have received a copy of the GNU General Public License
26     ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30     ' /********************************************************************************/
31
32     ' Summaries:
33
34     '     Module Effects
35     
36     '         Function: RotateImage, Vignette
37     
38     
39     ' /********************************************************************************/
40
41 #End Region
42
43 Imports System.Drawing
44 Imports System.Math
45 Imports System.Runtime.CompilerServices
46 Imports Microsoft.VisualBasic.CommandLine.Reflection
47 Imports Microsoft.VisualBasic.Language
48 Imports sys = System.Math
49
50 Namespace Imaging.BitmapImage
51
52     Public Module Effects
53
54         ''' <summary>
55         ''' 羽化
56         ''' </summary>
57         ''' <param name="Image"></param>
58         ''' <param name="y1"></param>
59         ''' <param name="y2"></param>
60         ''' <returns></returns>
61         ''' <remarks></remarks>
62         <Extension> Public Function Vignette(image As Image, y1%, y2%, Optional renderColor As Color = NothingAs Image
63             Dim alpha As Integer = 0
64             Dim delta = (Math.PI / 2) / sys.Abs(y1 - y2)
65             Dim offset As Double = 0
66
67             renderColor = renderColor Or Color.White.AsDefaultColor
68
69             Using g As Graphics2D = image.CreateCanvas2D
70                 With g
71                     Dim rect As New Rectangle With {
72                         .Location = New Point(0, y2),
73                         .Size = New Size(.Width, .Height - y2)
74                     }
75
76                     For y As Integer = y1 To y2
77                         Dim color As Color = Color.FromArgb(alpha, renderColor.R, renderColor.G, renderColor.B)
78                         Dim pen As New Pen(color)
79
80                         .DrawLine(pen, New Point(0, y), New Point(.Width, y))
81                         alpha = CInt(255 * sys.Sin(offset) ^ 2)
82                         offset += delta
83                     Next
84
85                     Call .FillRectangle(New SolidBrush(renderColor), rect)
86
87                     Return .ImageResource
88                 End With
89             End Using
90         End Function
91
92         Const pi2 As Double = PI / 2.0
93
94         ''' <summary>
95         ''' Creates a new Image containing the same image only rotated
96         ''' </summary>
97         ''' <param name="image">The <see cref="System.Drawing.Image"/> to rotate</param>
98         ''' <param name="angle">The amount to rotate the image, clockwise, in degrees</param>
99         ''' <returns>A new <see cref="System.Drawing.Bitmap"/> that is just large enough
100         ''' to contain the rotated image without cutting any corners off.</returns>
101         ''' <exception cref="System.ArgumentNullException">Thrown if <see cref="image"/> is null.</exception>
102         ''' <remarks>
103         ''' 
104         ''' Explaination of the calculations
105         '''
106         ''' The trig involved in calculating the new width and height
107         ''' is fairly simple; the hard part was remembering that when 
108         ''' PI/2 &lt;= theta &lt;= PI and 3PI/2 &lt;= theta &lt; 2PI the width and 
109         ''' height are switched.
110         '''  
111         ''' When you rotate a rectangle, r, the bounding box surrounding r
112         ''' contains for right-triangles of empty space.  Each of the 
113         ''' triangles hypotenuse's are a known length, either the width or
114         ''' the height of r.  Because we know the length of the hypotenuse
115         ''' and we have a known angle of rotation, we can use the trig
116         ''' function identities to find the length of the other two sides.
117         '''  
118         ''' sine = opposite/hypotenuse
119         ''' cosine = adjacent/hypotenuse
120         '''  
121         ''' solving for the unknown we get
122         '''  
123         ''' opposite = sine * hypotenuse
124         ''' adjacent = cosine * hypotenuse
125         '''  
126         ''' Another interesting point about these triangles is that there
127         ''' are only two different triangles. The proof for which is easy
128         ''' to see, but its been too long since I've written a proof that
129         ''' I can't explain it well enough to want to publish it.  
130         '''  
131         ''' Just trust me when I say the triangles formed by the lengths 
132         ''' width are always the same (for a given theta) and the same 
133         ''' goes for the height of r.
134         '''  
135         ''' Rather than associate the opposite/adjacent sides with the
136         ''' width and height of the original bitmap, I'll associate them
137         ''' based on their position.
138         '''  
139         ''' adjacent/oppositeTop will refer to the triangles making up the 
140         ''' upper right and lower left corners
141         '''  
142         ''' adjacent/oppositeBottom will refer to the triangles making up 
143         ''' the upper left and lower right corners
144         '''  
145         ''' The names are based on the right side corners, because thats 
146         ''' where I did my work on paper (the right side).
147         '''  
148         ''' Now if you draw this out, you will see that the width of the 
149         ''' bounding box is calculated by adding together adjacentTop and 
150         ''' oppositeBottom while the height is calculate by adding 
151         ''' together adjacentBottom and oppositeTop.
152         ''' 
153         ''' </remarks>
154         <ExportAPI("Image.Rotate"Info:="Creates a new Image containing the same image only rotated.")>
155         <Extension> Public Function RotateImage(image As Image, angle!) As Bitmap
156             If image Is Nothing Then
157                 Throw New ArgumentNullException("image value is nothing!")
158             End If
159
160             Dim oldWidth As Double = CDbl(image.Width)
161             Dim oldHeight As Double = CDbl(image.Height)
162
163             ' Convert degrees to radians
164             Dim theta As Double = CDbl(angle) * sys.PI / 180.0
165             Dim locked_theta As Double = theta
166
167             ' Ensure theta is now [0, 2pi)
168             While locked_theta < 0.0
169                 locked_theta += 2 * sys.PI
170             End While
171
172             Dim newWidth As Double, newHeight As Double
173             Dim nWidth As Integer, nHeight As Integer        ' The newWidth/newHeight expressed as ints
174
175             Dim adjacentTop As Double, oppositeTop As Double
176             Dim adjacentBottom As Double, oppositeBottom As Double
177
178             ' We need to calculate the sides of the triangles based
179             ' on how much rotation is being done to the bitmap.
180             '   Refer to the first paragraph in the explaination above for 
181             '   reasons why.
182             If (locked_theta >= 0.0 AndAlso locked_theta < pi2) OrElse (locked_theta >= sys.PI AndAlso locked_theta < (Math.PI + pi2)) Then
183                 adjacentTop = sys.Abs(Cos(locked_theta)) * oldWidth
184                 oppositeTop = sys.Abs(Sin(locked_theta)) * oldWidth
185
186                 adjacentBottom = sys.Abs(Cos(locked_theta)) * oldHeight
187                 oppositeBottom = sys.Abs(Sin(locked_theta)) * oldHeight
188             Else
189                 adjacentTop = sys.Abs(Sin(locked_theta)) * oldHeight
190                 oppositeTop = sys.Abs(Cos(locked_theta)) * oldHeight
191
192                 adjacentBottom = sys.Abs(Sin(locked_theta)) * oldWidth
193                 oppositeBottom = sys.Abs(Cos(locked_theta)) * oldWidth
194             End If
195
196             newWidth = adjacentTop + oppositeBottom
197             newHeight = adjacentBottom + oppositeTop
198
199             nWidth = CInt(Truncate(Ceiling(newWidth)))
200             nHeight = CInt(Truncate(Ceiling(newHeight)))
201
202             Dim rotatedBmp As New Bitmap(nWidth, nHeight)
203
204             ' This array will be used to pass in the three points that 
205             ' make up the rotated image
206             Dim points As Point()
207
208             ' The values of opposite/adjacentTop/Bottom are referring to 
209             ' fixed locations instead of in relation to the
210             ' rotating image so I need to change which values are used
211             ' based on the how much the image is rotating.
212
213             For each point, one of the coordinates will always be 0, 
214             ' nWidth, or nHeight.  This because the Bitmap we are drawing on
215             ' is the bounding box for the rotated bitmap.  If both of the 
216             ' corrdinates for any of the given points wasn't in the set above
217             ' then the bitmap we are drawing on WOULDN'T be the bounding box
218             ' as required.
219
220             If locked_theta >= 0.0 AndAlso locked_theta < pi2 Then
221
222                 points = {
223                     New Point(CInt(Truncate(oppositeBottom)), 0),
224                     New Point(nWidth, CInt(Truncate(oppositeTop))),
225                     New Point(0, CInt(Truncate(adjacentBottom)))
226                 }
227
228             ElseIf locked_theta >= pi2 AndAlso locked_theta < sys.PI Then
229
230                 points = {
231                     New Point(nWidth, CInt(Truncate(oppositeTop))),
232                     New Point(CInt(Truncate(adjacentTop)), nHeight),
233                     New Point(CInt(Truncate(oppositeBottom)), 0)
234                 }
235
236             ElseIf locked_theta >= sys.PI AndAlso locked_theta < (Math.PI + pi2) Then
237
238                 points = {
239                     New Point(CInt(Truncate(adjacentTop)), nHeight),
240                     New Point(0, CInt(Truncate(adjacentBottom))),
241                     New Point(nWidth, CInt(Truncate(oppositeTop)))
242                 }
243
244             Else
245
246                 points = {
247                     New Point(0, CInt(Truncate(adjacentBottom))),
248                     New Point(CInt(Truncate(oppositeBottom)), 0),
249                     New Point(CInt(Truncate(adjacentTop)), nHeight)
250                 }
251
252             End If
253
254             Using g As Graphics = Graphics.FromImage(rotatedBmp)
255                 Call g.DrawImage(image, points)
256             End Using
257
258             Return rotatedBmp
259         End Function
260     End Module
261 End Namespace