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

prng.image.Voronoi Maven / Gradle / Ivy

package prng.image;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.DoubleConsumer;

public class Voronoi extends BasePainter {

    static class Cell {
        Point center_;

        List vert_ = new ArrayList();


        Cell(Point center, List box) {
            center_ = center;
            vert_.addAll(box);
            vert_.add(vert_.get(0));
        }


        void cut(Point o, double len) {
            if( o.equals(center_) ) {
                return;
            }

            // find which points are closer to o than to the center
            int s = vert_.size();

            // test point zero
            Point v = vert_.get(0);
            double myDist = v.dist(center_);
            double oDist = v.dist(o);
            boolean ok = myDist < oDist;

            // find the entry and exit lines
            boolean previous = ok;
            int entry = -1;
            int exit = -1;
            for(int i = 1;i < s;i++) {
                v = vert_.get(i);
                myDist = v.dist(center_);
                oDist = v.dist(o);
                boolean c = myDist < oDist;
                ok = ok && c;
                if( c && !previous ) {
                    exit = i;
                }
                if( previous && !c ) {
                    entry = i;
                }
                previous = c;
            }

            // if all points closer, we are OK
            if( ok ) {
                return;
            }

            // create cutting edge
            Edge cut = new Edge(center_, o, len);
            Point entryPoint = cut.intersect(vert_.get(entry - 1),
                    vert_.get(entry));
            Point exitPoint = cut.intersect(vert_.get(exit - 1),
                    vert_.get(exit));
            List newVerts = new ArrayList();
            newVerts.add(entryPoint);
            newVerts.add(exitPoint);
            int p = exit;
            while( p != entry ) {
                newVerts.add(vert_.get(p));
                p++;
                if( p == s ) {
                    p = 1;
                }
            }
            newVerts.add(entryPoint);
            vert_ = newVerts;
        }


        Shape getPoly() {
            int s = vert_.size() - 1;
            Path2D.Double ret = new Path2D.Double(Path2D.WIND_NON_ZERO, s);
            Point p = vert_.get(0);
            ret.moveTo(p.x, p.y);
            for(int i = 1;i < s;i++) {
                p = vert_.get(i);
                ret.lineTo(p.x, p.y);
            }
            ret.closePath();
            return ret;
        }
    }

    static class Edge {
        Point e_;

        Point s_;


        Edge(Point a, Point b) {
            s_ = a;
            e_ = b;
        }


        Edge(Point a, Point b, double len) {
            s_ = new Point();
            e_ = new Point();
            double mx = 0.5 * (a.x + b.x);
            double my = 0.5 * (a.y + b.y);

            double dx = b.x - a.x;
            double dy = b.y - a.y;
            double r = Math.sqrt((dx * dx) + (dy * dy));
            len /= r;
            dx *= len;
            dy *= len;

            s_.x = mx - dy;
            s_.y = my + dx;

            e_.x = mx + dy;
            e_.y = my - dx;
        }


        Point intersect(Edge o) {
            return Voronoi.intersect(s_, e_, o.s_, o.e_);
        }


        Point intersect(Point s, Point e) {
            return Voronoi.intersect(s_, e_, s, e);
        }


        void next(Point c) {
            s_ = e_;
            e_ = c;
        }
    }

    static class Point {
        double x, y;


        Point() {}


        Point(double x, double y) {
            this.x = x;
            this.y = y;
        }


        double dist(Point p) {
            double dx = p.x - x;
            double dy = p.y - y;
            return Math.sqrt((dx * dx) + (dy * dy));
        }


