A Java implementation of the X11 region : Area Calculation « 2D Graphics GUI « Java

Java
1. 2D Graphics GUI
2. 3D
3. Advanced Graphics
4. Ant
5. Apache Common
6. Chart
7. Collections Data Structure
8. Database SQL JDBC
9. Design Pattern
10. Development Class
11. Email
12. Event
13. File Input Output
14. Game
15. Hibernate
16. J2EE
17. J2ME
18. JDK 6
19. JSP
20. JSTL
21. Language Basics
22. Network Protocol
23. PDF RTF
24. Regular Expressions
25. Security
26. Servlets
27. Spring
28. Swing Components
29. Swing JFC
30. SWT JFace Eclipse
31. Threads
32. Tiny Application
33. Velocity
34. Web Services SOA
35. XML
Microsoft Office Word 2007 Tutorial
Java Tutorial
Java Source Code / Java Documentation
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
C# / C Sharp
C# / CSharp Tutorial
ASP.Net
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
PHP
Python
SQL Server / T-SQL
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Java » 2D Graphics GUI » Area CalculationScreenshots 
A Java implementation of the X11 region


/*
 * (C) 2004 - Geotechnical Software Services
 
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA  02111-1307, USA.
 */
//package no.geosoft.cc.geometry;



/**
 * A <em>Region</em> is simply an area, as the name implies, and is
 * implemented as a so called "y-x-banded" array of rectangles; Each Region
 * is made up of a certain number of rectangles sorted by y coordinate first,
 * and then by x coordinate.
 * <p>
 * Furthermore, the rectangles are banded such that every rectangle with a
 * given upper-left y coordinate (y1) will have the same lower-right y
 * coordinate (y2) and vice versa. If a rectangle has scanlines in a band,
 * it will span the entire vertical distance of the band. This means that
 * some areas that could be merged into a taller rectangle will be represented
 * as several shorter rectangles to account for shorter rectangles to its
 * left or right but within its "vertical scope".
 * <p>
 * An added constraint on the rectangles is that they must cover as much
 * horizontal area as possible. E.g. no two rectangles in a band are allowed
 * to touch. Whenever possible, bands will be merged together to cover a
 * greater vertical distance (and thus reduce the number of rectangles).
 * Two bands can be merged only if the bottom of one touches the top of the
 * other and they have rectangles in the same places (of the same width, of
 * course). This maintains the y-x-banding.
 * <p>
 * Region operations includes add (union), subtract, intersect, and
 * exclusive-or.
 * <p>
 * This class corresponds to Region.c of the X11 distribution and the
 * implemntation is based on it.
 * <p>
 * The <em>Region</em> is essentially equivalent to an AWT <em>Area</em>
 * but with different back-end implementation. Becnhmarking proves it more
 * than 100 times faster than AWT Area for binary CAG operations,
 * <p>
 * Thanks to:
 * <ul>
 * <li>Bryan Lin @ China Minmetals Corporation - for identifying
 *     synchronization errors when run on the MS WindowsXP platform.
 * <li>Maxim Butov @ Belhard - for identifying error in the
 *     isInside(Rect) method.
 * </ul>
 *
 @author <a href="mailto:jacob.dreyer@geosoft.no">Jacob Dreyer</a>
 */   
