The central component of the Imaging.NET application is the Image class. It relies heavily on the Pixel class to perform all image processing operations in the application. All source code markup was produced by the excellent CopySourceAsHtml Visual Studio 2005 Add-In.
1 Imports System.IO
2
3
4 '////////////////////////////////////////////////////////////////////////////////////////////
5 '// Pixel Class
6 '////////////////////////////////////////////////////////////////////////////////////////////
7 '// For purposes of this project,
8 '// a Pixel consists of i (row) and j (column) coordinates only.
9 '//
10 '// NOTE: a Pixel (and/or its neighbors) can have negative coordinates
11 '//
12 Public Class Pixel
13 Implements IComparable
14 Implements ICloneable
15
16 Public i As Integer
17 Public j As Integer
18
19 Public Sub New(ByVal new_i As Integer, ByVal new_j As Integer)
20 i = new_i
21 j = new_j
22 End Sub
23
24 Public Sub New(ByVal pixel As Pixel)
25 i = pixel.i
26 j = pixel.j
27 End Sub
28
29 '// Gets the specified 8 neighbor of this pixel //
30 Public Function Neighbor(ByVal index As Integer) As Pixel
31 Dim di() As Integer = {0, -1, -1, -1, 0, 1, 1, 1}
32 Dim dj() As Integer = {-1, -1, 0, 1, 1, 1, 0, -1}
33
34 Return New Pixel(i + di(index), j + dj(index))
35 End Function
36
37
38 '// Returns a collection of the 4 neighbors of this pixel //
39 Public Function Get4Neighbors() As PixelCollectionList
40 Dim result As PixelCollectionList
41
42 result = New PixelCollectionList
43
44 result.Add(Neighbor(0))
45 result.Add(Neighbor(2))
46 result.Add(Neighbor(4))
47 result.Add(Neighbor(6))
48
49 Return result
50 End Function
51
52 '// Returns a collection of the 8 neighbors of this pixel //
53 Public Function Get8Neighbors() As PixelCollectionList
54 Dim result As PixelCollectionList
55
56 result = New PixelCollectionList
57 result.Add(Neighbor(0))
58 result.Add(Neighbor(1))
59 result.Add(Neighbor(2))
60 result.Add(Neighbor(3))
61 result.Add(Neighbor(4))
62 result.Add(Neighbor(5))
63 result.Add(Neighbor(6))
64 result.Add(Neighbor(7))
65
66 Return result
67 End Function
68
69
70 '// Key for comparing pixels //
71 Public ReadOnly Property SortingKey() As Integer
72 Get
73 Const INDEX_MULT As Integer = 10000
74
75 '// Return a unique index for this pixel (in a 10000 x 10000 matrix) //
76 Return (i * INDEX_MULT) + j
77 End Get
78 End Property
79
80 Public ReadOnly Property ArrayIndex(ByVal width As Integer) As Integer
81 Get
82 Return (i * width) + j
83 End Get
84 End Property
85
86
87 '// Compare pixels //
88 Public Overloads Function Equals(ByVal oRef As Pixel) As Boolean
89 '// Pixels are equal if their sorting keys are equal //
90 Return SortingKey() = oRef.SortingKey
91 End Function
92
93
94 '// Compare two pixels
95 '// IComparable interface requires this function //
96 '// It returns -1 if less, 0 if equal, 1 if more
97 Public Function CompareTo(ByVal obj As Object) As Integer Implements IComparable.CompareTo
98 Dim sortingKey1 As Integer
99 Dim sortingKey2 As Integer
100
101 '// Prepare to compare sorting keys //
102 sortingKey1 = SortingKey
103 sortingKey2 = CType(obj, Pixel).SortingKey
104
105 If sortingKey1 < sortingKey2 Then
106 Return -1
107 End If
108
109 If sortingKey1 = sortingKey2 Then
110 Return 0
111 End If
112
113 Return 1
114 End Function
115
116 '// Make a copy of this pixel
117 Public Function Clone() As Object Implements ICloneable.Clone
118 Return New Pixel(Me)
119 End Function
120 End Class
121
122
123
124 '////////////////////////////////////////////////////////////////////////////////////////////
125 '// Image Class
126 '////////////////////////////////////////////////////////////////////////////////////////////
127 '// This class contains all image processing code in the application //
128 '//
129 '// It contains displayable bitmap objects
130 '// as well as color, type, and label information for every pixel in the image.
131 '//
132 '// Methods are provided to transform, filter, and manipulate image data
133 '// An enumerator class enables this object to be treated as a collection of pixels.
134 '// (For each pixel in Me)
135 '//
136 '// Two bitmap objects are provided, one with raw pixel color data
137 '// and another for "color labels"...
138 '// color labels are used when the font size for displaying text labels
139 '// would be too small to see.
140 '//
141 '////////////////////////////////////////////////////////////////////////////////////////////
142 '//
143 Public Class Image
144 Implements ICloneable
145 Implements IEnumerable
146 Implements ICollection
147
148 '// Every pixel is either a background, foreground, or a boundary pixel //
149 Public Enum PixelTypes
150 Background = 0
151 Foreground = 1
152 Boundary = 2
153 End Enum
154
155 Public Enum ColorLabelStates
156 Enabled = 1
157 Disabled = 0
158 End Enum
159
160 Public Enum AnimationStates
161 Enabled = 1
162 Disabled = 0
163 End Enum
164
165
166 '////////////////////////////////////////////////////////////////////////////////////////////
167 '// PRIVATE DATA MEMBERS
168 '////////////////////////////////////////////////////////////////////////////////////////////
169
170 Const PIXEL_LABEL_MARK As Integer = 2000000000 '// Big number for marking pixels during processing
171
172 Private _height As Integer '// The height of the contained image (in pixels)
173 Private _width As Integer '// The width of the contained image (in pixels)
174 Private _count As Integer
175 Private _backColor As Drawing.Color '// The background color to apply to new images
176
177 Private _colorLabels As ColorLabelStates '// Indicates if color labels are enabled (see class description)
178
179 Private _bitmap As Drawing.Bitmap '// Image to display to user
180 Private _bitmapWithColorLabels As Drawing.Bitmap '// Image to display to user (when color labels are needed)
181
182 Private _pixelColorArray() As Drawing.Color '// Array of drawing.colors (for faster access to color data)
183 Private _pixelTypeArray() As PixelTypes '// Array of pixel types (background, foreground, border, etc)
184 Private _pixelLabelArray() As Integer '// Array of (numeric) pixel labels.
185
186 Private lastModified As Integer '// Invalidates collection enumerator when underlying image size changes
187
188 Private _animation As AnimationStates
189 Private _animationState As Boolean
190 Private _animationStateStack As Collections.Stack
191 Private _animationLastScannedPixel As Pixel
192 Private _progressMin As Integer
193 Private _progressMax As Integer
194 Private _progressCount As Integer
195
196 Public CancelOperation As Boolean
197
198
199 '////////////////////////////////////////////////////////////////////////////////////////////
200 '// PUBLIC DELEGATES
201 '////////////////////////////////////////////////////////////////////////////////////////////
202
203 '// Events for animation, progress bar, and status changes //
204 Public Event AnimationPixelScanned(ByVal pixel As Pixel, ByVal lastScannedPixel As Pixel)
205 Public Event AnimationPixelChanged(ByVal pixel As Pixel)
206 Public Event AnimationImageChanged()
207 Public Event ProgressChanged(ByVal progressMin As Integer, ByVal progressMax As Integer, ByVal progressCount As Integer, ByVal refreshImage As Boolean)
208 Public Event StatusChanged(ByVal text As String)
209
210 '// Delegate (function pointer) for transforming an image (called for every pixel during transform).
211 Public Delegate Sub PixelTransformDelegate(ByVal image As Image, ByVal pixel As Pixel, ByVal transformOptions As PixelTransformOptions)
212
213
214 '////////////////////////////////////////////////////////////////////////////////////////////
215 '// CONSTRUCTORS
216 '////////////////////////////////////////////////////////////////////////////////////////////
217
218 Public Sub New(ByVal height As Integer, ByVal width As Integer, ByVal backColor As Drawing.Color, ByVal colorLabels As ColorLabelStates, ByVal animation As AnimationStates)
219 _height = height
220 _width = width
221 _count = _width * _height
222
223 _backColor = backColor
224
225 _animation = animation
226 _animationStateStack = New Collections.Stack
227 _animationState = False
228
229 _bitmap = CreateNewBitmap(_height, _width, _backColor)
230
231 _colorLabels = colorLabels
232 If _colorLabels = ColorLabelStates.Enabled Then
233 _bitmapWithColorLabels = CreateNewBitmap(_height, _width, _backColor)
234 End If
235
236 ResizeTypes(_height, _width)
237 ResizeLabels(_height, _width)
238 ResizeColors(_height, _width)
239
240 InitializeBackground()
241 End Sub
242
243
244 '// COPY CONSTRUCTOR //
245 Public Sub New(ByVal image As Image)
246 _height = image._height
247 _width = image._width
248 _count = _width * _height
249 _backColor = image._backColor
250
251 _bitmap = CType(image._bitmap.Clone(), Drawing.Bitmap)
252 _colorLabels = image._colorLabels
253 If _colorLabels = ColorLabelStates.Enabled Then
254 _bitmapWithColorLabels = CType(image._bitmapWithColorLabels.Clone(), Drawing.Bitmap)
255 End If
256
257 _pixelColorArray = CType(image._pixelColorArray.Clone(), Drawing.Color())
258 _pixelTypeArray = CType(image._pixelTypeArray.Clone(), PixelTypes())
259 _pixelLabelArray = CType(image._pixelLabelArray.Clone(), Integer())
260
261 _animation = image._animation
262 _animationState = image._animationState
263 _animationStateStack = CType(image._animationStateStack.Clone(), Collections.Stack)
264 End Sub
265
266
267 Public Function Clone() As Object Implements ICloneable.Clone
268 '// Clone by invoking copy contructor //
269 Return New Image(Me)
270 End Function
271
272
273 '////////////////////////////////////////////////////////////////////////////////////////////
274 '// PUBLIC PROPERTIES
275 '////////////////////////////////////////////////////////////////////////////////////////////
276
277 '// Return drawing.bitmap object (for rendering on screen) //
278 Public ReadOnly Property Bitmap(ByVal withColorLabels As Boolean) As Drawing.Bitmap
279 Get
280 If withColorLabels Then
281 Return _bitmapWithColorLabels
282 End If
283
284 Return _bitmap
285 End Get
286 End Property
287
288
289 '// Get/set color for specified pixel //
290 Public Property PixelColor(ByVal pixel As Pixel) As Drawing.Color
291 Get
292 Return _pixelColorArray(pixel.ArrayIndex(_width))
293 End Get
294
295 Set(ByVal Value As Drawing.Color)
296 Dim i As Integer
297 Dim j As Integer
298
299 i = pixel.i
300 j = pixel.j
301
302 _pixelColorArray((i * _width) + j) = Value
303 _bitmap.SetPixel(j, i, Value)
304 If _colorLabels = ColorLabelStates.Enabled Then
305 _bitmapWithColorLabels.SetPixel(j, i, Value)
306 End If
307
308 AnimationRaisePixelChanged(pixel)
309 End Set
310 End Property
311
312
313 '// Get/set type for specified pixel //
314 Public Property PixelType(ByVal pixel As Pixel) As PixelTypes
315 Get
316 Return _pixelTypeArray(pixel.ArrayIndex(_width))
317 End Get
318
319 Set(ByVal Value As PixelTypes)
320
321 _pixelTypeArray(pixel.ArrayIndex(_width)) = Value
322
323 AnimationRaisePixelChanged(pixel)
324 End Set
325 End Property
326
327
328 '// Get/set label for specified pixel //
329 Public Property PixelLabel(ByVal pixel As Pixel) As Integer
330 Get
331 Return _pixelLabelArray(pixel.ArrayIndex(_width))
332 End Get
333
334 Set(ByVal Value As Integer)
335
336 _pixelLabelArray(pixel.ArrayIndex(_width)) = Value
337
338 AnimationRaisePixelChanged(pixel)
339
340 End Set
341 End Property
342
343
344 '// Get height of image //
345 Public ReadOnly Property Height() As Integer
346 Get
347 Return _height
348 End Get
349 End Property
350
351
352 '// Get width of image //
353 Public ReadOnly Property Width() As Integer
354 Get
355 Return _width
356 End Get
357 End Property
358
359 Public Property AnimationState() As AnimationStates
360 Get
361 Return _animation
362 End Get
363
364 Set(ByVal Value As AnimationStates)
365 _animation = Value
366 End Set
367 End Property
368
369
370 '////////////////////////////////////////////////////////////////////////////////////////////
371 '// IMAGE RESIZE/CLEAR METHODS
372 '////////////////////////////////////////////////////////////////////////////////////////////
373
374 Sub ResizeColors(ByVal height As Integer, ByVal width As Integer)
375 Dim pixel As Pixel
376
377 AnimationPushState()
378 AnimationOff()
379 UpdateStatus("Initializing image")
380
381 ReDim _pixelColorArray((height * width) - 1)
382 For Each pixel In Me
383 UpdateProgress(pixel)
384 _pixelColorArray(pixel.ArrayIndex(_width)) = _backColor
385
386 If CancelOperation Then
387 GoTo CancelOperation
388 End If
389 Next
390
391 CancelOperation:
392 AnimationPopState()
393 UpdateProgress(0, 0, 0)
394 End Sub
395
396
397 Sub ResizeTypes(ByVal height As Integer, ByVal width As Integer)
398 ReDim _pixelTypeArray((height * width) - 1)
399 End Sub
400
401
402 Sub ResizeLabels(ByVal height As Integer, ByVal width As Integer)
403 ReDim _pixelLabelArray((height * width) - 1)
404 End Sub
405
406
407 Public Sub ClearColors()
408 ResizeColors(_height, _width)
409
410 AnimationRaiseImageChanged()
411 End Sub
412
413
414 Public Sub ClearTypes()
415 ResizeTypes(_height, _width)
416
417 AnimationRaiseImageChanged()
418 End Sub
419
420
421 Public Sub ClearLabels()
422 ResizeLabels(_height, _width)
423
424 If _colorLabels = ColorLabelStates.Enabled Then
425 '// No labels means no color labels either...
426 _bitmapWithColorLabels = CType(_bitmap.Clone, Drawing.Bitmap)
427 End If
428
429 AnimationRaiseImageChanged()
430 End Sub
431
432
433 Public Sub ClearBoundaryTypes()
434 UpdateStatus("Changing boundary pixels to foreground pixels")
435
436 AnimationPushState()
437 AnimationOff()
438
439 ImageTransform(AddressOf PixelTransformClearBoundaryType, Nothing)
440
441 AnimationPopState()
442
443 End Sub
444
445
446 '// Clear entire image //
447 Public Sub Clear()
448 _bitmap = CreateNewBitmap(_height, _width, _backColor)
449
450 ClearColors()
451 ClearTypes()
452 ClearLabels()
453 End Sub
454
455
456 '// Remove labels from all pixels that are not on the boundary of a component
457 '// ... for cleaning up connected components after marking boundaries //
458 Public Sub ClearNonBoundaryLabels()
459 Dim pixel As Pixel
460
461 UpdateStatus("Removing labels from non-boundary pixels")
462 AnimationPushState()
463 AnimationOff()
464
465 For Each pixel In Me
466
467 UpdateProgress(pixel)
468
469 If IsForeground(pixel) Then
470
471 If Not IsBoundary(pixel) Then
472
473 PixelLabel(pixel) = 0
474 End If
475 End If
476
477 If CancelOperation Then
478 GoTo CancelOperation
479 End If
480 Next
481
482 CancelOperation:
483 AnimationPopState()
484 UpdateProgress(0, 0, 0)
485 End Sub
486
487 '// Resize this image //
488 Public Sub Resize(ByVal height As Integer, ByVal width As Integer)
489 FromBitmap(_bitmap, height, width)
490 End Sub
491
492
493 '////////////////////////////////////////////////////////////////////////////////////////////
494 '// IMAGE LOADING METHODS
495 '////////////////////////////////////////////////////////////////////////////////////////////
496
497 Private Function CreateNewBitmap(ByVal height As Integer, ByVal width As Integer, ByVal backColor As Drawing.Color) As Drawing.Bitmap
498 Dim result As Drawing.Bitmap
499 Dim graphics As Drawing.Graphics
500 Dim clearBrush As Drawing.Brush
501
502 result = New Bitmap(width, height, Drawing.Imaging.PixelFormat.Format24bppRgb)
503
504 clearBrush = New Drawing.SolidBrush(_backColor)
505 graphics = Graphics.Fro_image(result)
506 graphics.FillRectangle(clearBrush, 0, 0, width, height)
507 graphics.Dispose()
508 clearBrush.Dispose()
509
510 Return result
511 End Function
512
513
514 '// Initialize this image using the specified bitmap object //
515 Public Sub FromBitmap(ByVal bitmap As Drawing.Bitmap, ByVal height As Integer, ByVal width As Integer)
516 Dim newBitmap As Drawing.Bitmap
517 Dim graphics As Drawing.Graphics
518
519 Dim screenHeight As Integer
520 Dim screenWidth As Integer
521
522 Dim newWidth As Integer
523 Dim newHeight As Integer
524 Dim maxPixelSize As Single
525
526 Dim srcRect As Rectangle
527 Dim destRect As Rectangle
528
529 _animationLastScannedPixel = Nothing
530
531 screenHeight = Screen.PrimaryScreen.WorkingArea.Height
532 screenWidth = Screen.PrimaryScreen.WorkingArea.Width
533
534 '// Preserve aspect ratio
535 newWidth = Math.Min(width, screenWidth)
536 If newWidth < width Then
537 height = CInt(height * (newWidth / width))
538 End If
539 width = newWidth
540
541 '// Preserve aspect ratio
542 newHeight = Math.Min(height, screenHeight)
543 If newHeight < height Then
544 width = CInt(width * (newHeight / height))
545 End If
546 height = newHeight
547
548 '// Size of virtual pixel (in screen pixels) //
549 maxPixelSize = Math.Min(CSng(screenHeight / height), CSng(screenWidth / width))
550
551 newBitmap = New Bitmap(width, height, Drawing.Imaging.PixelFormat.Format24bppRgb)
552
553 srcRect = New Rectangle(0, 0, bitmap.Size.Width, bitmap.Size.Height)
554 destRect = New Rectangle(0, 0, width, height)
555
556 graphics = Graphics.Fro_image(newBitmap)
557
558 If maxPixelSize > 1 Then
559 '// Virtual pixels are visible, copy bitmap exactly (no interpolation) //
560 graphics.CompositingMode = Drawing2D.CompositingMode.SourceCopy
561 graphics.CompositingQuality = Drawing2D.CompositingQuality.HighSpeed
562 graphics.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor
563 graphics.PixelOffsetMode = Drawing2D.PixelOffsetMode.Half
564 graphics.SmoothingMode = Drawing2D.SmoothingMode.None
565 End If
566
567 graphics.DrawImage(bitmap, destRect, srcRect, GraphicsUnit.Pixel)
568
569 graphics.Dispose()
570
571 _bitmap = newBitmap
572 If _colorLabels = ColorLabelStates.Enabled Then
573 _bitmapWithColorLabels = CType(newBitmap.Clone(), Drawing.Bitmap)
574 End If
575
576 lastModified = (lastModified + 1) Mod 2000000000
577
578 _height = height
579 _width = width
580 _count = _width * _height
581
582 ResizeTypes(_height, _width)
583 ResizeLabels(_height, _width)
584 ResizeColors(_height, _width)
585
586 ClearLabels()
587 ClearTypes()
588 InitializeBackground()
589 End Sub
590
591
592 Public Sub FromFile(ByVal fileName As String)
593 Dim newBitmap As Drawing.Bitmap
594
595 newBitmap = New Bitmap(fileName)
596
597 FromBitmap(newBitmap, newBitmap.Height, newBitmap.Width)
598 End Sub
599
600
601 Public Sub FromStream(ByVal stream As Stream)
602 Dim newBitmap As Drawing.Bitmap
603
604 newBitmap = New Bitmap(stream)
605
606 FromBitmap(newBitmap, newBitmap.Height, newBitmap.Width)
607 End Sub
608
609
610 Public Sub ToFile(ByVal fileName As String)
611 _bitmap.Save(fileName)
612 End Sub
613
614
615 '// Separate background pixels from foreground pixels //
616 Private Sub InitializeBackground()
617
618 Dim pixel As Pixel
619
620 AnimationPushState()
621 AnimationOff()
622 UpdateStatus("Identifying background pixels")
623
624 For Each pixel In Me
625
626 UpdateProgress(pixel)
627
628 _pixelColorArray(pixel.ArrayIndex(_width)) = _bitmap.GetPixel(pixel.j, pixel.i)
629
630 If Color.ColorsAreEqual(PixelColor(pixel), _backColor) Then
631 PixelType(pixel) = PixelTypes.Background
632 Else
633 PixelType(pixel) = PixelTypes.Foreground
634 End If
635
636 If CancelOperation Then
637 GoTo CancelOperation
638 End If
639 Next
640
641 CancelOperation:
642 AnimationPopState()
643 UpdateProgress(0, 0, 0)
644 End Sub
645
646
647 '// Assign color labels to every pixel in image //
648 Public Sub AssignColorLabels()
649 Dim pixel As Pixel
650
651 AnimationPushState()
652 AnimationOff()
653 UpdateStatus("Assigning color labels")
654
655 If _colorLabels = ColorLabelStates.Enabled Then
656
657 For Each pixel In Me
658
659 UpdateProgress(pixel)
660
661 _bitmapWithColorLabels.SetPixel(pixel.j, pixel.i, Color.ColorFromNumber(PixelLabel(pixel), _backColor))
662
663 If CancelOperation Then
664 GoTo CancelOperation
665 End If
666 Next
667 End If
668
669 CancelOperation:
670 AnimationPopState()
671 UpdateProgress(0, 0, 0)
672 End Sub
673
674
675 '////////////////////////////////////////////////////////////////////////////////////////////
676 '// INDIVIDUAL PIXEL METHODS
677 '////////////////////////////////////////////////////////////////////////////////////////////
678
679 Function IsBackground(ByVal pixel As Pixel) As Boolean
680 If PixelType(pixel) = PixelTypes.Background Then
681 Return True
682 End If
683
684 Return False
685 End Function
686
687
688 '// For performance, rewrite negated IsBackground()
689 Function IsForeground(ByVal pixel As Pixel) As Boolean
690 If PixelType(pixel) = PixelTypes.Background Then
691 Return False
692 End If
693
694 Return True
695 End Function
696
697
698 Function IsBoundary(ByVal pixel As Pixel) As Boolean
699 If PixelType(pixel) = PixelTypes.Boundary Then
700 Return True
701 End If
702
703 Return False
704 End Function
705
706
707 Function IsLabeled(ByVal pixel As Pixel) As Boolean
708 If PixelLabel(pixel) <> 0 Then
709 Return True
710 End If
711
712 Return False
713 End Function
714
715 '// For performance, rewrite negated IsLabeled() //
716 Function IsUlabeled(ByVal pixel As Pixel) As Boolean
717 If PixelLabel(pixel) <> 0 Then
718 Return False
719 End If
720
721 Return True
722 End Function
723
724 Function IsMarked(ByVal pixel As Pixel) As Boolean
725 If PixelLabel(pixel) = PIXEL_LABEL_MARK Then
726 Return True
727 End If
728
729 Return False
730 End Function
731
732 '// For performance, rewrite negated IsMarked() //
733 Function IsUnmarked(ByVal pixel As Pixel) As Boolean
734 If PixelLabel(pixel) = PIXEL_LABEL_MARK Then
735 Return False
736 End If
737
738 Return True
739 End Function
740
741 Function GetDisplayText(ByVal pixel As Pixel) As String
742 If PixelLabel(pixel) = 0 Then
743 Return ""
744 End If
745
746 If PixelLabel(pixel) = PIXEL_LABEL_MARK Then
747 Return "M"
748 End If
749
750 Return CStr(PixelLabel(pixel))
751 End Function
752
753
754 '// Set specified pixel color and type //
755 Sub SetPixel(ByVal pixel As Pixel, ByVal color As Drawing.Color, ByVal nPixelType As PixelTypes)
756 PixelType(pixel) = nPixelType
757
758 If nPixelType = PixelTypes.Background Then
759 PixelLabel(pixel) = 0
760 PixelColor(pixel) = _backColor
761 Else
762 PixelColor(pixel) = color
763
764 If _colorLabels = ColorLabelStates.Enabled Then
765 _bitmapWithColorLabels.SetPixel(pixel.j, pixel.i, color)
766 End If
767 End If
768 End Sub
769
770 '// OVERLOADED: Set specified pixel color, type, and label //
771 Sub SetPixel(ByVal pixel As Pixel, ByVal color As Drawing.Color, ByVal nPixelType As PixelTypes, ByVal nPixelLabel As Integer)
772 PixelLabel(pixel) = nPixelLabel
773 SetPixel(pixel, color, nPixelType)
774 End Sub
775
776
777 Public Sub MarkPixel(ByVal pixel As Pixel)
778 PixelLabel(pixel) = PIXEL_LABEL_MARK
779 End Sub
780
781
782 Public Sub RemovePixel(ByVal pixel As Pixel)
783 SetPixel(pixel, _backColor, PixelTypes.Background)
784 End Sub
785
786
787 '// Return a new color object that will contrast with the specified pixel
788 '// ... for displaying label text //
789 Public Function FilterContrast(ByVal pixel As Pixel) As Drawing.Color
790
791 If PixelColor(pixel).GetBrightness() >= 0.65 Then
792
793 Return Drawing.Color.Black
794 Else
795
796 Return Drawing.Color.White
797 End If
798 End Function
799
800
801 '// Get the 3x3 image around specified pixel (oSourceCenter) //
802 Public Function GetImage3x3(ByVal oSourceCenter As Pixel) As Image
803 Dim oSourceNeighbor As Pixel
804 Static oTargetCenter As Pixel
805 Dim oTargetNeighbor As Pixel
806 Static image3x3 As Image
807 Dim index As Integer
808 Dim result As Image
809
810 '// For performance, allocate and re-use a blank 3x3 image //
811 If image3x3 Is Nothing Then
812 image3x3 = New Image(3, 3, _backColor, ColorLabelStates.Disabled, AnimationStates.Disabled)
813 oTargetCenter = New Pixel(1, 1)
814 End If
815
816 '// Copy constructor is faster than New()
817 result = New Image(image3x3)
818
819 '// Copy center pixel //
820 If IsInBounds(oSourceCenter) Then
821 result.PixelType(oTargetCenter) = PixelType(oSourceCenter)
822 result.PixelLabel(oTargetCenter) = PixelLabel(oSourceCenter)
823 End If
824
825 '// Copy all neighbor pixels //
826 For index = 0 To 7
827 oSourceNeighbor = oSourceCenter.Neighbor(index)
828 oTargetNeighbor = oTargetCenter.Neighbor(index)
829
830 '// Don't copy out-of-bounds pixels (treat as blank) //
831 If IsInBounds(oSourceNeighbor) Then
832
833 result.PixelType(oTargetNeighbor) = PixelType(oSourceNeighbor)
834 result.PixelLabel(oTargetNeighbor) = PixelLabel(oSourceNeighbor)
835 End If
836 Next
837
838 Return result
839 End Function
840
841
842 '////////////////////////////////////////////////////////////////////////////////////////////
843 '// GROUP PIXEL METHODS
844 '////////////////////////////////////////////////////////////////////////////////////////////
845
846 Public Sub RemoveCollectedPixels(ByVal oCollection As ICollection)
847 Dim pixel As Pixel
848 Dim progressMax As Integer
849 Dim progressCount As Integer
850
851 progressMax = oCollection.Count
852 progressCount = 0
853
854 UpdateStatus("Removing collected pixels")
855
856 For Each pixel In oCollection
857
858 progressCount = progressCount + 1
859 'UpdateProgress(0, progressMax, progressCount, False)
860
861 If PixelLabel(pixel) = PIXEL_LABEL_MARK Then
862 RemovePixel(pixel)
863 End If
864
865 If CancelOperation Then
866 GoTo CancelOperation
867 End If
868 Next
869
870 CancelOperation:
871 End Sub
872
873
874 Public Sub SetCollectedPixels(ByVal oCollection As ICollection)
875 Dim pixel As Pixel
876
877 UpdateStatus("Setting collected pixels")
878 _progressMax = oCollection.Count
879 _progressCount = 0
880
881 For Each pixel In oCollection
882
883 _progressCount = _progressCount + 1
884 'UpdateProgress(0, _progressMax, _progressCount, False)
885
886 SetPixel(pixel, Drawing.Color.Gray, PixelTypes.Foreground)
887
888 If CancelOperation Then
889 GoTo CancelOperation
890 End If
891 Next
892
893 CancelOperation:
894 End Sub
895
896
897 Public Sub MarkCollectedPixels(ByVal oCollection As ICollection)
898 Dim pixel As Pixel
899
900 UpdateStatus("Marking collected pixels")
901 _progressMax = oCollection.Count
902 _progressCount = 0
903
904 For Each pixel In oCollection
905
906 _progressCount = _progressCount + 1
907 'UpdateProgress(0, _progressMax, _progressCount, False)
908
909 PixelLabel(pixel) = PIXEL_LABEL_MARK
910
911 If CancelOperation Then
912 GoTo CancelOperation
913 End If
914 Next
915
916 CancelOperation:
917 End Sub
918
919
920 '// Build a tree of all boundary pixels //
921 Function GetBoundaryPixelCollectionTree() As PixelCollectionTree
922 Dim pixel As Pixel
923 Dim result As PixelCollectionTree
924
925 UpdateStatus("Collecting boundary pixels")
926 AnimationPushState()
927 AnimationOff()
928
929 result = New PixelCollectionTree
930
931 For Each pixel In Me
932
933 UpdateProgress(pixel)
934
935 If IsBoundary(pixel) Then
936 result.Add(pixel)
937 End If
938
939 If CancelOperation Then
940 GoTo CancelOperation
941 End If
942 Next
943
944 CancelOperation:
945 AnimationPopState()
946 UpdateProgress(0, 0, 0)
947
948 Return result
949 End Function
950
951
952 '////////////////////////////////////////////////////////////////////////////////////////////
953 '// PIXEL POSITION METHODS
954 '////////////////////////////////////////////////////////////////////////////////////////////
955
956 '// Determine if specified pixel position is outside of image boundary //
957 Public Function IsOutOfBounds(ByVal pixel As Pixel) As Boolean
958 Dim i As Integer
959 Dim j As Integer
960
961 i = pixel.i
962
963 If i < 0 Then
964 Return True
965 End If
966
967 If i >= _height Then
968 Return True
969 End If
970
971 j = pixel.j
972
973 If j < 0 Then
974 Return True
975 End If
976
977 If j >= _width Then
978 Return True
979 End If
980
981 Return False
982 End Function
983
984
985 '// For performance, rewrite negated logic for IsOutOfBounds //
986 Public Function IsInBounds(ByVal pixel As Pixel) As Boolean
987 Dim i As Integer
988 Dim j As Integer
989
990 i = pixel.i
991
992 If i < 0 Then
993 Return False
994 End If
995
996 If i >= _height Then
997 Return False
998 End If
999
1000 j = pixel.j
1001
1002 If j < 0 Then
1003 Return False
1004 End If
1005
1006 If j >= _width Then
1007 Return False
1008 End If
1009
1010 AnimationRaisePixelScanned(pixel)
1011
1012 Return True
1013 End Function
1014
1015
1016 '// If specified pixel is outside image boundary, snap it to just inside the closest edge //
1017 Function SnapPixelToEdge(ByVal pixel As Pixel) As Pixel
1018 If pixel.i < 0 Then
1019 pixel.i = 0
1020 End If
1021
1022 If pixel.j < 0 Then
1023 pixel.j = 0
1024 End If
1025
1026 If pixel.i >= _height Then
1027 pixel.i = _height - 1
1028 End If
1029
1030 If pixel.j >= _width Then
1031 pixel.j = _width - 1
1032 End If
1033
1034 Return pixel
1035 End Function
1036
1037
1038 Function Count4Neighbors(ByVal pixel As Pixel, Optional ByVal nReturnIfGreaterThan As Integer = 4) As Integer
1039 Dim result As Integer
1040 Dim oNeighbor As Pixel
1041
1042 result = 0
1043
1044 For Each oNeighbor In pixel.Get4Neighbors()
1045
1046 If IsInBounds(oNeighbor) Then
1047
1048 If IsForeground(oNeighbor) Then
1049 result = result + 1
1050
1051 If (result > nReturnIfGreaterThan) Then
1052 Return result
1053 End If
1054 End If
1055
1056 End If
1057 Next
1058
1059 Return result
1060 End Function
1061
1062
1063 Public Function Count8Neighbors(ByVal pixel As Pixel, Optional ByVal nReturnIfGreaterThan As Integer = 8) As Integer
1064 Dim result As Integer
1065 Dim oNeighbor As Pixel
1066
1067 result = 0
1068
1069 For Each oNeighbor In pixel.Get8Neighbors()
1070 If IsInBounds(oNeighbor) Then
1071
1072 If IsForeground(oNeighbor) Then
1073 result = result + 1
1074
1075 If result > nReturnIfGreaterThan Then
1076 Return result
1077 End If
1078 End If
1079
1080 End If
1081 Next
1082
1083 Return result
1084 End Function
1085
1086
1087 '// Search through a range of neighbors (by index) and return the count of foreground pixels //
1088 Function CountRangeNeighbors(ByVal pixel As Pixel, ByVal nMin As Integer, ByVal nMax As Integer) As Integer
1089 Dim n As Integer
1090 Dim oNeighbor As Pixel
1091 Dim result As Integer
1092
1093 result = 0
1094
1095 For n = nMin To nMax
1096 oNeighbor = pixel.Neighbor(n Mod 8)
1097
1098 If IsInBounds(oNeighbor) Then
1099 If IsForeground(oNeighbor) Then
1100 result = result + 1
1101 End If
1102 End If
1103 Next
1104
1105 Return result
1106 End Function
1107
1108
1109 '////////////////////////////////////////////////////////////////////////////////////////////
1110 '// IMAGE TRANSFORM METHODS
1111 '////////////////////////////////////////////////////////////////////////////////////////////
1112
1113 Public Sub ImageTransform(ByVal pixelTransformDelegate As PixelTransformDelegate, ByVal pixelTransformOptions As PixelTransformOptions)
1114 Dim pixel As Pixel
1115
1116 For Each pixel In Me
1117
1118 UpdateProgress(pixel)
1119
1120 pixelTransformDelegate(Me, pixel, pixelTransformOptions)
1121
1122 If CancelOperation Then
1123 GoTo CancelOperation
1124 End If
1125 Next
1126
1127 AnimationRaiseImageChanged()
1128
1129 CancelOperation:
1130 UpdateProgress(0, 0, 0)
1131 End Sub
1132
1133
1134 Public Shared Sub PixelTransformRed(ByVal image As Image, ByVal pixel As Pixel, ByVal pixelTransformOptions As PixelTransformOptions)
1135 If image.IsForeground(pixel) Then
1136 image.PixelColor(pixel) = Drawing.ColorTranslator.FromWin32(RGB(image.PixelColor(pixel).R, 0, 0))
1137 End If
1138 End Sub
1139
1140
1141 Public Shared Sub PixelTransformGreen(ByVal image As Image, ByVal pixel As Pixel, ByVal pixelTransformOptions As PixelTransformOptions)
1142 If image.IsForeground(pixel) Then
1143 image.PixelColor(pixel) = Drawing.ColorTranslator.FromWin32(RGB(0, image.PixelColor(pixel).G, 0))
1144 End If
1145 End Sub
1146
1147
1148 Public Shared Sub PixelTransformBlue(ByVal image As Image, ByVal pixel As Pixel, ByVal pixelTransformOptions As PixelTransformOptions)
1149 If image.IsForeground(pixel) Then
1150 image.PixelColor(pixel) = Drawing.ColorTranslator.FromWin32(RGB(0, 0, image.PixelColor(pixel).B))
1151 End If
1152 End Sub
1153
1154
1155 Public Shared Sub PixelTransformMonochrome(ByVal image As Image, ByVal pixel As Pixel, ByVal pixelTransformOptions As PixelTransformOptions)
1156 Dim brightness As Integer
1157
1158 If image.IsForeground(pixel) Then
1159 brightness = CInt(image.PixelColor(pixel).GetBrightness * 255)
1160 image.PixelColor(pixel) = Drawing.ColorTranslator.FromWin32(RGB(brightness, brightness, brightness))
1161 End If
1162 End Sub
1163
1164
1165 Public Shared Sub PixelTransformNegative(ByVal image As Image, ByVal pixel As Pixel, ByVal pixelTransformOptions As PixelTransformOptions)
1166 image.PixelColor(pixel) = Drawing.ColorTranslator.FromWin32(RGB(Not image.PixelColor(pixel).R, Not image.PixelColor(pixel).G, Not image.PixelColor(pixel).B))
1167
1168 If Color.ColorsAreEqual(image.PixelColor(pixel), image._backColor) Then
1169
1170 image.PixelType(pixel) = PixelTypes.Background
1171 Else
1172 image.PixelType(pixel) = PixelTypes.Foreground
1173 End If
1174 End Sub
1175
1176
1177 Public Shared Sub PixelTransformThreshold(ByVal image As Image, ByVal pixel As Pixel, ByVal pixelTransformOptions As PixelTransformOptions)
1178 Dim brightness As Integer
1179 Dim bKeepPixel As Boolean
1180 Dim thresholdOptions As TransformOptionsThreshold
1181
1182 '// Don't process background pixels (they're already removed, silly) //
1183 If image.IsBackground(pixel) Then
1184 Exit Sub
1185 End If
1186
1187 '// Get threshold options //
1188 thresholdOptions = CType(pixelTransformOptions, TransformOptionsThreshold)
1189
1190 '// Get brightness of current pixel //
1191 brightness = CInt(image.PixelColor(pixel).GetBrightness * 255)
1192
1193 '// Check if brightness falls within threshold range //
1194 If thresholdOptions.RemovePixelsAbove > thresholdOptions.RemovePixelsBelow Then
1195 If brightness > thresholdOptions.RemovePixelsAbove _
1196 Or brightness < thresholdOptions.RemovePixelsBelow Then
1197 bKeepPixel = False
1198 Else
1199 bKeepPixel = True
1200 End If
1201 Else
1202 If brightness >= thresholdOptions.RemovePixelsAbove _
1203 And brightness <= thresholdOptions.RemovePixelsBelow Then
1204 bKeepPixel = False
1205 Else
1206 bKeepPixel = True
1207 End If
1208 End If
1209
1210 '// Check if pixel should be removed //
1211 If bKeepPixel = False Then
1212
1213 '// Pixel selected for removal, remove it //
1214 image.RemovePixel(pixel)
1215 End If
1216 End Sub
1217
1218
1219 '// Convert boundary pixels to foreground pixels
1220 '// (... clear existing boundaries before boundary search)
1221 Public Shared Sub PixelTransformClearBoundaryType(ByVal image As Image, ByVal pixel As Pixel, ByVal pixelTransformOptions As PixelTransformOptions)
1222 If image.IsBoundary(pixel) Then
1223 image.PixelType(pixel) = PixelTypes.Foreground
1224 End If
1225 End Sub
1226
1227
1228 '////////////////////////////////////////////////////////////////////////////////////////////
1229 '// IMAGE PROCESSING METHODS
1230 '////////////////////////////////////////////////////////////////////////////////////////////
1231
1232 Public Function GetHistogram() As Histogram
1233 Return New Histogram(Me)
1234 End Function
1235
1236
1237 '// Assign unique labels to each connected component in image //
1238 Public Function LabelComponents() As Integer
1239 Dim pixel As Pixel
1240 Dim nComponentNumber As Integer
1241
1242 ClearLabels()
1243 ClearBoundaryTypes()
1244
1245 nComponentNumber = 0
1246
1247 _progressMax = _count + (_count \ 2)
1248 _progressCount = 0
1249
1250 UpdateStatus("Identifying connected components")
1251
1252 For Each pixel In Me
1253
1254 _progressCount = _progressCount + 1
1255 UpdateProgress(0, _progressMax, _progressCount)
1256
1257 If IsForeground(pixel) Then
1258
1259 If IsUlabeled(pixel) Then
1260
1261 nComponentNumber = nComponentNumber + 1
1262
1263 UpdateStatus("Identifying connected components (" & nComponentNumber & " so far)")
1264
1265 LabelConnectedPixels(pixel, nComponentNumber)
1266
1267 End If
1268 End If
1269
1270 If CancelOperation Then
1271 GoTo CancelOperation
1272 End If
1273 Next
1274
1275 CancelOperation:
1276 Return nComponentNumber
1277 End Function
1278
1279
1280 '// Given a pixel, assign specified label to all pixels that are connected to it //
1281 Sub LabelConnectedPixels(ByVal pixel As Pixel, ByVal nComponentNumber As Integer)
1282 Dim pixelCollection As PixelCollectionList
1283 Dim oNeighborCollection As PixelCollectionList
1284 Dim oNeighbor As Pixel
1285
1286 '// Recursive version of this routine causes stack overflow in .NET //
1287 '// Perform operation iteratively using pixel collections //
1288
1289 If IsOutOfBounds(pixel) Then
1290 Return
1291 End If
1292
1293 If IsBackground(pixel) Then
1294 Return
1295 End If
1296
1297 pixelCollection = New PixelCollectionList
1298 oNeighborCollection = pixelCollection
1299
1300 PixelLabel(pixel) = nComponentNumber
1301 pixelCollection.Add(pixel)
1302
1303 While pixelCollection.Count > 0
1304 pixelCollection = oNeighborCollection
1305 oNeighborCollection = New PixelCollectionList
1306
1307 For Each pixel In pixelCollection
1308
1309 For Each oNeighbor In pixel.Get8Neighbors()
1310
1311 If IsInBounds(oNeighbor) Then
1312
1313 If IsForeground(oNeighbor) Then
1314
1315 If IsUlabeled(oNeighbor) Then
1316
1317 PixelLabel(oNeighbor) = nComponentNumber
1318 oNeighborCollection.Add(oNeighbor)
1319
1320 _progressCount = _progressCount + 1
1321 UpdateProgress(0, _progressMax, _progressCount)
1322 End If
1323 End If
1324 End If
1325 Next
1326
1327 If CancelOperation Then
1328 GoTo CancelOperation
1329 End If
1330 Next
1331 End While
1332
1333 CancelOperation:
1334 End Sub
1335
1336
1337 '// Label connected components, then find (and mark) the boundary pixels for all components //
1338 Public Sub IdentifyBoundaries(ByVal bAnimation As Boolean)
1339 Dim pixel As Pixel
1340
1341 AnimationPushState()
1342 AnimationOff()
1343
1344 ClearBoundaryTypes()
1345 LabelComponents()
1346
1347 UpdateStatus("Identifying component boundaries")
1348
1349 _progressMax = _count + (_count \ 4)
1350 _progressCount = 0
1351
1352 If bAnimation Then
1353 AnimationOn()
1354 AnimationRaiseImageChanged()
1355 End If
1356
1357
1358 For Each pixel In Me
1359
1360 _progressCount = _progressCount + 1
1361 UpdateProgress(0, _progressMax, _progressCount)
1362
1363 If IsForeground(pixel) Then
1364
1365 If Not IsBoundary(pixel) Then
1366
1367 If Count4Neighbors(pixel) < 4 Then
1368 SetBoundaryPixels(pixel, PixelLabel(pixel))
1369 End If
1370
1371 End If
1372 End If
1373
1374 If CancelOperation Then
1375 GoTo CancelOperation
1376 End If
1377 Next
1378
1379 CancelOperation:
1380 AnimationPopState()
1381 UpdateProgress(0, 0, 0)
1382 End Sub
1383
1384
1385 '// Given a pixel, follow the boundary of connected pixels and
1386 '// mark them as boundary pixels //
1387 Private Sub SetBoundaryPixels(ByVal pixel As Pixel, ByVal nComponentNumber As Integer)
1388 Dim s As Pixel
1389 Dim c As Pixel
1390 Dim n As Pixel
1391 Dim k As Integer
1392
1393 Dim bFinished As Boolean
1394 Dim bFoundBoundaryPixel As Boolean
1395
1396 Dim bFoundStartingPoint As Boolean
1397 Dim oStartingPoint As Pixel = Nothing
1398 Dim nPixelCount As Integer
1399
1400 Dim bFoundBackground As Boolean
1401
1402 c = New Pixel(pixel)
1403
1404 '// Make sure that starting pixel is not surrounded (just in case) //
1405 If Count4Neighbors(c) = 4 Then
1406 '// Pixel is surrounded, not a boundary pixel!
1407 Stop
1408 Return
1409 End If
1410
1411 s = New Pixel(pixel)
1412 k = 0
1413
1414 '// Start with first 4-neighbor background pixel around c (in clockwise direction)
1415 bFoundBackground = False
1416 While Not bFoundBackground
1417 nPixelCount = 0
1418
1419 n = c.Neighbor(k)
1420
1421 If IsInBounds(n) Then
1422
1423 If IsBackground(n) Then
1424
1425 bFoundBackground = True
1426 End If
1427 Else
1428
1429 bFoundBackground = True
1430 End If
1431
1432 If Not bFoundBackground Then
1433 k = (k + 2) Mod 8
1434 End If
1435
1436 nPixelCount = nPixelCount + 1
1437
1438 If nPixelCount > 4 Then
1439 Stop '// Starting pixel surrounded, not a border pixel!
1440 GoTo CancelOperation
1441 End If
1442
1443 If CancelOperation Then
1444 GoTo CancelOperation
1445 End If
1446 End While
1447
1448 bFoundStartingPoint = False
1449 bFinished = False
1450
1451 While Not bFinished
1452
1453 nPixelCount = 1
1454
1455 bFoundBoundaryPixel = False
1456
1457 '// Find next boundary pixel //
1458 While Not bFoundBoundaryPixel
1459 n = c.Neighbor(k)
1460
1461 If IsInBounds(n) Then
1462
1463 If PixelLabel(n) = nComponentNumber Then
1464
1465 bFoundBoundaryPixel = True
1466
1467 _progressCount = _progressCount + 1
1468 UpdateProgress(0, _progressMax, _progressCount)
1469
1470 '// Check if this is the first boundary pixel found //
1471 If Not bFoundStartingPoint Then
1472
1473 '// Remember first pixel
1474 '// when search reaches this pixel again, it is finished //
1475 bFoundStartingPoint = True
1476 oStartingPoint = n
1477
1478 Else
1479
1480 '// Check if search is finished
1481 If c.Equals(s) Then
1482
1483 If n.Equals(oStartingPoint) Then
1484
1485 '// Boundary search complete
1486 bFinished = True
1487 End If
1488 End If
1489 End If
1490
1491 c = n
1492
1493 '// Continue clockwise scan from next neighbor (relative to new c)
1494 Select Case k
1495 Case 0, 1 : k = 6
1496 Case 2, 3 : k = 0
1497 Case 4, 5 : k = 2
1498 Case 6, 7 : k = 4
1499 Case Else : Stop
1500 End Select
1501 Else
1502 '// Handle component composed of only one pixel
1503 nPixelCount = nPixelCount + 1
1504
1505 '// A component with 1 pixel has no connected boundary pixels...
1506 If nPixelCount > 8 Then
1507 bFoundBoundaryPixel = True
1508 bFinished = True
1509 End If
1510 End If
1511 End If
1512
1513 '// Prepare to scan next neighbor
1514 k = (k + 1) Mod 8
1515
1516 If CancelOperation Then
1517 GoTo CancelOperation
1518 End If
1519
1520 End While
1521
1522 '// Boundary pixel found, set Pixel Type accordingly //
1523 PixelType(c) = PixelTypes.Boundary
1524
1525 End While
1526
1527 CancelOperation:
1528 End Sub
1529
1530
1531 '// Assign a unique label to each pixel in the image
1532 '// based on its distance from the background
1533 Public Sub LabelComponentDistances()
1534 Dim pixel As Pixel
1535 Dim oNeighbor As Pixel
1536 Dim oDistancePixel As Pixel
1537
1538 Dim oDistanceCollection As PixelCollectionList
1539 Dim oNestedCollection As PixelCollectionList
1540 Dim nDistance As Integer
1541
1542 oDistanceCollection = New PixelCollectionList
1543
1544 AnimationPushState()
1545 AnimationOff()
1546
1547 ClearLabels()
1548 ClearBoundaryTypes()
1549
1550 AnimationOn()
1551 AnimationRaiseImageChanged()
1552
1553 nDistance = 1
1554
1555 UpdateStatus("Identifying pixels at distance 1")
1556
1557 _progressMax = _count
1558
1559 '// Assign "1" label to all pixels next to a background pixel //
1560 For Each pixel In Me
1561
1562 UpdateProgress(pixel)
1563
1564 If IsBackground(pixel) Then
1565
1566 For Each oNeighbor In pixel.Get4Neighbors()
1567
1568 If IsInBounds(oNeighbor) Then
1569
1570 If IsForeground(oNeighbor) Then
1571
1572 If IsUlabeled(oNeighbor) Then
1573
1574 _progressMax = _progressMax - 1
1575
1576 PixelLabel(oNeighbor) = nDistance
1577 oDistanceCollection.Add(oNeighbor)
1578 End If
1579
1580 End If
1581 End If
1582 Next
1583 End If
1584
1585 If CancelOperation Then
1586 GoTo CancelOperation
1587 End If
1588 Next
1589
1590 _progressCount = 0
1591
1592 oNestedCollection = oDistanceCollection
1593
1594 '// For each previously labelled pixel,
1595 '// assign new distance label to its ulabeled neighbors //
1596 While oNestedCollection.Count > 0
1597
1598 oDistanceCollection = oNestedCollection
1599 oNestedCollection = New PixelCollectionList
1600
1601 nDistance = nDistance + 1
1602 UpdateStatus("Identifying pixels at distance " & nDistance)
1603
1604 For Each oDistancePixel In oDistanceCollection
1605
1606 UpdateProgress(0, _progressMax, _progressCount)
1607
1608 For Each oNeighbor In oDistancePixel.Get4Neighbors()
1609
1610 If IsInBounds(oNeighbor) Then
1611
1612 If IsForeground(oNeighbor) Then
1613
1614 If IsUlabeled(oNeighbor) Then
1615
1616 _progressCount = _progressCount + 1
1617
1618 PixelLabel(oNeighbor) = nDistance
1619 oNestedCollection.Add(oNeighbor)
1620
1621 End If
1622 End If
1623 End If
1624 Next
1625
1626 If CancelOperation Then
1627 GoTo CancelOperation
1628 End If
1629 Next
1630 End While
1631
1632 CancelOperation:
1633 AnimationPopState()
1634 UpdateProgress(0, 0, 0)
1635 End Sub
1636
1637
1638 '// For each background pixel in the image,
1639 '// if 1 or more of its neighbors is a foreground pixel,
1640 '// make it a foreground pixel.
1641 Public Sub Expand()
1642 Dim pixel As Pixel
1643 Dim oMarkedPixelCollection As PixelCollectionList
1644
1645 oMarkedPixelCollection = New PixelCollectionList
1646
1647 ClearLabels()
1648
1649 UpdateStatus("Expanding image")
1650
1651 For Each pixel In Me
1652
1653 UpdateProgress(pixel)
1654
1655 If IsBackground(pixel) Then
1656
1657 If IsUnmarked(pixel) Then
1658
1659 If Count8Neighbors(pixel, 0) > 0 Then
1660
1661 MarkPixel(pixel)
1662 oMarkedPixelCollection.Add(pixel)
1663
1664 End If
1665 End If
1666 End If
1667
1668 If CancelOperation Then
1669 GoTo CancelOperation
1670 End If
1671 Next
1672
1673 SetCollectedPixels(oMarkedPixelCollection)
1674 ClearLabels()
1675
1676 CancelOperation:
1677 End Sub
1678
1679
1680 '// For each foreground pixel in the image,
1681 '// if the pixel is not surrounded (8 neighbors)
1682 '// make it a background pixel.
1683 Public Sub Shrink()
1684 Dim pixel As Pixel
1685 Dim oMarkedPixelCollection As PixelCollectionList
1686
1687 oMarkedPixelCollection = New PixelCollectionList
1688
1689 ClearLabels()
1690
1691 UpdateStatus("Shrinking image")
1692
1693 For Each pixel In Me
1694
1695 UpdateProgress(pixel)
1696
1697 If IsForeground(pixel) Then
1698
1699 If IsUnmarked(pixel) Then
1700
1701 If Count8Neighbors(pixel) < 8 Then
1702
1703 MarkPixel(pixel)
1704 oMarkedPixelCollection.Add(pixel)
1705 End If
1706 End If
1707 End If
1708
1709 If CancelOperation Then
1710 GoTo CancelOperation
1711 End If
1712 Next
1713
1714 RemoveCollectedPixels(oMarkedPixelCollection)
1715 ClearLabels()
1716
1717 CancelOperation:
1718 End Sub
1719
1720
1721 '// Iteratively remove boundary pixels until
1722 '// no more pixels can be removed without breaking
1723 '// a connected component
1724 Sub Thin()
1725
1726 Dim oBoundaryPixels As PixelCollectionTree
1727 Dim oBoundaryPixel As Pixel
1728
1729 Dim oThinnedPixels As PixelCollectionList
1730 Dim oThinnedPixel As Pixel
1731
1732 Dim oNeighbor As Pixel
1733 Dim image3x3 As Image
1734 Dim oTargetPixel As Pixel
1735
1736 Dim progressMin As Integer
1737 Dim progressCount As Integer
1738
1739 Dim nComponentCount As Integer
1740 Dim nNewComponentCount As Integer
1741
1742 Dim bRemovePixel As Boolean
1743
1744
1745 AnimationPushState()
1746 AnimationOff()
1747
1748 '// Target pixel for removal is always center of 3x3 image
1749 oTargetPixel = New Pixel(1, 1)
1750
1751 oThinnedPixels = New PixelCollectionList
1752
1753 '// First, identify boundaries //
1754 IdentifyBoundaries(False)
1755
1756 '// For efficiency, create a tree of boundary pixels //
1757 oBoundaryPixels = GetBoundaryPixelCollectionTree()
1758
1759 AnimationOn()
1760 AnimationRaiseImageChanged(True)
1761
1762 progressMin = oBoundaryPixels.Count
1763
1764 '// Do until there are no more boundary pixels to consider //
1765 While oBoundaryPixels.Count > 0
1766
1767 progressCount = oBoundaryPixels.Count
1768 UpdateProgress(progressMin, 0, progressCount, True)
1769 AnimationRaiseImageChanged(True)
1770
1771 UpdateStatus("Thinning corner pixels")
1772
1773 '// Don't animate corner pixel (hack)
1774 AnimationOff()
1775
1776 For Each oBoundaryPixel In oBoundaryPixels
1777
1778 bRemovePixel = False
1779
1780 If IsForeground(oBoundaryPixel) Then
1781
1782 If IsUnmarked(oBoundaryPixel) Then
1783
1784 If Count8Neighbors(oBoundaryPixel, 3) = 3 Then
1785
1786 '// Top left corner pixel
1787 If CountRangeNeighbors(oBoundaryPixel, 4, 6) = 3 Then
1788 bRemovePixel = True
1789
1790 '// Top right corner pixel
1791 ElseIf CountRangeNeighbors(oBoundaryPixel, 6, 8) = 3 Then
1792 bRemovePixel = True
1793
1794 '// Bottom left corner pixel
1795 ElseIf CountRangeNeighbors(oBoundaryPixel, 2, 4) = 3 Then
1796 bRemovePixel = True
1797
1798 '// Bottom right corner pixel
1799 ElseIf CountRangeNeighbors(oBoundaryPixel, 0, 2) = 3 Then
1800 bRemovePixel = True
1801
1802 End If
1803 End If
1804 End If
1805 End If
1806
1807 If bRemovePixel Then
1808 AnimationOn()
1809 oThinnedPixels.Add(oBoundaryPixel)
1810 RemovePixel(oBoundaryPixel)
1811 AnimationOff()
1812 End If
1813
1814 If CancelOperation Then
1815 GoTo CancelOperation
1816 End If
1817 Next
1818
1819 AnimationOn()
1820 UpdateStatus("Thinning image")
1821
1822 '// Clear marked pixels collection //
1823 oThinnedPixels = New PixelCollectionList
1824
1825 For Each oBoundaryPixel In oBoundaryPixels
1826
1827 bRemovePixel = True
1828
1829 If Count8Neighbors(oBoundaryPixel, 3) = 2 Then
1830
1831 If CountRangeNeighbors(oBoundaryPixel, 6, 7) = 2 Then
1832
1833 oNeighbor = oBoundaryPixel.Neighbor(7)
1834
1835 If IsInBounds(oNeighbor) Then
1836
1837 If CountRangeNeighbors(oNeighbor, 3, 6) = 4 Then
1838 If Count8Neighbors(oNeighbor, 4) = 4 Then
1839 bRemovePixel = False
1840 End If
1841 ElseIf CountRangeNeighbors(oNeighbor, 3, 5) = 3 Then
1842 If Count8Neighbors(oNeighbor, 3) = 3 Then
1843 bRemovePixel = False
1844 End If
1845 End If
1846 End If
1847
1848 End If
1849 End If
1850
1851 If bRemovePixel Then
1852
1853 bRemovePixel = False
1854
1855 If Count8Neighbors(oBoundaryPixel, 1) > 1 Then
1856 image3x3 = GetImage3x3(oBoundaryPixel)
1857
1858 nComponentCount = image3x3.LabelComponents()
1859 image3x3.RemovePixel(oTargetPixel)
1860 nNewComponentCount = image3x3.LabelComponents()
1861
1862 If nNewComponentCount = nComponentCount Then
1863 bRemovePixel = True
1864 End If
1865 End If
1866 End If
1867
1868 If bRemovePixel Then
1869 oThinnedPixels.Add(oBoundaryPixel)
1870 RemovePixel(oBoundaryPixel)
1871 End If
1872
1873 If CancelOperation Then
1874 GoTo CancelOperation
1875 End If
1876
1877 Next
1878
1879 oBoundaryPixels = New PixelCollectionTree
1880
1881 '// Determine the new boundary (from thinned pixels) //
1882 For Each oThinnedPixel In oThinnedPixels
1883
1884 For Each oNeighbor In oThinnedPixel.Get4Neighbors()
1885
1886 If IsInBounds(oNeighbor) Then
1887
1888 If IsForeground(oNeighbor) Then
1889
1890 If Not oBoundaryPixels.Contains(oNeighbor) Then
1891
1892 PixelType(oNeighbor) = PixelTypes.Boundary
1893 oBoundaryPixels.Add(oNeighbor)
1894 End If
1895 End If
1896 End If
1897 Next
1898
1899 If CancelOperation Then
1900 GoTo CancelOperation
1901 End If
1902 Next
1903 End While
1904
1905 CancelOperation:
1906 AnimationPopState()
1907 UpdateProgress(0, 0, 0)
1908 End Sub
1909
1910
1911 '////////////////////////////////////////////////////////////////////////////////////////////
1912 '// ANIMATION, PROGRESS BAR, AND STATUS METHODS
1913 '////////////////////////////////////////////////////////////////////////////////////////////
1914
1915 Public Sub AnimationPushState()
1916 _animationStateStack.Push(_animationState)
1917 End Sub
1918
1919 Public Sub AnimationPopState()
1920 _animationState = CType(_animationStateStack.Pop(), Boolean)
1921 End Sub
1922
1923 Private ReadOnly Property AnimationNow() As Boolean
1924 Get
1925 '// Don't animate if animation disabled for this image //
1926 If _animation = AnimationStates.Disabled Then
1927 Return False
1928 End If
1929
1930 '// Don't animate if temporarily disabled //
1931 If _animationState = False Then
1932 Return False
1933 End If
1934
1935 '// Don't animate big images
1936 If _count > 2500 Then
1937 Return False
1938 End If
1939
1940 Return True
1941 End Get
1942 End Property
1943
1944
1945 Public Sub AnimationOn()
1946 _animationState = True
1947 End Sub
1948
1949
1950 Public Sub AnimationOff()
1951 _animationState = False
1952 End Sub
1953
1954
1955 Private Sub AnimationRaisePixelScanned(ByVal pixel As Pixel)
1956 If AnimationNow() Then
1957 RaiseEvent AnimationPixelScanned(New Pixel(pixel), _animationLastScannedPixel)
1958
1959 _animationLastScannedPixel = New Pixel(pixel)
1960 End If
1961
1962 Application.DoEvents()
1963 End Sub
1964
1965
1966 Private Sub AnimationRaisePixelChanged(ByVal pixel As Pixel)
1967 If AnimationNow() Then
1968 RaiseEvent AnimationPixelChanged(New Pixel(pixel))
1969 End If
1970
1971 Application.DoEvents()
1972 End Sub
1973
1974
1975 Private Sub AnimationRaiseImageChanged(Optional ByVal bForceEvent As Boolean = False)
1976 If AnimationNow() Or bForceEvent Then
1977 RaiseEvent AnimationImageChanged()
1978 End If
1979
1980 Application.DoEvents()
1981 End Sub
1982
1983
1984 Public Sub UpdateProgress(ByVal progressMin As Integer, ByVal progressMax As Integer, ByVal progressCount As Integer, Optional ByVal refreshImage As Boolean = False)
1985
1986 If progressMin < progressMax Then
1987
1988 If progressCount < progressMin Then
1989 progressMin = progressCount
1990 End If
1991 If progressCount > progressMax Then
1992 progressMax = progressCount
1993 End If
1994
1995 RaiseEvent ProgressChanged(progressMin, progressMax, progressCount, refreshImage)
1996
1997 Else
1998
1999 If progressCount > progressMin Then
2000 progressMin = progressCount
2001 End If
2002 If progressCount < progressMax Then
2003 progressMax = progressCount
2004 End If
2005
2006 progressCount = progressMin - progressCount
2007
2008 RaiseEvent ProgressChanged(progressMax, progressMin, progressCount, refreshImage)
2009 End If
2010
2011 Application.DoEvents()
2012 End Sub
2013
2014
2015 '// Overloaded progress method using pixel from iterator //
2016 Public Sub UpdateProgress(ByVal pixel As Pixel, Optional ByVal refreshImage As Boolean = False)
2017 RaiseEvent ProgressChanged(0, _count, pixel.ArrayIndex(_width), refreshImage)
2018 End Sub
2019
2020
2021 Sub UpdateStatus(ByVal text As String)
2022 RaiseEvent StatusChanged(text)
2023 End Sub
2024
2025
2026
2027 '// Enumerator for image class
2028 '// Allows iteration through every pixel position in the image
2029 Private Class ImagePixelEnumerator
2030 Implements IEnumerator
2031
2032 Private lastModified As Integer
2033 Private moCurrent As Pixel
2034 Private moParent As Image
2035
2036
2037 Public Sub New(ByVal parent As Image)
2038 moParent = parent
2039 lastModified = parent.lastModified
2040 moCurrent = New Pixel(0, -1)
2041 Reset()
2042 End Sub
2043
2044
2045 Public ReadOnly Property Current() As Object Implements Collections.IEnumerator.Current
2046 Get
2047 If moParent.lastModified <> lastModified Then
2048 Throw New InvalidOperationException("collection modified during iteration")
2049 End If
2050
2051 moParent.AnimationRaisePixelScanned(moCurrent)
2052
2053 Return moCurrent
2054 End Get
2055 End Property
2056
2057
2058 Public Function MoveNext() As Boolean Implements Collections.IEnumerator.MoveNext
2059 If moParent.lastModified <> lastModified Then
2060 Throw New InvalidOperationException("collection modified during iteration")
2061 End If
2062
2063 Application.DoEvents()
2064
2065 moCurrent.j = moCurrent.j + 1
2066 If moCurrent.j >= moParent._width Then
2067 moCurrent.j = 0
2068 moCurrent.i = moCurrent.i + 1
2069
2070 If moCurrent.i >= moParent._height Then
2071 Return False
2072 End If
2073 End If
2074
2075 Return True
2076 End Function
2077
2078
2079 Public Sub Reset() Implements Collections.IEnumerator.Reset
2080 If moParent.lastModified <> lastModified Then
2081 Throw New InvalidOperationException
2082 End If
2083
2084 moCurrent.i = 0
2085 moCurrent.j = -1
2086 End Sub
2087 End Class
2088
2089
2090 Public Function GetEnumerator() As Collections.IEnumerator Implements Collections.IEnumerable.GetEnumerator
2091 Return New ImagePixelEnumerator(Me)
2092 End Function
2093
2094
2095 Public Sub CopyTo(ByVal array As Array, ByVal index As Integer) Implements Collections.ICollection.CopyTo
2096 Dim value As Object
2097
2098 For Each value In Me
2099 array.SetValue(value, index)
2100 index = index + 1
2101 Next
2102 End Sub
2103
2104
2105 Public ReadOnly Property Count() As Integer Implements Collections.ICollection.Count
2106 Get
2107 Return _count
2108 End Get
2109 End Property
2110
2111
2112 Public ReadOnly Property IsSynchronized() As Boolean Implements Collections.ICollection.IsSynchronized
2113 Get
2114 '// Ignore synchronization (for performance)
2115 Return False
2116 End Get
2117 End Property
2118
2119
2120 Public ReadOnly Property SyncRoot() As Object Implements Collections.ICollection.SyncRoot
2121 Get
2122 Return Me
2123 End Get
2124 End Property
2125 End Class