Switchboard

Warhol Applet

This tutorial assumes that you have already been through he Quick Start.

The Warhol Applet was originally made for my thesis defense to demonstrate how Real-time art techniques can allow artists to make work that is adaptive to the time and place in which it is viewed. The piece is a spoof of the well-known "100 Cans" piece by Andy Warhol (see slide 1). The point is: if one assumes that Warhol was making a statement about high art by featuring a mundane object such as a Campbell's soup can, then the message of the work is undermined by the ever-evolving design of the soup can (see slide 2). The image of the soup can is no longer read as "mundane", but rather antiquated, or perhaps even nostalgic. But if the artist uses live data sources and abstracts his definition of "mundane" to a set of rules, then the message can be maintained no matter when the work is viewed (see slide 3).

Three slides from my thesis defense. The first shows Warhol's famous painting, "100 Cans". the second shows the current design of the Campbell's soup can, and the third shows a screenshot from a Switchboard application, "100 Blackberries".

How it Works

As mentioned above, this piece uses the Amazon Web Service, so the first step is to set the Amazon Web Services key, which you can get at the Amazon Web Services site.

  //  Set the developer key.
  board.setAmazonKey("11ZG2NFP21BC8WVD31R2");

If you check out the amazon() documentation, you will see that there are two ways of using the amazon service. For the purposes of this project, we will use the second type, which is used for browsing, as opposed to the first type, which is used for searching. The arguments for this type of query are a node ID (int) and a mode (String). You can see around lines 33-37 that I am making a series of queries using node 1064954 within the "kitchen" mode, node 540744 within the "baby" mode, and node 15735731 in the "automotive" mode. As I mention in the documentation, Amazon has managed to keep their node and mode lists well hidden, so figuring out the right combinations can be tricky.

//  Do some amazon queries to build up our collection of products.
board.amazon(1064954, "kitchen");
board.amazon(15684181, "photo");
board.amazon(540744, "baby");
board.amazon(172282, "electronics");
board.amazon(15735731, "automotive");

Once these queries are made, the applet is off and running. But before we look at the draw() function, we need to see what's going on behind the scenes.

Callback Functions

Now we will look at the Switchboard callback functions, which are inside the section which starts on line 41. First look at resultReceived().

void resultReceived() {
  // Get some info from the amazon object.
  String imgUrl = board.amazon.getImageUrlMedium();
  String name = board.amazon.getProductName();
  products.add(new Product(name, imgUrl));
}

As you know from the Quick Start, this method is called whenever the Switchboard receives a result from a service. Since we have only made queries to the amazon() service, we know that every time this method is called, we will have a new result from Amazon. Inside resultReceived(), we are getting two values from board.amazon, which is the

Take a quick look at the Product class that is defined on line 166. The constructor takes a string for the name of the product, and a string for the URL of an image. that's really all you need to know about the Product class - it stores an image and a string for the name. The important thing is that whenever we get a result from Amazon, we construct a Product and then add that Product to a list called 'products' (line 63).

Now skip down to endOfResults(). This method is called when a service finishes. As you can see on line 78, when our amazon service finishes, we call nextProduct() on line 87, which chooses a random index from our product array and assigns it to currentProductIndex.

 
void endOfResults() {
    nextProduct();
}
 
/*
  Picks a random product from the assembled list and
 restarts the timer by setting lastProductChange
 */
void nextProduct() {
  lastProductChange = System.currentTimeMillis();
  currentProductIndex = (int)random(products.size()); 
}

It also notes the time at which this was done be calling System.getTimeMillis() (the number of milliseconds since January 1, 1970) and assigns that to lastProductChange. And that's it! Now we move on to the drawing.

The Details

So now we have everything we're going to get from Switchboard. We have an array full of product names and images. All that's left to do is print them out. Go down to the draw() function on line 93.

Remember that we also have a random index from the product array: currentProductIndex. You'll notice that, when we declare currentProductIndex on line 5, we set it to -1. So now, in the draw() method, we test whether it is still set to -1. If it is, that means that endOfResults() has not been called yet, so we should print a message telling folks that stuff is still loading. And that's just what we do on line 98.

void draw() {
  background(255);
 
  //  If there are some products, print them to the screen.
  if(currentProductIndex == -1) {
    text("Loading...", (width/2)-textWidth("Loading..."), height/2);
  } 
  else if(frameCount%4>2) {  // If there are no products yet, draw the loading bar.
     drawProducts();  
  }
}

However, if it is no longer equal to -1, that means we have some products to display, so we can call the drawProducts() method. I didn't include any comments in the drawProducts method because it doesn't really have anything to do with Switchboard, but basically we're just using the built-in processing methods to draw the images that we loaded