public class Region
  implements Cloneable
{
  private static final int OPERATION_UNION        = 0;
  private static final int OPERATION_INTERSECTION = 1;
  private static final int OPERATION_SUBTRACTION  = 2;
  private static final int OPERATION_XOR          = 3;      

  private static final int INITIAL_SIZE = 40// 10 rectangles
  
  // Temporary working area common for all regions for maximum performance
  private static int       gRectangles_[] new int[INITIAL_SIZE];
  private static int       gNRectangles_  = 0;
  private static boolean   isLocked_      = false;

  private Box              extent_;
  private int              rectangles_[]// y0,y1,x0,x1,.....
  private int              nRectangles_;

  
  
  /**
   * Create an empty region. Corresponds to XCreateRegion of X11.
   */
  public Region()
  {
    extent_      = new Box (0000);
    rectangles_  = new int[INITIAL_SIZE];  
    nRectangles_ = 0;
  }

  
  
  /**
   * Create the region covering the (possibly complex) polygon specified
   * by the x and y parameters.
   * Corresponds to XPolygonRegion of X11.
   
   @param x  X values of polygon vertices.
   @param y  Y values of polygon vertices.
   */
  public Region (int x[]int y[])  
  {
    // TODO. See PolyReg.c of X11.
  }


  
  /**
   * Create a region constituting of a single rectangle as specified.
   
   @param rectangle  Rectangle to create region from.
   */
  public Region (Rect rectangle)
  {
    this();
    set (rectangle);
  }
  

  
  /**
   * Create a region consisting of one rectangle as specified.
   
   @param x       X position of upper left corner of rectangle.
   @param y       Y position of upper left corner of rectangle.
   @param width   Width of rectangle.
   @param height  Height of rectangle.
   */
  public Region (int x, int y, int width, int height)
  {
    this (new Rect (x, y, width, height));
  }



  /**
   * Create a region consisting of one rectangle as specified.
   
   @param box  Box specification of rectangle to create region from.
   */
  public Region (Box box)
  {
    this (new Rect (box));
  }
  
  

  /**
   * Create a region as a copy of the specified region.
   
   @param region  Region to copy.
   */
  public Region (Region region)
  {
    extent_ = new Box();
    rectangles_ = new int[region.nRectangles_ << 2];
    set (region);
  }
  


  /**
   * Clone this region.
   
   @return  Clone of this region.
   */
  public Object clone()
  {
    return new Region (this);
  }


  
  /**
   * Convert this region to an AWT Area.
   * <p>
   * Note: The AWT classes are referenced explicitly here rather tham
   * importing them to indicate that the Region implementation does not
   * dependent on the AWT.
   
   @return  Area equivalent of this rectangle.
   */
  public java.awt.geom.Area createArea()
  {
    if (nRectangles_ == 0return null;
    
    java.awt.Rectangle rectangle =
      new java.awt.Rectangle (rectangles_[2],
                              rectangles_[0],
                              rectangles_[3- rectangles_[2],
                              rectangles_[1- rectangles_[0]);
    java.awt.geom.Area area = new java.awt.geom.Area (rectangle);
    
    for (int i = 1; i < nRectangles_; i++) {
      int j = i * 4;
      rectangle = new java.awt.Rectangle (rectangles_[j+2],
                                          rectangles_[j+0],
                                          rectangles_[j+3- rectangles_[j+2],
                                          rectangles_[j+1- rectangles_[j+0]);
      
      area.add (new java.awt.geom.Area (rectangle));
    }

    return area;
  }


  
  private static void checkMemory (Region region, int nRectangles)
  {
    int nEntries = nRectangles << 2;

    if (region == null) {
      if (gRectangles_.length < nEntries) {
        int newSize = nEntries * 2;
        int newArray[] new int[newSize];
        System.arraycopy (gRectangles_, 0, newArray, 0, gRectangles_.length);
        gRectangles_ = newArray;
      }
    }
    else {
      if (region.rectangles_.length < nEntries) {
        int newSize = nEntries * 2;
        int newArray[] new int[newSize];
        System.arraycopy (region.rectangles_, 0, newArray, 0,
                          region.rectangles_.length);
        region.rectangles_ = newArray;
      }
    }
  }
  
  

  /**
   * Set the content of this region according to the specified
   * region.
   
   @param region  Region to copy.
   */
  public void set (Region region)
  {
    extent_.copy (region.extent_);

    checkMemory (this, region.nRectangles_);

    System.arraycopy (region.rectangles_, 0,
                      rectangles_, 0, region.nRectangles_ << 2);

    nRectangles_ = region.nRectangles_;
  }


  
  /**
   * Set the content of this region according to the specified
   * rectangle.
   *
   @param rectangle  Rectangle to set region according to.
   */
  public void set (Rect rectangle)
  {
    rectangles_ = new int[INITIAL_SIZE];      

    if (rectangle.isEmpty()) {
      extent_      = new Box();
      nRectangles_ = 0;
    }
    else {
      extent_ = new Box (rectangle);
      rectangles_[0= extent_.y1;
      rectangles_[1= extent_.y2;    
      rectangles_[2= extent_.x1;
      rectangles_[3= extent_.x2;    
      nRectangles_ = 1;
    }
  }
  
  

  /**
   * Clear the region.
   */
  public void clear()
  {
    nRectangles_ = 0;
    extent_.set (0000);
  }



  /**
   * Return true if this region equals the specified object.
   * Corrensponds to XEqualRegion of X11.
   
   @param object  Object to check against.
   @return        True if the two regions equals, false otherwise.
   @throws        ClassCastException if object is not of type Region.
   */
  public boolean equals (Object object
  {
    Region region = (Regionobject;
    
    if      (nRectangles_ != region.nRectangles_return false;
    else if (nRectangles_ == 0)                   return true;
    else if (extent_.x1 != region.extent_.x1)     return false;
    else if (extent_.x2 != region.extent_.x2)     return false;
    else if (extent_.y1 != region.extent_.y1)     return false;
    else if (extent_.y2 != region.extent_.y2)     return false;
    else {
      for (int i = 0; i < nRectangles_ << 2; i++)
        if (rectangles_[i!= region.rectangles_[i]) return false;
    }
    
    return true;
  }
  
  

  /**
   * Return true if the region is empty. Corresponds to XEmptyRegion in X11.
   
   @return  True if the region is empty, false otherwise.
   */
  public boolean isEmpty()
  {
    return nRectangles_ == 0;
  }
  


  /**
   * Offset the entire region a specified distance.
   * Corresponds to XOffsetRegion in X11.
   
   @param dx  Offset in x direction.
   @param dy  Offset in y direction.
   */
  public void offset (int dx, int dy)
  {
    for (int i = 0; i < rectangles_.length; i+=4) {
      rectangles_[i+0+= dy;
      rectangles_[i+1+= dy;      
      rectangles_[i+2+= dx;
      rectangles_[i+3+= dx;      
    }

    extent_.offset (dx, dy);
  }

  

  /**
   * Return true if the specified region intersect this region.
   
   @param region  Region to check against.
   @return        True if the region intersects this one, false otherwise.
   */
  public boolean isIntersecting (Region region)
  {
    Region r = (Regionclone();
    r.intersect (region);
    return !r.isEmpty();
  }



  /**
   * Return true if the specified rectangle intersect this region.   
   
   @param rectangle  Rectangle to check against.
   @return           True if the rectangle intersects this, false otherwise.
   */
  public boolean isIntersecting (Rect rectangle)
  {
    Region region = new Region (rectangle);
    return isIntersecting (region);
  }



  /**
   * Return true if the specified point is inside this region.
   * This method corresponds to XPointInRegion in X11.
   
   @param x  X part of point to check.
   @param y  Y part of point to check.
   @return   True if the point is inside the region, false otherwise.
   */
  public boolean isInside (int x, int y)
  {
    if (isEmpty())
      return false;
    
    if (!extent_.isInside (x, y))
      return false;

    int rEnd = nRectangles_ << 2;
    
    // Find correct band
    int i = 0;
    while (i < rEnd && rectangles_[i+1< y) {
      if (rectangles_[i> yreturn false// Passed the band
      i += 4;
    }

    // Check each rectangle in the band
    while (i < rEnd && rectangles_[i<= y) {
      if (x >= rectangles_[i+2&& x < rectangles_[i+3]) return true;
      i += 4;
    }

    return false;
  }



  /**
   * Return true if the specified rectangle is inside this region.
   * This method corresponds to XRectInRegion in X11.   
   
   @param rectangle  Rectangle to check.
   @return           True if the rectangle is inside this region,
   *                   false otherwise.
   */
  public boolean isInside (Rect rectangle)
  {
    // Trivial reject case 1 
    if (isEmpty() || rectangle.isEmpty())
      return false;

    // Trivial reject case 2
    if (!extent_.isOverlapping (rectangle))
      return false;

    int x1 = rectangle.x;
    int x2 = rectangle.x + rectangle.width;
    int y1 = rectangle.y;
    int y2 = rectangle.y + rectangle.height;

    int rEnd = nRectangles_ << 2;

    // Trivial reject case 3
    if (rectangles_[0> y1return false;
    
    // Loop to start band
    int i = 0;
    while (i < rEnd && rectangles_[i+1<= y1) {
      i += 4;
      if (rectangles_[i> y1return false;
    }
    
    while (i < rEnd) {
      int yTop    = rectangles_[i];
      int yBottom = rectangles_[i+1];      
      
      // Find start rectangle within band
      while (i < rEnd && rectangles_[i+3<= x1) {
        i += 4;
        if (rectangles_[i> yTopreturn false// Passed the band
      }
      
      if (i == rEndreturn false;
      
      // This rectangle must cover the entire rectangle horizontally
      if (x1 < rectangles_[i+2|| x2 > rectangles_[i+3]) return false;
      
      // See if we are done
      if (rectangles_[i+1>= y2return true;
      
      // Move to next band
      i += 4;
      while (i < rEnd && rectangles_[i== yTop)
        i += 4;

      if (i == rEndreturn false;

      if (rectangles_[i> yBottomreturn false;
    }

    return false;
  }
  

  
  /**
   * Return true if this region is inside of the specified rectangle.
   
   @param rectangle  Rectangle to check if this is inside of.
   @return           True if this region is inside the specified rectangle,
   *                   false otherwise.
   */
  public boolean isInsideOf (Rect rectangle)
  {
    return subtract (this, rectangle).isEmpty();
  }

  

  /**
   * Return the extent of the region.
   * Correspond to XClipBox in X11.
   
   @return  The extent of this region.
   */
  public Rect getExtent()
  {
    return new Rect (extent_);
  }



  /**
   * Return the number of rectangles in the region. In case the number
   * is getting very high, the application might choose to call collapse().
   *
   @return  Number of rectangles this region consists of.
   */
  public int getNRectangles()
  {
    return nRectangles_;
  }
  
  

  /**
   * Collapse the region into its extent box. Useful if the region becomes
   * very complex (number of rectangles is getting high) and the client
   * accepts the (in general) coarser result region.
   */
  public void collapse()
  {
    rectangles_[0= extent_.y1;
    rectangles_[1= extent_.y2;    
    rectangles_[2= extent_.x1;
    rectangles_[3= extent_.x2;
    nRectangles_ = 1;
  }
  
  

  /**
   * Perform a logical set operation between this and the specified
   * region. Corresponds to miRegionOp in Region.c of X11.
   
   @param region         Region to combine with.
   @param operationType  Combination operator.
   */
  private void combine (Region region, int operationType)
  {
    // This is the only method (with sub methods) that utilize the
    // common working area gRectangles_. The lock ensures that only
    // one thread access this variable at any time.
    while (isLocked_);
    isLocked_ = true;

    int r1 = 0;
    int r2 = 0;
    int r1End = nRectangles_        << 2;
    int r2End = region.nRectangles_ << 2;

    // Initialize the working region
    gNRectangles_ = 0;

    int yTop    = 0;
    int yBottom = extent_.y1 < region.extent_.y1 ?
                  extent_.y1 : region.extent_.y1;

    int previousBand = 0;
    int currentBand;

    int r1BandEnd, r2BandEnd;
    int top, bottom;
    
    // Main loop
    do {
      currentBand = gNRectangles_;

      // Find end of the current r1 band
      r1BandEnd = r1 + 4;
      while (r1BandEnd != r1End &&
             rectangles_[r1BandEnd== rectangles_[r1])
        r1BandEnd += 4;

      // Find end of the current r2 band
      r2BandEnd = r2 + 4;
      while (r2BandEnd != r2End &&
             region.rectangles_[r2BandEnd== region.rectangles_[r2])
        r2BandEnd += 4;

      // First handle non-intersection band if any
      if (rectangles_[r1< region.rectangles_[r2]) {
        top    = Math.max (rectangles_[r1],    yBottom);
        bottom = Math.min (rectangles_[r1+1],  region.rectangles_[r2]);

        if (top != bottom)
          nonOverlap1 (rectangles_, r1, r1BandEnd, top, bottom, operationType);

        yTop = region.rectangles_[r2];
      }
      else if (region.rectangles_[r2< rectangles_[r1]) {
        top    = Math.max (region.rectangles_[r2],   yBottom);
        bottom = Math.min (region.rectangles_[r2+1], rectangles_[r1]);

        if (top != bottom)
          nonOverlap2 (region.rectangles_,
                       r2, r2BandEnd, top, bottom, operationType);

        yTop = rectangles_[r1];
      }
      else
        yTop = rectangles_[r1];
      
      // Then coalesce if possible
      if (gNRectangles_ != currentBand)
        previousBand = coalesceBands (previousBand, currentBand);
      currentBand = gNRectangles_;

      // Check if this is an intersecting band
      yBottom = Math.min (rectangles_[r1+1], region.rectangles_[r2+1]);
      if (yBottom > yTop)
        overlap (rectangles_,        r1, r1BandEnd,
                 region.rectangles_, r2, r2BandEnd,
                 yTop, yBottom, operationType);

      // Coalesce again
      if (gNRectangles_ != currentBand)
        previousBand = coalesceBands (previousBand, currentBand);

      // If we're done with a band, skip forward in the region to the next band
      if (rectangles_[r1+1]        == yBottomr1 = r1BandEnd;
      if (region.rectangles_[r2+1== yBottomr2 = r2BandEnd;

    while (r1 != r1End && r2 != r2End);

    currentBand = gNRectangles_;
    
    //
    // Deal with whichever region still has rectangles left
    //
    if (r1 != r1End) {
      do {

        r1BandEnd = r1;
        while (r1BandEnd < r1End &&
               rectangles_[r1BandEnd== rectangles_[r1])
          r1BandEnd += 4;

        top    = Math.max (rectangles_[r1], yBottom);
        bottom = rectangles_[r1+1];
        
        nonOverlap1 (rectangles_, r1, r1BandEnd, top, bottom, operationType);
        r1 = r1BandEnd;
        
      while (r1 != r1End);
    }
    else if (r2 != r2End) {
      do {

        r2BandEnd = r2;
        while (r2BandEnd < r2End &&
               region.rectangles_[r2BandEnd== region.rectangles_[r2])
          r2BandEnd += 4;

        top    = Math.max (region.rectangles_[r2], yBottom);
        bottom = region.rectangles_[r2+1];
        
        nonOverlap2 (region.rectangles_, r2, r2BandEnd, top, bottom,
                     operationType);
        r2 = r2BandEnd;
        
      while (r2 != r2End);
    }

    // Coalesce again
    if (currentBand != gNRectangles_)
      coalesceBands (previousBand, currentBand