1
0
mirror of https://github.com/kennycason/kumo synced 2025-03-25 08:38:53 -04:00

add layered word clouds, general refactoring

This commit is contained in:
Kenny Cason 2014-07-05 14:45:15 -05:00
parent ee28090626
commit 6edd4af1c2
14 changed files with 233 additions and 44 deletions

Binary file not shown.

After

(image error) Size: 148 KiB

Binary file not shown.

Before

(image error) Size: 19 KiB

After

(image error) Size: 81 KiB

View File

@ -0,0 +1,104 @@
package wordcloud;
import org.apache.log4j.Logger;
import wordcloud.bg.Background;
import wordcloud.font.FontOptions;
import wordcloud.font.scale.FontScalar;
import wordcloud.image.AngleGenerator;
import wordcloud.palette.ColorPalette;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by kenny on 7/5/14.
*/
public class LayeredWordCloud {
private static final Logger LOGGER = Logger.getLogger(LayeredWordCloud.class);
private final int layers;
private final int width;
private final int height;
private final List<WordCloud> wordClouds = new ArrayList<>();
private Color backgroundColor = Color.BLACK;
public LayeredWordCloud(int layers, int width, int height, CollisionMode collisionMode) {
this.layers = layers;
this.width = width;
this.height = height;
for(int i = 0; i < layers; i++) {
final WordCloud wordCloud = new WordCloud(width, height, collisionMode);
wordCloud.setBackgroundColor(null);
wordClouds.add(wordCloud);
}
}
public void build(int layer, List<WordFrequency> wordFrequencies) {
wordClouds.get(layer).build(wordFrequencies);
}
public void setPadding(int layer, int padding) {
this.wordClouds.get(layer).setPadding(padding);
}
public void setColorPalette(int layer, ColorPalette colorPalette) {
this.wordClouds.get(layer).setColorPalette(colorPalette);
}
public void setBackground(int layer, Background background) {
this.wordClouds.get(layer).setBackground(background);
}
public void setFontScalar(int layer, FontScalar fontScalar) {
this.wordClouds.get(layer).setFontScalar(fontScalar);
}
public void setFontOptions(int layer, FontOptions fontOptions) {
this.wordClouds.get(layer).setFontOptions(fontOptions);
}
public void setAngleGenerator(int layer, AngleGenerator angleGenerator) {
this.wordClouds.get(layer).setAngleGenerator(angleGenerator);
}
public void setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
}
public BufferedImage getBufferedImage() {
final BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final Graphics graphics = bufferedImage.getGraphics();
graphics.setColor(backgroundColor);
graphics.fillRect(0, 0, width, height);
for(WordCloud wordCloud : wordClouds) {
graphics.drawImage(wordCloud.getBufferedImage(), 0, 0, null);
}
return bufferedImage;
}
public void writeToFile(final String outputFileName) {
String extension = "";
int i = outputFileName.lastIndexOf('.');
if (i > 0) {
extension = outputFileName.substring(i + 1);
}
try {
LOGGER.info("Saving Layered WordCloud to " + outputFileName);
ImageIO.write(getBufferedImage(), extension, new File(outputFileName));
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
}
}
}

View File

@ -130,16 +130,12 @@ public class PolarWordCloud {
final Word word = wordIterator.next();
final Vector2d startPosition = getStartPosition(pole1);
final double theta = angleGenerator.randomNext();
word.setBufferedImage(ImageRotation.rotate(word.getBufferedImage(), theta));
place(word, startPosition.getX(), startPosition.getY());
}
if(wordIterator2.hasNext()) {
final Word word = wordIterator2.next();
final Vector2d startPosition = getStartPosition(pole2);
final double theta = angleGenerator.randomNext();
word.setBufferedImage(ImageRotation.rotate(word.getBufferedImage(), theta));
place(word, startPosition.getX(), startPosition.getY());
}
}
@ -297,8 +293,12 @@ public class PolarWordCloud {
final FontMetrics fontMetrics = graphics.getFontMetrics(font);
final Word word = new Word(wordFrequency.getWord(), colorPalette.next(), fontMetrics, this.collisionChecker);
final double theta = angleGenerator.randomNext();
if(theta != 0) {
word.setBufferedImage(ImageRotation.rotate(word.getBufferedImage(), theta));
}
if(padding > 0) {
padder.pad(word, padding, backgroundColor);
padder.pad(word, padding);
}
return word;
}
@ -336,6 +336,10 @@ public class PolarWordCloud {
this.angleGenerator = angleGenerator;
}
public BufferedImage getBufferedImage() {
return bufferedImage;
}
public Set<Word> getSkipped() {
return skipped;
}

View File

@ -111,12 +111,8 @@ public class WordCloud {
Collections.sort(wordFrequencies, WORD_FREQUENCE_COMPARATOR);
for(final Word word : buildwords(wordFrequencies)) {
final double theta = angleGenerator.randomNext();
if(theta != 0) {
word.setBufferedImage(ImageRotation.rotate(word.getBufferedImage(), theta));
}
final int startX = RANDOM.nextInt(width - word.getWidth());
final int startY = RANDOM.nextInt(height - word.getHeight());
final int startX = RANDOM.nextInt(Math.max(width - word.getWidth(), width));
final int startY = RANDOM.nextInt(Math.max(height - word.getHeight(), height));
place(word, startX, startY);
}
@ -143,6 +139,8 @@ public class WordCloud {
* for a more flexible pixel perfect collision
*/
private void drawForgroundToBackground() {
if(backgroundColor == null) { return; }
final BufferedImage backgroundBufferedImage = new BufferedImage(width, height, this.bufferedImage.getType());
final Graphics graphics = backgroundBufferedImage.getGraphics();
@ -237,8 +235,13 @@ public class WordCloud {
final FontMetrics fontMetrics = graphics.getFontMetrics(font);
final Word word = new Word(wordFrequency.getWord(), colorPalette.next(), fontMetrics, this.collisionChecker);
final double theta = angleGenerator.randomNext();
if(theta != 0) {
word.setBufferedImage(ImageRotation.rotate(word.getBufferedImage(), theta));
}
if(padding > 0) {
padder.pad(word, padding, backgroundColor);
padder.pad(word, padding);
}
return word;
}
@ -275,6 +278,10 @@ public class WordCloud {
this.angleGenerator = angleGenerator;
}
public BufferedImage getBufferedImage() {
return bufferedImage;
}
public Set<Word> getSkipped() {
return skipped;
}

View File

@ -34,6 +34,10 @@ public class CollisionRaster {
return data[x][y];
}
public void setRGB(int x, int y, int rgb) {
data[x][y] = rgb;
}
public void mask(final CollisionRaster collisionRaster, int x, int y) {
final int maxHeight = Math.min(y + collisionRaster.getHeight(), height);
final int maxWidth = Math.min(x + collisionRaster.getWidth(), width);

View File

@ -2,11 +2,9 @@ package wordcloud.padding;
import wordcloud.Word;
import java.awt.*;
/**
* Created by kenny on 7/2/14.
*/
public interface Padder {
void pad(final Word word, final int padding, final Color color);
void pad(final Word word, final int padding);
}

View File

@ -11,7 +11,7 @@ import java.awt.image.BufferedImage;
public class RectanglePadder implements Padder {
@Override
public void pad(Word word, int padding, Color color) {
public void pad(Word word, int padding) {
if(padding <= 0) { return; }
final BufferedImage bufferedImage = word.getBufferedImage();

View File

@ -2,9 +2,9 @@ package wordcloud.padding;
import wordcloud.Word;
import wordcloud.collide.Vector2d;
import wordcloud.image.CollisionRaster;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.Set;
@ -14,41 +14,42 @@ import java.util.Set;
*/
public class WordPixelPadder implements Padder {
// TODO as CollisionRaster changes to use boolean states rgb is not really needed or makes sense for padding
// it used to actually changed the buffered image that the word's text is written into.
private static final Color PAD_COLOR = Color.BLACK;
private RectanglePadder rectanglePadder = new RectanglePadder();
public void pad(final Word word, final int padding, final Color color) {
public void pad(final Word word, final int padding) {
if(padding <= 0) { return; }
rectanglePadder.pad(word, padding, color);
rectanglePadder.pad(word, padding);
final BufferedImage bufferedImage = word.getBufferedImage();
final CollisionRaster collisionRaster = word.getCollisionRaster();
final Set<Vector2d> toPad = new HashSet<>();
final int width = bufferedImage.getWidth();
final int height = bufferedImage.getHeight();
final int width = collisionRaster.getWidth();
final int height = collisionRaster.getHeight();
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
if(shouldPad(word, x, y, padding, color)) {
if(shouldPad(collisionRaster, x, y, padding)) {
toPad.add(new Vector2d(x, y));
}
}
}
for(Vector2d padPoint : toPad) {
bufferedImage.setRGB(padPoint.getX(), padPoint.getY(), color.getRGB());
collisionRaster.setRGB(padPoint.getX(), padPoint.getY(), PAD_COLOR.getRGB());
}
word.setBufferedImage(bufferedImage);
}
private boolean shouldPad(final Word word, final int cx, final int cy, final int padding, final Color color) {
final BufferedImage bufferedImage = word.getBufferedImage();
if(!isTransparent(bufferedImage, cx, cy)) { return false; }
private boolean shouldPad(final CollisionRaster collisionRaster, final int cx, final int cy, final int padding) {
if(!collisionRaster.isTransparent(cx, cy)) { return false; }
for(int y = cy - padding; y <= cy + padding; y++) {
for(int x = cx - padding; x <= cx + padding; x++) {
if(x == cx && y == cy) { continue; }
if(inBounds(bufferedImage, x, y)) {
if(!isTransparent(bufferedImage, x, y)) {
if(inBounds(collisionRaster, x, y)) {
if(!collisionRaster.isTransparent(x, y)) {
return true;
}
}
@ -57,19 +58,11 @@ public class WordPixelPadder implements Padder {
return false;
}
private boolean inBounds(final BufferedImage bufferedImage, final int x, final int y) {
private boolean inBounds(final CollisionRaster collisionRaster, final int x, final int y) {
return x >= 0
&& y >= 0
&& x < bufferedImage.getWidth()
&& y < bufferedImage.getHeight();
}
private boolean isTransparent(final BufferedImage bufferedImage, final int x, final int y) {
int pixel = bufferedImage.getRGB(x, y);
if((pixel & 0xFF000000) == 0x00000000) {
return true;
}
return false;
&& x < collisionRaster.getWidth()
&& y < collisionRaster.getHeight();
}
}

Binary file not shown.

After

(image error) Size: 905 KiB

Binary file not shown.

After

(image error) Size: 905 KiB

View File

@ -0,0 +1,79 @@
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.junit.Test;
import wordcloud.CollisionMode;
import wordcloud.LayeredWordCloud;
import wordcloud.WordFrequency;
import wordcloud.bg.PixelBoundryBackground;
import wordcloud.font.FontOptions;
import wordcloud.font.scale.SqrtFontScalar;
import wordcloud.nlp.FrequencyAnalizer;
import wordcloud.palette.ColorPalette;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
* Created by kenny on 7/5/14.
*/
public class TestLayeredWordCloud {
private static final Logger LOGGER = Logger.getLogger(TestLayeredWordCloud.class);
private static final Random RANDOM = new Random();
@Test
public void layeredExample() throws IOException {
final FrequencyAnalizer frequencyAnalizer = new FrequencyAnalizer();
frequencyAnalizer.setWordFrequencesToReturn(300);
frequencyAnalizer.setMinWordLength(5);
frequencyAnalizer.setStopWords(loadStopWords());
final List<WordFrequency> wordFrequencies = frequencyAnalizer.load(getInputStream("text/new_york_positive.txt"));
final List<WordFrequency> wordFrequencies2 = frequencyAnalizer.load(getInputStream("text/new_york_negative.txt"));
final LayeredWordCloud layeredWordCloud = new LayeredWordCloud(2, 600, 386, CollisionMode.PIXEL_PERFECT);
layeredWordCloud.setPadding(0, 1);
layeredWordCloud.setPadding(1, 1);
layeredWordCloud.setFontOptions(0, new FontOptions("LICENSE PLATE", Font.BOLD));
layeredWordCloud.setFontOptions(1, new FontOptions("Comic Sans MS", Font.BOLD));
layeredWordCloud.setBackground(0, new PixelBoundryBackground(getInputStream("backgrounds/cloud_bg.bmp")));
layeredWordCloud.setBackground(1, new PixelBoundryBackground(getInputStream("backgrounds/cloud_fg.bmp")));
layeredWordCloud.setColorPalette(0, new ColorPalette(new Color(0xABEDFF), new Color(0x82E4FF), new Color(0x55D6FA)));
layeredWordCloud.setColorPalette(1, new ColorPalette(new Color(0xFFFFFF), new Color(0xDCDDDE), new Color(0xCCCCCC)));
layeredWordCloud.setFontScalar(0, new SqrtFontScalar(10, 40));
layeredWordCloud.setFontScalar(1, new SqrtFontScalar(10, 40));
final long startTime = System.currentTimeMillis();
layeredWordCloud.build(0, wordFrequencies);
layeredWordCloud.build(1, wordFrequencies2);
LOGGER.info("Took " + (System.currentTimeMillis() - startTime) + "ms to build");
layeredWordCloud.writeToFile("output/layered_word_cloud.png");
}
private static Set<String> loadStopWords() {
try {
final List<String> lines = IOUtils.readLines(getInputStream("text/stop_words.txt"));
return new HashSet<>(lines);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
}
return Collections.emptySet();
}
private static InputStream getInputStream(String path) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
}
}

View File

@ -88,7 +88,7 @@ public class TestWordCloud {
final List<WordFrequency> wordFrequencies = frequencyAnalizer.load(getInputStream("text/datarank.txt"));
final WordCloud wordCloud = new WordCloud(500, 312, CollisionMode.PIXEL_PERFECT);
wordCloud.setPadding(2);
wordCloud.setPadding(1);
wordCloud.setBackground(new PixelBoundryBackground(getInputStream("backgrounds/whale_small.png")));
wordCloud.setColorPalette(new ColorPalette(new Color(0x4055F1), new Color(0x408DF1), new Color(0x40AAF1), new Color(0x40C5F1), new Color(0x40D3F1), new Color(0xFFFFFF)));
wordCloud.setFontScalar(new LinearFontScalar(10, 40));

View File

@ -8,7 +8,7 @@ import static org.junit.Assert.assertTrue;
/**
* Created by kenny on 7/4/14.
*/
public class TestImageData {
public class TestCollisionRaster {
@Test
public void basicTests() {