Shapes for WordCram

This has been a feature request for a long time: making WordCrams in arbitrary shapes, like Tagxedo does.

It’s currently slow, and a bit limited – you have to provide a java.awt.Shape, not (say) an image mask – but it’s a first step. I hope to package it up nicely for the next WordCram release.

Below is the guts of the code, but here’s the output:

wordcram-love

class ShapeBasedPlacer implements WordPlacer, WordNudger {

  Area area;
  float minX;
  float minY;
  float maxX;
  float maxY;

  public ShapeBasedPlacer(Shape shape) {
    this.area = new Area(shape);

    Rectangle2D areaBounds = area.getBounds2D();
    this.minX = (float)areaBounds.getMinX();
    this.minY = (float)areaBounds.getMinY();
    this.maxX = (float)areaBounds.getMaxX();
    this.maxY = (float)areaBounds.getMaxY();
  }

  public PVector place(Word w, int rank, int count, int ww, int wh, int fw, int fh) {

    w.setProperty("width", ww);
    w.setProperty("height", wh);

    for (int i = 0; i < 1000; i++) {
      float newX = randomBetween(minX, maxX);
      float newY = randomBetween(minY, maxY);
      if (area.contains(newX, newY, ww, wh)) {
        return new PVector(newX, newY);
      }
    }

    return new PVector(-1, -1);
  }

  public PVector nudgeFor(Word word, int attempt) {
    PVector target = word.getTargetPlace();
    float wx = target.x;
    float wy = target.y;
    float ww = (Integer)word.getProperty("width");
    float wh = (Integer)word.getProperty("height");

    for (int i = 0; i < 1000; i++) {
      float newX = randomBetween(minX, maxX);
      float newY = randomBetween(minY, maxY);

      if (area.contains(newX, newY, ww, wh)) {
        return new PVector(newX - wx, newY - wy);
      }
    }

    return new PVector(-1, -1);
  }

  float randomBetween(float a, float b) {
    return a + random(b - a);
  }
}

void setup() {
  Shape heart = makeHeart();
  ShapeBasedPlacer shapeBasedPlacer = new ShapeBasedPlacer(heart);

  new WordCram(this)
    .withPlacer(shapeBasedPlacer)
    .withNudger(shapeBasedPlacer)
    .drawAll();
}
About these ads
This entry was posted in improvements. Bookmark the permalink.