        @Override
        public boolean equals(Object obj) {
            if( this == obj ) {
                return true;
            }
            if( obj == null ) {
                return false;
            }
            if( getClass() != obj.getClass() ) {
                return false;
            }
            Point other = (Point) obj;
            if( Double.doubleToLongBits(x) != Double.doubleToLongBits(
                    other.x) ) {
                return false;
            }
            return Double.doubleToLongBits(y) == Double.doubleToLongBits(
                other.y);
        }


        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            long temp;
            temp = Double.doubleToLongBits(x);
            result = (prime * result) + (int) (temp ^ (temp >>> 32));
            temp = Double.doubleToLongBits(y);
            result = (prime * result) + (int) (temp ^ (temp >>> 32));
            return result;
        }
    }


    private static double dist(Color a, Color b) {
        int r = a.getRed() - b.getRed();
        int g = a.getGreen() - b.getGreen();
        int l = a.getBlue() - b.getBlue();
        return Math.sqrt((r * r) + (g * g) + (l * l));
    }


    /**
     * Find the point where a line from p0 to p1 meets a line from p2 to p3.
     *
     * @param p0
     *            start of first line
     * @param p1
     *            end of first line
     * @param p2
     *            start of second line
     * @param p3
     *            end of second line
     * @return point of intersection or null if no such point
     */
    static Point intersect(Point p0, Point p1, Point p2, Point p3) {
        double s1x = p1.x - p0.x;
        double s1y = p1.y - p0.y;
        double s2x = p3.x - p2.x;
        double s2y = p3.y - p2.y;

        double z = (-s2x * s1y) + (s1x * s2y);
        if( z == 0 ) {
            return null;
        }

        double s = ((-s1y * (p0.x - p2.x)) + (s1x * (p0.y - p2.y))) / z;
        double t = ((s2x * (p0.y - p2.y)) - (s2y * (p0.x - p2.x))) / z;

        if( (s >= 0) && (s <= 1) && (t >= 0) && (t <= 1) ) {
            Point p = new Point();
            p.x = p0.x + (t * s1x);
            p.y = p0.y + (t * s1y);
            return p;
        }

        return null;
    }

    Cell[] cells;

    Point[] pnts;

    int points;


    public Voronoi(int points, Random rand) {
        this.rand = rand;
        this.points = points;
    }


    @Override
    public void create(DoubleConsumer progress) {
        make();
        int size = points;

        // create palette
        Color[] pal = new Color[size];
        for(int i = 0;i < size;i++) {
            pal[i] = new Color(rand.nextInt(0x1000000));
        }

        // create color difference matrix
        double[][] colorDiff = new double[size][size];
        for(int i = 1;i < size;i++) {
            for(int j = 0;j < i;j++) {
                double diff = dist(pal[i], pal[j]);
                colorDiff[i][j] = diff;
                colorDiff[j][i] = diff;
            }
        }

        // create xy difference matrix
        double[][] xyDiff = new double[size][size];
        for(int i = 1;i < size;i++) {
            for(int j = 0;j < i;j++) {
                double diff = pnts[i].dist(pnts[j]);
                diff *= diff;
                xyDiff[i][j] = diff;
                xyDiff[j][i] = diff;
            }
        }

        // color permutation
        int[] perm = new int[size];
        for(int i = 0;i < size;i++) {
            perm[i] = i;
        }

        // do some sorting
        boolean didChange = false;
        do {
            for(int a = 1;a < size;a++) {
                for(int b = 0;b < a;b++) {
                    int ca = perm[a];
                    int cb = perm[b];

                    // does swapping a and b improve things?
                    double change = 0;
                    for(int i = 0;i < size;i++) {
                        int ci = perm[i];
                        if( (a == i) || (b == i) ) {
                            continue;
                        }
                        change -= colorDiff[ca][ci] / xyDiff[a][i];
                        change -= colorDiff[cb][ci] / xyDiff[b][i];
                        change += colorDiff[cb][ci] / xyDiff[a][i];
                        change += colorDiff[ca][ci] / xyDiff[b][i];
                    }

                    // if change is negative, it is better
                    if( change < 0 ) {
                        int t = perm[a];
                        perm[a] = perm[b];
                        perm[b] = t;
                    }

                }
            }
        } while( didChange );

        // create image
        BufferedImage image = new BufferedImage(512, 512,
                BufferedImage.TYPE_INT_RGB);
        myImage = image;
        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND,
                BasicStroke.JOIN_ROUND));

        Shape[] shapes = getPolys();
        for(int i = 0;i < size;i++) {
            Shape s = shapes[i];
            Color col = pal[perm[i]];
            graphics.setColor(col);
            graphics.fill(s);
            graphics.setColor(Color.WHITE);
            graphics.draw(s);
        }
        progress.accept(0.4);

        for(int j = 0;j < (100 * size);j++) {
            Point2D p = new Point2D.Double();
            p.setLocation(512 * rand.nextDouble(), 512 * rand.nextDouble());
            double radius = Math.abs((3 + rand.nextGaussian()));
            Color col = new Color(rand.nextBoolean() ? 1f : 0f,
                    rand.nextBoolean() ? 1f : 0f, rand.nextBoolean() ? 1f : 0f,
                    0.1f);
            Ellipse2D dot = new Ellipse2D.Double(p.getX() - radius,
                    p.getY() - radius, 2 * radius, 2 * radius);
            graphics.setColor(col);
            graphics.fill(dot);
        }
        progress.accept(0.9);

        // create a 3x3 Gaussian blur filter
        float[] blurMatrix = new float[] { 1f / 16, 1f / 8, 1f / 16, 1f / 8,
                1f / 4, 1f / 8, 1f / 16, 1f / 8, 1f / 16 };
        BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurMatrix),
                ConvolveOp.EDGE_NO_OP, null);

        // blur the image
        myImage = op.filter(image, null);
    }


    public Shape[] getPolys() {
        Shape[] ret = new Shape[cells.length];
        for(int i = 0;i < ret.length;i++) {
            ret[i] = cells[i].getPoly();
        }
        return ret;
    }


    public void make() {
        List bbox2 = new ArrayList(4);
        double x = 0;
        double y = 0;
        double w = 512;
        double h = 512;
        bbox2.add(new Point(x, y));
        bbox2.add(new Point(x + w, y));
        bbox2.add(new Point(x + w, y + h));
        bbox2.add(new Point(x, y + h));
        double scale = w + h;

        // set the points
        pnts = new Point[points];
        for(int i = 0;i < points;i++) {
            pnts[i] = new Point(x + (w * rand.nextDouble()),
                    y + (h * rand.nextDouble()));
        }

        // make the cells
        cells = new Cell[points];
        for(int i = 0;i < points;i++) {
            Cell c = new Cell(pnts[i], bbox2);
            for(int j = 0;j < points;j++) {
                c.cut(pnts[j], scale);
            }
            cells[i] = c;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy