View Javadoc

1   package com.atlassian.selenium.visualcomparison.utils;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   public class BoundingBox
7   {
8       private int left;
9       private int top;
10      private int right;
11      private int bottom;
12  
13      private int leftMargin;
14      private int topMargin;
15      private int rightMargin;
16      private int bottomMargin;
17      private static final int MARGIN = 25;
18  
19      public BoundingBox(int x, int y)
20      {
21          this(x, y, x, y);
22      }
23  
24      public BoundingBox(int left, int top, int right, int bottom)
25      {
26          setLeft(left);
27          setTop(top);
28          setRight(right);
29          setBottom(bottom);
30      }
31  
32      // Although the margin values are derived values, let's cache them because we'll be using them
33      // far more often than we set them.
34      private void setLeft(int left)
35      {
36          this.left = left;
37          this.leftMargin = left - MARGIN;
38      }
39  
40      private void setRight(int right)
41      {
42          this.right = right;
43          this.rightMargin = right + MARGIN;
44      }
45  
46      private void setTop(int top)
47      {
48          this.top = top;
49          this.topMargin = top - MARGIN;
50      }
51  
52      private void setBottom(int bottom)
53      {
54          this.bottom = bottom;
55          this.bottomMargin = bottom + MARGIN;
56      }
57  
58      public int getLeft()
59      {
60          return left;
61      }
62  
63      public int getTop()
64      {
65          return top;
66      }
67  
68      public int getRight()
69      {
70          return right;
71      }
72  
73      public int getBottom()
74      {
75          return bottom;
76      }
77  
78      public int getWidth()
79      {
80          return right - left + 1;
81      }
82  
83      public int getHeight()
84      {
85          return bottom - top + 1;
86      }
87  
88      // Accessor functions for the box's margins that restrict them to fit within the image.
89      public int getMarginLeft()
90      {
91          return Math.max(left - MARGIN, 0);
92      }
93  
94      public int getMarginTop()
95      {
96          return Math.max(top - MARGIN, 0);
97      }
98  
99      public int getMarginRight(int maxX)
100     {
101         return Math.min(right + MARGIN, maxX);
102     }
103 
104     public int getMarginBottom(int maxY)
105     {
106         return Math.min(bottom + MARGIN, maxY);
107     }
108 
109     public int getMarginWidth(int maxX)
110     {
111         return getMarginRight(maxX) - getMarginLeft() + 1;
112     }
113 
114     public int getMarginHeight(int maxY)
115     {
116         return getMarginBottom(maxY) - getMarginTop() + 1;
117     }
118 
119     public boolean contains (int x, int y)
120     {
121         // Return true if the given co-ords are within this box.
122         return ((x >= left) && (x <= right) &&
123                 (y >= top) && (y <= bottom));
124     }
125 
126     public boolean isNear(int x, int y)
127     {
128         // Return true if the given co-ords are within this box, or within the given margin of it.
129         return ((x >= leftMargin) && (x <= rightMargin) &&
130                 (y >= topMargin) && (y <= bottomMargin));
131     }
132 
133     public boolean isNear(BoundingBox box)
134     {
135         // If any of this box's corners are near the other box, or any of the other box's corners are
136         // near this box, then the boxes overlap or are very close.
137         return (box.isNear(left, top) || box.isNear(right, top) ||
138                 box.isNear(left, bottom) || box.isNear(right, bottom) ||
139                 isNear(box.left, box.top) || isNear(box.right, box.top) ||
140                 isNear(box.left, box.bottom) || isNear(box.right, box.bottom));
141     }
142 
143     public void merge(int x, int y)
144     {
145         // Expand this box to contain the given co-ords.
146         if (x < left)
147         {
148             setLeft(x);
149         }
150         if (x > right)
151         {
152             setRight(x);
153         }
154         if (y < top)
155         {
156             setTop(y);
157         }
158         if (y > bottom)
159         {
160             setBottom(y);
161         }
162     }
163 
164     public void merge(BoundingBox box)
165     {
166         // Expand this box to contain the given box.
167         merge(box.left, box.top);
168         merge(box.right, box.bottom);
169     }
170 
171     public static void mergeOverlappingBoxes(ArrayList<BoundingBox> boxes)
172     {
173         // This is very messy. The basic idea is to merge all boxes that overlap.
174         // The current approach (until I think of a better one) is to compare every box
175         // in the array with every box after it, and keep iterating over the whole
176         // array until there are no longer any overlaps. The outer loop is needed because
177         // merging two boxes could bring the combined box into conflict with a box we've
178         // already checked.
179         boolean mergePerformedThisLoop;
180         do
181         {
182             mergePerformedThisLoop = false;
183             for (int iCurrent = 0; iCurrent < boxes.size(); iCurrent++)
184             {
185                 BoundingBox current = boxes.get(iCurrent);
186                 for (int iOther = iCurrent + 1; iOther < boxes.size();)
187                 {
188                     if (current.isNear(boxes.get(iOther)))
189                     {
190                         current.merge(boxes.get(iOther));
191                         boxes.remove(iOther);
192                         mergePerformedThisLoop = true;
193                     }
194                     else
195                     {
196                         iOther++;
197                     }
198                 }
199             }
200         }
201         while (mergePerformedThisLoop);
202     }
203 
204     public static void deleteSingleLineBoxes(List<BoundingBox> boxes)
205     {
206         // Remove any changes that are only one pixel wide or high
207         for (int i = 0; i < boxes.size(); )
208         {
209             BoundingBox box = boxes.get(i);
210             if (box.getWidth() == 1 || box.getHeight() == 1)
211             {
212                 boxes.remove(i);
213             }
214             else
215             {
216                 i++;
217             }
218         }
219     }
220 }