Imaging.NET Source Code - Pixel/Image Sample

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



Copyright (C) 2007-2009 by Robert Pinchbeck
All Rights Reserved