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; + } +}