All Downloads are FREE. Search and download functionalities are using the official Maven repository.

signalprocesser.voronoi.shapegeneration.ShapeGeneration Maven / Gradle / Ivy

/*
 * "Concave" hulls by Glenn Hudson and Matt Duckham
 *
 * Source code downloaded from https://archive.md/l3Un5#selection-571.0-587.218 on 3rd November 2021.
 *
 * - This software is Copyright (C) 2008 Glenn Hudson released under Gnu Public License (GPL). Under 
 *   GPL you are free to use, modify, and redistribute the software. Please acknowledge Glenn Hudson 
 *   and Matt Duckham as the source of this software if you do use or adapt the code in further research 
 *   or other work. For full details of GPL see http://www.gnu.org/licenses/gpl-3.0.txt.
 * - This software comes with no warranty of any kind, expressed or implied.
 * 
 * A paper with full details of the characteristic hulls algorithm is published in Pattern Recognition.
 * Duckham, M., Kulik, L., Worboys, M.F., Galton, A. (2008) Efficient generation of simple polygons for
 * characterizing the shape of a set of points in the plane. Pattern Recognition v41, 3224-3236
 *
 * The software was developed by Glenn Hudson while working with me as an RA. The characteristic shapes 
 * algorithm is collaborative work between Matt Duckham, Lars Kulik, Antony Galton, and Mike Worboys.
 * 
 */

package signalprocesser.voronoi.shapegeneration;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.util.ArrayList;

import signalprocesser.voronoi.VPoint;

public class ShapeGeneration {
    
    /* ********************************************************* */
    // Constants
    
    private static final int POINTSLEFT_BEFORE_CUTOFF = 100;
    
    private static final FontRenderContext FONT_RENDER = new FontRenderContext(null, true, true);
    
    /* ********************************************************* */
    // Helpful Methods (for debugging)
    
    public static Shape createShape(Area area) {
        return new PathIteratorWrapper(area.getPathIterator(null));
    }
    public static Shape createShape(PathIterator pathiter) {
        return new PathIteratorWrapper(pathiter);
    }
    public static Shape createShape(ArrayList points) {
        return new PathIteratorWrapper(new ListPathIterator(points));
    }
    
    public static Area createArea(ArrayList points) {
        return new Area(new PathIteratorWrapper(new ListPathIterator(points)));
    }
    
    
    /* ********************************************************* */
    // Fill with points
    
    public static ArrayList addRandomPoints(ArrayList points_original,
            boolean splitlonglines,
            int shapepoints, int shapepoint_mindensity,
            int internalpoints, int internal_mindensity) throws ShapeGenerationException {
        // Protect the original border points - we don't want them to be changed
        ArrayList points = new ArrayList();
        points.addAll( points_original );
        
        // Create a new array that contains the same boundary points as before
        ArrayList randompoints = new ArrayList();
        
        // Can't add points to a line, just return
        if ( points.size()<=2 ) {
            // Add points to random points
            randompoints.addAll(points);
            
            // Return list
            return randompoints;
        }
        
        // Determine boundary of points
        VPoint first = points.get(0);
        int min_x = first.x, max_x = first.x;
        int min_y = first.y, max_y = first.y;
        for ( VPoint point : points ) {
            // Check x value
            if ( point.xmax_x ) {
                max_x = point.x;
            }
            
            // Check y value
            if ( point.ymax_y ) {
                max_y = point.y;
            }
        }
        
        // Calculate size of image
        int width  = max_x - min_x + 1;
        int height = max_y - min_y + 1;
        
        // Verify width/height (just in case! - below will fail badly if not positive)
        if ( width<=0 ) {
            throw new ShapeGenerationException("Width of shape is zero - cannot add random points");
        } else if ( height<=0 ) {
            throw new ShapeGenerationException("Height of shape is zero - cannot add random points");
        }
        
        // Initialise array entirely to TRUE values
        int x=0, y=0;
        boolean[][] array = new boolean[width][height];
        for ( x=0 ; xshapepoints ) {
            // Determine a random point to remove
            int index = (int) ( Math.random() * (points.size() - 1) );
            
            // Remove that index
            points.remove(index);
        }
        
        // Clear around the radius of shape points - set radius around corner
        //  points to FALSE, everything else at the end will be TRUE (including
        //  those points inside AND outside of the shape required)
        int index;
        VPoint currpoint = null;
        for ( index=0 ; index=2 ) {
            currpoint = points.get(0);
            VPoint prevpoint;
            for ( index=1 ; index2*shapepoint_mindensity ) {
                    index = addPointsToLine(index, points, randompoints, prevpoint, currpoint, min_x, min_y,
                            array, width, height, shapepoint_circle, shapepoint_mindensity);
                }
            }
            
            // As we've been removing points from the points list we may not have
            //  a complete shape - make sure the point is closed, if not close it
            VPoint firstpoint = points.get(0);
            VPoint lastpoint  = points.get(points.size()-1);
            if (!( firstpoint.x==lastpoint.x && firstpoint.y==lastpoint.y )) {
                // Check if the line needs to be split
                if ( splitlonglines && lastpoint!=null && lastpoint.distanceTo(firstpoint)>2*shapepoint_mindensity ) {
                    addPointsToLine(points.size(), points, randompoints, lastpoint, firstpoint, min_x, min_y,
                            array, width, height, shapepoint_circle, shapepoint_mindensity);
                }
                
                // Add in the final line segment
                points.add( first );
            }
        }
        
