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 = Nothing) As 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 <= theta <= PI and 3PI/2 <= theta < 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 |