View Javadoc

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