        // Reset array back to true and put on circles at internal_mindensity
        //  rather than shapepoint_mindensity
        for ( x=0 ; x0 ; internalpoints-- ) {
            // Return if no more positions left
            if ( pointsleft<=POINTSLEFT_BEFORE_CUTOFF ) {
                return randompoints;
            }
            
            // Decide on the position of the next point
            int point = (int)( Math.random() * (pointsleft - 1) + 1 );
            {foundxycoord:
                 for ( x=0 ; x points, ArrayList randompoints, VPoint prevpoint, VPoint currpoint, int min_x, int min_y, boolean[][] array, int width, int height, boolean[][] circle, int maxdensity) {
        // Determine skip
        double length = prevpoint.distanceTo(currpoint);
        int pointstoadd = (int)( length/(maxdensity+1) );
        
        // Otherwise consider whether x/y axis has the lower gradient
        double grad = (double)(prevpoint.y-currpoint.y) / (double)(prevpoint.x-currpoint.x);
        double skip_x = Math.sqrt( ( (length*length) / (pointstoadd*pointstoadd) ) / ( 1 + grad*grad ) );
        double skip_y = Math.sqrt( ( (length*length) / (pointstoadd*pointstoadd) ) / ( 1 + 1/(grad*grad) ) );
        if ( prevpoint.x>currpoint.x ) skip_x *= -1;
        if ( prevpoint.y>currpoint.y ) skip_y *= -1;
        
        // Consider pixel by pixel
        int x, y;
        for ( int pointnum=1 ; pointnum<=pointstoadd ; pointnum++ ) {
            // Determine coordinates
            x = prevpoint.x-min_x + (int)( skip_x * (double)pointnum );
            y = prevpoint.y-min_y + (int)( skip_y * (double)pointnum );
            
            // If position is not taken then take it
            if ( array[x][y] ) {
                // Unset the position and the pieces around this position
                unsetCircle(-1, x, y,
                        array, width, height, circle, maxdensity);
                
                // Add this point to both the points list and the
                //  random points list
                VPoint point = new VPoint(x+min_x, y+min_y);
                points.add(index, point );
                randompoints.add( point );
                
                // Increment index so as to skip over this new point
                index++;
            }
        }
        
        // Return final index
        return index;
    }
    
    static private int unsetCircle(int pointsleft, int x, int y, boolean[][] array, int width, int height, boolean[][] circle, int maxdensity ) {
        int index_x, index_y;
        for ( int x2=0 ; x2=width ) {
                        return pointsleft;
                    } else if ( index_x<0 || index_y>=height ) {
                        break;
                    } else if ( index_y<0 ) {
                        continue;
                    } else if ( array[index_x][index_y] ) {
                        pointsleft--;
                        array[index_x][index_y] = false;
                    }
                }
            }
        }
        return pointsleft;
    }
    
    /* ********************************************************* */
