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