Introduction

In my last blog I was playing with the AForge.Net framework and trying to get it to work. This resulted in less than optimal code and no tests what so ever. I find it is nearly impossible to write tests for something you don’t even know what you want it to do. So today I sat behind my PC and improved the code a bit. So that my colleagues can now test this and see if they like it or have any comments on it. The step after that is to incorporate it into the application and to make it possible to search in our database for fibers with a similar hue.

This is the finished result.

Separating

To begin with everything was in my view. I don’t mind using the codebehind but it should only be for view things in my mind. So I think it needed a Facade/ViewModel to put things in it that do not belong in the view.

Here is the interface I came up with.

Namespace FiberColor.View.Facade.Interfaces
	Public Interface IFiberColorFacade
		Event BlobsCountChanged(ByVal Count As Integer)
		Event CurrentBlobChanged(ByVal Index As Integer, ByVal Blob As Blob)
		Sub FindBlobs(ByVal Image As System.Drawing.Image)
		Sub FirstBlob()
		Sub NextBlob()
		Sub PreviousBlob()
	End Interface
End Namespace```
And this is the implementation.

```vbnet
Option Infer On

Imports System.Drawing
Imports AForge
Imports AForge.Imaging
Imports TDB2009.View.Winforms.FiberColor.View.Facade.Interfaces

Namespace FiberColor.View.Facade
	Public Class FiberColorFacade
		Implements IFiberColorFacade
		
		Private _Blobs() As Blob
		Private _BlobCounter As BlobCounter
		Private _CurrentImage As Bitmap
		Private _Index As Integer

		Public Event BlobsCountChanged(ByVal Count As Integer) Implements Interfaces.IFiberColorFacade.BlobsCountChanged

		Public Event CurrentBlobChanged(ByVal Index As Integer, ByVal Blob As AForge.Imaging.Blob) Implements Interfaces.IFiberColorFacade.CurrentBlobChanged

		Private Sub ChangeCurrentBlob()
			If _Blobs.Count > 0 Then
				_BlobCounter.ExtractBlobsImage(_CurrentImage, _Blobs(_Index), True)
				RaiseEvent CurrentBlobChanged(_Index, _Blobs(_Index))
			Else
				RaiseEvent CurrentBlobChanged(_Index, Nothing)
			End If
		End Sub

		Public Sub FindBlobs(ByVal Image As System.Drawing.Image) Implements Interfaces.IFiberColorFacade.FindBlobs
			Dim _ColorFilter = New AForge.Imaging.Filters.ColorFiltering
			_CurrentImage = New Bitmap(Image.Width, Image.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb)
			Using _Graphics = Graphics.FromImage(_CurrentImage)
				_Graphics.DrawImage(Image, 0, 0, New Rectangle(0, 0, Image.Width, Image.Height), GraphicsUnit.Pixel)
			End Using
			_ColorFilter.Red = New IntRange(150, 255)
			_ColorFilter.Green = New IntRange(150, 255)
			_ColorFilter.Blue = New IntRange(150, 255)
			_ColorFilter.FillOutsideRange = False
			_ColorFilter.ApplyInPlace(_CurrentImage)
			_BlobCounter = New AForge.Imaging.BlobCounter(_CurrentImage)
			_Blobs = (From e In _BlobCounter.GetObjectsInformation Where e.Area > 20000 Order By e.Area Descending Select e).ToArray
			FirstBlob()
			RaiseEvent BlobsCountChanged(_Blobs.Count)
		End Sub

		Public Sub FirstBlob() Implements Interfaces.IFiberColorFacade.FirstBlob
			_Index = 0
			ChangeCurrentBlob()
		End Sub

		Public Sub NextBlob() Implements Interfaces.IFiberColorFacade.NextBlob
			If _Index < _Blobs.Count - 1 Then
				_Index += 1
			End If
			ChangeCurrentBlob()
		End Sub

		Public Sub PreviousBlob() Implements Interfaces.IFiberColorFacade.PreviousBlob
			If _Index > 0 Then
				_Index -= 1
			End If
			ChangeCurrentBlob()
		End Sub
	End Class
