Processing: A Programming Handbook for Visual Designers and Artists PDF
Document Details
Uploaded by Deleted User
2007
Casey Reas, Ben Fry
Tags
Summary
This book is a programming handbook for visual designers and artists. It provides a comprehensive guide to the Processing programming language, which is used to create interactive visual art, graphics, and applications. The book covers various topics, including data structures, algorithms, and visual aesthetics.
Full Transcript
Processing Processing: a programming handbook for visual designers and artists Casey Reas Ben Fry The MIT Press Cambridge, Massachusetts London, England © 2007 Massachusetts Institute of Technology All rights reserved. No part of this book may be reproduced in any form by any electronic or me...
Processing Processing: a programming handbook for visual designers and artists Casey Reas Ben Fry The MIT Press Cambridge, Massachusetts London, England © 2007 Massachusetts Institute of Technology All rights reserved. No part of this book may be reproduced in any form by any electronic or mechanical means (including photocopying, recording, or information storage and retrieval) without permission in writing from the publisher. MIT Press books may be purchased at special quantity discounts for business or sales promotional use. For information, please email [email protected] or write to Special Sales Department, The MIT Press, 55 Hayward Street, Cambridge, MA 02142. Printed and bound in the United States of America. Library of Congress Cataloging-in-Publication Data Reas, Casey. Processing : a programming handbook for visual designers and artists / Casey Reas & Ben Fry ; foreword by John Maeda. p. cm. Includes bibliographical references and index. ISBN 978-0-262-18262-1 (hardcover : alk. paper) 1. Computer programming. 2. Computer graphics—Computer programs. 3. Digital art—Computer programs. 4. Art—Data processing. 5. Art and technology. I. Fry, Ben. II. Title. QA76.6.R4138 2007 005.1—dc22 2006034768 10 9 8 7 6 5 4 3 2 1 For the ACG 29 34 45 57 67 72 84 91 99 113 121 131 141 189 192 204 208 221 225 233 244 247 289 297 307 320 324 331 336 344 352 354 359 409 415 447 451 472 493 530 535 551 Contents xix Foreword 279 Motion 1: Lines, Curves xxi Preface 291 Motion 2: Machine, Organism 301 Data 4: Arrays 1 Processing... 315 Image 2: Animation 9 Using Processing 321 Image 3: Pixels 327 Typography 2: Motion 17 Structure 1: Code Elements 333 Typography 3: Response 23 Shape 1: Coordinates, Primitives 337 Color 2: Components 37 Data 1: Variables 347 Image 4: Filter, Blend, Copy, Mask 43 Math 1: Arithmetic, Functions 355 Image 5: Image Processing 51 Control 1: Decisions 367 Output 1: Images 61 Control 2: Repetition 371 Synthesis 3: Motion and Arrays 69 Shape 2: Vertices 377 Interviews 3: Animation, Video 79 Math 2: Curves 85 Color 1: Color by Numbers 395 Structure 4: Objects I 95 Image 1: Display, Tint 413 Drawing 2: Kinetic Forms 101 Data 2: Text 421 Output 2: File Export 105 Data 3: Conversion, Objects 427 Input 6: File Import 111 Typography 1: Display 435 Input 7: Interface 117 Math 3: Trigonometry 453 Structure 5: Objects II 127 Math 4: Random 461 Simulate 1: Biology 133 Transform 1: Translate, Matrices 477 Simulate 2: Physics 137 Transform 2: Rotate, Scale 495 Synthesis 4: Structure, Interface 145 Development 1: Sketching, Techniques 501 Interviews 4: Performance, Installation 149 Synthesis 1: Form and Code 155 Interviews 1: Print 519 Extension 1: Continuing... 525 Extension 2: 3D 173 Structure 2: Continuous 547 Extension 3: Vision 181 Structure 3: Functions 563 Extension 4: Network 197 Shape 3: Parameters, Recursion 579 Extension 5: Sound 205 Input 1: Mouse I 603 Extension 6: Print 217 Drawing 1: Static Forms 617 Extension 7: Mobile 223 Input 2: Keyboard 633 Extension 8: Electronics 229 Input 3: Events 237 Input 4: Mouse II 661 Appendixes 245 Input 5: Time, Date 693 Related Media 251 Development 2: Iteration, Debugging 699 Glossary 255 Synthesis 2: Input and Response 703 Code Index 261 Interviews 2: Software, Web 705 Index vii 88 342 55 65 305 220 415 98 319 323 351 353 359 207 225 232 240 247 444 44 83 124 129 288 296 29 32 75 202 470 488 184 190 407 455 141 113 329 335 530 535 551 viii Contents by category xix Foreword 23 Shape 1: Coordinates, Primitives xxi Preface 69 Shape 2: Vertices 197 Shape 3: Parameters, Recursion 1 Processing... 461 Simulate 1: Biology 9 Using Processing 477 Simulate 2: Physics 17 Structure 1: Code Elements 85 Color 1: Color by Numbers 173 Structure 2: Continuous 337 Color 2: Components 181 Structure 3: Functions 51 Control 1: Decisions 395 Structure 4: Objects I 61 Control 2: Repetition 453 Structure 5: Objects II 37 Data 1: Variables 149 Synthesis 1: Form and Code 101 Data 2: Text 255 Synthesis 2: Input and Response 105 Data 3: Conversion, Objects 371 Synthesis 3: Motion and Arrays 301 Data 4: Arrays 495 Synthesis 4: Structure, Interface 145 Development 1: Sketching, Techniques 133 Transform 1: Translate, Matrices 251 Development 2: Iteration, Debugging 137 Transform 2: Rotate, Scale 217 Drawing 1: Static Forms 111 Typography 1: Display 413 Drawing 2: Kinetic Forms 327 Typography 2: Motion 95 Image 1: Display, Tint 333 Typography 3: Response 315 Image 2: Animation 321 Image 3: Pixels 155 Interviews 1: Print 347 Image 4: Filter, Blend, Copy, Mask 261 Interviews 2: Software, Web 355 Image 5: Image Processing 377 Interviews 3: Animation, Video 205 Input 1: Mouse I 501 Interviews 4: Performance, Installation 223 Input 2: Keyboard 229 Input 3: Events 519 Extension 1: Continuing... 237 Input 4: Mouse II 525 Extension 2: 3D 245 Input 5: Time, Date 547 Extension 3: Vision 427 Input 6: File Import 563 Extension 4: Network 435 Input 7: Interface 579 Extension 5: Sound 43 Math 1: Arithmetic, Functions 603 Extension 6: Print 79 Math 2: Curves 617 Extension 7: Mobile 117 Math 3: Trigonometry 633 Extension 8: Electronics 127 Math 4: Random 279 Motion 1: Lines, Curves 661 Appendixes 291 Motion 2: Machine, Organism 693 Related Media 367 Output 1: Images 699 Glossary 421 Output 2: File Export 703 Code Index 705 Index ix 29 30 44 55 63 70 83 88 97 113 124 128 137 174 186 200 206 219 225 231 239 246 281 293 306 316 322 329 334 340 349 353 356 406 414 441 458 464 484 530 535 551 x Extended contents xix Foreword by John Maeda 23 Shape 1: Coordinates, Primitives 23 Coordinates xxi Preface size() xxi Contents 25 Primitive shapes xxii How to read this book point(), line(), xxiii Casey’s introduction triangle(), quad(), rect(), xxiv Ben’s introduction ellipse(), bezier() xxv Acknowledgments 31 Drawing order 31 Gray values 1 Processing... background(), 1 Software fill(),stroke(), 3 Literacy noFill(), noStroke() 4 Open 33 Drawing attributes 4 Education smooth(), noSmooth(), 6 Network strokeWeight(), strokeCap(), 7 Context strokeJoin() 34 Drawing modes 9 Using Processing ellipseMode(), rectMode() 9 Download, Install 9 Environment 37 Data 1: Variables 10 Export 37 Data types 11 Example walk-through int, float, boolean, 16 Reference true, false 38 Variables 17 Structure 1: Code Elements = 17 Comments 40 Processing variables //, width, height 18 Functions 18 Expressions, Statements 43 Math 1: Arithmetic, Functions “;”, “,” 43 Arithmetic 20 Case sensitivity +, -, *, /, % 20 Whitespace 47 Operator precedence, Grouping 20 Console () print(), println() 48 Shortcuts ++, --, +=, -=, *=, /=, - 49 Constraining numbers ceil(), floor(), round(), min(), max() xi 51 Control 1: Decisions 101 Data 2: Text 51 Relational expressions 102 Characters >, =, 100" and "x < 100" 5-03 // Because x is 150, the code inside the first block // runs and the ellipse draws, but the code in the second // block is not run and the rectangle is not drawn int x = 150; if (x > 100) { // If x is greater than 100, ellipse(50, 50, 36, 36); // draw this ellipse } if (x < 100) { // If x is less than 100 rect(35, 35, 30, 30); // draw this rectangle } line(20, 20, 80, 80); // Because x is 50, only the rectangle draws 5-04 int x = 50; if (x > 100) { // If x is greater than 100, ellipse(50, 50, 36, 36); // draw this ellipse } if (x < 100) { // If x is less than 100, rect(33, 33, 34, 34); // draw this rectangle } line(20, 20, 80, 80); 53 Control 1: Decisions General case if structure false if (test) { if test statements true } statements A specific if structure false if (x < 150) { if x 100) { // If x is greater than 100, ellipse(50, 50, 36, 36); // draw this ellipse } if (x < 100) { // If x is less than 100, rect(33, 33, 34, 34); // draw this rectangle } line(20, 20, 80, 80); // Always draw the line To run a different set of code when the relational expression for an if structure is not true, use the else keyword. The keyword else extends an if structure so that when the expression associated with the structure is false, the code in the else block is run instead. // Because x is 90, only the rectangle draws 5-06 int x = 90; if (x > 100) { // If x is greater than 100, ellipse(50, 50, 36, 36); // draw this ellipse } else { // Otherwise, rect(33, 33, 34, 34); // draw this rectangle } line(20, 20, 80, 80); // Always draw the line // Because x is 290, only the ellipse draws 5-07 int x = 290; if (x > 100) { // If x is greater than 100, ellipse(50, 50, 36, 36); // draw this ellipse } else { // Otherwise, rect(33, 33, 34, 34); // draw this rectangle } line(20, 20, 80, 80); // Always draw the line 55 Control 1: Decisions Conditionals can be embedded within other conditionals to control which lines of code will run. In the next example, the code for drawing the ellipse or line can be reached only if x is larger than 100. If this expression evaluates to true, a second comparison of x determines which of these shapes will be drawn. // If x is greater than 100 and less than 300, draw the 5-08 // ellipse. If x is greater than or equal to 300, draw // the line. If x is not greater than 100, draw the // rectangle. Because x is 420, only the line draws. int x = 420; if (x > 100) { // First test to draw ellipse or line if (x < 300) { // Second test determines which to draw ellipse(50, 50, 36, 36); } else { line(50, 0, 50, 100); } } else { rect(33, 33, 34, 34); } Conditionals can be extended further by combining an else with an if. This allows conditionals to use multiple tests to determine which lines the program should run. This technique is used when there are many choices and only one can be selected at a time. // If x is less than or equal to 100, then draw 5-09 // the rectangle. Otherwise, if x is greater than // or equal to 300, draw the line. If x is between // 100 and 300, draw the ellipse. Because x is 101, // only the ellipse draws. int x = 101; if (x = 300) { line(50, 0, 50, 100); } else { ellipse(50, 50, 36, 36); } 56 Control 1: Decisions Logical operators Logical operators are used to combine two or more relational expressions and to invert logical values. They allow for more than one condition to be considered simultaneously. The logical operators are symbols for the logical concepts of AND, OR, and NOT: Operator Meaning && AND || OR ! NOT The following table outlines all possible combinations and the results. Expression Evaluation true && true true true && false false false && false false true || true true true || false true false || false false !true false !false true The logical OR operator, two vertical bars (sometimes called pipes), makes the relational expression true if only one part is true. The following example shows how to use it: int a = 10; 5-10 int b = 20; // The expression "a > 5" must be true OR "b < 30" // must be true. Because they are both true, the code // in the block will run. if ((a > 5) || (b < 30)) { line(20, 50, 80, 50); } // The expression "a > 15" is false, but "b < 30" // is true. Because the OR operator requires only one part // to be true in the entire expression, the code in the // block will run. if ((a > 15) || (b < 30)) { ellipse(50, 50, 36, 36); } Compound logical expressions can be tricky to figure out, but they are simpler when looked at step by step. Parentheses are useful hints in determining the order of 57 Control 1: Decisions evaluation. Looking at the test of the if structure in line 6 of the previous example, first the variables are replaced with their values, then each subexpression is evaluated, and finally the expression with the logical operator is evaluated: Step 1 (a > 5) || (b < 30) Step 2 (10 > 5) || (20 < 30) Step 3 true || true Step 4 true The logical AND operator, two ampersands, allows the entire relational statement to be true only if both parts are true. The following example is the same as the last except the logical OR operators have been changed to the logical AND. Because each operator compares the values differently, only the line is drawn here, whereas the previous example drew both the line and circle. int a = 10; 5-11 int b = 20; // The expression "a > 5" must be true AND "b < 30" // must be true. Because they are both true, the code // in the block will run. if ((a > 5) && (b < 30)) { line(20, 50, 80, 50); } // The expression "a > 15" is false, but "b < 30" is // true. Because the AND operator requires both to be // true, the code in the block will not run. if ((a > 15) && (b < 30)) { ellipse(50, 50, 36, 36); } Technically, the steps shown above aren’t the whole story. When using AND, the first part of the expression will be evaluated. If that part is false, then the second part of the expression won’t even be evaluated. For example, in this expression... (a > 5) && (b < 30)... if a > 5 evaluates to false, then b < 30 is ignored for efficiency. This is called a short circuit operator. The same happens for the OR operator, where the first true statement will end evaluation. For example, if the expression is: (a > 5) || (b < 30) If a > 5 is true, then the b < 30 will be ignored, because the entire expression will evaluate to true, regardless of the value of b < 30. Outside of efficiency, this has many 58 Control 1: Decisions practical applications in more advanced code. The logical NOT operator is an exclamation mark. It inverts the logical value of the associated boolean variables. It changes true to false, and false to true. The logical NOT operator can be applied only to boolean variables. boolean b = true; // Assign true to b 5-12 println(b); // Prints "true" println(!b); // Prints "false" b = !b; // Assign false to b println(b); // Prints "false" println(!b); // Prints "true" println(5 > 3); // Prints "true" println(!(5 > 3)); // Prints "false" int x = 5; println(!x); // ERROR! It's only possible to ! a boolean variable // Because b is true, the line draws 5-13 boolean b = true; if (b == true) { // If b is true, line(20, 50, 80, 50); // draw the line } if (!b == true) { // If b is false, ellipse(50, 50, 36, 36); // draw the ellipse } Exercises 1. Create a few relational expressions and print their evaluation to the console with println(). 2. Create a composition with a series of lines and ellipses. Use an if structure to select which lines of code to run and which to skip. 3. Add an else to the code from exercise 2 to change which code is run. 59 Control 1: Decisions Control 2: Repetition This unit focuses on controlling the flow of programs with iterative structures. Syntax introduced: for The early history of computers is the history of automating calculation. A “computer” was originally a person who was paid to calculate math by hand. What we know as a computer today emerged from machines built to automate tedious mathematical calculations. The earliest mechanical computers were calculators developed for speed and accuracy in performing repetitive calculations. Because of this heritage, computers are excellent at executing repetitive tasks accurately and quickly. Modern computers are also logic machines. Building on the work of the logicians Leibniz and Boole, modern computers use logical operations such as AND, OR, and NOT to determine which lines of code are run and which are not. Iteration Iterative structures are used to compact lengthy lines of repetitive code. Decreasing the length of the code can make programs easier to manage and can also help to reduce errors. The table below shows equivalent programs written without an iterative structure and with a for structure. The original 14 lines of code on the left are reduced to the 4 lines on the right: Original code Code expressed using a for structure size(200, 200); size(200, 200); line(20, 20, 20, 180); for (int i = 20; i < 150; i += 10) { line(30, 20, 30, 180); line(i, 20, i, 180); line(40, 20, 40, 180); } line(50, 20, 50, 180); line(60, 20, 60, 180); line(70, 20, 70, 180); line(80, 20, 80, 180); line(90, 20, 90, 180); line(100, 20, 100, 180); line(110, 20, 110, 180); line(120, 20, 120, 180); line(130, 20, 130, 180); line(140, 20, 140, 180); 61 General case for structure for (init; test; update) { init statements false } test true statements update A specific for structure for (int i = 20; i < 80; i += 5) { int i = 20 line(20, i, 80, i+15); false } i < 80 true line(20, i, 80, i+15); i += 5 Repetition The flow of a for structure shown as a diagram. These images show the central importance of the test statement in deciding whether to run the code in the block or to exit. The general case shows the generic format, and the specific case shows one example of how the format can be used within a program. 62 Control 2: Repetition The for structure performs repetitive calculations and is structured like this: for (init; test; update) { statements } The parentheses associated with the structure enclose three statements: init, test, and update. The statements inside the block are run continuously while the test evaluates to true. The init portion assigns the initial value of the variable used in the test. The update is used to modify the variable after each iteration through the block. A for structure runs in the following sequence: 1. The init statement is run 2. The test is evaluated to true or false 3. If the test is true, continue to step 4. If the test is false, jump to step 6 4. Run the statements within the block 5. Run the update statement and jump to step 2 6. Exit the structure and continue running the program The following examples demonstrate how the for structure is used within a program to control the way shapes are drawn to the display window. // The init is "int i = 20", the test is "i < 80", 6-01 // and the update is "i += 5". Notice the semicolon // terminating the first two elements for (int i = 20; i < 80; i += 5) { // This line will continue to run until "i" // is greater than or equal to 80 line(20, i, 80, i+15); } for (int x = -16; x < 100; x += 10) { 6-02 line(x, 0, x+15, 50); } strokeWeight(4); for (int x = -8; x < 100; x += 10) { line(x, 50, x+15, 100); } noFill(); 6-03 for (int d = 150; d > 0; d -= 10) { ellipse(50, 50, d, d); } 63 Control 2: Repetition for (int x = 20; x 20; x /= 1.2) { line(x, 20, x, 80); line(20, x, 80, x); } } for (int x = 20; x E> E>$' IL%TE> The following two examples change the x-coordinate and diameter of circles to demonstrate phase shifting. In the first example, each circle has the same horizontal motion, but the motion is offset in time. In the second example, each circle has the same position and rate of growth cycle, but the growth cycle is offset. float angle = 0.0; 32-03 float speed = 0.1; void setup() { size(100, 100); noStroke(); smooth(); } void draw() { background(0); angle = angle + speed; ellipse(50 + (sin(angle + PI) * 5), 25, 30, 30); ellipse(50 + (sin(angle + HALF_PI) * 5), 55, 30, 30); ellipse(50 + (sin(angle + QUARTER_PI) * 5), 85, 30, 30); } float angle = 0.0; // Changing angle 32-04 float speed = 0.05; // Speed of growth void setup() { size(100, 100); noStroke(); smooth(); fill(255, 180); } void draw() { background(0); 294 Motion 2: Machine, Organism circlePhase(0.0); 32-04 cont. circlePhase(QUARTER_PI); circlePhase(HALF_PI); angle += speed; } void circlePhase(float phase) { float diameter = 65 + (sin(angle + phase) * 45); ellipse(50, 50, diameter, diameter); } Organic motion Examples of organic movement include a leaf falling, an insect walking, a bird flying, a person breathing, a river flowing, and smoke rising. This type of motion is often considered idiosyncratic and stochastic. Explorations in photography have led to a new understanding of human and animal motion. Étienne-Jules Marey and Eadweard Muybridge focused the lenses of their cameras on bodies in motion. In the 1880s Marey famously captured birds in flight, revealing the changing shapes of wings in image montages. Muybridge’s sensational photographs of horses in motion used fifty cameras in sequence along a track to capture still images of a running horse. These photographs provided visual evidence regarding the horse’s stride that had previously been impossible to collect. In the 1930s, Harold Edgerton invented technologies for capturing unique movements such as the beating of a hummingbird’s wings and the motion of a starfish across the sea floor. Such images and films have changed the way the world is understood and can inform the way organic motion is approached using software. Software explorations within the last twenty years have also provided a new understanding of organic motion. The Boids (pp. 473–475) software created by Craig Reynolds in 1986 simulates the flocking behavior found in birds and fish and has led to a new understanding of these emergent behaviors of animals. Karl Sims’s Evolved Virtual Creatures from 1994 presents an artificial evolution, focusing on locomotion, where virtual block creatures engage in walking, jumping, and swimming competitions. The simple blocks demonstrate extraordinary emotive qualities as they twist and turn, appearing to struggle in their pursuit of locomotion. Brownian motion, named in honor of the botanist Robert Brown, is jittery, stochastic motion that was originally ascribed to the movements of minute particles within fluids or the air; it appears entirely random. This motion can be simulated in software by setting a new position for a particle each frame, without preference as to the direction of motion. Leaving a trail of the previous positions of an element is a good technique for tracing its path through space. 295 Motion 2: Machine, Organism float x = 50.0; // X-coordinate 32-05 float y = 80.0; // Y-coordinate void setup() { size(100, 100); randomSeed(0); // Force the same random values background(0); stroke(255); } void draw() { x += random(-2, 2); // Assign new x-coordinate y += random(-2, 2); // Assign new y-coordinate point(x, y); } The sin() and cos() functions can be used to create unpredictable motion when employed with the random() function. The following example presents a line with a position and direction, and every frame the direction is changed by a small value between -0.3 and 0.3. The position of the line at each frame is based on its current position and the slight variation to its direction. The cos() function uses the angle to calculate the next x-coordinate for the line, and the sin() function uses the same angle to calculate the next y-coordinate. float x = 0.0; // X-coordinate 32-06 float y = 50.0; // Y-coordinate float angle = 0.0; // Direction of motion float speed = 0.5; // Speed of motion void setup() { size(100, 100); background(0); stroke(255, 130); randomSeed(121); // Force the same random values } void draw() { angle += random(-0.3, 0.3); x += cos(angle) * speed; // Update x-coordinate y += sin(angle) * speed; // Update y-coordinate translate(x, y); rotate(angle); line(0, -10, 0, 10); } 296 Motion 2: Machine, Organism The following example is an animated extension of code 22-04 (p. 200). Here the angle variable for the tail() function is continually changing to produce a swaying motion. Because the angles for each shape accumulate with each unit, the longest shapes with the most units swing from side to side with a greater curvature. float inc = 0.0; 32-07 void setup() { size(100, 100); stroke(255, 204); smooth(); } void draw() { background(0); inc += 0.01; float angle = sin(inc)/10.0 + sin(inc*1.2)/20.0; tail(18, 9, angle/1.3); tail(33, 12, angle); tail(44, 10, angle/1.3); tail(62, 5, angle); tail(88, 7, angle*2); } void tail(int x, int units, float angle) { pushMatrix(); translate(x, 100); for (int i = units; i > 0; i--) { strokeWeight(i); line(0, 0, 0, -8); translate(0, -8); rotate(angle); } popMatrix(); } The noise() function introduced in Math 4 (p. 127) is another resource for producing organic motion. Because the numbers returned from noise() are easy to control, they are a good way to add subtle irregularity to movement. The following example draws two lines to the screen and sets their endpoints based on numbers returned from noise(). 297 Motion 2: Machine, Organism float inc1 = 0.1; 32-08 float n1 = 0.0; float inc2 = 0.09; float n2 = 0.0; void setup() { size(100, 100); stroke(255); strokeWeight(20); smooth(); } void draw() { background(0); float y1 = (noise(n1) - 0.5) * 30.0; // Values -15 to 15 float y2 = (noise(n2) - 0.5) * 30.0; // Values -15 to 15 line(0, 50, 40, 50 + y1); line(100, 50, 60, 50 + y2); n1 += inc1; n2 += inc2; } The noise() function can also be used to generate dynamic textures. In the following example, the first two parameters are used to produce a two-dimensional texture and the third parameter increases its value each frame to vary the texture. Changing the density parameter increases the image resolution, and changing the inc parameter changes the texture resolution. float inc = 0.06; 32-09 int density = 4; float znoise = 0.0; void setup() { size(100, 100); noStroke(); } void draw() { float xnoise = 0.0; float ynoise = 0.0; for (int y = 0; y < height; y += density) { for (int x = 0; x < width; x += density) { float n = noise(xnoise, ynoise, znoise) * 256; fill(n); 298 Motion 2: Machine, Organism rect(y, x, density, density); 32-09 cont. xnoise += inc; } xnoise = 0; ynoise += inc; } znoise += inc; } Exercises 1. Make a shape move with numbers returned from sin() and cos(). 2. Develop a kinetic composition using the concept of phase shifting. 3. Use code 32-06 as a base for creating a more advanced organism. 299 Motion 2: Machine, Organism Data 4: Arrays This unit introduces arrays of data. Syntax introduced: Array, [] (array access), new, Array.length append(), shorten(), expand(), arraycopy() The term array refers to a structured grouping or an imposing number—“The dinner buffet offers an array of choices,” “The city of Los Angeles faces an array of problems.” In computer programming, an array is a set of data elements stored under the same name. Arrays can be created to hold any type of data, and each element can be individually assigned and read. There can be arrays of numbers, characters, sentences, boolean values, etc. Arrays might store vertex data for complex shapes, recent keystrokes from the keyboard, or data read from a file. Five integer variables (1919, 1940, 1975, 1976, 1990) can be stored in one integer array rather than defining five separate variables. For example, let’s call this array “dates” and store the values in sequence: dates 1919 1940 1975 1976 1990 Array elements are numbered starting with zero, which may seem confusing at first but is important for more advanced programming. The first element is at position , the second is at , and so on. The position of each element is determined by its offset from the start of the array. The first element is at position because it has no offset; the second element is at position because it is offset one place from the beginning. The last position in the array is calculated by subtracting 1 from the array length. In this example, the last element is at position because there are five elements in the array. Arrays can make the task of programming much easier. While it’s not necessary to use them, they can be valuable structures for managing data. Let’s begin with a set of data points we want to add to our program in order to draw a star: (50,18) (39,37) (61,37) (17,43) (83,43) (31,60) (69,60) (50,73) (29,82) (71,82) The following example to draw this shape demonstrates some of the benefits of using arrays, like avoiding the cumbersome chore of storing data points in individual 301 variables. The star has 10 vertex points, each with 2 values, for a total of 20 data elements. Inputting this data into a program requires either creating 20 variables or using an array. The code (below) on the left demonstrates using separate variables. The code in the middle uses 10 arrays, one for each point of the shape. This use of arrays improves the situation, but we can do better. The code on the right shows how the data elements can be logically grouped together in two arrays, one for the x-coordinates and one for the y-coordinates. Separate variables One array for each point One array for each axis int x0 = 50; int[] p0 = { 50, 18 }; int[] x = { 50, 61, 83, 69, 71, int y0 = 18; int[] p1 = { 61, 37 }; 50, 29, 31, 17, 39 }; int x1 = 61; int[] p2 = { 83, 43 }; int[] y = { 18, 37, 43, 60, 82, int y1 = 37; int[] p3 = { 69, 60 }; 73, 82, 60, 43, 37 }; int x2 = 83; int[] p4 = { 71, 82 }; int y2 = 43; int[] p5 = { 50, 73 }; int x3 = 69; int[] p6 = { 29, 82 }; int y3 = 60; int[] p7 = { 31, 60 }; int x4 = 71; int[] p8 = { 17, 43 }; int y4 = 82; int[] p9 = { 39, 37 }; int x5 = 50; int y5 = 73; int x6 = 29; int y6 = 82; int x7 = 31; int y7 = 60; int x8 = 17; int y8 = 43; int x9 = 39; int y9 = 37; This example shows how to use the arrays within a program. The data for each vertex is accessed in sequence with a for structure. The syntax and usage of arrays is discussed in more detail in the following pages. int[] x = { 50, 61, 83, 69, 71, 50, 29, 31, 17, 39 }; 33-01 int[] y = { 18, 37, 43, 60, 82, 73, 82, 60, 43, 37 }; beginShape(); // Reads one array element every time through the for() for (int i = 0; i < x.length; i++) { vertex(x[i], y[i]); } endShape(CLOSE); 302 Data 4: Arrays Using arrays Arrays are declared similarly to other data types, but they are distinguished with brackets, [ and ]. When an array is declared, the type of data it stores must be specified. After the array is declared, the array must be created with the keyword “new.” This additional step allocates space in the computer’s memory to store the array’s data. After the array is created, the values can be assigned. There are different ways to declare, create, and assign arrays. In the following examples explaining these differences, an array with five elements is created and filled with the values 19, 40, 75, 76, and 90. Note the different way each method for creating and assigning elements of the array relates to setup(). int[] data; // Declare 33-02 void setup() { size(100, 100); data = new int; // Create data = 19; // Assign data = 40; data = 75; data = 76; data = 90; } int[] data = new int; // Declare, create 33-03 void setup() { size(100, 100); data = 19; // Assign data = 40; data = 75; data = 76; data = 90; } int[] data = { 19, 40, 75, 76, 90 }; // Declare, create, assign 33-04 void setup() { size(100, 100); } The previous three examples assume the arrays are used in a sketch with setup() and draw(). If arrays are not used with these functions, they can be created and assigned in the simpler ways shown in the following examples. 303 Data 4: Arrays int[] data; // Declare 33-05 data = new int; // Create data = 19; // Assign data = 40; data = 75; data = 76; data = 90; int[] data = new int; // Declare, create 33-06 data = 19; // Assign data = 40; data = 75; data = 76; data = 90; int[] data = { 19, 40, 75, 76, 90 }; // Declare, create, assign 33-07 The declare, create, and assign steps allow an array’s values to be read. An array element is accessed using the name of the variable followed by brackets around the position from which you are trying to read. int[] data = { 19, 40, 75, 76, 90 }; 33-08 line(data, 0, data, 100); line(data, 0, data, 100); line(data, 0, data, 100); line(data, 0, data, 100); line(data, 0, data, 100); Remember, the first element in the array is in the 0 position. If you try to access a member of the array that lies outside the array boundaries, your program will terminate and give an ArrayIndexOutOfBoundsException. int[] data = { 19, 40, 75, 76, 90 }; 33-09 println(data); // Prints 19 to the console println(data); // Prints 75 to the console println(data); // ERROR! The last element of the array is 4 The length field stores the number of elements in an array. This field is stored within the array and can be accessed with the dot operator (p. 107–108). The following example demonstrates how to utilize it. 304 Data 4: Arrays int[] data1 = { 19, 40, 75, 76, 90 }; 33-10 int[] data2 = { 19, 40 }; int[] data3 = new int; println(data1.length); // Prints "5" to the console println(data2.length); // Prints "2" to the console println(data3.length); // Prints "127" to the console Usually, a for structure is used to access array elements, especially with large arrays. The following example draws the same lines as code 33-08 but uses a for structure to iterate through every value in the array. int[] data = { 19, 40, 75, 76, 90 }; 33-11 for (int i = 0; i < data.length; i++) { line(data[i], 0, data[i], 100); } A for structure can also be used to put data inside an array—for instance, it can calculate a series of numbers and then assign each value to an array element. The following example stores the values from the sin() function in an array within setup() and then displays these values as the stroke values for lines within draw(). float[] sineWave = new float[width]; 33-12 for (int i = 0; i < width; i++) { // Fill the array with values from sin() float r = map(i, 0, width, 0, TWO_PI); sineWave[i] = abs(sin(r)); } for (int i = 0; i < sineWave.length; i++) { // Set the stroke values to numbers read from the array stroke(sineWave[i] * 255); line(i, 0, i, height); } Storing the coordinates of many elements is another way to use arrays to make a program easier to read and manage. In the following example, the x[] array stores the x-coordinate for each of the 12 elements in the array, and the speed[] array stores a rate corresponding to each. Writing this program without arrays would have required 24 separate variables. Instead, it’s written in a flexible way; simply changing the value assigned to numLines sets the number of elements drawn to the screen. 305 Data 4: Arrays int numLines = 12; 33-13 float[] x = new float[numLines]; float[] speed = new float[numLines]; float offset = 8; // Set space between lines void setup() { size(100, 100); smooth(); strokeWeight(10); for (int i = 0; i < numLines; i++) { x[i] = i; // Set initial position speed[i] = 0.1 + (i / offset); // Set initial speed } } void draw() { background(204); for (int i = 0; i < x.length; i++) { x[i] += speed[i]; // Update line position if (x[i] > (width + offset)) { // If off the right, x[i] = -offset * 2; // return to the left } float y = i * offset; // Set y-coordinate for line line(x[i], y, x[i]+offset, y+offset); // Draw line } } Storing mouse data Arrays are often used to store data from the mouse. The pmouseX and pmouseY variables store the cursor coordinates from the previous frame, but there’s no built-in way to access the cursor values from earlier frames. At every frame, the mouseX, mouseY, pmouseX, and pmouseY variables are replaced with new numbers and their previous numbers are discarded. Creating an array is the easiest way to store the history of these values. In the following example, the most recent 100 values from mouseY are stored in a array and displayed on screen as a line from the left to the right edge of the screen. At each frame, the values in the array are shifted to the right and the newest value is added to the beginning. 306 Data 4: Arrays int[] y; 33-14 void setup() { size(100, 100); y = new int[width]; } void draw() { background(204); // Shift the values to the right for (int i = y.length-1; i > 0; i--) { y[i] = y[i-1]; } // Add new values to the beginning y = constrain(mouseY, 0, height-1); // Display each pair of values as a line for (int i = 1; i < y.length; i++) { line(i, y[i], i-1, y[i-1]); } } Apply the same code simultaneously to the mouseX and mouseY values to store the position of the cursor. Displaying these values each frame creates a trail behind the cursor. int num = 50; 33-15 int[] x = new int[num]; int[] y = new int[num]; void setup() { size(100, 100); noStroke(); smooth(); fill(255, 102); } void draw() { background(0); // Shift the values to the right for (int i = num-1; i > 0; i--) { x[i] = x[i-1]; y[i] = y[i-1]; } 307 Data 4: Arrays // Add the new values to the beginning of the array 33-15 cont. x = mouseX; y = mouseY; // Draw the circles for (int i = 0; i < num; i++) { ellipse(x[i], y[i], i/2.0, i/2.0); } } The following example produces the same result as the previous one but uses a more efficient technique. Instead of sorting the array elements in each frame, the program writes the new data to the next available array position. The elements in the array remain in the same position once they are written, but they are read in a different order each frame. Reading begins at the location of the oldest data element and continues to the end of the array. At the end of the array, the % operator (p. 45) is used to wrap back to the beginning. This technique is especially useful with larger arrays, to avoid unnecessary copying of data that can slow down a program. int num = 50; 33-16 int[] x = new int[num]; int[] y = new int[num]; int indexPosition = 0; void setup() { size(100, 100); noStroke(); smooth(); fill(255, 102); } void draw() { background(0); x[indexPosition] = mouseX; y[indexPosition] = mouseY; // Cycle between 0 and the number of elements indexPosition = (indexPosition + 1) % num; for (int i = 0; i < num; i++) { // Set the array position to read int pos = (indexPosition + i) % num; float radius = (num-i) / 2.0; ellipse(x[pos], y[pos], radius, radius); } } 308 Data 4: Arrays Array functions Processing provides a group of functions that assist in managing array data. Only four of these functions are introduced here, but more are explained in the Extended Reference included with the software and available online at www.processing.org/reference. The append() function expands an array by one element, adds data to the new position, and returns the new array: String[] trees = { "ash", "oak" }; 33-17 append(trees, "maple"); // INCORRECT! Does not change the array print(trees); // Prints "ash oak" println(); trees = append(trees, "maple"); // Add "maple" to the end print(trees); // Prints "ash oak maple" println(); // Add "beech" to the end of the trees array, and creates a new // array to store the change String[] moretrees = append(trees, "beech"); print(moretrees); // Prints "ash oak maple beech" The shorten() function decreases an array by one element by removing the last element and returns the shortened array: String[] trees = { "lychee", "coconut", "fig"}; 33-18 trees = shorten(trees); // Remove the last element from the array print(trees); // Prints "lychee coconut" println(); trees = shorten(trees); // Remove the last element from the array print(trees); // Prints "lychee" The expand() function increases the size of an array. It can expand to a specific size, or if no size is specified, the array’s length will be doubled. If an array needs to have many additional elements, it’s faster to use expand() to double the size than to use append() to continually add one value. The following example saves a new mouseX value to an array every frame. When the array becomes full, the size of the array is doubled and new mouseX values proceed to fill the enlarged array. int[] x = new int; // Array to store x-coordinates 33-19 int count; // Store the number of array positions void setup() { size(100, 100); } 309 Data 4: Arrays void draw() { 33-19 cont. x[count] = mouseX; // Assign new x-coordinate to the array count++; // Increment the counter if (count == x.length) { // If the x array is full, x = expand(x); // double the size of x println(x.length); // Write the new size to the console } } Array values cannot be copied with the assignment operator because they are objects. The most common way to copy elements from one array to another is to use special functions or to copy each element individually within a for structure. The arraycopy() function is the most efficient way to copy the entire contents of one array to another. The data is copied from the array used as the first parameter to the array used as the second parameter. Both arrays must be the same length for it to work in the configuration shown below. String[] north = { "OH", "IN", "MI" }; 33-20 String[] south = { "GA", "FL", "NC" }; arraycopy(north, south); // Copy from north array to south array print(south); // Prints "OH IN MI" println(); String[] east = { "MA", "NY", "RI" }; String[] west = new String[east.length]; // Create a new array arraycopy(east, west); // Copy from east array to west array print(west); // Prints "MA NY RI" New functions can be written to perform operations on arrays, but arrays behave differently than data types such as int and char. When an array is used as a parameter to a function, the address (location in memory) of the array is transferred into the function instead of the actual data. No new array is created, and changes made within the function affect the array used as the parameter. In the following example, the data[] array is used as the parameter to halve(). The address of data[] is passed to the d[] array in the halve() function. Because the address of d[] and data[] is the same, they both affect the same data. When changes are made to d[] on line 14, these changes are made to the values in data[]. The draw() function is not used because the calculation is made only once and nothing is drawn to the diplay window. 310 Data 4: Arrays float[] data = { 19.0, 40.0, 75.0, 76.0, 90.0 }; 33-21 void setup() { halve(data); println(data); // Prints "9.5" println(data); // Prints "20.0" println(data); // Prints "37.5" println(data); // Prints "38.0" println(data); // Prints "45.0" } void halve(float[] d) { for (int i = 0; i < d.length; i++) { // For each array element, d[i] = d[i] / 2.0; // divide the value by 2 } } Changing array data within a function without modifying the original array requires some additional lines of code. In the following example, the array is passed into the function as a parameter, a new array is made, the values from the original array are copied in the new array, changes are made to the new array, and finally the modified array is returned. Like the previous example, the draw() function is not used because nothing is drawn to the display window and the calculation is made only once. float[] data = { 19.0, 40.0, 75.0, 76.0, 90.0 }; 33-22 float[] halfData; void setup() { halfData = halve(data); // Run the halve() function println(data + ", " + halfData); // Prints "19.0, 9.5" println(data + ", " + halfData); // Prints "40.0, 20.0" println(data + ", " + halfData); // Prints "75.0, 37.5" println(data + ", " + halfData); // Prints "76.0, 38.0" println(data + ", " + halfData); // Prints "90.0, 45.0" } float[] halve(float[] d) { float[] numbers = new float[d.length]; // Create a new array arraycopy(d, numbers); for (int i = 0; i < numbers.length; i++) { // For each element, numbers[i] = numbers[i] / 2; // divide the value by 2 } return numbers; // Return the new array } 311 Data 4: Arrays Two-dimensional arrays Data can also be stored and retrieved from arrays with more than one dimension. Using the example from the beginning of this unit, the data points for the star are put into a 2D array instead of two 1D arrays: points 50 61 83 69 71 50 29 31 17 39 0 18 1 37 2 43 3 60 4 82 5 73 6 82 7 60 8 43 9 37 A 2D array is essentially a list of 1D arrays. It must be declared, then created, and then the values can be assigned just as in a 1D array. The following syntax converts this array to code: int[][] points = { {50,18}, {61,37}, {83,43}, {69,60}, {71,82}, 33-23 {50,73}, {29,82}, {31,60}, {17,43}, {39,37} }; println(points); // Prints 71 println(points); // Prints 82 println(points); // ERROR! This element is outside the array println(points); // Prints 50 println(points); // Prints 37 This program shows how it all fits together. int[][] points = { {50,18}, {61,37}, {83,43}, {69,60}, 33-24 {71,82}, {50,73}, {29,82}, {31,60}, {17,43}, {39,37} }; void setup() { size(100, 100); fill(0); smooth(); } void draw() { background(204); translate(mouseX - 50, mouseY - 50); beginShape(); for (int i = 0; i < points.length; i++) { vertex(points[i], points[i]); } endShape(); } 312 Data 4: Arrays It’s possible to continue and make 3D and 4D arrays by extrapolating these techniques. However, multidimensional arrays can be confusing, and it’s often a better idea to maintain multiple 1D or 2D arrays. Exercises 1. Create an array to store the y-coordinates of a sequence of shapes. Draw each shape inside draw() and use the values from the array to set the y-coordinate of each. 2. Write a function to multiply the values from two arrays together and return the result as a new array. Print the results to the console. 3. Use a 2D array to store the coordinates for a shape of your own invention. Use a for structure to draw the shape to the display window. 313 Data 4: Arrays Image 2: Animation This unit introduces techniques for displaying sequences of images successively, creating animation. Animation occurs when a series of images, each slightly different, are presented in quick succession. A diverse medium with a century of history, animation has progressed from the initial experiments of Winsor McCay to the commercial and realistic innovations of early Walt Disney studio productions, to the experimental films by such animators as Lotte Reiniger and James Whitney in the mid-twentieth century. The high volume of animated special effects in live-action film and the deluge of animated children’s films are changing the role of animation in popular culture. There’s a long history of using software to extend the boundaries of animation. Some of the first computer graphics were presented as animation on film during the 1960s. Because of the cost and expertise required to make these films, they emerged from high-profile research facilities such as Bell Laboratories and IBM’s Scientific Center. Kenneth C. Knowlton, then a researcher at Bell Labs, is an important protagonist in the story of early computer animation. He worked separately with artists Stan VanDerBeek and Lillian Schwartz to produce some of the first films made using computer graphics. VanDerBeek and Knowlton’s Poem Field films, produced throughout the 1960s, utilized Knowlton’s BEFLIX code and punch cards to produce permutations of visual micropatterns. Schwartz and Knowlton’s Pixillation (1970) featured a wide range of effects made by contrasting geometric forms with organic motion. John Whitney worked in collaboration with Jack Citron at IBM to make a number of films including the innovative Permutations. This film expresses Whitney’s ideas about relationships to music and abstract form by permuting an array of dots into infinite kinetic patterns. Other artists working with computer animation around this time were Larry Cuba, Peter Foldes, and John Stehura. The paths of contemporary animation and software development often overlap. The 3D visualization of the Death Star in Star Wars (1977) was one of the first uses of computer-generated animation in a feature film. Custom software was written to produce a wire-frame fly-through of the massive ship. Interest in computer animation briefly peaked with Disney’s Tron in 1982, but soon receded due to the film’s commercial failure. The industry gradually rebuilt itself into its current role as a major force in contemporary film. Pixar, the hugely successful animation studio that produced Toy Story and The Incredibles, operated for many years as a software development company. Pixar’s RenderMan software (1989) enabled the rendering of 3D computer graphics as photorealistic images. RenderMan became an industry standard and Pixar continues to develop custom software for each film. The success of the company’s films reflects its successful marriage of technical virtuosity and masterful storytelling. Creating unique and experimental animation with software is no longer restricted 315 to research labs and film studios. The Internet has become a vast repository for experimental software animation. In the late 1990s, Turux was created by Lia and Dextro. This online collection of intricate animated images and sounds synthesizes a digital glitch aesthetic with organic qualities. The drawings continually change and sometimes respond to viewer input. James Paterson (p. 165), a Canadian animator, develops Presstube.com, where he produces thousands of drawings, typically organizing tight loops of elements that materialize and dissipate. This technique allows him to arrange these loops in nearly any order while maintaining a fluid progression of growth and decay. The sequences of David Crawford’s Stop Motion Studies are series of photographs—typically of people in subways in different cities around the world. These photographs are taken in quick succession; presented again in a nonlinear sequence of animated frames, they reveal an incredibly complex and subtle range of human gesture. Sequential images Before a series of images can be presented sequentially in a program, all the images must first be loaded. The image variables should be declared outside of setup() and draw() and then assigned within setup(). The following program loads these twelve images from James Paterson...... and then draws them to the display window in numeric order. frame=0 int numFrames = 12; // The number of animation frames 34-01 int frame = 0; // The frame to display PImage[] images = new PImage[numFrames]; // Image array frame=3 void setup() { size(100, 100); frameRate(30); // Maximum 30 frames per second images = loadImage("ani-000.gif"); frame=11 images = loadImage("ani-001.gif"); images = loadImage("ani-002.gif"); images = loadImage("ani-003.gif"); images = loadImage("ani-004.gif"); images = loadImage("ani-005.gif"); images = loadImage("ani-006.gif"); images = loadImage("ani-007.gif"); images = loadImage("ani-008.gif"); images = loadImage("ani-009.gif"); 316 Image 2: Animation images = loadImage("ani-010.gif"); 34-01 cont. images = loadImage("ani-011.gif"); } void draw() { frame++; if (frame == numFrames) { frame = 0; } image(images[frame], 0, 0); } The next example shows an alternative way of loading images by utilizing a for structure. These lines of code can load between 1 and 999 images by changing the value of the numFrames variable. This shortens the code that flips through each image and returns to the first image at the end of the animation. The nf() function (p. 422) on line 11 is used to format the name of the image to be loaded. The names of frames with small numbers are prefaced with zeros so that the images remain in the correct sequence in their folder. For example, instead of ani-1.gif, the file is named ani-001.gif. The nf() function pads the small numbers created in a for structure with zeros on the left of the number, so 1 becomes 001, 2 becomes 002, etc. The % operator (p. 45) on line 18 uses the frameCount variable to make the frame variable increase by 1 each frame and return to 0 once it exceeds 11. int numFrames = 12; // The number of animation frames 34-02 PImage[] images = new PImage[numFrames]; // Image array void setup() { size(100, 100); frameRate(30); // Maximum 30 frames per second // Automate the image loading procedure. Numbers less than 100 // need an extra zero added to fit the names of the files. for (int i = 0; i < images.length; i++) { // Construct the name of the image to load String imageName = "ani-" + nf(i, 3) + ".gif"; images[i] = loadImage(imageName); } } void draw() { // Calculate the frame to display, use % to cycle through frames int frame = frameCount % numFrames; image(images[frame], 0, 0); } 317 Image 2: Animation Displaying the images in random order and for different amounts of time enhances the visual interest of a few frames of animation. Replaying a sequence at irregular intervals, in a random order with random timing, can give the appearance of more different frames than actually exist. int numFrames = 5; // The number of animation frames 34-03 PImage[] images = new PImage[numFrames]; void setup() { size(100, 100); for (int i = 0; i < images.length; i++) { String imageName = "ani-" + nf(i, 3) + ".gif"; images[i] = loadImage(imageName); } } void draw() { int frame = int(random(0, numFrames)); // The frame to display image(images[frame], 0, 0); frameRate(random(1, 60.0)); } There are many ways to control the speed at which an animation plays. The frameRate() function provides the simplest way. Place the frameRate() function in setup() as seen in the previous example. Use this function to ensure that the software will run at the same speed on other machines. If you want other elements to move independently of the sequential images, set up a timer and advance the frame only when the timer value grows larger than a predefined value. In the following example, the animation playing in the top of the window is updated each frame and the speed is controlled by the parameter to frameRate(). The animation in the bottom frame is updated only twice a second; the timer checks the milliseconds since the last update and changes the frame only if 500 milliseconds (half a second) have elapsed. int numFrames = 12; // The number of animation frames 34-04 int topFrame = 0; // The top frame to display int bottomFrame = 0; // The bottom frame to display PImage[] images = new PImage[numFrames]; int lastTime = 0; void setup() { size(100, 100); frameRate(30); for (int i = 0; i < images.length; i++) { 318 Image 2: Animation String imageName = "ani-" + nf(i, 3) + ".gif"; 34-04 cont. images[i] = loadImage(imageName); } } void draw() { topFrame = (topFrame + 1) % numFrames; image(images[topFrame], 0, 0); if ((millis() - lastTime) > 500) { bottomFrame = (bottomFrame + 1) % numFrames; lastTime = millis(); } image(images[bottomFrame], 0, 50); } Images in motion Moving one image, rather than presenting a sequence, is another approach to animating images. The same techniques for creating movement presented in Motion 1 (p. 279) apply to images. The following example moves an image from left to right, returning it to the left when it disappears off the edge of the screen. PImage img; 34-05 float x; void setup() { size(100, 100); img = loadImage("PT-Shifty-0020.gif"); } void draw() { background(204); x += 0.5; if (x > width) { x = -width; } image(img, x, 0); } The transformation functions also apply to images. They can be translated, rotated, and scaled over time to produce motion. In this example, an image is drawn to the center of the display window and rotated slowly around its center. 319 Image 2: Animation PImage img; 34-06 float angle; void setup() { size(100, 100); img = loadImage("PT-Shifty-0023.gif"); } void draw() { background(204); angle += 0.01; translate(50, 50); rotate(angle); image(img, -100, -100); } Images can also be animated by changing their drawing attributes. In this example, the opacity of an image is increased so that it is brought into view over the background. PImage img; 34-07 float opacity = 0; // Set opacity to the minimum void setup() { size(100, 100); img = loadImage("PT-Teddy-0017.gif"); } void draw() { background(0); if (opacity < 255) { // When less than the maximum, opacity += 0.5; // increase opacity } tint(255, opacity); image(img, -25, -75); } Exercises 1. Load a sequence of related images into an array and use them to create a linear animation. 2. Modify the program for exercise 1 to present each frame of animation at a different rate and in a different sequence. 3. Animate an image by changing more than one of its attributes (e.g., size, position, tint). 320 Image 2: Animation Image 3: Pixels This unit introduces techniques for getting and setting the values for single pixels and groups of pixels. Syntax introduced: get(), set() Image 1 (p. 95) defined an image as a rectangular grid of pixels in which each element is a number specifying a color. Because the screen itself is an image, its individual pixels are also defined as numbers. The color values of individual pixels can be read and changed. Reading pixels When a Processing program starts, the display window opens at the dimension requested in size(). The program gains control over that area of the screen and sets the color value for each pixel. The display window communicates with the operating system, so when the window moves, it takes control of its new area of the screen and gives control of its previous space to the operating system. The get() function can read the color of any pixel in the display window. It can also grab the whole display window or a section of it. There are three versions of this function, one for each use. get() get(x, y) get(x, y, width, height) If get() is used without parameters, a copy of the entire display window is returned as a PImage. The version with two parameters returns the color value of a single pixel at the location specified by the x and y parameters. A rectangular area of the display window is returned if the additional width and height parameters are used. If get() grabs the entire display window or a section of the window, the returned data must be assigned to a variable of type PImage. These images can be redrawn to the screen in different positions and resized. strokeWeight(8); 35-01 line(0, 0, width, height); line(0, height, width, 0); PImage cross = get(); // Get the entire window image(cross, 0, 50); // Draw the image in a new position 321 smooth(); 35-02 strokeWeight(8); line(0, 0, width, height); line(0, height, width, 0); noStroke(); ellipse(18, 50, 16, 16); PImage cross = get(); // Get the entire window image(cross, 42, 30, 40, 40); // Resize to 40 x 40 pixels strokeWeight(8); 35-03 line(0, 0, width, height); line(0, height, width, 0); PImage slice = get(0, 0, 20, 100); // Get window section set(18, 0, slice); set(50, 0, slice); The get() function always grabs the pixels in the display window in the same way, regardless of what is drawn to the window. In the previous examples, the get() function grabbed the images of the lines after they had been converted to pixels for display on screen. The following example is the same as code 35-01, but a photograph is first loaded into the display window, so get() grabs that image. PImage trees; 35-04 trees = loadImage("topanga.jpg"); image(trees, 0, 0); PImage crop = get(); // Get the entire window image(crop, 0, 50); // Draw the image in a new position When used with an x- and y-coordinate, the get() function returns values that should be assigned to a variable of the color data type. These values can be used to set the color of other pixels or can serve as parameters to fill() or stroke(). In the following example, the color of one pixel is used to set the color of the rectangle. PImage trees; 35-05 trees = loadImage("topanga.jpg"); noStroke(); image(trees, 0, 0); color c = get(20, 30); // Get color at (20, 30) fill(c); rect(20, 30, 40, 40); The mouse values can be used as the parameters to the get() function. This allows the cursor to select colors from the display window. In the following example, the pixel beneath the cursor is read and defines the fill value for the rectangle on the right. 322 Image 3: Pixels PImage trees; 35-06 void setup() { size(100, 100); noStroke(); trees = loadImage("topangaCrop.jpg"); } void draw() { image(trees, 0, 0); color c = get(mouseX, mouseY); fill(c); rect(50, 0, 50, 100); } The get() function can be used within a for structure to grab many pixels or groups of pixels. In the following example, the values from each row of pixels in the image are used to set the values for the lines on the right. Run this code and move the mouse up and down to see the relation between the image on the left and the bands of color on the right. PImage trees; 35-07 int y = 0; void setup() { size(100, 100); trees = loadImage("topangaCrop.jpg"); } void draw() { image(trees, 0, 0); y = constrain(mouseY, 0, 99); for (int i = 0; i < 49; i++) { color c = get(i, y); stroke(c); line(i+50, 0, i+50, 100); } stroke(255); line(0, y, 49, y); } 323 Image 3: Pixels Every PImage variable has its own get() to grab pixels from the image. This allows pixels to be grabbed from an image independently of the pixels in the display window. Because a PImage is an object, the get() function is accessed with the name of the image and the dot operator. In the following example, the pixels are grabbed directly from the image and not from the screen, so white lines drawn to the display window are not grabbed. PImage trees; 35-08 trees = loadImage("topanga.jpg"); stroke(255); strokeWeight(12); image(trees, 0, 0); line(0, 0, width, height); line(0, height, width, 0); PImage treesCrop = trees.get(20, 20, 60, 60); image(treesCrop, 20, 20); Writing pixels The pixels in Processing’s display window can be written directly with the set() function. There are two versions of this function, each with three parameters. set(x, y, color) set(x, y, image) When the third parameter is a color, set() changes the color of any pixel in the display window. When the third parameter is an image, set() writes an image at the coordinates specified by the x and y parameters. color black = color(0); 35-09 set(20, 80, black); set(20, 81, black); set(20, 82, black); set(20, 83, black); for (int i = 0; i < 55; i++) { 35-10 for (int j = 0; j < 55; j++) { color c = color((i+j) * 1.8); set(30+i, 20+j, c); } } 324 Image 3: Pixels The set() function can write an image to the display window at any location. Using set() to draw an image is faster than using the image() function because the pixels are copied directly. However, images drawn with set() cannot be resized or tinted, and they are not affected by the transformation functions. PImage trees; 35-11 void setup() { size(100, 100); trees = loadImage("topangaCrop.jpg"); } void draw() { int x = constrain(mouseX, 0, 50); set(x, 0, trees); } Every PImage variable has its own set() function to write pixels directly to the image. This allows pixels to be written to an image independently of the pixels in the display window. Because a PImage is an object, the set() function is run with the name of the image and the dot operator. In the following example, four white pixels are set into the image variable trees, and the image is then drawn to the display window. PImage trees; 35-12 trees = loadImage("topangaCrop.jpg"); background(0); color white = color(255); trees.set(0, 50, white); trees.set(1, 50, white); trees.set(2, 50, white); trees.set(3, 50, white); image(trees, 20, 0); Exercises 1. Load an image and use get() to create a collage by overlaying different sections of the same image. 2. Load an image and use mouseX and mouseY to read the value of the pixel beneath the cursor. Use this value to change some aspect of the image. 3. Draw a shape in the display window. Copy a section of the window to another by using get() and set() within a for structure. 325 Image 3: Pixels Typography 2: Motion This unit introduces typography in motion. Despite the potential for kinetic type within film, animated typography didn’t begin to flourish until the 1950s with the film title work of Saul Bass, Maurice Binder, and Pablo Ferro. These designers and their peers set a high standard with their kinetic title sequences for films such as North by Northwest (1959), Dr. No (1962), and Bullitt (1968). They explored the evocative power of kinetic letterforms to set a mood and express additional layers of meaning in relation to written language. In subsequent years the design of film titles has matured and been augmented by experimental typography for television and the Internet. Software has played a large role in extending the possibilities of type in motion. The Visual Language Workshop (VLW), founded by Muriel Cooper at the MIT Media Lab in 1985, applied rigorous design thinking to the presentation of kinetic and spatial typography. Researchers including Suguru Ishizaki, Lisa Strausfeld, Yin Yin Wong, and David Small produced progressive typographic explorations ranging from the expression of animated phrases to the navigation of vast typographic landscapes. Because programs did not exist to perform these experiments, the researchers developed custom software to realize their ideas. While at the VLW, David Small created the Talmud Project to explore reading in a unique way. It displays the Talmud and related commentaries on the screen simultaneously. A dial controls the legibility of each text through blurring and fading, while keeping each source in context. Peter Cho, building on the explorations of the VLW, wrote software that continued to push the boundaries of expressive kinetic typography. His Letterscapes website presents every letter of the Roman alphabet as a character with a unique motion and response in relation to its form. With his Takeluma project, Cho went even further by inventing a kinetic alphabet for visualizing speech. In the last decade, many software tools have been released that facilitate working with kinetic typography. Adobe’s Flash software has provided new freedom for working with type on the Web, and Adobe After Effects has supported more sophisticated typography in film and television. This unit introduces techniques for exploring kinetic typography with code. Words in motion For typography to move, the program must run continuously, and therefore it requires a draw() function. Using typography within draw() requires three steps. First, a PFont variable must be declared outside of setup() and draw(). Next, the font should be loaded and set within setup(). Finally, the font can be used to place characters on the screen inside draw() with the text() function. 327 The following examples use a font named Eureka. To run these examples, you will need to use the “Create Font” tool to create your own font. Change the name of the parameter to loadFont() to the name of the font that you created. PFont font; 36-01 String s = "Pea"; void setup() { size(100, 100); font = loadFont("Eureka-48.vlw"); textFont(font); fill(0); } void draw() { background(204); text(s, 22, 20); } To put type into motion, simply draw it at a different position each frame. Words can move in an orderly fashion if their position is changed slightly each frame, and they can move without apparent order if placed in an arbitrary position each frame. PFont font; 36-02 float x1 = 0; float x2 = 100; void setup() { size(100, 100); font = loadFont("Eureka-48.vlw"); textFont(font); fill(0); } void draw() { background(204); text("Right", x1, 50); text("Left", x2, 100); x1 += 1.0; if (x1 > 100) { x1 = -150; } x2 -= 0.8; if (x2 < -150) { x2 = 100; } } 328 Typography 2: Motion PFont font; 36-03 void setup() { size(100, 100); font = loadFont("Eureka-48.vlw"); textFont(font); noStroke(); } void draw() { fill(204, 24); rect(0, 0, width, height); fill(0); text("flicker", random(-100, 100), random(-20, 120)); } Typography need not move in order to change over time. More subtle transformations, such as changes in the gray value or transparency of the text, can be made by changing the value of a variable within draw(). PFont font; 36-04 int opacity = 0; int direction = 1; void setup() { size(100, 100); font = loadFont("EurekaSmallCaps-36.vlw"); textFont(font); } void draw() { background(204); opacity += 2 * direction; if ((opacity < 0) || (opacity > 255)) { direction = -direction; } fill(0, opacity); text("fade", 4, 60); } 329 Typography 2: Motion Applying the transformations translate(), scale(), and rotate() can also create motion. PFont font; 36-05 String s = "VERTIGO"; float angle = 0.0; void setup() { size(100, 100); font = loadFont("Eureka-90.vlw"); textFont(font, 24); fill(0); } void draw() { background(204); angle += 0.02; pushMatrix(); translate(33, 50); scale((cos(angle/4.0) + 1.2) * 2.0); rotate(angle); text(s, 0, 0); popMatrix(); } Another technique, called rapid serial visual presentation (RSVP), displays words on the screen sequentially and provides a fundamentally different way to think about reading. Run this program and change the frame rate to see how it affects the process of reading. To store the words within one variable called words, this example uses a data element called an Array (explained in Data 4, p. 301). PFont font; 36-06 String[] words = { "Three", "strikes", "and", "you're", "out...", " " }; int whichWord = 0; void setup() { size(100, 100); font = loadFont("Eureka-32.vlw"); textFont(font); textAlign(CENTER); frameRate(4); } 330 Typography 2: Motion void draw() { 36-06 cont. background(204); whichWord++; if (whichWord == words.length) { whichWord = 0; } text(words[whichWord], width/2, 55); } Letters in motion Individually animated letters offer more flexibility than entire moving words. Building words letter by letter, each with a different movement or speed, can convey a particular meaning or tone. Working in this way requires more patience and often longer programs, but the results can be more rewarding because of the increased possibilities. // The size of each letter grows and shrinks from 36-07 // left to right PFont font; String s = "AREA"; float angle = 0.0; void setup() { size(100, 100); font = loadFont("EurekaMono-48.vlw"); textFont(font); fill(0); } void draw() { background(204); angle += 0.1; for (int i = 0; i < s.length(); i++) { float c = sin(angle + i/PI); textSize((c + 1.0) * 32 + 10); text(s.charAt(i), i*26, 60); } } 331 Typography 2: Motion // Each letter enters from the bottom in sequence and 36-08 // stops when it reaches its destination PFont font; String word = "rise"; char[] letters; float[] y; // Y-coordinate for each letter int currentLetter = 0; // Letter currently in motion void setup() { size(100, 100); font = loadFont("EurekaSmallCaps-36.vlw"); textFont(font); letters = word.toCharArray(); y = new float[letters.length]; for (int i = 0; i < letters.length; i++) { y[i] = 130; // Position off the screen } fill(0); } void draw() { background(204); if (y[currentLetter] > 35) { y[currentLetter] -= 3; // Move current letter up } else { if (currentLetter < letters.length-1) { currentLetter++; // Switch to next letter } } // Calculate x to center the word on screen float x = (width - textWidth(word)) / 2; for (int i = 0; i < letters.length; i++) { text(letters[i], x, y[i]); x += textWidth(letters[i]); } } Exercises 1. Select a noun and an adjective. Animate the noun to reveal the adjective. 2. Use the transformation functions to animate a short phrase. 3. Select a verb and animate each letter of the word to convey its meaning. 332 Typography 2: Motion Typography 3: Response This unit introduces typography that responds to input from the mouse and keyboard. Many people spend hours a day inputting letters into computers, but this action is very constrained. What features could be added to a text editor to make it more responsive to the typist? For example, the speed of typing could decrease the size of the letters, or a long pause in typing could add many spaces, mimicking a person’s pause while speaking. What if the keyboard could register how hard a person is typing (the way a piano plays a soft note when a key is pressed gently) and could automatically assign attributes such as italics for soft presses and bold for forceful presses? These analogies suggest how conservatively current software treats typography and typing. Many artists and designers are fascinated with type and have created unique ways of exploring letterforms with the mouse, keyboard, and more exotic input devices. A minimal yet engaging example is John Maeda’s Type, Tap, Write software, created in 1998 as an homage to manual typewriters. This software uses the keyboard as the input to a black-and-white screen representation of a keyboard. Pressing the number keys cause the software to cycle through different modes, each revealing a playful interpretation of keyboard data. Casey Reas and Golan Levin’s Dakadaka software from 2000, named after the sounds made while hitting a keyboard, explores the percussive and rhythmic aspects of typing. Input from the keyboard is translated into four different positional abstract alphabets that change according to the speed of typing and the order of the pressed keys. In Jeffrey Shaw and Dirk Groeneveld’s The Legible City (1989–91), buildings are replaced with three-dimensional letters to create a city of typography that conforms to the streets of a real place. In the Manhattan version, for instance, texts from the mayor, a taxi driver, and Frank Lloyd Wright comprise the city. The image is presented on a projection screen, and the user navigates by pedaling and steering a stationary bicycle situated in front of the projected image. Projects such as these demonstrate that software presents an extraordinary opportunity to extend the way we read and write. Responsive words Typographic elements can be assigned behaviors that define a personality in relation to the mouse or keyboard. A word can express aggression by moving quickly toward the mouse, or one moving away slowly can express timidity. 333 // The word "avoid" stays away from the mouse because its 37-01 // position is set to the inverse of the cursor position PFont f; void setup() { size(100, 100); f = loadFont("Eureka-24.vlw"); textFont(f); textAlign(CENTER); fill(0); } void draw() { background(204); text("avoid", width-mouseX, height-mouseY); } // The word "tickle" jitters when the cursor hovers over 37-02 PFont f; float x = 33; // X-coordinate of text float y = 60; // Y-coordinate of text void setup() { size(100, 100); f = loadFont("Eureka-24.vlw"); textFont(f); noStroke(); } void draw() { fill(204, 120); rect(0, 0, width, height); fill(0); // If the cursor is over the text, change the position if ((mouseX >= x) && (mouseX = y-24) && (mouseY width) { x[i] = 0; y[i] = random(height); } point(x[i], y[i]); } } Dynamic color palettes One of the most important concepts in working with color is relativity. When one color is positioned next to another, they both appear to change. If a color is to appear the same in a new juxtaposition, it often must be physically different (defined with different numbers). This is important to consider when working with color in software, since elements are often moving and changing colors. For example, placing these five colors... A B C D E... in a different order changes their appearance: ABCDE CADBE The phenomenon of color relativity can be extended in software by linking colors’ relations an