11 Responses to Shapes for WordCram

  1. simpsus says:

    Hallo,

    is it possible to get the code from somewhere?
    I checked the github page but the last commit time seems to be too old to include this feature, or am I mistaken?

    Cheers
    Bastian

  2. simpsus says:

    Is there any way to get the sourcecode of this?
    I checked the github page and the last commit is imho too old to include this.

    Thank you very much.

    Bastian

  3. You’re right, no code has been committed to the repo yet – but none needs to be.

    If you look at the above, it has everything you need. The example setup() method points the way: make a Shape, make a ShapeBasedPlacer for it (all the source code for that is in the post), and use the ShapeBasedPlacer as both the Placer and the Nudger for your WordCram.

  4. simpsus says:

    Thank you very much for your quick reply!
    I had to modify the code from the post a bit:
    – imports are missing. processing gives very weird errors on this (must implement method).
    – the random function “random”.

    The following does the trick for me:

    import wordcram.WordPlacer;
    import wordcram.WordNudger;
    import wordcram.Word;
    import java.awt.geom.Area;
    import java.awt.geom.Rectangle2D;
    import java.awt.Shape;
    import processing.core.PVector;
    import java.util.Random;

    class ShapeBasedPlacer implements WordPlacer, WordNudger {

    Area area;
    float minX;
    float minY;
    float maxX;
    float maxY;
    Random random;

    public ShapeBasedPlacer(Shape shape) {
    this.area = new Area(shape);
    random = new Random();
    Rectangle2D areaBounds = area.getBounds2D();
    this.minX = (float)areaBounds.getMinX();
    this.minY = (float)areaBounds.getMinY();
    this.maxX = (float)areaBounds.getMaxX();
    this.maxY = (float)areaBounds.getMaxY();
    }

    public PVector place(Word w, int rank, int count, int ww, int wh, int fw, int fh) {

    w.setProperty(“width”, ww);
    w.setProperty(“height”, wh);

    for (int i = 0; i < 1000; i++) {
    float newX = randomBetween(minX, maxX);
    float newY = randomBetween(minY, maxY);
    if (area.contains(newX, newY, ww, wh)) {
    return new PVector(newX, newY);
    }
    }

    return new PVector(-1, -1);
    }

    public PVector nudgeFor(Word word, int attempt) {
    PVector target = word.getTargetPlace();
    float wx = target.x;
    float wy = target.y;
    float ww = (Integer)word.getProperty("width");
    float wh = (Integer)word.getProperty("height");

    for (int i = 0; i < 1000; i++) {
    float newX = randomBetween(minX, maxX);
    float newY = randomBetween(minY, maxY);

    if (area.contains(newX, newY, ww, wh)) {
    return new PVector(newX – wx, newY – wy);
    }
    }

    return new PVector(-1, -1);
    }

    float randomBetween(float a, float b) {
    return a + random.nextFloat() * (b – a);
    }
    }

    • It looks like you implemented it as a stand-alone java class, am I right? I just copied the code from my sketch, pasted it into the blog post, and took out the details that would be specific to a particular sktech (like making the heart Shape) or common to every wordcram sketch (like the imports). You shouldn’t need to import anything beyond wordcram.* – Processing imports processing.* already, and it provides a random method, so you don’t need a java.util.Random.

      • simpsus says:

        You are perfectly right.
        In the meantime I have learned that as well (and several other things about processing).
        The Shape based placer is working like a charm.

        What would be the next big thing is to get shapes from “outlines” of images. I think that is what you mean with an imagemask.

        I managed to get the shape from a text in a few lines, if you are interested I can post them here or send them somewhere.

        Thanks again!!

      • You’re right about shapes from images – that’d be a much nicer way to do it. :)

        Sure, post the code!

      • simpsus says:

        To create a Shape from a text (here “PhD”) take the following lines:

        Font f = new Font(“DroidSans”, Font.PLAIN, 500);
        Shape shape;
        BufferedImage img;
        Graphics2D g2d;
        FontRenderContext frc;
        img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
        g2d = img.createGraphics();
        frc = g2d.getFontRenderContext();
        GlyphVector gv = f.createGlyphVector(frc, “PhD”);
        shape = gv.getOutline(50,900);

  5. simpsus says:

    I have improved the ShapeBasedPlacer to be able to do two things:
    1) Place inside the glyphs of a passed text
    2) Place inside the shape from an image (passed per text)

    The code is a bit lengthy to post here, so I have uploaded it to dropbox (https://www.dropbox.com/s/knjmgusabuc933k/ShapeBasedPlacer.java?m):
    With this shaper, it is possible to do this attached sketch to layout the content of a webpage inside its own title. The code to get the image outline shape goes back to stackoverflow:

    http://stackoverflow.com/questions/7052422/image-graphic-into-a-shape

    @Daniel: I have a few comments and suggestion for inclusion into WordCram. Please give me a ping if you are interested.

    import processing.pdf.*;
    import wordcram.*;

    String url = “http://www.spiegel.de”;
    String page = url.split(“\\.”)[1];

    size(4000, 4000, PDF, page + “.pdf”);
    background(255);

    ShapeBasedPlacer placer = ShapeBasedPlacer.fromTextGlyphs(page,”DroidSans”);

    WordCram cram = new WordCram(this);
    cram.fromWebPage(url);
    cram.withPlacer(placer);
    cram.withNudger(placer);
    cram.sizedByWeight(10,120);
    cram.maxNumberOfWordsToDraw(200);

    System.out.println(“Cram prepared”);
    int drawCount = 0;
    while (cram.hasMore()) {
    cram.drawNext();
    System.out.println(++drawCount);
    }
    //cram.drawAll();
    System.out.println(“Cram drawn”);

    //tell me what didn’t get drawn
    int noSpace = 0;
    int tooSmall = 0;
    Word[] skippedWords = cram.getSkippedWords();
    Word[] placedWords = cram.getWords();
    for (Word skipped: skippedWords) {
    if (skipped.wasSkippedBecause() == WordCram.NO_SPACE) {
    noSpace++;
    } else if (skipped.wasSkippedBecause() == WordCram.SHAPE_WAS_TOO_SMALL) {
    tooSmall++;
    }
    }
    System.out.println(“Total placed Words: ” + placedWords.length);
    System.out.println(“Total Skipped Words: ” + skippedWords.length);
    System.out.println(“Skipped because no Space: ” + noSpace);
    System.out.println(“Skipped because too small: ” + tooSmall);
    System.out.println(“Finished”);

  6. simpsus says:

    I hope I have done everything right and you have the pull request.
    If so, I would be thrilled to learn how you organized your coding of wordcram, because I hope you do not develop the lib inside processing but in some other IDE.

Comments are closed.