End Namespace```
You see that all references to AForge.Net are now in here. There should not be any in the view. I go by the principle Tell don’t ask. I tell the facade to do something and it raises an event that tells the view it can update. What remains is the view.

## The view

The view has a reference to the Facade. And reacts to the events of our facade.

```vbnet
Imports System.Drawing
Imports TDB2009.View.Winforms.FiberColor.View.Facade.Interfaces
Imports TDB2009.View.Interfaces.FiberColor.Forms

Namespace FiberColor.View.Forms
	''' <summary>
	''' 
	''' </summary>
	''' <remarks></remarks>
	Public Class FiberColor
		Implements IFiberColor

		Private WithEvents _Facade As IFiberColorFacade

		Public Sub New(ByVal Facade As IFiberColorFacade)

			' This call is required by the designer.
			InitializeComponent()

			' Add any initialization after the InitializeComponent() call.
			_Facade = Facade
		End Sub

		Private Sub mnuOpen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuOpen.Click
			OpenFileDialog1.Filter = "Jpeg files(*.jpg)|*.jpg"
			If OpenFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
				Me.lblFilename.Text = OpenFileDialog1.FileName
				OriginalImage.Image = Image.FromFile(OpenFileDialog1.FileName, False)
				_Facade.FindBlobs(OriginalImage.Image)
			End If
		End Sub

		Private Sub mnuExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuExit.Click
			Me.Close()
		End Sub

		Private Sub _Facade_BlobsCountChanged(ByVal Count As Integer) Handles _Facade.BlobsCountChanged
			If Count > 0 Then
				panMoreImages.Visible = True
				lblCount.Text = "of " & Count
			Else
				panMoreImages.Visible = False
				lblStatus.Text = "No images found"
			End If
		End Sub

		Private Sub _Facade_CurrentBlobChanged(ByVal Index As Integer, ByVal Blob As AForge.Imaging.Blob) Handles _Facade.CurrentBlobChanged
			If Blob IsNot Nothing Then
				Me.lblStatus.Text = "Mean: (R = " & Blob.ColorMean.R & " - G = " & Blob.ColorMean.G & " - B = " & Blob.ColorMean.B & ") STD: (R = " & Blob.ColorStdDev.R & " - G = " & Blob.ColorStdDev.G & " - B = " & Blob.ColorStdDev.B & ") Surface: " & Blob.Area
				Me.panMean.BackColor = Blob.ColorMean
				Me.HsbColorIndicator1.AverageColor = Blob.ColorMean
				Me.panStandarddeviation.BackColor = Blob.ColorStdDev
				FoundFiber.Image = Blob.Image
				lblNumber.Text = (Index + 1).ToString
			Else
				Me.HsbColorIndicator1.AverageColor = Color.Transparent
				Me.lblStatus.Text = "No Blob found"
				Me.panMean.BackColor = Color.Transparent
				Me.panStandarddeviation.BackColor = Color.Transparent
				FoundFiber.Image = Nothing
			End If
		End Sub

		Private Sub btnNextImage_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnNextImage.Click
			_Facade.NextBlob()
		End Sub

		Private Sub btnPreviousImage_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnPreviousImage.Click
			_Facade.PreviousBlob()
		End Sub
	End Class
End Namespace```
The view has no longer any reference to AForge.Net. I also improved the HSBColorIndicator a bit.

```vbnet
Imports System.Drawing.Drawing2D
Imports System.Drawing
Imports TDB2009.View.Winforms.Controls.HSB

