Search code examples
geometryrectangles

Difference (XOR) between two rectangles, as rectangles?


I'm looking for a simple way to calculate the difference between two rectangles. I mean all points which belong to one of the rectangles, but not to both (so it's like XOR).

The rectangles are axis-aligned in this case, so there will be only right angles. I believe the difference region can be expressed in 0-4 rectangles (0 if both rectangles are the same, 1 if just one edge is different, 4 in the general case), and I'd like to get the difference region as a list of rectangles.

You can also think of it as the areas of the screen that have to be updated when a solid rectangle is moved/resized.

Examples: Doubling the width of rectangle "a" - I want the added region (R).

+----+----+
| a  | R  |
|    |    |
+----+----+                   

Intersecting rectangles (a and b) - I want the area given by T, L, R and B in rectangles (other partitioning possible), but excluding X:

+------------+  a
|      T     |
|·····+------+-----+  b
|  L  | X    |  R  |
|     |      |     |
+-----+------+·····|
      |    B       |
      +------------+

I'd prefer a python solution/library, but any robust algorithm would be helpful.


Solution

  • Split the problem down onto a per-axis basis. Your rectangles can be defined in terms of their spans on each axis - find the interesting points on each axis where a rectangle starts or ends and then define your results in those terms. This'll give you 6 rectangles of difference areas, you can easily combine them down to the four you've illustrated or eliminate degenerate zero-area rectangles if you need to.

    Here's a Java implementation:

    public class Rect
    {
        private float minX, maxX, minY, maxY;
    
        public Rect( float minX, float maxX, float minY, float maxY )
        {
            this.minX = minX;
            this.maxX = maxX;
            this.minY = minY;
            this.maxY = maxY;
        }
    
        /**
         * Finds the difference between two intersecting rectangles
         * 
         * @param r
         * @param s
         * @return An array of rectangle areas that are covered by either r or s, but
         *         not both
         */
        public static Rect[] diff( Rect r, Rect s )
        {
            float a = Math.min( r.minX, s.minX );
            float b = Math.max( r.minX, s.minX );
            float c = Math.min( r.maxX, s.maxX );
            float d = Math.max( r.maxX, s.maxX );
    
            float e = Math.min( r.minY, s.minY );
            float f = Math.max( r.minY, s.minY );
            float g = Math.min( r.maxY, s.maxY );
            float h = Math.max( r.maxY, s.maxY );
    
            // X = intersection, 0-7 = possible difference areas
            // h ┌─┬─┬─┐
            // . │5│6│7│
            // g ├─┼─┼─┤
            // . │3│X│4│
            // f ├─┼─┼─┤
            // . │0│1│2│
            // e └─┴─┴─┘
            // . a b c d
    
            Rect[] result = new Rect[ 6 ];
    
            // we'll always have rectangles 1, 3, 4 and 6
            result[ 0 ] = new Rect( b, c, e, f );
            result[ 1 ] = new Rect( a, b, f, g );
            result[ 2 ] = new Rect( c, d, f, g );
            result[ 3 ] = new Rect( b, c, g, h );
    
            // decide which corners
            if( r.minX == a && r.minY == e || s.minX == a && s.minY == e )
            { // corners 0 and 7
                result[ 4 ] = new Rect( a, b, e, f );
                result[ 5 ] = new Rect( c, d, g, h );
            }
            else
            { // corners 2 and 5
                result[ 4 ] = new Rect( c, d, e, f );
                result[ 5 ] = new Rect( a, b, g, h );
            }
    
            return result;
        }
    }