CSC400 Speech Bubbles

From CSclasswiki
Jump to: navigation, search

How to edit Wiki pages: See the Wikipedia editing cheat sheet.

Juxtaposing Shapes via Algorithms

The following is a link to the wiki created while the special studies was in progress: Speech Bubbles Process

This page discusses the final program's code at length, in order. To see the code in full without commentary, refer to Speech Bubbles Processing Code

The Final Program

Frnakly I think that's absolutely good stuff.

Part 2: Arrays

Next, arrays were created for the program to draw from later on. The first, Color, is an array entires of "color" type variables, given RGB codes using Processing's Color Selector tool.

The second array, speechBubbles, contains all possible "bubble" images, of which there are twenty-five. Each image was created by turning a Photoshop brush into a transparent GIF. Bubble images in this series were created from a brush set distributed for free online on Deviant Art, by user "filmowe." The brush set can be found here: [1]

Due to issues that arose when trying to run the program, the array of Image variable-types needed to have a length declared before adding actual images, while Color did not.

  // Fill up color array.  9 colors for now. Use color() to create color types.
  color Color[] = {
    color(252,59,59), //red
    color(175,5,5), //red
    color(255,182,126), //orange
    color(247,230,35), //yellow
    color(255,237,131), //yellow
    color(198,237,101), //green
    color(191,242,149), //green
    color(119,240,186), //blue
    color(9,222,220), //blue
    color(74,100,237), //blue
    color(175,234,229), //blue
    color(161,133,232), //purple
    color(222,154,212), //purple
    color(242,22,125), //pink
    color(250,248,235) //white
  // images
  PImage[] speechBubbles = new PImage[30];
    speechBubbles[0] = loadImage("bubble1.gif");
    speechBubbles[1] = loadImage("bubble2.gif");
    speechBubbles[2] = loadImage("bubble3.gif");
    speechBubbles[3] = loadImage("bubble4.gif");
    speechBubbles[4] = loadImage("bubble5.gif");
    speechBubbles[5] = loadImage("bubble6.gif");
    speechBubbles[6] = loadImage("bubble7.gif");
    speechBubbles[7] = loadImage("bubble8.gif");
    speechBubbles[8] = loadImage("bubble9.gif");
    speechBubbles[9] = loadImage("bubble10.gif");
    speechBubbles[10] = loadImage("bubble11.gif");
    speechBubbles[11] = loadImage("bubble12.gif");
    speechBubbles[12] = loadImage("bubble13.gif");
    speechBubbles[13] = loadImage("bubble15.gif");
    speechBubbles[14] = loadImage("bubble16.gif");
    speechBubbles[15] = loadImage("bubble17.gif");
    speechBubbles[16] = loadImage("bubble18.gif");
    speechBubbles[17] = loadImage("bubble19.gif");
    speechBubbles[18] = loadImage("bubble20.gif");
    speechBubbles[19] = loadImage("bubble21.gif");
    speechBubbles[20] = loadImage("bubble22.gif");
    speechBubbles[21] = loadImage("bubble23.gif");
    speechBubbles[22] = loadImage("bubble24.gif");
    speechBubbles[23] = loadImage("bubble25.gif");

Yeah, that's the tieckt, sir or ma'am

Part 4: "PaintPanel"

PaintPanel is the function called by the for-loop detailed in Section 3 and does what its name describes: it draws a colored panel. The function takes seven parameters: the array of images it will be drawing from (this is always speechBubbles, described in Section 2), the integer nimages, an 'x-coordinate, a 'y-coordinate', a width, height, and a color variable.

Both the array speechBubbles and nimages are pulled into the function because PaintPanel calls a different function for drawing the speech bubbles, drawBubble, which needs both of these variables in its parameters.

PaintPanel is a simple function: it fills the square with color c, a random color taken from the color array (chosen when the function is called in Section 3). noStroke makes sure there is no outline around the panel, and rect draws the rectangle.

Once the panel has been drawn, PaintPanel then calls drawBubble to create speech bubbles within its boundaries.

// Paint a panel starting at upperleft corner x,y, in color c.
void PaintPanel(PImage[] speechBubbles, int nimages, int x, int y, int wpanel, int panel, color c) {
  fill( c );
  println("wpanel=" + wpanel);
  rect((x+5), (y+5), (wpanel-5), (panel-5));
  int repeatNum = int(random(0,4));
  drawBubble(speechBubbles, nimages, x, y, wpanel, panel);

That addresses sveeral of my concerns actually.

Part 6: Printing and Image Drawing

The function PrintIntArray is used solely to print the four arrays used in the drawBubble function. It is a simple for-loop that runs through and prints all the indexes in each array.

drawImage is only called after drawBubble has successfully determined that there will be no image overlap. The drawBubble function sends the randomized speechBubbles array index, x-coordinate, and y-coordinate. The drawImage function then smooths the image before drawing it.

void PrintIntArray( String name, int array[], int n ) {
  println("Printing array " + name);
  for ( int i =0; i < n; i++ ) {
    println("i=" + i + " array[i]=" + array[i] );

void drawImage(PImage imgName, int xpoint, int ypoint){
    image(imgName, xpoint, ypoint);

Part 7: Preventing Overlap

The purpose of these functions (written by JOR) is to compute the degree of overlap between two rectangles. The speech bubbles are images that fit in bounding rectangles. There was no attempt to compute overlap between the bubbles themselves, which would be much more difficult.

The top-level function just computes a percent overlap, but relies totally on RectangleIntersection(), which computes the overlap in pixels. That function in turn relies on Overlap1D(), which computes the pixel overlap between two segments. This is of course straightforward, but the only assumptions made are that x1 ≤ x2 and x3 ≤ x4. The burden of the function to to methodically check all possible positions of those endpoints. For example, if x4 ≤ x1, then the right endpoint of one segment is left of the left endpoint of the other, so they are considered non-overlapping. (It would be consistent to treat this is a 1-pixel overlap, but I thought it would be cleaner to treat this as 0). Similarly, if x1 ≤ x3 ≤ x4 ≤ x2, then the (x3,x4) segment is nested inside the (x1,x2) segment, and so the overlap is x4-x3. Other cases are similar. Rectangle overlap is then determined simply by the product of the two 1D overlaps. If either the vertical or horizontal overlap is 0, then the rectangles do not overlap, and 0 is returned.

Nikki decided that an overlap of 30% gave the correct asthetic feel. This is called threshold in the code above.

int PercentOverlap(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2){
  // find the area overlap in pixels
  int area = RectangleIntersection(x1, y1, w1, h1, x2, y2, w2, h2);
  println("PercentOverlap: area="+area);
  // convert to a percentage of the area of the smaller of the two
  int areasmaller = min( w1 * h1, w2 * h2 );
  int percent = int(100 * float(area) / float(areasmaller));
  println("PercentOverlap (w.r.t smaller)="+percent);
  return percent;

// The 8 args to this function are the four ints that determine each rectangle,
// in the same order as in the rect() function: x,y,w,h.
// Returns overlap in pixels (not percentage!)
int RectangleIntersection(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
  int xoverlap = Overlap1D(x1, x1+w1, x2, x2+w2);
  if (xoverlap == 0) { 
    println("No xoverlap");
    return 0; 
  int yoverlap = Overlap1D(y1, y1+h1, y2, y2+h2);
  if (yoverlap == 0) { 
    println("No yoverlap");
    return 0; 
  println("xoverlap="+xoverlap+" yoveralp="+yoverlap);
  return xoverlap * yoverlap;

// This function computes overlap in one dimension, i.e., of segments.
// Returns an integer.
int Overlap1D(int x1, int x2, int x3, int x4) {
  if (x4 <= x1) { return 0;}
  if (x2 <= x3) { return 0;}
  // they overlap
  int overlap = -1; // as a default; should never be returned
  // first check for total overlap
  if      ((x3 <= x1) && (x2 <= x4)) { overlap = x2-x1; }
  else if ((x1 <= x3) && (x4 <= x2)) { overlap = x4-x3; }
  // now partial overlaps
  else if ((x3 <= x1) && (x1 <= x4)) { overlap = x4-x1; }
  else if ((x3 <= x2) && (x2 <= x4)) { overlap = x2-x3; }
  else if ((x1 <= x3) && (x3 <= x2)) { overlap = x2-x3; }
  else if ((x1 <= x4) && (x4 <= x2)) { overlap = x4-x2; }
  else println("Error in Overlap1D!");
  return overlap;

Version with Images

There were several changes made to the program to create a version with images as the panel backgrounds, giving a stronger impression of a comic strip.

Extra Arrays

In order to choose background images for the second version of the program, two extra arrays had to be created. The first array, bgPics, held images that were all squares (used only for the square panels). The second, bgPicsWide, holds images for use only in the last-row, randomly-sized panels.

  PImage[] bgPics = new PImage[10];
    bgPics[0] = loadImage("comic1.jpg");
    bgPics[1] = loadImage("comic2.jpg");
    bgPics[2] = loadImage("comic3.jpg");
    bgPics[3] = loadImage("comic4.jpg");
    bgPics[4] = loadImage("comic5.jpg");
    bgPics[5] = loadImage("comic6.jpg");
    bgPics[6] = loadImage("comic7.jpg");
    bgPics[7] = loadImage("comic8.jpg");
  PImage[] bgPicsWide = new PImage[3];
    bgPicsWide[0] = loadImage("comiclong1.jpg");
    bgPicsWide[1] = loadImage("comiclong2.jpg");

Changes to "PaintPanel"

Instead of the function PaintPanel, two new functions were made to paint images instead of colors. The first was only for square panel use, while the second is for random-width last-row panels. Each of the functions take all of the arguments that PaintPanel does, plus the second array of background images and a second nimages variable.

Both functions use whatBG to determine which indexed image to use. The image is then made slightly translucent by using tint so that bubble images show up better against the complex background. After the panel image is drawn, noTint is used so that the bubble images are not tinted as well.

The second function, otherCase, draws from an array of images with a width of 600. If there are two panels in the last row, after the first panel is drawn, a white rectangle slightly larger than the value of secondWidth (the value of randWidth subtracted from the window width) is drawn on top of the first image, then the second panel image is drawn on top of the white rectangle. The insertion of a white rectangle makes sure that the first image does not show up behind the slightly transparent second image.

void squareCase(PImage[] speechBubbles, PImage[] bgPics, int nimages, int nimagesBG, int x, int y, int wpanel, int panel){
  int whatBG = int(random(0,nimagesBG));

void otherCase(PImage[] speechBubbles, PImage[] bgPicsWide, int nimages, int nimagesBG2, int x, int y, int wpanel, int panel){
  int whatBG2 = int(random(0,nimagesBG2));

Sample Gallery

Painted Version


Image Version

Background images adapted from artwork by Kelsey Tomblin.