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:
parent
ee28090626
commit
6edd4af1c2
output
src
main
java/wordcloud
resources/backgrounds
test/java
BIN
output/layered_word_cloud.png
Normal file
BIN
output/layered_word_cloud.png
Normal file
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 ![]() ![]() |
104
src/main/java/wordcloud/LayeredWordCloud.java
Normal file
104
src/main/java/wordcloud/LayeredWordCloud.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
BIN
src/main/resources/backgrounds/cloud_bg.bmp
Normal file
BIN
src/main/resources/backgrounds/cloud_bg.bmp
Normal file
Binary file not shown.
After ![]() (image error) Size: 905 KiB |
BIN
src/main/resources/backgrounds/cloud_fg.bmp
Normal file
BIN
src/main/resources/backgrounds/cloud_fg.bmp
Normal file
Binary file not shown.
After ![]() (image error) Size: 905 KiB |
79
src/test/java/TestLayeredWordCloud.java
Normal file
79
src/test/java/TestLayeredWordCloud.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
|
@ -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() {
|
Loading…
Reference in New Issue
Block a user