1 #Region "Microsoft.VisualBasic::06ec40e2ebe79f96f8ab8b90ec897f42, 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 lockedTheta As Double = theta
166
167             ' Ensure theta is now [0, 2pi)
168             While lockedTheta < 0.0
169                 lockedTheta += 2 * sys.PI
170             End While
171
172             Dim newWidth As Double, newHeight As Double
173             ' The newWidth/newHeight expressed as ints
174             Dim nWidth As Integer, nHeight As Integer
175
176             Dim adjacentTop As Double, oppositeTop As Double
177             Dim adjacentBottom As Double, oppositeBottom As Double
178
179             ' We need to calculate the sides of the triangles based
180             ' on how much rotation is being done to the bitmap.
181             '   Refer to the first paragraph in the explaination above for 
182             '   reasons why.
183             If (lockedTheta >= 0.0 AndAlso lockedTheta < pi2) OrElse (lockedTheta >= sys.PI AndAlso lockedTheta < (Math.PI + pi2)) Then
184                 adjacentTop = sys.Abs(Cos(lockedTheta)) * oldWidth
185                 oppositeTop = sys.Abs(Sin(lockedTheta)) * oldWidth
186
187                 adjacentBottom = sys.Abs(Cos(lockedTheta)) * oldHeight
188                 oppositeBottom = sys.Abs(Sin(lockedTheta)) * oldHeight
189             Else
190                 adjacentTop = sys.Abs(Sin(lockedTheta)) * oldHeight
191                 oppositeTop = sys.Abs(Cos(lockedTheta)) * oldHeight
192
193                 adjacentBottom = sys.Abs(Sin(lockedTheta)) * oldWidth
194                 oppositeBottom = sys.Abs(Cos(lockedTheta)) * oldWidth
195             End If
196
197             newWidth = adjacentTop + oppositeBottom
198             newHeight = adjacentBottom + oppositeTop
199
200             nWidth = CInt(Truncate(Ceiling(newWidth)))
201             nHeight = CInt(Truncate(Ceiling(newHeight)))
202
203             Dim rotatedBmp As New Bitmap(nWidth, nHeight)
204
205             ' This array will be used to pass in the three points that 
206             ' make up the rotated image
207             Dim points As Point()
208
209             ' The values of opposite/adjacentTop/Bottom are referring to 
210             ' fixed locations instead of in relation to the
211             ' rotating image so I need to change which values are used
212             ' based on the how much the image is rotating.
213
214             For each point, one of the coordinates will always be 0, 
215             ' nWidth, or nHeight.  This because the Bitmap we are drawing on
216             ' is the bounding box for the rotated bitmap.  If both of the 
217             ' corrdinates for any of the given points wasn't in the set above
218             ' then the bitmap we are drawing on WOULDN'T be the bounding box
219             ' as required.
220
221             If lockedTheta >= 0.0 AndAlso lockedTheta < pi2 Then
222
223                 points = {
224                     New Point(CInt(Truncate(oppositeBottom)), 0),
225                     New Point(nWidth, CInt(Truncate(oppositeTop))),
226                     New Point(0, CInt(Truncate(adjacentBottom)))
227                 }
228
229             ElseIf lockedTheta >= pi2 AndAlso lockedTheta < sys.PI Then
230
231                 points = {
232                     New Point(nWidth, CInt(Truncate(oppositeTop))),
233                     New Point(CInt(Truncate(adjacentTop)), nHeight),
234                     New Point(CInt(Truncate(oppositeBottom)), 0)
235                 }
236
237             ElseIf lockedTheta >= sys.PI AndAlso lockedTheta < (Math.PI + pi2) Then
238
239                 points = {
240                     New Point(CInt(Truncate(adjacentTop)), nHeight),
241                     New Point(0, CInt(Truncate(adjacentBottom))),
242                     New Point(nWidth, CInt(Truncate(oppositeTop)))
243                 }
244
245             Else
246
247                 points = {
248                     New Point(0, CInt(Truncate(adjacentBottom))),
249                     New Point(CInt(Truncate(oppositeBottom)), 0),
250                     New Point(CInt(Truncate(adjacentTop)), nHeight)
251                 }
252
253             End If
254
255             Using g As Graphics = Graphics.FromImage(rotatedBmp)
256                 Call g.DrawImage(image, points)
257             End Using
258
259             Return rotatedBmp
260         End Function
261     End Module
262 End Namespace