From ee9ad38574e4f2b0b7b5fa4d5d32f784ad26a3f5 Mon Sep 17 00:00:00 2001
From: joerg1985 <github@nurfuerspam.de>
Date: Thu, 31 Jan 2019 22:04:38 +0100
Subject: [PATCH] fixed the calculation of the maximum spiral radius and added
 a test to it

the maximum spiral radius might be bigger or smaller than the image width, e.g. if the image height is bigger than the width.
---
 .../java/com/kennycason/kumo/WordCloud.java   |  19 ++-
 .../java/com/kennycason/kumo/SpiralTest.java  | 115 ++++++++++++++++++
 2 files changed, 132 insertions(+), 2 deletions(-)
 create mode 100644 kumo-core/src/test/java/com/kennycason/kumo/SpiralTest.java

diff --git a/kumo-core/src/main/java/com/kennycason/kumo/WordCloud.java b/kumo-core/src/main/java/com/kennycason/kumo/WordCloud.java
index 08f5c7e..2d8031c 100644
--- a/kumo-core/src/main/java/com/kennycason/kumo/WordCloud.java
+++ b/kumo-core/src/main/java/com/kennycason/kumo/WordCloud.java
@@ -161,6 +161,21 @@ public class WordCloud {
         graphics2.drawImage(backgroundBufferedImage, 0, 0, null);
     }
 
+    /**
+     * compute the maximum radius for the placing spiral
+     *
+     * @param dimension the size of the backgound
+     * @param start the center of the spiral
+     * @return the maximum usefull radius
+     */
+    static int computeRadius(Dimension dimension, Point start) {
+        int maxDistanceX = Math.max(start.x, dimension.width - start.x) + 1;
+        int maxDistanceY = Math.max(start.y, dimension.height - start.y) + 1;
+        
+        // we use the pythagorean theorem to determinate the maximum radius
+        return (int) Math.ceil(Math.sqrt(maxDistanceX * maxDistanceX + maxDistanceY * maxDistanceY));
+    }
+    
     /**
      * try to place in center, build out in a spiral trying to place words for N steps
      * @param word the word being placed
@@ -169,12 +184,12 @@ public class WordCloud {
     protected boolean place(final Word word, final Point start) {
         final Graphics graphics = this.bufferedImage.getGraphics();
 
-        final int maxRadius = dimension.width;
+        final int maxRadius = computeRadius(dimension, start);
 
         for (int r = 0; r < maxRadius; r += 2) {
             for (int x = -r; x <= r; x++) {
                 if (start.x + x < 0) { continue; }
-                if (start.x + x >= maxRadius) { continue; }
+                if (start.x + x >= dimension.width) { continue; }
 
                 boolean placed = false;
                 word.getPosition().x = start.x + x;
diff --git a/kumo-core/src/test/java/com/kennycason/kumo/SpiralTest.java b/kumo-core/src/test/java/com/kennycason/kumo/SpiralTest.java
new file mode 100644
index 0000000..f52a160
--- /dev/null
+++ b/kumo-core/src/test/java/com/kennycason/kumo/SpiralTest.java
@@ -0,0 +1,115 @@
+package com.kennycason.kumo;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import javax.imageio.ImageIO;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ *
+ * @author joerg1985
+ */
+public class SpiralTest {
+
+    @Test
+    public void NewImplementationVsOldImplementation() throws IOException {
+        // draw the spiral as image?
+        // red pixels -> only returned by the old implementation
+        // blue pixels -> only returned by the new implementation
+        // pink pixels -> returned by the old and the new implementation
+        boolean debug = false;
+        
+        final int white = 0;
+        final int red = 0xFFFF0000;
+        final int blue = 0xFF0000FF;
+        final int pink = red | blue;
+
+        // we seed to get the same numbers on each run
+        Random random = new Random(42);
+
+        for (int i = 0; i < 20; i++) {
+            Dimension dimension = new Dimension(
+                    100 + random.nextInt(900),
+                    100 + random.nextInt(900)
+            );
+
+            for (int j = 0; j < 20; j++) {
+                Point start = new Point(
+                        random.nextInt(dimension.width),
+                        random.nextInt(dimension.height)
+                );
+
+                // the old implementation did not repect images higher than wide
+                int originalRadius = dimension.width;
+                int optimizedRadius = WordCloud.computeRadius(dimension, start);
+
+                List<Point> original = spiral(dimension, start, originalRadius);
+                List<Point> optimized = spiral(dimension, start, optimizedRadius);
+
+                BufferedImage img = new BufferedImage(
+                        dimension.width, dimension.height, BufferedImage.TYPE_4BYTE_ABGR
+                );
+
+                original.forEach((p) -> img.setRGB(p.x, p.y, red));
+                optimized.forEach((p) -> {
+                    if (img.getRGB(p.x, p.y) != 0) {
+                        img.setRGB(p.x, p.y, pink);
+                    } else {
+                        img.setRGB(p.x, p.y, blue);
+                    }
+                });
+
+                boolean next = false;
+                
+                for (int y = 0; !next && y < dimension.height; y++) {
+                    for (int x = 0; !next && x < dimension.width; x++) {
+                        int rgb = img.getRGB(x, y);
+
+                        if (rgb == red) {
+                            ImageIO.write(img, "png", new File("output\\failed_spiral_test.png"));
+                            Assert.fail();
+                        } else if (debug && rgb != white && rgb != pink) {
+                            ImageIO.write(img, "png", new File("output\\debug_spiral_test_" + System.currentTimeMillis() + ".png"));
+                            next = true;
+                        } 
+                    }
+                }
+            }
+        }
+    }
+
+    private List<Point> spiral(Dimension dimension, Point start, final int maxRadius) {
+        List<Point> points = new ArrayList<>();
+
+        for (int r = 0; r < maxRadius; r += 2) {
+            for (int x = -r; x <= r; x++) {
+                if (start.x + x < 0) {
+                    continue;
+                }
+                if (start.x + x >= dimension.width) {
+                    continue;
+                }
+
+                // try positive root
+                final int y1 = (int) Math.sqrt(r * r - x * x);
+                if (start.y + y1 >= 0 && start.y + y1 < dimension.height) {
+                    points.add(new Point(start.x + x, start.y + y1));
+                }
+                // try negative root
+                final int y2 = -y1;
+                if (start.y + y2 >= 0 && start.y + y2 < dimension.height) {
+                    points.add(new Point(start.x + x, start.y + y2));
+                }
+            }
+        }
+
+        return points;
+    }
+}