1 #Region "Microsoft.VisualBasic::b61de16d2ae57d372c3eba82c6521787, 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, (+2 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 IntegerAs 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$ = NothingAs 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