// Create Shape Outline
    
    public static ArrayList createShapeOutline(String text, Rectangle bounds, Font font) throws ShapeGenerationException {
        // Create text layout
        TextLayout textlayout = new TextLayout(text, font, FONT_RENDER);
        
        // Get the bounds of the shape
        Rectangle2D shapebounds = textlayout.getBounds();
        
        // Tranform to the correct location
        double scale_x = (double)bounds.width / (double)shapebounds.getWidth();
        double scale_y = (double)bounds.height / (double)shapebounds.getHeight();
        double translate_x = bounds.x/scale_x - shapebounds.getX();
        double translate_y = bounds.y/scale_y - shapebounds.getY();
        AffineTransform transform = AffineTransform.getScaleInstance(scale_x, scale_y);
        transform.translate(translate_x, translate_y);
        
        // Get the outline of the shape
        Shape outline = textlayout.getOutline(transform);
        PathIterator pathiter = outline.getPathIterator(null, 0.0);
        
        // Collect the points that form the shape
        ArrayList points = new ArrayList();
        double currpoint[] = new double[2];
        while (!( pathiter.isDone() )) {
            // Consider the current element
            int type = pathiter.currentSegment(currpoint);
            if ( type==PathIterator.SEG_MOVETO ) {
                points.add( new VPoint((int)currpoint[0], (int)currpoint[1]) );
            } else if ( type==PathIterator.SEG_LINETO ) {
                points.add( new VPoint((int)currpoint[0], (int)currpoint[1]) );
            } else if ( type==PathIterator.SEG_CLOSE ) {
                break;
            } else {
                throw new RuntimeException("Unexpected type " + type + " returned");
            }
            
            // Go to the next element
            pathiter.next();
        }
        
        // Return points
        return points;
    }
    
    /* ********************************************************* */
    // Classes used by the above
    
    private static class PathIteratorWrapper implements Shape {
        private PathIterator iter;
        
        public PathIteratorWrapper(PathIterator _iter) {
            this.iter = _iter;
        }
        
        public PathIterator getPathIterator(AffineTransform at) { return iter; }
        public PathIterator getPathIterator(AffineTransform at, double flatness) { return iter; }
        
        public boolean contains(double x, double y) { throw new RuntimeException("Unimplemented method"); }
        public boolean contains(double x, double y, double w, double h) { throw new RuntimeException("Unimplemented method"); }
        public boolean contains(Point2D p) { throw new RuntimeException("Unimplemented method"); }
        public boolean contains(Rectangle2D r) { throw new RuntimeException("Unimplemented method"); }
        public Rectangle getBounds() { throw new RuntimeException("Unimplemented method"); }
        public Rectangle2D getBounds2D() { throw new RuntimeException("Unimplemented method"); }
        public boolean intersects(double x, double y, double w, double h) { throw new RuntimeException("Unimplemented method"); }
        public boolean intersects(Rectangle2D r) { throw new RuntimeException("Unimplemented method"); }
    }
    
    private static class ListPathIterator implements PathIterator {
        private int index = 0;
        private java.util.List points;
        
        private int offset_x;
        private int offset_y;
        
        public ListPathIterator(java.util.List points) {
            this(points, 0, 0);
        }
        public ListPathIterator(java.util.List _points, int _offset_x, int _offset_y) {
            this.points = _points;
            this.offset_x = _offset_x;
            this.offset_y = _offset_y;
        }
        
        public void resetIterator() {
            index = 0;
        }
        
        public boolean isDone() { return ( index>=points.size() ); }
        public void next() { index++; }
        
        public int currentSegment(double[] coords) {
            VPoint point = points.get(index);
            coords[0] = point.x - offset_x;
            coords[1] = point.y - offset_y;
            return getReturnValue();
        }
        public int currentSegment(float[] coords) {
            VPoint point = points.get(index);
            coords[0] = point.x - offset_x;
            coords[1] = point.y - offset_y;
            return getReturnValue();
        }
        private int getReturnValue() {
            if ( index==0 ) {
                return SEG_MOVETO;
            } else if ( index<(points.size()-1) ) {
                return SEG_LINETO;
            } else {
                return SEG_CLOSE;
            }
        }
        
        /**
         * WIND_NON_ZERO appears to be the only value ever returned
         *   - but their may be instances where this should not be the case
         */
        public int getWindingRule() {
            return WIND_NON_ZERO;
        }
    }
    
    /* ********************************************************* */
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy