| 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 |