Namespace FiberColor.View.Controls.HSB
	
	Public Class HSBColorIndicator

		Private Const LEFTMARGIN As Integer = 5
		Private Const TOPMARGIN As Integer = 10
		Private Const COLORBARHEIGHT As Integer = 360
		Private Const COLORBARWIDTH As Integer = 20
		Private Const COLORBARTEXTWIDTH = 50
		Private Const ARROWWIDTH As Integer = 10
		Private Const ARROWHEIGHT As Integer = 6
		Private Const RIGHTMARGIN As Integer = 50

		Private _AverageColor As Color
		Private _Boundries As ISet(Of Boundry)

		Public Sub New()

			InitializeComponent()

			Boundries = New HashSet(Of Boundry)
			Boundries.Add(New Boundry With {.ColorName = "Red", .LowerLimit = 0, .UpperLimit = 30})
			Boundries.Add(New Boundry With {.ColorName = "Magenta", .LowerLimit = 30, .UpperLimit = 90})
			Boundries.Add(New Boundry With {.ColorName = "Blue", .LowerLimit = 90, .UpperLimit = 150})
			Boundries.Add(New Boundry With {.ColorName = "Cyan", .LowerLimit = 150, .UpperLimit = 210})
			Boundries.Add(New Boundry With {.ColorName = "Green", .LowerLimit = 210, .UpperLimit = 270})
			Boundries.Add(New Boundry With {.ColorName = "Yellow", .LowerLimit = 270, .UpperLimit = 330})
			Boundries.Add(New Boundry With {.ColorName = "Red", .LowerLimit = 330, .UpperLimit = 360})
			DrawColorImage()
		End Sub

		<System.ComponentModel.Browsable(False)> _
		<System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Hidden)> _
		Public Property Boundries As ISet(Of Boundry)
			Get
				Return _Boundries
			End Get
			Set(ByVal value As ISet(Of Boundry))
				_Boundries = value
				DrawColorImage()
			End Set
		End Property

		Public Property AverageColor As Color
			Get
				Return _AverageColor
			End Get
			Set(ByVal value As Color)
				_AverageColor = value
				DrawArrowAndHue()
			End Set
		End Property

		Public Overrides Property MinimumSize As System.Drawing.Size
			Get
				Return New Size(150, 400)
			End Get
			Set(ByVal value As System.Drawing.Size)
				MyBase.MinimumSize = New Size(150, 400)
			End Set
		End Property

		Private ReadOnly Property Hue As Integer
			Get
				Return Convert.ToInt32(360 - _AverageColor.GetHue)
			End Get
		End Property

		Private Sub DrawArrowAndHue()
			Me.Invalidate(New Rectangle(LEFTMARGIN + COLORBARWIDTH, TOPMARGIN, 40, COLORBARHEIGHT))
			Me.Refresh()
			If Not _AverageColor = Color.Transparent Then
				Using _Graphics = Me.CreateGraphics
					Dim points = {New Point(LEFTMARGIN + COLORBARWIDTH + COLORBARTEXTWIDTH, Hue + TOPMARGIN), New Point(LEFTMARGIN + COLORBARWIDTH + ARROWWIDTH + COLORBARTEXTWIDTH, Convert.ToInt32(Hue + TOPMARGIN - (ARROWHEIGHT / 2))), New Point(LEFTMARGIN + COLORBARWIDTH + ARROWWIDTH + COLORBARTEXTWIDTH, Convert.ToInt32(Hue + TOPMARGIN + (ARROWHEIGHT / 2)))}
					_Graphics.DrawPolygon(New Pen(Me.ForeColor), points)
					_Graphics.DrawString("Hue = " & Hue.ToString("0"), Me.Font, New SolidBrush(Me.ForeColor), LEFTMARGIN + COLORBARWIDTH + ARROWWIDTH + COLORBARTEXTWIDTH + 1, Hue + TOPMARGIN - 5)
				End Using
			End If
		End Sub

		Private Function GetColorName() As String
			For Each _Boundry In Boundries
				If Hue > _Boundry.LowerLimit AndAlso Hue <= _Boundry.UpperLimit Then
					Return _Boundry.ColorName
				End If
			Next
			Return "Not defined"
		End Function

		Private Sub DrawColorImage()
			Dim rect = New Rectangle(COLORBARTEXTWIDTH, 0, COLORBARWIDTH, COLORBARHEIGHT)
			Dim _ColorBarBitmap = New Bitmap(COLORBARWIDTH + COLORBARTEXTWIDTH, COLORBARHEIGHT)
			Using _Graphics = Graphics.FromImage(_ColorBarBitmap)
				Using _Brush = New LinearGradientBrush(rect, Color.Blue, Color.Red, 90.0F, False)
					Dim _Colors = {Color.Red, Color.Magenta, Color.Blue, Color.Cyan, Color.FromArgb(0, 255, 0), Color.Yellow, Color.Red}
					Dim _Positions = {0.0F, 0.1667F, 0.3372F, 0.502F, 0.6686F, 0.8313F, 1.0F}

					Dim colorBlend = New ColorBlend()
					colorBlend.Colors = _Colors
					colorBlend.Positions = _Positions
					_Brush.InterpolationColors = colorBlend

					_Graphics.FillRectangle(_Brush, rect)
					DrawBoundries(_Graphics)
				End Using
			End Using
			Me.hsbcolorbar.Top = TOPMARGIN
			Me.hsbcolorbar.Left = LEFTMARGIN
			Me.hsbcolorbar.Width = COLORBARWIDTH + COLORBARTEXTWIDTH
			Me.hsbcolorbar.Height = COLORBARHEIGHT
			Me.hsbcolorbar.Image = _ColorBarBitmap
		End Sub

		Private Sub DrawBoundries(ByRef ImageGraphics As Graphics)
			Dim penColor = Pens.White
			For Each _Boundry In Boundries
				ImageGraphics.DrawLine(penColor, COLORBARTEXTWIDTH, _Boundry.UpperLimit, COLORBARWIDTH + COLORBARTEXTWIDTH, _Boundry.UpperLimit)
				ImageGraphics.DrawString(_Boundry.ColorName, Me.Font, New SolidBrush(Me.ForeColor), 0, Convert.ToInt32(_Boundry.LowerLimit + ((_Boundry.UpperLimit - _Boundry.LowerLimit) / 2)))
			Next
		End Sub

	End Class
End Namespace```
With this as the Boundry class.

```vbnet
Namespace Controls.HSB
	<Serializable()>
	Public Class Boundry
		Implements IComparable

		Public Property LowerLimit As Integer
		Public Property UpperLimit As Integer
		Public Property ColorName As String

		Public Overloads Function Equals(ByVal other As Boundry) As Boolean
			If ReferenceEquals(Nothing, other) Then Return False
			If ReferenceEquals(Me, other) Then Return True
			Return other._LowerLimit = _LowerLimit AndAlso other._UpperLimit = _UpperLimit
		End Function

		Public Overrides Function GetHashCode() As Integer
			Dim hashCode As Integer = _LowerLimit
			hashCode = (hashCode * 397) Xor _UpperLimit
			Return hashCode
		End Function

		Public Overrides Function Equals(ByVal obj As Object) As Boolean
			If ReferenceEquals(Nothing, obj) Then Return False
			If ReferenceEquals(Me, obj) Then Return True
			Return Equals(TryCast(obj, Boundry))
		End Function

		Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
			If ReferenceEquals(Nothing, obj) Then Return -1
			If ReferenceEquals(Me, obj) Then Return 0
			If Not TypeOf obj Is Boundry Then Return -1
			Return (UpperLimit + LowerLimit).CompareTo(CType(obj, Boundry).UpperLimit + CType(obj, Boundry).LowerLimit)
		End Function
	End Class
End Namespace```
Here I could have choosen to also inject the facade and let it update the usercontrol that way but for now I will just let the codebehind of the form do that.

## Conclusion

I am very happy with the current results and will move on until I have had some feedback. I don’t want to do more work for nothing if they decide it will not suit them. Like everybody else I always have more things to do than I have time so I have to balance between “good” and “good enough for now”. Perfect does not exist in the software world so I do not strive for it.