1 | #Region "Microsoft.VisualBasic::cbafd53bea044bb84eae18620f9e2624, Microsoft.VisualBasic.Core\Extensions\Image\Bitmap\Utils.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 Utils |
35 | ' |
36 | ' Function: (+2 Overloads) ColorReplace, CorpBlank, (+3 Overloads) ImageCrop, Resize, ResizeUnscaled |
37 | ' ResizeUnscaledByHeight, TrimRoundAvatar |
38 | ' |
39 | ' |
40 | ' /********************************************************************************/ |
41 | |
42 | #End Region |
43 | |
44 | Imports System.Drawing |
45 | Imports System.Drawing.Drawing2D |
46 | Imports System.Drawing.Text |
47 | Imports System.Runtime.CompilerServices |
48 | Imports Microsoft.VisualBasic.CommandLine.Reflection |
49 | Imports Microsoft.VisualBasic.Serialization.JSON |
50 | |
51 | Namespace Imaging.BitmapImage |
52 | |
53 | ''' <summary> |
54 | ''' Tools function for processing on <see cref="Image"/>/<see cref="Bitmap"/> |
55 | ''' </summary> |
56 | Public Module Utils |
57 | |
58 | ''' <summary> |
59 | ''' 图片剪裁小方块区域 |
60 | ''' </summary> |
61 | ''' <param name="pos">左上角的坐标位置</param> |
62 | ''' <param name="size">剪裁的区域的大小</param> |
63 | ''' <returns></returns> |
64 | ''' <remarks></remarks> |
65 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
66 | <ExportAPI("Image.Corp")> |
67 | <Extension> Public Function ImageCrop(source As Image, pos As Point, size As Size) As Image |
68 | Return source.ImageCrop(New Rectangle(pos, size)) |
69 | End Function |
70 | |
71 | <Extension> |
72 | Public Function ImageCrop(source As Image, rect As Rectangle) As Image |
73 | SyncLock source |
74 | With CType(source.Clone, Bitmap) |
75 | Try |
76 | Return .Clone(rect, source.PixelFormat) |
77 | Catch ex As Exception |
78 | Throw New InvalidExpressionException($"Image size: {source.Size.ToString} AND clone rectangle: {rect.ToString}") |
79 | End Try |
80 | End With |
81 | End SyncLock |
82 | End Function |
83 | |
84 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
85 | <Extension> |
86 | Public Function ImageCrop(source As Bitmap, rect As RectangleF) As Bitmap |
87 | Return source.Clone(rect, source.PixelFormat) |
88 | End Function |
89 | |
90 | <ExportAPI("Image.Resize")> |
91 | <Extension> |
92 | Public Function Resize(Image As Image, newSize As Size) As Image |
93 | SyncLock Image |
94 | ' 在这里不适用默认的白色做填充,而是使用透明色来进行填充 |
95 | ' 因为图片可能会是透明的,使用默认的白色填充会导致结果图片失去了透明 |
96 | Using g As Graphics2D = newSize.CreateGDIDevice(Color.Transparent) |
97 | With g |
98 | Call .DrawImage(Image, 0, 0, newSize.Width, newSize.Height) |
99 | Return .ImageResource |
100 | End With |
101 | End Using |
102 | End SyncLock |
103 | End Function |
104 | |
105 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
106 | <Extension> |
107 | Public Function ResizeUnscaled(image As Image, width%) As Image |
108 | Return image.Resize(New Size(width, image.Height * (width / image.Width))) |
109 | End Function |
110 | |
111 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
112 | <Extension> |
113 | Public Function ResizeUnscaledByHeight(image As Image, height%) As Image |
114 | Return image.Resize(New Size(image.Width * (height / image.Height), height)) |
115 | End Function |
116 | |
117 | ''' <summary> |
118 | ''' 图片剪裁为圆形的头像 |
119 | ''' </summary> |
120 | ''' <param name="resAvatar">要求为正方形或者近似正方形</param> |
121 | ''' <param name="OutSize"></param> |
122 | ''' <returns></returns> |
123 | ''' <remarks></remarks> |
124 | <Extension> Public Function TrimRoundAvatar(resAvatar As Image, OutSize As Integer) As Image |
125 | If resAvatar Is Nothing Then |
126 | Return Nothing |
127 | End If |
128 | |
129 | SyncLock resAvatar |
130 | Dim Bitmap As New Bitmap(OutSize, OutSize) |
131 | |
132 | resAvatar = DirectCast(resAvatar.Clone, Image) |
133 | resAvatar = Resize(resAvatar, Bitmap.Size) |
134 | |
135 | Using g = Graphics.FromImage(Bitmap) |
136 | Dim image As New TextureBrush(resAvatar) |
137 | |
138 | With g |
139 | .CompositingQuality = CompositingQuality.HighQuality |
140 | .InterpolationMode = InterpolationMode.HighQualityBicubic |
141 | .SmoothingMode = SmoothingMode.HighQuality |
142 | .TextRenderingHint = TextRenderingHint.ClearTypeGridFit |
143 | |
144 | Call .FillPie(image, Bitmap.EntireImage, 0, 360) |
145 | End With |
146 | |
147 | Return Bitmap |
148 | End Using |
149 | End SyncLock |
150 | End Function |
151 | |
152 | ''' <summary> |
153 | ''' 将图像的多余的空白处给剪裁掉,确定边界,然后进行剪裁,使用这个函数需要注意下设置空白色,默认使用的空白色为<see cref="Color.White"/> |
154 | ''' </summary> |
155 | ''' <param name="res"></param> |
156 | ''' <param name="margin"></param> |
157 | ''' <param name="blankColor">默认白色为空白色</param> |
158 | ''' <returns></returns> |
159 | <ExportAPI("Image.CorpBlank")> |
160 | <Extension> Public Function CorpBlank(res As Image, |
161 | Optional margin% = 0, |
162 | Optional blankColor As Color = Nothing, |
163 | <CallerMemberName> |
164 | Optional trace$ = Nothing) As Image |
165 | |
166 | If blankColor.IsNullOrEmpty Then |
167 | blankColor = Color.White |
168 | ElseIf blankColor.Name = NameOf(Color.Transparent) Then |
169 | ' 系统的transparent颜色为 0,255,255,255 |
170 | ' 但是bitmap之中的transparent为 0,0,0,0 |
171 | ' 在这里要变换一下 |
172 | blankColor = New Color |
173 | End If |
174 | |
175 | Dim bmp As BitmapBuffer |
176 | Dim top%, left% |
177 | |
178 | Try |
179 | bmp = BitmapBuffer.FromImage(res) |
180 | Catch ex As Exception |
181 | |
182 | ' 2017-9-21 ??? |
183 | ' 未经处理的异常: |
184 | ' System.ArgumentException: 参数无效。 |
185 | ' 在 System.Drawing.Bitmap..ctor(Int32 width, Int32 height, PixelFormat format) |
186 | ' 在 System.Drawing.Bitmap..ctor(Image original, Int32 width, Int32 height) |
187 | ' 在 System.Drawing.Bitmap..ctor(Image original) |
188 | |
189 | ex = New Exception(trace & " --> " & res.Size.GetJson, ex) |
190 | Throw ex |
191 | End Try |
192 | |
193 | ' top |
194 | For top = 0 To res.Height - 1 |
195 | Dim find As Boolean = False |
196 | |
197 | For left = 0 To res.Width - 1 |
198 | Dim p = bmp.GetPixel(left, top) |
199 | |
200 | If Not GDIColors.Equals(p, blankColor) Then |
201 | ' 在这里确定了左右 |
202 | find = True |
203 | |
204 | If top > 0 Then |
205 | top -= 1 |
206 | End If |
207 | |
208 | Exit For |
209 | End If |
210 | Next |
211 | |
212 | If find Then |
213 | Exit For |
214 | End If |
215 | Next |
216 | |
217 | Dim region As New Rectangle(0, top, res.Width, res.Height - top) |
218 | res = res.ImageCrop(region.Location, region.Size) |
219 | bmp = BitmapBuffer.FromImage(res) |
220 | |
221 | ' left |
222 | For left = 0 To res.Width - 1 |
223 | Dim find As Boolean = False |
224 | |
225 | For top = 0 To res.Height - 1 |
226 | Dim p = bmp.GetPixel(left, top) |
227 | If Not GDIColors.Equals(p, blankColor) Then |
228 | ' 在这里确定了左右 |
229 | find = True |
230 | |
231 | If left > 0 Then |
232 | left -= 1 |
233 | End If |
234 | |
235 | Exit For |
236 | End If |
237 | Next |
238 | |
239 | If find Then |
240 | Exit For |
241 | End If |
242 | Next |
243 | |
244 | region = New Rectangle(left, 0, res.Width - left, res.Height) |
245 | res = res.ImageCrop(region.Location, region.Size) |
246 | bmp = BitmapBuffer.FromImage(res) |
247 | |
248 | Dim right As Integer |
249 | Dim bottom As Integer |
250 | |
251 | ' bottom |
252 | For bottom = res.Height - 1 To 0 Step -1 |
253 | Dim find As Boolean = False |
254 | |
255 | For right = res.Width - 1 To 0 Step -1 |
256 | Dim p = bmp.GetPixel(right, bottom) |
257 | If Not GDIColors.Equals(p, blankColor) Then |
258 | ' 在这里确定了左右 |
259 | find = True |
260 | |
261 | bottom += 1 |
262 | |
263 | Exit For |
264 | End If |
265 | Next |
266 | |
267 | If find Then |
268 | Exit For |
269 | End If |
270 | Next |
271 | |
272 | region = New Rectangle(0, 0, res.Width, bottom) |
273 | res = res.ImageCrop(region.Location, region.Size) |
274 | bmp = BitmapBuffer.FromImage(res) |
275 | |
276 | ' right |
277 | For right = res.Width - 1 To 0 Step -1 |
278 | Dim find As Boolean = False |
279 | |
280 | For bottom = res.Height - 1 To 0 Step -1 |
281 | Dim p = bmp.GetPixel(right, bottom) |
282 | If Not GDIColors.Equals(p, blankColor) Then |
283 | ' 在这里确定了左右 |
284 | find = True |
285 | right += 1 |
286 | |
287 | Exit For |
288 | End If |
289 | Next |
290 | |
291 | If find Then |
292 | Exit For |
293 | End If |
294 | Next |
295 | |
296 | region = New Rectangle(0, 0, right, res.Height) |
297 | res = res.ImageCrop(region.Location, region.Size) |
298 | |
299 | If margin > 0 Then |
300 | With New Size(res.Width + margin * 2, res.Height + margin * 2).CreateGDIDevice |
301 | Call .Clear(blankColor) |
302 | Call .DrawImage(res, New Point(margin, margin)) |
303 | |
304 | Return .ImageResource |
305 | End With |
306 | Else |
307 | Return res |
308 | End If |
309 | End Function |
310 | |
311 | ''' <summary> |
312 | ''' A, R, G, B |
313 | ''' </summary> |
314 | Public Const PixelSize% = 4 |
315 | Public Const RGBSize% = 3 |
316 | |
317 | ''' <summary> |
318 | ''' Color replace using memory pointer |
319 | ''' </summary> |
320 | ''' <param name="image"></param> |
321 | ''' <param name="subject"></param> |
322 | ''' <param name="replaceAs"></param> |
323 | ''' <returns></returns> |
324 | <Extension> Public Function ColorReplace(image As Bitmap, subject As Color, replaceAs As Color) As Bitmap |
325 | Using bitmap As BitmapBuffer = BitmapBuffer.FromBitmap(image) |
326 | Dim byts As BitmapBuffer = bitmap |
327 | |
328 | For x As Integer = 0 To byts.Width - 1 |
329 | For y As Integer = 0 To byts.Height - 1 |
330 | If GDIColors.Equals(byts.GetPixel(x, y), subject) Then |
331 | Call byts.SetPixel(x, y, replaceAs) |
332 | End If |
333 | Next |
334 | Next |
335 | End Using |
336 | |
337 | Return image |
338 | End Function |
339 | |
340 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
341 | <Extension> |
342 | Public Function ColorReplace(image As Image, subject As Color, replaceAs As Color) As Bitmap |
343 | Return New Bitmap(image).ColorReplace(subject, replaceAs) |
344 | End Function |
345 | End Module |
346 | End Namespace |