Source

  1. import org.switchboard.*;
  2.  
  3. Switchboard board; // Our beloved Switchboard
  4. ArrayList products = new ArrayList(); // To be filled with products from Amazon
  5. int currentProductIndex = -1; // The index of the current product
  6. long lastProductChange = -1; // The system time of the last product change
  7. color[] colors = new color[100]; // Some random colors that we will use
  8. float xStart = 5;
  9.  
  10. void setup() {
  11. // Uninteresting setup stuff
  12. size(500, 700);
  13. framerate(12);
  14. textFont(loadFont("ArialMT-12.vlw"));
  15. textSize(14);
  16. fill(0);
  17. for(int i=0; i<colors.length; i++) {
  18. colors[i] = color(random(255), random(255), random(255), 200);
  19. }
  20.  
  21. // Create a new Switchboard.
  22. // This will allow us to access all of the Switchboard functions
  23. board = new Switchboard(this);
  24.  
  25. // Set the developer key.
  26. board.setAmazonKey("11ZG2NFP21BC8WVD31R2");
  27.  
  28. // Do some amazon queries to build up our collection of products.
  29. board.amazon(1064954, "kitchen");
  30. board.amazon(15684181, "photo");
  31. board.amazon(540744, "baby");
  32. board.amazon(172282, "electronics");
  33. board.amazon(15735731, "automotive");
  34. }
  35.  
  36. /*****************************************
  37. * BEGIN SWITCHBOARD LISTENERS
  38. ******************************************/
  39.  
  40. /*
  41. Whenever we receive a result from Amazon, create a Product
  42. */
  43. void resultReceived() {
  44. /*
  45. TIP: If we were expecting results from other services,
  46. if(board.resultService == Switchboard.AMAZON) {
  47. // Now we can be sure that it is really Amazon returning a result.
  48. }
  49. */
  50.  
  51. // Get some info from the amazon object.
  52. String imgUrl = board.amazon.getImageUrlMedium();
  53. String name = board.amazon.getProductName();
  54.  
  55. // If we wanted to, we could filter out the product based on sales rank
  56. // We could ensure that it is not too popular and not too obscure,
  57. // adding to the "mundane" factor.
  58. String salesRank = board.amazon.getSalesRank();
  59. products.add(new Product(name, imgUrl));
  60. }
  61.  
  62. /*
  63. If we get a status message from Switchboard...
  64. */
  65. void status() {
  66. println("**** "+board.statusMessage);
  67. }
  68.  
  69. /*
  70. This method is called when Switchboard reports that a query has finished.
  71. */
  72. void endOfResults() {
  73. nextProduct();
  74. }
  75.  
  76. /*****************************************
  77. * END SWITCHBOARD LISTENERS
  78. ******************************************/
  79.  
  80.  
  81.  
  82. /*
  83. Picks a random product from the assembled list and
  84. restarts the timer by setting lastProductChange
  85. */
  86. void nextProduct() {
  87. lastProductChange = System.currentTimeMillis();
  88. currentProductIndex = (int)random(products.size());
  89. }
  90.  
  91.  
  92. void draw() {
  93. background(255);
  94.  
  95. // If there are some products, print them to the screen.
  96. if(currentProductIndex == -1) {
  97. text("Loading...", (width/2)-textWidth("Loading..."), height/2);
  98. }
  99. else if(frameCount%4>2) { // If there are no products yet, draw the loading bar.
  100. drawProducts();
  101. }
  102. }
  103.  
  104.  
  105. /*
  106. Does the work of drawing the product images
  107. */
  108. void drawProducts() {
  109. // Draw the products
  110. int n = 0;
  111. Product product = (Product)products.get(currentProductIndex);
  112. if(product.img.width > 0 && product.img.height > 0) {
  113. int x;
  114. for(x=5; x<(width-product.img.width); x+=product.img.width) {
  115. for(int y=10; y<(height-product.img.height-20); y+= product.img.height) {
  116. n++;
  117. tint(colors[n]);
  118. product.img.mask(product.maskArray);
  119. image(product.img, x, y);
  120. }
  121. }
  122. xStart = (width-x)/2;
  123. }
  124. filter(POSTERIZE, 4);
  125. String msg = n+" "+product.name+"s";
  126. xStart = (width-textWidth(msg))/2;
  127. text(msg, xStart, height-16);
  128. // If more than 5000 milliseconds have passed since the last product change,
  129. // call nextProduct() to change the product.
  130. long elapsed = System.currentTimeMillis() - lastProductChange;
  131. if(elapsed > 5000) {
  132. nextProduct();
  133. xStart = 5;
  134. }
  135. }
  136.  
  137. /*
  138. * Product class
  139. * This is a little container class for the products that we will get back
  140. * Its only functions are to remember its name, create a PImage for itself,
  141. * and create an alpha mask.
  142. */
  143. class Product {
  144. String name;
  145. PImage img;
  146. int[] maskArray;
  147. int threshhold = 210;
  148.  
  149. public Product(String name, String imgUrl) {
  150. this.name = name;
  151. this.img = loadImage(imgUrl);
  152. // Loop through all of the pixels to create a mask so that the image is "transparent"
  153. // If the pixel is within 45 pixels of white, add a blakc pixel to the mask
  154. maskArray = new int[img.width*img.height];
  155. for(int x=0; x<img.width; x++) {
  156. for(int y=0; y<img.height; y++) {
  157. int n = (y * img.width) + x;
  158. float r = red(img.pixels[n]);
  159. float g = green(img.pixels[n]);
  160. float b = blue(img.pixels[n]);
  161. if(r > threshhold && g > threshhold && b > threshhold) {
  162. maskArray[n] = 0;
  163. }
  164. else {
  165. maskArray[n] = 255;
  166. }
  167. }
  168. }
  169. }
  170. }