Guzdial, Mark Ericson, Barbara - Introduction To Computing and Programming With Java - A Multimedia Approach PDF
Guzdial, Mark Ericson, Barbara - Introduction To Computing and Programming With Java - A Multimedia Approach PDF
main
2005/1/11
page i
i
main
2005/1/11
page ii
i
ii
main
2005/1/11
page iii
i
iii
main
2005/1/11
page iv
i
Contents
Contents
iv
Preface
Introduction
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
8
13
15
16
17
17
2 Introduction to Java
2.1 Java . . . . . . . . . . . . . . . . . . . . . .
2.1.1 History of Java . . . . . . . . . . . .
2.1.2 Introduction to Objects and Classes
2.2 Introduction to DrJava . . . . . . . . . . . .
2.2.1 Starting DrJava . . . . . . . . . . .
2.3 Java Basics . . . . . . . . . . . . . . . . . .
2.3.1 Math Operators . . . . . . . . . . .
2.3.2 Printing the Result of a Statement .
2.3.3 Types in Math Expressions . . . . .
2.3.4 Casting . . . . . . . . . . . . . . . .
2.3.5 Relational Operators . . . . . . . . .
2.3.6 Strings . . . . . . . . . . . . . . . . .
2.4 Variables . . . . . . . . . . . . . . . . . . .
2.4.1 Declaring Variables . . . . . . . . . .
2.4.2 Using Variables in Calculations . . .
2.4.3 Memory Maps of Variables . . . . .
2.4.4 Object Variables . . . . . . . . . . .
2.4.5 Reusing Variables . . . . . . . . . .
2.4.6 Multiple References to an Object . .
2.5 Concepts Summary . . . . . . . . . . . . . .
2.5.1 Statements . . . . . . . . . . . . . .
2.5.2 Relational Operators . . . . . . . . .
2.5.3 Types . . . . . . . . . . . . . . . . .
2.5.4 Casting . . . . . . . . . . . . . . . .
2.5.5 Variables . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
19
20
21
21
24
24
24
27
27
27
29
31
31
31
32
34
36
37
39
39
40
40
40
41
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
iv
main
2005/1/11
page v
i
3 Introduction to Programming
3.1 Programming is About Naming . . . . . . . . .
3.2 Files and their Names . . . . . . . . . . . . . .
3.3 Class and Object Methods . . . . . . . . . . . .
3.3.1 Invoking Class Methods . . . . . . . . .
3.3.2 Invoking Object Methods . . . . . . . .
3.4 Working with Turtles . . . . . . . . . . . . . . .
3.4.1 Defining Classes . . . . . . . . . . . . .
3.4.2 Creating Objects . . . . . . . . . . . . .
3.4.3 Sending Messages to Objects . . . . . .
3.4.4 Objects Control Their State . . . . . . .
3.4.5 Additional Turtle Capabilities . . . . . .
3.5 Creating Methods . . . . . . . . . . . . . . . .
3.5.1 Methods that Take Input . . . . . . . .
3.6 Working with Media . . . . . . . . . . . . . . .
3.6.1 Creating a Picture Object . . . . . . . .
3.6.2 Showing a Picture . . . . . . . . . . . .
3.6.3 Variable Substitution . . . . . . . . . .
3.6.4 Object references . . . . . . . . . . . . .
3.6.5 Playing a Sound . . . . . . . . . . . . .
3.6.6 Naming your Media (and other Values)
3.6.7 Naming the Result of a Method . . . . .
3.7 Concepts Summary . . . . . . . . . . . . . . . .
3.7.1 Invoking Object Methods . . . . . . . .
3.7.2 Invoking Class Methods . . . . . . . . .
3.7.3 Creating Objects . . . . . . . . . . . . .
3.7.4 Creating new Methods . . . . . . . . . .
II
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Pictures
43
43
45
46
46
47
48
48
48
51
52
53
56
61
64
64
66
68
70
71
72
72
74
74
74
74
75
79
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
Blue)
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
80
80
84
88
94
95
96
100
113
115
118
122
124
125
127
129
main
2005/1/11
page vi
i
vi
4.4.1
4.4.2
4.4.3
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
. . . .
Loop
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
136
136
137
139
147
148
154
159
162
166
171
171
172
173
174
176
185
186
187
187
192
194
194
195
195
196
7 Drawing
7.1 Drawing on Images Using the Graphics Class
7.1.1 Drawing with Graphics methods . . .
7.1.2 Vector and bitmap representations . .
7.1.3 Drawing Text (Strings) . . . . . . . .
224
224
227
233
235
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
198
206
207
210
214
218
218
219
220
main
2005/1/11
page vii
i
vii
7.2
7.3
7.4
III
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Sounds
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
239
242
242
243
244
248
249
251
251
253
255
257
257
258
258
259
263
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
264
264
265
268
270
274
275
279
280
284
284
286
290
292
293
294
296
298
298
298
300
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
304
304
306
308
315
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
main
2005/1/11
page viii
i
viii
9.5
9.6
Mirroring a Sound . . . . . . . . . . . . .
Concepts Summary . . . . . . . . . . . . .
9.6.1 Ranges in Loops . . . . . . . . . .
9.6.2 Returning a Value from a Method
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
317
318
318
318
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
321
321
323
324
327
328
334
334
334
335
337
338
339
342
343
343
344
346
352
352
353
353
main
2005/1/11
page ix
i
List of Figures
1.1
1.2
1.3
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
3.12
3.13
3.14
3.15
4.1
4.2
4.3
4.4
A cooking recipeyou can always double the ingredients, but throwing in an extra cup of flour wont cut it, and dont try to brown the
chicken after adding the tomato sauce! . . . . . . . . . . . . . . . . .
Comparing programming languages: A common simple programming task is to print the words Hello, World! to the screen. . . . .
Eight wires with a pattern of voltages is a byte, which gets interpreted as a pattern of eight 0s and 1s, which gets interpreted as a
decimal number. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
DrJava Preferences Window . . . . . . . . . . . . . . . . . . . . . . .
DrJava Splash Screen . . . . . . . . . . . . . . . . . . . . . . . . . .
DrJava (with annotations) . . . . . . . . . . . . . . . . . . . . . . . .
A calculator with a number in memory . . . . . . . . . . . . . . . . .
Declaring primitive variables and memory assignment . . . . . . . .
Showing the parent and child relationship between mammal and dog
(left) and Object and String (right) . . . . . . . . . . . . . . . . . .
Declaring object variables and memory assignment . . . . . . . . . .
Shows creation and reuse of an object variable. . . . . . . . . . . . .
An object with multiple references to it . . . . . . . . . . . . . . . .
An object with no references to it . . . . . . . . . . . . . . . . . . . .
A window that shows a World object. . . . . . . . . . . . . . . . . .
A window that shows a Turtle object in a World object. . . . . . .
A window that shows two Turtle objects in a World object. . . . . .
The result of messages to the first Turtle object. . . . . . . . . . . .
The result of messages to the second Turtle object. . . . . . . . . .
The turtle wont leave the world . . . . . . . . . . . . . . . . . . . .
Drawing two squares with a turtle. . . . . . . . . . . . . . . . . . . .
Defining and executing drawSquare() . . . . . . . . . . . . . . . . .
Diagram of an object . . . . . . . . . . . . . . . . . . . . . . . . . . .
Showing the result of sending the width as a parameter to drawSquare
Creating a Picture object using new Picture() . . . . . . . . . . .
The File Chooser . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
File chooser with media types identified . . . . . . . . . . . . . . . .
Picking, making, and showing a picture, using the result of each
method in the next method. The picture used is matt-spaceman.jpg.
Picking, making, and showing a picture, when naming the pieces.
The picture shown is katie.jpg. . . . . . . . . . . . . . . . . . . . . .
A depiction of the first five elements in an array . . . . . . . . . . .
An example matrix (two-dimensional array) of numbers . . . . . . .
The top left corner of the battleship guess board with a miss at B-3.
Upper left corner of DrJava window with part magnified 600% . . .
11
12
13
22
22
23
31
33
34
35
37
38
38
49
50
51
52
53
54
55
59
60
63
64
65
67
68
71
81
82
83
83
ix
main
2005/1/11
page x
i
LIST OF FIGURES
4.5
4.6
4.7
4.8
4.9
4.10
4.11
4.12
4.13
4.14
4.15
4.16
4.17
4.18
4.19
4.20
4.21
4.22
4.23
4.24
5.1
5.2
5.3
5.4
5.5
5.6
5.7
5.8
5.9
5.10
5.11
5.12
5.13
5.14
5.15
5.16
5.17
5.18
5.19
5.20
5.21
Image shown in the picture explorer: 100% image on left and 500%
on right (close-up of the branch over the mountain) . . . . . . . . . .
Merging red, green, and blue to make new colors . . . . . . . . . . .
Picking colors using the HSB color model . . . . . . . . . . . . . . .
The ends of this figure are the same colors of gray, but the middle
two quarters contrast sharply so the left looks darker than the right
The Macintosh OS X RGB color picker . . . . . . . . . . . . . . . .
Picking a color using RGB sliders from Java . . . . . . . . . . . . . .
RGB triplets in a matrix representation . . . . . . . . . . . . . . . .
Directly modifying the pixel colors via commands: Note the small
black line on the left under the line across the leaf . . . . . . . . . .
Exploring the caterpillar with the line . . . . . . . . . . . . . . . . .
Using the MediaTools image exploration tools . . . . . . . . . . . . .
Flowchart of a while loop . . . . . . . . . . . . . . . . . . . . . . . .
The original picture (left) and red-decreased version (right) . . . . .
Using the picture explorer to convince ourselves that the red was
decreased . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Overly blue (left) and red increased by 30% (right) . . . . . . . . . .
Original (left) and blue erased (right) . . . . . . . . . . . . . . . . .
Original beach scene (left) and at (fake) sunset (right) . . . . . . . .
Flowchart of a for loop . . . . . . . . . . . . . . . . . . . . . . . . . .
Original picture, lightened picture, and darkened picture . . . . . .
Negative of the image . . . . . . . . . . . . . . . . . . . . . . . . . .
Color picture converted to grayscale . . . . . . . . . . . . . . . . . .
Once we pick a mirror point, we can just walk x halfway and subtract/add to the mirror point . . . . . . . . . . . . . . . . . . . . . .
Original picture (left) and mirrored along the vertical axis (right) . .
A motorcycle mirrored horizontally, top to bottom (left) and bottom
to top (right) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Temple of Hephaistos from the ancient agora in Athens, Greece . . .
Coordinates where we need to do the mirroring . . . . . . . . . . . .
The manipulated temple . . . . . . . . . . . . . . . . . . . . . . . . .
Copying a picture to a canvas . . . . . . . . . . . . . . . . . . . . . .
Copying a picture midway into a canvas . . . . . . . . . . . . . . . .
Copying part of a picture onto a canvas . . . . . . . . . . . . . . . .
Flowers in the mediasources folder . . . . . . . . . . . . . . . . . .
Collage of flowers . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Blending the picture of Katie and Jenny . . . . . . . . . . . . . . .
Rotating some numbers in a table to the left 90 degrees . . . . . . .
Copying a picture to a blank page rotated to the left 90 degrees . . .
Scaling the picture down . . . . . . . . . . . . . . . . . . . . . . . . .
Scaling up a picture . . . . . . . . . . . . . . . . . . . . . . . . . . .
Merging red, green, and blue to make new colors . . . . . . . . . . .
Color: RGB triplets in a matrix representation . . . . . . . . . . . .
Color: The original picture (left) and red-decreased version (right) .
Color: Overly blue (left) and red increased by 30% (right) . . . . . .
Color: Original (left) and blue erased (right) . . . . . . . . . . . . .
84
85
85
86
87
87
88
94
94
95
98
103
110
112
114
115
123
126
127
128
140
141
143
143
144
145
149
150
152
154
157
162
163
164
167
169
176
176
177
177
177
main
2005/1/11
page xi
i
LIST OF FIGURES
5.22
5.23
5.24
5.25
5.26
5.27
5.28
5.29
5.30
5.31
5.32
5.33
5.34
5.35
6.1
6.2
6.3
6.4
6.5
6.6
6.7
6.8
6.9
6.10
6.11
6.12
6.13
6.14
6.15
6.16
6.17
6.18
6.19
6.20
6.21
6.22
6.23
7.1
7.2
7.3
7.4
7.5
7.6
7.7
xi
main
2005/1/11
page xii
i
xii
LIST OF FIGURES
7.8
7.9
7.10
7.11
7.12
7.13
7.14
7.15
7.16
7.17
7.18
7.19
8.1
8.2
8.3
8.4
8.5
8.6
8.7
8.8
8.9
8.10
8.11
8.12
8.13
8.14
8.15
8.16
8.17
8.18
8.19
8.20
9.1
9.2
9.3
9.4
9.5
10.1
10.2
main
2005/1/11
page xiii
i
LIST OF FIGURES
10.3 The original This is a test sound (left), and the sound with an
echo (right) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.4 The original sound (left), and the sound with the frequency doubled
(right) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.5 The sine wave with a frequency of 880 and a maximum amplitude of
4000 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.6 The raw 440 Hz signal on top, then the 440+880+1320 Hz signal on
the bottom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.7 FFT of the 440 Hz sound . . . . . . . . . . . . . . . . . . . . . . . .
10.8 FFT of the combined sound . . . . . . . . . . . . . . . . . . . . . . .
10.9 The 440 Hz square wave (top) and additive combination of square
waves (bottom) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.10FFTs of the 440 Hz square wave (top) and additive combination of
square waves (bottom) . . . . . . . . . . . . . . . . . . . . . . . . . .
xiii
326
329
337
339
339
339
341
341
main
2005/1/11
page xiv
i
xiv
LIST OF FIGURES
main
2005/1/11
page 1
i
Preface
One of the clearest lessons from the research on computing education is that
one doesnt just learn to program. One learns to program something [4, 15]. How
motivating that something is can make the difference between learning to program
or not [6].
In this book students will learn about programming by writing programs to
manipulate media. Students will create and modify images, such as the image
collage on the front cover of this book. Students will modify sounds, like splicing
words into sentences or reversing sounds to make interesting effects. Students will
write programs to generate web pages from data in databases, in the same way
that CNN.com and Amazon.com do. They will create animations and movies using
special effects like the ones students see on television and in movies.
Students in courses taught at Georgia Tech have found these programs interesting and motivating. Students have even reported that they sometimes turn in
their programs and then keep working on them to see what else they can do.
People want to communicate. We are social creatures, and the desire to
communicate is one of our primal motivations. Increasingly, the computer is used
as a tool for communication, even more than as a tool for calculation. Virtually
all published text, images, sounds, music, and movies today are prepared using
computing technology.
This book is about teaching people to program in order to communicate. The
book focuses on how to manipulate images, sounds, text, and movies as professionals
might, but with programs written by the students. We realize that most people
will use professional-grade applications to perform these same manipulations. But
knowing how to do it with your own programs means that you can do it if you need
to. You may want to say something with your media, but you may not know how
to make PhotoShop or Final Cut Pro do what you want. Knowing how to program
means that you have power of expression that is not limited by your application
software.
This book is not just about programming to manipulate media. Media manipulation programs can be hard to write, or behave in unexpected ways. Questions
arise like Why is this same image filter faster in Photoshop? and That was hard
to debugare there ways of writing programs that are easier to debug? Answering
questions like these is what computer scientists do. There are several chapters at
the end of the book that are about computing, not just programming.
The computer is the most amazingly creative device that humans have ever
conceived of. It is literally completely made up of mind-stuff. The notion Dont
just dream it, be it is really possible on a computer. If you can imagine it, you
can make it real on the computer. Playing with programming can be and should
be enormous fun.
TO TEACHERS
The media computation approach used in this book starts with what students use
computers for: image manipulation, digital music, web pages, games, and so on.
1
main
2005/1/11
page 2
i
LIST OF FIGURES
main
2005/1/11
page 3
i
LIST OF FIGURES
dents. Ann Fleury has shown that novice students just dont buy what we tell
them about encapsulation and reuse (e.g., [10]). Students prefer simpler code that
they can trace easily, and actually think that code that an expert would hate is
better. It takes time and experience for students to realize that there is value in
well-designed systems, and without experience, its very difficult for students to
learn the abstractions.
Another unusual thing about this book is that we start using arrays in chapter
4, in our first significant programs. Typically, introductory computing courses push
arrays off until later, since theyre obviously more complicated than variables with
simple values. But a relevant context is very powerful [15]. The matrices of pixels
in images occur in the students everyday lifea magnifying glass on a computer
monitor or television makes that clear.
Our goal is to teach programming in a way that students find relevant, motivating, and social. To be relevant we have the students write programs to do things
that students currently use computers for: i.e. image, sound, and text manipulation. For motivation we assign open-ended creative assignments such as: create an
image collage with the same image at least 4 times using 3 different image manipulations and a mirroring. As for the social aspect we encourage collaboration on
assignments and posting of student work. Students learn from each other and try
to outdo each other.
Ways to Use This Book
This book is based on content that we teach at Georgia Tech. Individual teachers
may skip some sections (e.g., the section on additive synthesis, MIDI, and MP3),
but all of the content here has been tested with our students.
However, we can imagine using this material in many other ways:
A short introduction to computing could be taught with just chapters 2-4.
We have taught even single day workshops on media computation using just
this material.
Chapters 8 and 9 replicate the computer science basics from chapters 4
through 6, but in the context of sounds rather than images. We find the
replication usefulsome students seem to relate better to the concepts of iteration and conditionals better when working with one medium than the other.
Further, it gives us the opportunity to point out that the same algorithm can
have similar effects in different media (e.g., scaling a picture up or down and
shifting a sound higher or lower in pitch is the same algorithm). But it could
certainly be skipped to save time.
Chapter 12 (on movies) introduces no new programming or computing concepts. While motivating, movie processing could be skipped for time.
We do recommend getting to at least some of the chapters in the last unit, in
order to lead students into thinking about the computing and programming
in a more abstract manner, but clearly not all of the chapters have to be
covered.
main
2005/1/11
page 4
i
LIST OF FIGURES
TYPOGRAPHICAL NOTATIONS
Examples of Java code look like this: x = x + 1;. Longer examples look look like
this:
public c l a s s G r e e t e r {
public s t a t i c void main ( S t r i n g [ ] argv )
{
// show t h e s t r i n g H e l l o World on t h e c o n s o l e
System . out . p r i n t l n ( H e l l o World ) ;
}
}
When showing something that the user types in the interactions pane with
DrJavas response, it will have a similar font and style, but the users typing will
appear after a DrJava prompt (>):
> 3 + 4
7
main
2005/1/11
page 5
i
LIST OF FIGURES
Java
The programming language used in this book is Java. Java is a high-level objectoriented programming language that runs on most computers and many small electronic devices. It is widely used in industry and in universities.
The development environment used in this book is DrJava. It is a free and
easy to use development environment. DrJava lets the student focus on learning
to program in Java and not on how to use the development environment. An
advantage of DrJava is that you can try out Java code in the interactions pane
without having to write a main method.
You dont have to use this development environment. There are many development environments that are available for use with Java. If you use another
development environment just add the directory that has the Java classes developed for this book to the classpath. See the documentation for your development
environment for how to do this.
ACKNOWLEDGEMENTS
Our sincere thanks go out to the following:
Adam Wilson built the MediaTools that are so useful for exploring sounds
and images and processing video.
main
2005/1/11
page 6
i
LIST OF FIGURES
Matthew, Katherine, and Jennifer Guzdial all contributed pictures for use in
this book.
Thanks for permission to use their snapshots from class in examples are former Media Computation students Constantino Kombosch, Joseph Clark, and
Shannon Joiner.
The cover image was created by Rachel Cobb who was a first year Architecture
student at Georgia Institute of Technology in Spring 2003 when she created the
image for a homework assignment to build a collage. The original arch image came
from the National Park Service gallery at http://www.nps.gov/arch/gallery/
index.htm. The original image and the collage are both used with permission, and
our thanks!
main
2005/1/11
page 7
i
P A R T
O N E
INTRODUCTION
Chapter 1
Chapter 2
Introduction to Java
Chapter 3
Introduction to Programming
main
2005/1/11
page 8
i
C H A P T E R
Introduction to Computer
Science and Media
Computation
1.1
1.2
1.3
1.4
main
2005/1/11
page 9
i
Section 1.1
position, then being able to write a program that specifies exactly what happens, in
terms that can be completely defined and understood, is very useful. This exactness
is part of why computers have radically changed so much of how science is done
and understood.
It may sound funny to call programs or algorithms a recipe, but the analogy
goes a long way. Much of what computer scientists study can be defined in terms
of recipes:
Some computer scientists study how recipes are written: Are there better
or worse ways of doing something? If youve ever had to separate whites
from yolks in eggs, you know that knowing the right way to do it makes a
world of difference. Computer science theoreticians worry about the fastest
and shortest recipes, and the ones that take up the least amount of space
(you can think about it as counter space the analogy works). How a
recipe works, completely apart from how its written, is called the study of
algorithms. Software engineers worry about how large groups can put together
recipes that still work. (The recipe for some programs, like the one that keeps
track of Visa/MasterCard records has literally millions of steps!)
Other computer scientists study the units used in recipes. Does it matter
whether a recipe uses metric or English measurements? The recipe may work
in either case, but if you have the read the recipe and you dont know what
a pound or a cup is, the recipe is a lot less understandable to you. There are
also units that make sense for some tasks and not others, but if you can fit the
units to the tasks well, you can explain yourself more easily and get things
done fasterand avoid errors. Ever wonder why ships at sea measure their
speed in knots? Why not use things like meters per second? There are places,
like at sea, where more common terms arent appropriate or dont work as
well. The study of computer science units is referred to as data structures.
Computer scientists who study ways of keeping track of lots of data in lots of
different kinds of units are studying databases.
Can recipes be written for anything? Are there some recipes that cant be
written? Computer scientists actually do know that there are recipes that
cant be written. For example, you cant write a recipe that can absolutely
tell, for any other recipe, if the other recipe will actually work. How about
intelligence? Can we write a recipe that, when a computer followed it, the
computer would actually be thinking (and how would you tell if you got it
right)? Computer scientists in theory, intelligent systems, artificial intelligence, and systems worry about things like this.
There are even computer scientists who worry about whether people like
what the recipes produce, like the restaurant critics for the newspaper. Some
of these are human-computer interface specialists who worry about whether
people like how the recipes work (those recipes that produce an interface
that people use, like windows, buttons, scrollbars, and other elements of what
we think about as a running program).
main
2005/1/11
page 10
i
10
Chapter 1
Just as some chefs specialize in certain kinds of recipes, like crepes or barbecue, computer scientists also specialize in special kinds of recipes. Computer
scientists who work in graphics are mostly concerned with recipes that produce pictures, animations, and even movies. Computer scientists who work
in computer music are mostly concerned with recipes that produce sounds
(often melodic ones, but not always).
Still other computer scientists study the emergent properties of recipes. Think
about the World Wide Web. Its really a collection of millions of recipes
(programs) talking to one another. Why would one section of the Web get
slower at some point? Its a phenomena that emerges from these millions
of programs, certainly not something that was planned. Thats something
that networking computer scientists study. Whats really amazing is that
these emergent properties (that things just start to happen when you have
many, many recipes interacting at once) can also be used to explain noncomputational things. For example, how ants forage for food or how termites
make mounds can also be described as something that just happens when you
have lots of little programs doing something simple and interacting.
The recipe metaphor also works on another level. Everyone knows that some
things in a recipe can be changed without changing the result dramatically. You
can always increase all the units by a multiplier (say, double) to make more. You
can always add more garlic or oregano to the spaghetti sauce. But there are some
things that you cannot change in a recipe. If the recipe calls for baking powder,
you may not substitute baking soda. If youre supposed to boil the dumplings then
saute them, the reverse order will probably not work well (Figure 1.1.
Similarly, for software recipes (programs), there are usually things you can
easily change: The actual names of things (though you should change names consistently), some of the constants (numbers that appear as plain old numbers, not
as variables), and maybe even some of the data ranges (sections of the data) being
manipulated. But the order of the commands to the computer, however, almost
always has to stay exactly as stated. As we go on, youll learn what can be changed
safely, and what cant.
Computer scientists specify their programs with programming languages (Figure 1.2). Different programming languages are used for different purposes. Some of
them are wildly popular, like Java and Visual Basic. Others are more obscure, like
Squeak and T. Others are designed to make computer science ideas very easy to
learn, like Scheme or Python, but the fact that theyre easy to learn doesnt always
make them very popular nor the best choice for experts building larger or more
complicated programs. Its a hard balance in teaching computer science to pick a
language that is easy to learn and is popular and useful enough that students are
motivated to learn it.
Why dont computer scientists just use natural human languages, like English
or Spanish? The problem is that natural languages evolved the way that they did
to enhance communications between very smart beings, humans. As well go into
more in the next section, computers are exceptionally dumb. They need a level of
specificity that natural language isnt good at. Further, what we say to one another
in natural communication is not exactly what youre saying in a computational
main
2005/1/11
page 11
i
Section 1.1
11
FIGURE 1.1: A cooking recipeyou can always double the ingredients, but throwing
in an extra cup of flour wont cut it, and dont try to brown the chicken after adding
the tomato sauce!
recipe (program). When was the last time you told someone how a videogame like
Doom or Quake or Super Mario Brothers worked in such minute detail that they
could actually replicate the game (say, on paper)? English isnt good for that kind
of task.
There are so many different kinds of programming languages because there are
so many different kinds of programs to write. Programs written in the programming
language C tend to be very fast and efficient, but they also tend to be hard to read,
hard to write, and require units that are more about computers than about bird
migrations or DNA or whatever else you want to write your program about. The
programming language Lisp (and its related languages like Scheme, T, and Common
Lisp) is very flexible and is well suited to exploring how to write programs that have
never been written before, but Lisp looks so strange compared to languages like C
that many people avoid it and there are (natural consequence) few people who know
it. If you want to hire a hundred programmers to work on your project, youre going
to find it easier to find a hundred programmers who know a popular language than
a less popular onebut that doesnt mean that the popular language is the best
one for your task!
The programming language that were using in this book is Java (http:
//java.sun.com for more information on Java). Java is a very popular programming language. Delta uses it to handle its web site (http://www.delta.
com). NASA used it on the Mars Rover Spirit (http://www.sun.com/aboutsun/
media/features/mars.html). It has been used in touchscreen kiosks for Super
Bowl fans (http://java.sun.com/features/1998/01/superbowl.html).
Java is known for being object-oriented, platform neutral (runs on many com-
main
2005/1/11
page 12
i
12
Chapter 1
Python/Jython
def hello():
print "Hello World"
Java
class HelloWorld {
static public void main( String args[] ) {
System.out.println( "Hello World!" );
}
}
C++
#include <iostream.h>
main() {
cout << "Hello World!" << endl;
return 0;
}
Scheme
(define helloworld
(lambda ()
(display "Hello World")
(newline)))
FIGURE 1.2: Comparing programming languages: A common simple programming
task is to print the words Hello, World! to the screen.
puters and electronic devices), robust, and secure. An early drawback to Java was
that programs written in Java often had a slower execution time than ones written
in C or C++. However, current Java compilers and interpreters have essentially
eliminated this problem.
Lets make clear some of our language that well be using in this book. A
program is a description of a process in a particular programming language that
achieves some result that is useful to someone. A program could be small (like one
that implements a calculator), or could be huge (like the program that your bank
uses to track all of its accounts). An algorithm (in contrast) is a description of
a process apart from any programming language. The same algorithm might be
implemented in many different languages in many different ways in many different
programsbut it would all be the same process if were talking about the same
algorithm.
main
2005/1/11
page 13
i
Section 1.2
1.2
13
FIGURE 1.3: Eight wires with a pattern of voltages is a byte, which gets interpreted
as a pattern of eight 0s and 1s, which gets interpreted as a decimal number.
1 Well
main
2005/1/11
page 14
i
14
Chapter 1
A computer can do lots of things with numbers. It can add them, subtract
them, multiply them, divide them, sort them, collect them, duplicate them, filter
them (e.g., make a copy of these numbers, but only the even ones), and compare
them and do things based on the comparison. For example, a computer can be told
in a program Compare these two numbers. If the first one is less than the second
one, jump to step 5 in this program. Otherwise, continue on to the next step.
It sounds like computers are incredible calculators, and thats certainly why
they were invented. The first use of computers was during World War II for calculating trajectories of projectiles (If the wind is coming from the SE at 15 MPH,
and you want to hit a target 0.5 miles away at an angle of 30 degrees East of North,
then incline your launcher to . . .). The computer is an amazing calculator. But
what makes it useful for general programs is the concept of encodings.
Computer Science Idea: Computers can layer encodings
Computers can layer encodings to virtually any level of
complexity. Numbers can be interpreted as characters,
which can be interpreted in groups as Web pages. But
at the bottommost level, the computer only knows voltages which we interpret as numbers.
If one of these bytes is interpreted as the number 65, it could just be the
number 65. Or it could be the letter A using a standard encoding of numbers-toletters called the American Standard Code for Information Interchange (ASCII).
If that 65 appears in a collection of other numbers that were interpreting as text,
and thats in a file that ends in .html it might be part of something that looks
like this <a href=. . ., which a Web browser will interpret as the definition of a link.
Down at the level of the computer, that A is just a pattern of voltages. Many layers
of programs up, at the level of a Web browser, it defines something that you can
click on to get more information.
If the computer understands only numbers (and thats a stretch already), how
does it manipulate these encodings? Sure, it knows how to compare numbers, but
how does that extend to being able to alphabetize a class list? Typically, each layer
of encoding is implemented as a piece or layer of software. Theres software that
understands how to manipulate characters. The character software knows how to
do things like compare names because it has encoded that a comes before b and
so on, and that the numeric comparison of the order of numbers in the encoding
of the letters leads to alphabetical comparisons. The character software is used by
other software that manipulates text in files. Thats the layer that something like
Microsoft Word or Notepad or TextEdit would use. Still another piece of software
knows how to interpret HTML (the language of the Web), and another layer of
that software knows how to take HTML and display the right text, fonts, styles,
and colors.
We can similarly create layers of encodings in the computer for our specific
tasks. We can teach a computer that cells contain mitochondria and DNA, and that
DNA has four kinds of nucleotides, and that factories have these kinds of presses
and these kinds of stamps. Creating layers of encoding and interpretation so that
main
2005/1/11
page 15
i
Section 1.3
15
the computer is working with the right units (recall back to our recipe analogy)
for a given problem is the task of data representation or defining the right data
structures.
If this sounds like lots of software, it is. When software is layered like this,
it slows the computer down some. But the amazing thing about computers is that
theyre amazingly fastand getting faster all the time!
Computer Science Idea: Moores Law
Gordon Moore, one of the founders of Intel (maker of computer processing chips for computers running Windows operating systems), made the claim that the number of transistors (a key component of computers) would double at
the same price every 18 months, effectively meaning that
the same amount of money would buy twice as much computing power every 18 months. This Law has continued to
hold true for decades.
Computers today can execute literally BILLIONS of program steps per second! They can hold in memory entire encyclopedias of data! They never get tired
nor bored. Search a million customers for a particular card holder? No problem!
Find the right set of numbers to get the best value out of an equation? Piece of
cake!
Process millions of picture elements or sound fragments or movie frames?
Thats media computation.
1.3
main
2005/1/11
page 16
i
16
Chapter 1
Look at the real world: It isnt made up of lots of little dots that you can see.
Listen to a sound: Do you hear thousands of little bits of sound per second? The
fact that you cant hear little bits of sound per second is what makes it possible
to create these encodings. Our eyes and ears are limited: We can only perceive so
much, and only things that are just so small. If you break up an image into small
enough dots, your eyes cant tell that its not a continuous flow of color. If you
break up a sound into small enough pieces, your ears cant tell that the sound isnt
a continuous flow of auditory energy.
The process of encoding media into little bits is called digitization, sometimes
referred to as going digital . Digital means (according to the American Heritage
Dictionary) Of, relating to, or resembling a digit, especially a finger. Making
things digital is about turning things from continuous, uncountable, to something
that we can count, as if with our fingers.
Digital media, done well, feel the same to our limited human sensory apparatus as the original. Phonograph recordings (ever seen one of those?) capture sound
continuously, as an analog signal. Photographs capture light as a continuous flow.
Some people say that they can hear a difference between phonograph recordings
and CD recordings, but to my ear and most measurements, a CD (which is digitized sound) sounds just the samemaybe clearer. Digital cameras at high enough
resolutions produce photograph-quality pictures.
Why would you want to digitize media? Because its easier to manipulate, to
replicate, to compress, and to transmit. For example, its hard to manipulate images
that are in photographs, but its very easy when the same images are digitized. This
book is about using the increasingly digital world of media and manipulating it
and learning computation in the process.
Moores Law has made media computation feasible as an introductory topic.
Media computation relies on the computer doing lots and lots of operations on lots
and lots of bytes. Modern computers can do this easily. Even with slow (but easy to
understand) languages, even with inefficient (but easy to read and write) programs,
we can learn about computation by manipulating media.
1.4
main
2005/1/11
page 17
Section 1.4
17
questions.
1.4.1
1.4.2
PROBLEMS
1.1. Every profession uses computers today. Use a Web browser and a search engine
like Google to find sites that relate your field of study with computer science or
computing or computation. For example, search for biology computer science
or management computing.
1.2. Find an ASCII table on the Web: A table listing every character and its corresponding numeric representation. Write down the sequence of numbers whose
main
2005/1/11
page 18
i
18
Chapter 1
TO DIG DEEPER
James Gleicks book Chaos describes more on emergent propertieshow small changes
can lead to dramatic effects, and the unintended impacts of designs because of
difficult-to-foresee interactions.
Mitchel Resnicks book Turtles, Termites, and Traffic Jams: Explorations in
Massively Parallel Microworlds [21] describes how ants, termites, and even traffic
jams and slime molds can be described pretty accurately with hundreds or thousands of very small processes (programs) running and interacting all at once.
Exploring the Digital Domain [3] is a wonderful introductory book to computation with lots of good information about digital media.
main
2005/1/11
page 19
i
C H A P T E R
Introduction to Java
2.1
2.2
2.3
2.4
2.5
JAVA
INTRODUCTION TO DRJAVA
JAVA BASICS
VARIABLES
CONCEPTS SUMMARY
JAVA
The programming language that were going to be using in this book is called Java.
Its a language invented by James Gosling (http://java.sun.com/people/jag/)
at Sun Microsystems.
2.1.1
History of Java
Back in 1990 Sun created project Green to try and predict the next big thing in
computers. The goal of the project was to try and develop something to position
Sun ahead of its competitors. They thought that the next big thing would be
networked consumer electronics devices like set-top boxes for downloading video on
demand. They tried to develop a prototype using C++ but after many problems
they decided to develop a new object-oriented language which they originally named
Oak, after a tree outside James Goslings office. They created a demonstration but
the cable companies werent really interested and the future of the project was in
doubt.
At a brainstorming session they decided to try to reposition the language
for use with the internet. They created a web browser that had Java programs
19
main
2005/1/11
page 20
i
20
Chapter 2
Introduction to Java
main
2005/1/11
page 21
i
Section 2.2
Introduction to DrJava
21
can be efficient and effective. Object-oriented programs also try to distribute the
tasks to be done so that no one object does all the work. This makes it easier to
maintain and extend the program. It can also make the program more efficient.
2.2
INTRODUCTION TO DRJAVA
Youll actually be programming using a tool called DrJava. DrJava is a simple
editor (tool for entering program text) and interaction space so that you can try
things out in DrJava and create new programs (methods) and classes. DrJava is
available for free under the DrJava Open Source License, and it is under active
development by the JavaPLT group at Rice University.
If you dont wish to use DrJava you can use this book with another development environment. Simply set the classpath (place to look for classes that you
are using in your program) to include the classes used in this book. Check your
documentation for your development environment to see how to do this. We recommend using DrJava because it is free, easy to use, has an interactions pane for
trying out Java statements, is written in Java so it works on all platforms, and it
includes a debugger.
To install DrJava, youll have to do these things:
1. Make sure that you have Java 1.4 or above installed on your computer. If
you dont have it, load it from the CD, or you can get it from the Sun site at
http://www.java.sun.com.
2. Youll need to install DrJava. You can either load it from the CD or get it
from http://drjava.org/.
3. Add the Java classes that come with the book to the extra classpaths for
DrJava. Start DrJava (see the next section for how to do this), click on Edit
and then Preferences. This will show the Preferences window (Figure 2.1).
Click on the Add button below the Extra Classpath textarea and add the
path to the directory where the classes that come with the book are, such as:
c:/intro-prog-java/bookClasses.
2.2.1
Starting DrJava
How you start DrJava depends on your platform. In Windows, youll have a DrJava
icon that youll simply double-click. In Linux, youll probably cd into your DrJava directory and type a command like java -jar drjava-DATE-TIME.jar where
DATE-TIME are values for the release of DrJava that you are using. On the Macintosh, youll probably have to type commands in your Terminal application where
you cd to the correct directory then type ./DrJava. See the instructions on the
CD for what will work for your kind of computer.
main
2005/1/11
page 22
i
22
Chapter 2
Introduction to Java
'
Common Bug: Making DrJava run faster
As well talk more about later, when youre running DrJava, youre actually running Java. Java needs memory. If
youre finding that DrJava is running slowly, give it more
memory. You can do that by quitting out of other applications that youre running. Your email program, your
instant messenger, and your digital music player all take
up memory (sometimes lots of it!). Quit out of those and
DrJava will run faster.
&
%
Once you start DrJava, it will look something like Figure 2.3. There are three
main areas in DrJava (the bars between them move so that you can resize the
areas):
The top left window pane is the files pane. It has a list of the open files in
DrJava. In Java each class that you create is usually stored in its own file.
Java programs often consist of more than one class, thus more than one file.
You can click on a file name in the Files pane to view the contents of that file
in the top right window pane (definitions pane).
main
2005/1/11
page 23
i
Section 2.2
Introduction to DrJava
23
The top right part is the definitions pane. This is where you write your
classes: a collection of related data and methods. This area is simply a
text editorthink of it as Microsoft Word for your programs. The computer
doesnt actually try to interpret the names that you type up in the program
area until you compile. You can compile all the current files open in the files
pane by clicking on the Compile All button near the top of the DrJava
window. Compiling your code changes it into instructions that the computer
understands and can execute.
The bottom part is the interactions pane. This is where you can literally
command the computer to do something. You type your commands at the
> prompt, and when you hit return, the computer will interpret your words
(i.e., apply the meanings and encodings of the Java programming language)
and do what you have told it to do. This interpretation will include whatever
you typed and compiled in the definitions pane as well.
There are other features of DrJava visible in Figure 2.3. The Open button
will let you open a file, it will add the file name to the files pane, and show the
code in that file in the definitions pane. The Save button will save the file that is
currently displayed in the definitions pane. The Javadoc button creates HTML
main
2005/1/11
page 24
i
24
Chapter 2
Introduction to Java
documentation from the Javadoc comments in your files (comments that start with
/** and end with */.
'
$
Making it Work Tip: Get to know your Help!
An important feature to already start exploring is the
Help. If you click on Help and then click on Help again
when a menu is displayed you will see a help window. Start
exploring it now so that you have a sense for whats there.
&
2.3
JAVA BASICS
Were going to start out by simply typing commands in the interactions panenot
defining new names yet, but simply using the names and symbols that Java knows.
2.3.1
Math Operators
Try typing the following in the interactions pane.
> 34 + 56
90
> 26 - 3
23
> 3 * 4
12
> 4 / 2
2
As you can see Java understands how to recognize numbers, add, subtract,
multiply and divide. You can type a mathematical expression in the interactions
pane and then hit the Enter key and it will display the result of the expression.
Go ahead and try it.
2.3.2
main
2005/1/11
page 25
i
Section 2.3
Java Basics
25
main
2005/1/11
page 26
i
26
Chapter 2
Introduction to Java
%
$
Making it Work Tip: Using Another Development
Environment
If you are not using DrJava you will need to type all code
that we show in the interactions pane in a main method
instead. Compile and execute the class with the main
method. To get the above example to work in another
development environment I could have written the following class definition in a file named Test.java.
public c l a s s Test {
public s t a t i c void main ( S t r i n g [ ] a r g s ) {
System . out . p r i n t l n ( 3 4 + 5 6 ) ;
}
}
When you compile a Java source file the compiler will create a class file, so if you compile the source file Test.java
the compiler will create the file Test.class. This file will
have the same name as the source file but will have an
extension of .class. After you have compiled the source
you can execute the class. To execute it using the free
command line tools from Sun is to use:
> java Test
&
main
2005/1/11
page 27
i
Section 2.3
2.3.3
Java Basics
27
2.3.4
Casting
We could also have used casting to get the correct result from the division of two
integers. Casting is like using a mold to give some material a new shape. It tells
the compiler to change a value to a particular type even if it could lead to a loss of
data. To cast you put the type that you want the value changed to inside an open
and close parenthesis: (type). There are two floating point types in Java: float
and double. The type double is larger than the type float and thus more accurate.
We will use this type for most of the floating point numbers in this book. Notice
that we can cast either the 1 or 2 to double and the answer will then be given as
a double. We could cast both the 1 and 2 to double and the result would be fine.
However, if we cast the result of the integer division to a double it is too late since
the result of integer division of 1 by 2 is 0 since the result is an integer.
> System.out.println((double) 1 / 2);
0.5
> System.out.println(1 / (double) 2);
0.5
> System.out.println((double) (1/2));
0.0
2.3.5
Relational Operators
We can write Java statements that do simple math operations. But if that was all
we could do computers wouldnt be terribly useful. Computers can also decide if
something is true or false.
> System.out.println(3 > 2);
true
> System.out.println(2 > 3);
false
> System.out.println(a < b);
true
main
2005/1/11
page 28
i
28
Chapter 2
Introduction to Java
Using symbols we can check if one value is greater than another >, less than
another <, equal to another ==, not equal to another !=, greater or equal to
another >=, and less than or equal to another <=. You can use these relational
operators on many items such as numbers and characters as shown above. A
character can be specified between a pair of single quotes (a).
You might find == odd as a way to test for equality. But, in Java = is
used to assign a value, not check for equality, as you will see in the next section.
Notice that Java understands the concepts true and false. These are re-
main
2005/1/11
page 29
i
Section 2.3
Java Basics
29
served words in Java which means that they cant be used as names.
'
Strings
Computers can certainly work with numbers and even characters. They can also
work with strings. Strings are sequences of characters. Try the following in the
interactions pane.
> System.out.println("Mark");
Mark
> System.out.println("13 + 5");
13 + 5
Java knows how to recognize strings (lists of characters) that start and end
with " (double quotes). Notice what happens when you enclose a math expression
like 13 + 5 in a pair of double quotes. It doesnt print the result of the math
expression but the characters inside the pair of double quotes. Whatever is inside a
pair of double quotes is not evaluated, the value of it is exactly what was entered.
main
2005/1/11
page 30
i
30
Chapter 2
Introduction to Java
main
2005/1/11
page 31
i
Section 2.4
2.4
Variables
31
VARIABLES
We have used Java to do calculations and to append strings, but we have not stored
the results. The results would be in memory but we dont know where they are in
memory and we cant get back to them. On a calculator we can store the result
of one calculation to memory (Figure 2.4). We can then use that stored value in
other calculations. On a calculator you also have access to the result of the last
calculation.
2.4.1
Declaring Variables
On a computer we can store many calculated values by naming them. We can
then access those values by using the variable names. The computer takes care of
mapping the name to the memory location (address) that stores the value. We call
naming values declaring a variable.
When you declare a variable in Java you specify the type for the variable and
a name (type name ). You need to specify a type so that Java knows how many
bits to reserve in memory and how to interpret the bits. You can also assign a
value to a variable using the = operator and provide a value or an expression
(type name = expression ). Dont read = as equals but as assign the value of
the right side to the variable on the left (which makes using == for equals make
more sense). The bits in the variable will be set to represent the value. We will use
the type int for storing integer values (numbers without decimal points) and the
type double for storing floating point values (numbers with decimal points).
2.4.2
main
2005/1/11
page 32
i
32
Chapter 2
Introduction to Java
&
%
We dont have to print out the value of the variable after we assign a value to
it. We are doing that so that you see that the computer does return a value when
you use the name of a variable. What about the extra amount for the final answer?
The answer should be just $19.47 per person. If we look back at the printing of the
tip amount we see where this first occurred. Floating point numbers do not always
give exact results.
2.4.3
main
2005/1/11
page 33
i
Section 2.4
Variables
33
int numPeople = 2;
0
1
2
3
4
5
6
7
8
9
10
11
3
2
.
4
5
computer looks up the name bill to find the address of that variable in memory
and prints the value stored in that space. It knows how many bytes to use and
how to interpret the bytes in calculating the value based on the declared type of
the variable.
How would you calculate the cost of a shirt that was originally $47.99 dollars
but was 40% off? And, what if you also had a coupon for an additional 20% off the
sale price? First you would need to determine the first discount amount by multiplying 40% (0.40) times the original price. Next, calculate the first discount total
by subtracting the first discount amount from the original price. Next calculate
the second discount amount by mutliplying 20% (0.20) times the second discount
amount. The second discount total is the first discount total minus the second
discount amount. We would need variables to hold the first discount amount, first
discount total, second discount amount, and second discount total. What type
should those variables be declared to be? Since they have fractional parts they can
be declared as double.
> double originalPrice = 47.99;
> double firstDiscountAmount = originalPrice * 0.40;
> System.out.println(firstDiscountAmount);
19.196
> double firstDiscountTotal = originalPrice - firstDiscountAmount;
> System.out.println(firstDiscountTotal);
28.794
> double secondDiscountAmount = firstDiscountTotal * 0.20;
> System.out.println(secondDiscountAmount);
5.758800000000001
> double secondDiscountTotal = firstDiscountTotal secondDiscountAmount;
> System.out.println(secondDiscountTotal);
main
2005/1/11
page 34
i
34
Chapter 2
Introduction to Java
23.0352
When these statements are executed 64 bits of space is reserved for each
variable declared as a double. So how much memory does this calculation take?
We have declared 5 doubles so we have used 5 times 64 bits of space. Each byte
has 8 bits in it so how many bytes have we used? How much memory does your
computer have and how much of it have you used? If your computer has 128
Megabytes of memory then that is 128000 bytes of memory and we used 40 bytes
then we have only used 0.03125% of memory. That isnt very much. We can declare
lots of variables and still not use up all of the memory.
Each time we use the variable name above the computer substitutes the value
in the memory location associated with that name. What are the values in each of
the 5 declared variables when these statements are finished?
2.4.4
Object Variables
Variables that are declared to be of any of the primitive types: byte, short, int,
long, float, double, boolean or char reserve space and associate the variable
name with the starting location of that space. Variables that are declared to be of
any other type are object variables. This is because all other types inherit from
the class Object.
Mammal
Object
Dog
String
FIGURE 2.6: Showing the parent and child relationship between mammal and dog
(left) and Object and String (right)
You can think of inheritance as saying that one class is a kind of another
class like saying that a dog is a kind of mammal (Figure 7.14). If you need a
mammal you can use a dog, but if you need a dog another mammal wont do.
Because a dog is a kind of mammal we know that it has the same characteristics
that a mammal does such as breathing oxygen, bearing live young, having hair, etc.
We say that it inherits characteristics from mammal. The String class is a child
of the Object class so it is a kind of object (Figure 7.14). All of the classes that
you define will inherit from the Object class either directly or indirectly.
When you declare a variable you specify the type and a name type name ; or
type name = expression ;. What if you want to declare a variable that will refer
to a string? What type can you use? Well it cant be int or double because those
represent numbers. It cant be char because that represents a single character.
Java has a class String that is used to represent character strings. The String
class inherits from the Object class. So to declare a variable that represents a string
main
2005/1/11
page 35
i
Section 2.4
Variables
35
test
String test;
null
test
test = "Hi";
Hi
test
Bye
Hi
When the variable test was declared as type String space was reserved for
an object reference (a way to find the address of the object) and the value was set
to null (Figure 2.7). The default value for an object variable is null which means
it isnt referring to any object yet. The compiler will create a String object when
it sees characters enclosed in double quotes so the "Hi" creates an object of the
String class and sets the characters in that String object to be the characters
main
2005/1/11
page 36
i
36
Chapter 2
Introduction to Java
Hi. The code test = "Hi" changes the value of the space reserved for the object
reference from null to a reference to the String object with the characters Hi.
What happens to the String object with the characters Hi in it when you
changed the variable test to refer to the new String object with the characters
Bye? Java keeps track of used space and if there are no valid references to the
used space it will put it back into available space. This is called garbage collection.
The fact that Java automatically handles freeing used memory when it is no
longer needed is one of the advantages to Java over languages like C++ which
required the programmer to free memory when it was no longer needed. Programmers arent very good at keeping track of when memory is no longer needed and
so many programs never free memory when it is no longer needed. This is called a
memory leak and it is why some programs use more and more memory while they
are running. Sometimes programmers free memory when it is still being used which
can cause major problems such as incorrect results and even cause your computer
to crash.
2.4.5
Reusing Variables
Once we have declared variables we can reuse them by assigning new values to
them..
> String myName = "Mark";
> System.out.println(myName);
Mark
> myName = "Barb";
> System.out.println(myName);
Barb
This actually means to first set the variable myName to refer to the String
object with the characters Mark in it. Then it changes the variable myName to
refer to another String object with the characters Barb in it. The first String
object with the characters Mark in it still exists and can be garbage collected
(reused as available space).
'
$
Making it Work Tip: Variables versus Literals
Notice that we have changed the value of the variable test
several times. We call items like test variables because the
values inside of them can change. This is different from
literals such as the string literal "Hi" in that the value of
that wont change. You can set the value of a variable to a
literal but you cant set the value of a literal to a variable.
&
%
You cant declare the same variable name twice. Declare the name one time
(by specifying the type and name) and then you can use it many times.
> String myName = "Mark";
> System.out.println(myName);
main
2005/1/11
page 37
i
Section 2.4
myName
Mark
myName
Barb
Mark
Variables
37
Mark
> String myName = "Sue";
Error: Redefinition of myName
The binding between the name and the data only exists (a) until the name gets
assigned to something else or (b) you quit DrJava or (c) you reset the interactions
pane (by clicking on the Reset button. The relationship between names and data
in the interactions pane only exists during a session of DrJava.
'
$
Common Bug: Redefinition Error
You cant declare a variable with the same name more
than once in the interactions pane. If you do you will get a
Redefinition Error. If you want to start over click the
Reset button in DrJava to let it know that you want to
get rid of all currently defined variables. Or, just remove
the types and you wont be redeclaring the variables, just
changing their values (reusing them).
&
%
2.4.6
main
2005/1/11
page 38
i
38
Chapter 2
Introduction to Java
> System.out.println(name2);
Suzanne Clark
When the compiler encounters the characters inside the pair of double quotes
it creates a String object. The code String name1 creates a variable name1 that
will refer to this string object. Print out name1 to see what it refers to using
System.out.println(name1);. Next the code String name2 = name1; creates
another variable name2 and sets the value of it to refer to the same string. Printing
the new variable name2 will result in the same string being printed.
String Object
Suzanne Clark
name1
name2
An object can only be garbage collected when there are no current references
to it. To allow the String object with the characters "Suzanne Clark" in it to be
garbage collected set the variables that refer to it to null.
> name1 = null;
> System.out.println(name1);
null
> System.out.println(name2);
Suzanne Clark
> name2 = null;
> System.out.println(name2);
null
Now all references to the String object are set to null so the object can be
garbage collected.
String Object
Suzanne Clark
name1
name2
main
2005/1/11
page 39
i
Section 2.5
2.5
Concepts Summary
39
CONCEPTS SUMMARY
This chapter introduced many basic concepts: printing the result of a statement
(expression), math operators, relational operators, types, casting, and variables.
2.5.1
Statements
Java programs are made of statements or expressions. Java statements can end in
semicolons ; just like sentences can end in periods . in English. When you type
statements in the definitions pane (when you define methods) they must have some
sort of punctuation to show the end of the statement. One way to do this is to use
a semicolon ;.
If you leave off the semicolon in the interactions pane it will print the result
of the statement. If you do end a statement with a semicolon in the interactions
pane, and you want to print the result use System.out.println(expression); to
print the result of the expression.
Math Operators
+
Addition
Subtraction
Multiplication
Division
Modulus
mainder)
(Re-
main
2005/1/11
page 40
i
40
2.5.2
2.5.3
Chapter 2
Introduction to Java
Relational Operators
<
Less Than
>
Greater Than
==
Equals
!=
Not Equals
<=
Less Than
Equal
>=
Greater Than or
Equal
or
Types
A type is a description of the kind of thing something is. It affects how much
space is reserved for a variable and how the bits in that space are interpreted. In
this chapter, we talk about several kinds of types (encodings) of data.
Floating
numbers
Integers
Characters
Strings
Booleans
2.5.4
point
Casting
Java compilers recognize integer (-3) and floating point values (32.43). The result
of a mathematical expression depends on the types involved in the expression.
Expressions that involve integer values will have integer results. Expressions that
have floating point (decimal) values will have floating point results.
This can lead to unexpected results.
> 1 / 2
main
2005/1/11
page 41
Section 2.5
Concepts Summary
41
0
There are two ways to fix this problem. One is to make one of the numbers a
floating point number by adding .0 (it doesnt matter which one) and the other is
to use casting to change the type of one of the numbers to a floating point number
(the primitive type float or double).
> 1.0 / 2
0.5
> (double) 1 / 2
0.5
2.5.5
Variables
Variables are used to store and access values. You create variables by declaring
them: type name ; or type name = expression ;. Declaring a variable reserves
space for the variable and allows the computer to map the variable name to the
address of that reserved space.
We introduced two types of variables: primitive and object. Primitive variables are any of the types: int, byte, short, long, float, double, char, or boolean.
Object variables refer to an object of a class. Use the class name as the type when
declaring object variables ClassName name; or ClassName name = expression;.
Primitive variables store a value in the reserved space for that variable. You
can change the value using variableName = value;. You can access the value
using variableName.
Object variables store a reference to an object in the reserved space for that
variable. Object variables do not just store the address of the object. They store
something that allows the address to be determined.
If the object variable doesnt refer to any object yet it has the value null. You
can change the what object it references using variableName = objectReference;.
You can access the referenced object using variableName.
PROBLEMS
2.1. Some computer science concept questions:
What is an object?
What is a class?
What is a type? Why are types important?
What is casting? What is it used for?
What is a variable? When do you need one?
2.2.
main
2005/1/11
page 42
i
42
Chapter 2
Introduction to Java
2.3. Use the interactions pane to calculate how long it will take to travel 770 miles
at an average speed of 60 miles per hour? How much shorter will it take if you
average 70 miles per hour?
2.4. Use the interactions pane to calculate how much money will you make if you
work 40 hours at $13.00 and 10 hours at time and a half?
2.5. Test your understanding of Java with the following:
What does System.out.println(); do?
What does the statement System.out.println(3 + 2); do?
What does the statement System.out.println("The answer is:
2); do?
" + 3 +
do?
2.6. Declare variables for each of the following:
the number of people in your family
the cost of a video game
your name
answer to, Are you righthanded?
the temperature in your room
the number of items in a shopping cart
your grade point average
your telephone number
the number of times you were absent from class
the number of miles from your home to school
answer to, Do you wear glasses?
your credit card number
TO DIG DEEPER
There is a wealth of material for Java on Suns Java web site http://java.sun.com
including tutorials, papers, and APIs. To learn more about DrJava see the web site
http://www.drjava.org/. Thinking in Java by Bruce Eckel is a good book for
those who have some coding experience and like to understand a language deeply.
Beginners might want to start with Headfirst Java by Kathy Sierra and Bert Bates.
main
2005/1/11
page 43
i
C H A P T E R
Introduction to Programming
3.1
3.2
3.3
3.4
3.5
3.6
3.7
main
2005/1/11
page 44
i
44
Chapter 3
Introduction to Programming
humans. If the computer were just a calculator, then remembering names and the
names association with values would be just a waste of the computers memory.
But for humans, its very powerful. It allows us to work with the computer in a
natural way.
A programming language is really a set of names that a computer has encodings for, such that those names make the computer do expected actions and
interpret our data in expected ways. Some of the programming languages names
allow us to define new nameswhich allows us to create our own layers of encoding. We can associate a name with a location in memory, this is called declaring
a variable. We can associate a name with a group of Java statements, we call this
defining a method (function). In Java you can also assign a name to a group of
related variables and methods (functions) when you define a class (type).
Computer Science Idea: Programs are for people,
not computers.
Remember names are only meaningful for people, not computers. Computers just take instructions. A good program
is meaningful (understandable and useful) for humans.
A program is a set of names and their values, where some of these names have
values of instructions to the computer (code). Our instructions will be in the
Java programming language. Combining these two definitions means that the Java
programming language gives us a set of useful names that have a meaning to the
computer, and our programs are then made up of Javas useful names as a way of
specifying what we want the computer to do.
There are good names and bad names. Bad names arent curse words, or
TLAs (Three Letter Acronyms), but names that arent understandable or easy
to use. A good set of encodings and names allow one to describe methods in
a way thats natural, without having to say too much. The variety of different
programming languages can be thought of as a collection of sets of namings-andencodings. Some are better for some tasks than others. Some languages require you
to write more to describe the same program (function) than othersbut sometimes
that more leads to a much more (human) readable program that helps others to
understand what youre saying.
Philosophers and mathematicians look for very similar senses of quality. They
try to describe the world in few words, using an elegant selection of words that cover
many situations, while remaining understandable to their fellow philosophers and
mathematicians. Thats exactly what computer scientists do as well.
How the units and values (data) of a program can be interpreted is often also
named. Remember how we said in Section 1.2 (page 13) that everything is stored
in groups of eight bits called bytes, and we can interpret those bytes as numbers?
In some programming languages, you can say explicitly that some value is a byte,
and later tell the language to treat it as a number, an integer (or sometimes int).
Similarly, you can tell the computer that these series of bytes is a collection of
numbers (an array of integers), or a collection of characters (a String), or even
as a more complex encoding of a single floating point number (any number with a
decimal point in it).
main
2005/1/11
page 45
i
Section 3.2
45
In Java, we will explicitly tell the computer how to interpret our values.
Languages such as Java, C++, and C# are strongly typed . Names are strongly
associated with certain types or encodings. They require you to say that this name
will only be associated with integers, and that one with floating point numbers.
In Java, C++, and C# you can also create your own types which is part of what
makes object-oriented languages so powerful. We do this in Java by defining classes
such as Picture which represents a simple digital picture. An object of the Picture
class has a width and height and you can get and set the pixels of the Picture
object. This isnt a class that is part of the Java language, but a class that we have
defined using Java to make it easier for students to work with digital pictures.
3.2
main
2005/1/11
page 46
i
46
Chapter 3
Introduction to Programming
on your book shelf than can fit on your desk. A computer can fit much more data
on the hard disk than can fit in memory. However, data must be read from disk
into memory before you can work with it.
When you bring things into memory, you usually will name the value, so that
you can retrieve it and use it later. In that sense, programming is something like
algebra. To write generalizable equations and functions (those that work for any
number or value), you wrote equations and functions with variables, like P V = nRT
or e = M c2 or f (x) = sin(x). Those Ps, Vs, Rs, Ts, es, Ms, cs, and xs were
names for values. When you evaluated f (30), you knew that the x was the name
for 30 when computing f . Well be naming values when we program.
3.3
3.3.1
&
%
One of the class methods for the Character class takes a character as the
input value (the value that goes into the box) and returns (the value that comes
out of the box) the number that is the integer value for that character. Characters
main
2005/1/11
page 47
i
Section 3.3
47
in Java are specified between single quotes: A. The name of that function is
getNumericValue and you can use System.out.println to display the value that
the method getNumericValue returns:
> System.out.println(Character.getNumericValue(A));
10
Another class method thats built in to the Math class in Java is named abs
its the absolute value function. It returns the absolute value of the input numeric
value.
> System.out.println(Math.abs(1));
1
> System.out.println(Math.abs(-1));
1
Debugging Tip: Common typos
If you type a class name and Java cant figure out what
class you are taking about you will get an undefined class
error.
> Mat.abs(-3);
Error: Undefined class Mat
If you mistype a method (function) name you will get the
following error:
> Math.ab(-3);
Error: No ab method in java.lang.Math
3.3.2
main
2005/1/11
page 48
i
48
Chapter 3
Introduction to Programming
Fred Farmer
> String lowerName = name.toLowerCase();
> System.out.println(lowerName);
fred farmer
> String upperName = name.toUpperCase();
> System.out.println(upperName);
FRED FARMER
> System.out.println(name);
Fred Farmer
Notice that value of name didnt change even though we invoked toLowerCase
on it. All of the String methods that can modify a string dont change the original
string but instead return a new string with the action done on that string. We say
that strings are immutable, meaning that they dont change.
3.4
3.4.1
Defining Classes
How does the computer know what we mean by a world and a turtle? We have to
define what a world is, what it knows about, and what it can do. We have to define
what a turtle is, what it knows about, and what it can do. We do this by writing
class definitions for World and Turtle. In Java each new class is usually defined
in a file with the same name as the class and an extension of .java. Class names
start with a capital letter and the first letter of each additional word is capitalized.
So we define the class Turtle in the file Turtle.java. We define the class World in
the file World.java. The class Turtle inherits from a class called SimpleTurtle
(notice that the first letter of each additional word is capitalized). We have defined
these classes for you so that you can practice creating and sending messages to
objects.
3.4.2
Creating Objects
Object-oriented programs consist of objects. But, how do we create those objects?
The class knows what each object of that class needs to keep track of and what it
should be able to do, so the class creates the objects of that class. You can think
of the class as an object factory. It can create many objects of that class.
To create and initialize an object use new Class (parameterList ) where the
parameter list is a list of items used to initialize the new object. This asks the object
that defines the class to reserve space in memory for the data that an object of that
main
2005/1/11
page 49
i
Section 3.4
49
class needs to keep track of and also keep a reference to the object that defines the
class. The new objects data will be initialized based on the items passed in the
parameter list. There can be several ways to initialize a new object and which one
you are using depends on the order and types of things in the parameter list.
One way to create an object of the class World use new World(). We dont
have to pass any parameters to initialize the new world. Objects can have default
values.
> System.out.println(new World());
A 640 by 480 world with 0 turtles in it.
When you type the above in the interactions pane you will see a window
appear with the title World as shown in Figure 3.1. We have created an object
of the World class which has a width of 640 and a height of 480. The world doesnt
have any turtles in it yet. We would like to add a turtle to this world, but we have
a problem. We dont have any way to refer to this World object. We didnt declare
a variable that refers to that object in memory, so it will just be garbage collected
after you close the window. Go ahead and close the window and lets try again,
but this time we will declare a variable to let us refer to the World object again.
When we declare a variable we are associating a name with the memory
location so that we can access it again using its name. To declare a variable in
Java you must give the type of the variable and a name for it Type name ; The
Type is the name of the class if you are creating a variable that refers to an object.
So to create a variable that will refer to a World object we need to say the type is
World and give it a name. The first word in the variable name should be lowercase
but the first letter of each additional word should be uppercase. The name should
describe what the variable represents. So, lets declare a variable that refers to an
object of the class World using the name worldObj.
main
2005/1/11
page 50
i
50
Chapter 3
Introduction to Programming
We can create another Turtle object and this time we can say what location
we want it to appear at. To do this we need to pass more than one parameter in
the parameter list of items to use to initialize the new object. To do this separate
the values with commas.
> Turtle turtle2 = new Turtle(30,50,worldObj);
> System.out.println(turtle2);
No name turtle at 30, 50 heading 0.
main
2005/1/11
page 51
i
Section 3.4
51
Notice that the second turtle appears at the specified location (30,50). The
top left of the window is location (0,0). The x values increase going to the right
and the y values increase going down.
FIGURE 3.3: A window that shows two Turtle objects in a World object.
3.4.3
turtle1.forward(20);
turtle1.turnLeft();
turtle1.forward(30);
turtle1.turnRight();
turtle1.forward(40);
turtle1.turn(-45);
turtle1.forward(30);
main
2005/1/11
page 52
i
52
Chapter 3
Introduction to Programming
> turtle1.turn(90);
> turtle1.forward(20);
In Figure 3.4 we see the trail of the first turtles movements. Notice that all of
the messages were sent to the first Turtle object that is referenced by the turtle1
variable. The messages only get sent to that object. Notice that the second Turtle
object didnt move. It didnt get any messages yet. To send a message to the
second Turtle object we use the variable name that refers to that Turtle object
which is turtle2.
>
>
>
>
turtle2.turnRight();
turtle2.forward(200);
turtle2.turnRight();
turtle2.forward(200);
In Figure 3.5 we see the trail of the second turtles movement. Can you draw
a square with a turtle? Can you draw a triangle with a turtle? Can you draw a
pentagon with a turtle? How about a circle?
3.4.4
main
2005/1/11
page 53
i
Section 3.4
53
main
2005/1/11
page 54
i
54
Chapter 3
Introduction to Programming
On the other hand you may not want to see the trail. Ask the turtle to stop
showing the trail by asking it to pick up the pen penUp(). To start showing the
trail again send the turtle the message penDown().
You can ask a turtle to move to a particular location by sending it the message
moveTo(x,y) where x is the x value that you want to move to and y is the y value
that you want to move to.
You can ask a turtle to use a particular name by sending it the message
setName(name ) where name is the new name to use. If you print the variable that
refers to a turtle you will see the name printed. You can also get a turtles name
by sending it the message getName().
We can use these new messages to draw two squares with a turtle. First
reset the interactions pane and create a world and a turtle. Name the turtle
Jane. Draw one square with an upper left corner at (50,50) and a width and
height of 30. Draw another square at (200,200) with a width and height of 30.
We can use the way to create a Turtle object that takes a location to start new
Turtle(x,y,world). Remember that to create an object we ask the class using
new Class (parameterList ). Lets turn off seeing the turtle when we draw the
second square by sending it the message hide().
>
>
>
>
>
>
>
>
>
>
main
2005/1/11
page 55
i
Section 3.4
55
> turtle1.forward(30);
> turtle1.penUp();
> turtle1.moveTo(200,200);
> turtle1.hide();
> turtle1.penDown();
> turtle1.turnRight();
> turtle1.forward(30);
> turtle1.turnRight();
> turtle1.forward(30);
> turtle1.turnRight();
> turtle1.forward(30);
> turtle1.turnRight();
> turtle1.forward(30);
> System.out.println(turtle1);
Jane turtle at 200, 200 heading 0.
main
2005/1/11
page 56
i
56
3.5
Chapter 3
Introduction to Programming
CREATING METHODS
We had to send many messages to our Turtle object just to draw two squares.
Do you notice any similarities in how we draw the squares? Each time we draw a
square we turn right and go forward by 30 steps for a total of 4 times. It would be
nice to name the list of steps for drawing a square and then just do the list of steps
when a turtle is asked to draw a square. We do this by creating a method that
knows how to draw a square. Methods are named blocks of commands that are
defined inside a class definition. Once we have defined a method and successfully
compiled the class definition the objects of the class will respond to a message with
the same name and parameters as the new method. So if we want Turtle objects
to understand the message drawSquare() we define a method drawSquare().
Computer Science Idea: Messages Map to Methods
When we send an object a message it must map to a
method that objects of that class understand. If objects
of the class dont understand the message you will get an
error when you compile. Be sure that the parameter list
is correct because if it isnt you will get an error that says
such a method does not exist. Make sure that you compile
after you add a new method before you try and use it.
You have seen how you declare variables in Java
type name; or type name = expression ;.
To declare a method in Java use:
visibility type methodName(parameterList)
The structure of how you declare a method is referred to as the syntax the
words and characters that have to be there for Java to understand whats going on,
and the order of those things.
A method declaration usually has a visibility (usually the keyword public,
the type of the thing being returned from the method, the method name, and the
parameter list in parentheses. This is usually followed by a block which has curly
braces around the series of commands you want to have executed when the method
is invoked.
Visibility means who can invoke the method (ask for the method to be
executed). If the keyword public is used this method can be invoked by any code
in any class definition. If the keyword private is used then the method can only
be accessed from inside the class definition. You can think of this as a security
feature. If you keep your journal on the web (a blog) then it is open and anyone
can read it. If you keep it hidden in your room then it is private and hopefully only
you can read it.
The return type is required and is given before the method name. If you leave
off a return type you will get a compiler error. If your method returns a value the
return type must match the type of the value returned. Remember that types can
be any of the primitive types (char, byte, int, short, long, float, double, or boolean)
or a class name. Methods that dont return any value use the Java keyword void
main
2005/1/11
page 57
i
Section 3.5
Creating Methods
57
main
2005/1/11
page 58
i
58
Chapter 3
Introduction to Programming
(Figure 3.8).
Program 2: Draw a Square
'
Making it Work Tip: Copying and pasting
Text can be copied and pasted between the interactions
pane and definitions pane. To copy text select it and click
copy (in the Edit menu), then click in the definitions
pane and click on paste (also in the Edit menu). You
can also use keyboard shortcuts for copy (Control-c) and
paste (Control-c). This means to hold the Ctrl key and
then press the c key to copy and hold the Ctrl key and
the v key to paste. You can copy entire methods in the
definitions pane by selecting the text in the method and
then copying and pasting it. You can select a method name
in the definitions pane and paste it in the interactions pane
to send a message asking for that method to be executed.
You can also try things out in the interactions pane and
later save them in a method in the definitions pane.
&
%
Notice that we changed turtle1.turnRight() to this.turnRight(). The
variable turtle1 isnt defined inside the method drawSquare(). Variables names
are known in a context (area that they apply). The variables that we define in
the interactions pane are only known in the interactions pane, they arent known
inside methods. We need some other way to reference the object that we want to
turn. Object methods are implicitly passed a reference to the object the method
was invoked on. You can refer to that current object using the keyword this.
Compiling a Java class definition Turtle.java will produce a Turtle.class
file. Compiling translates the Java source code which is in a format that humans
understand into a format that computers understand. One of the advantages to
Java is that the .class files arent specific to any particular type of computer.
They can be understood by any computer that has a Java run-time environment.
So you can create your Java source code on a Windows based computer and run
main
2005/1/11
page 59
i
Section 3.5
Creating Methods
59
2. Compile
1. Type method
here
&
%
This code creates a method with the name drawSquare that takes no parameters and whenever the method is executed it will execute the statements inside of
the open and close curly braces. It is a public method. It doesnt return anything so
it uses the keyword void to indicate this. This method must be called on an object
of the Turtle class. The this is a keyword that refers to the object this method
was invoked on. Since this method is defined in the Turtle class the keyword this
will refer to a Turtle object.
Once the method has successfully compiled you can ask for it to be executed
by sending a message to a Turtle object with the same name and parameter list
as the method. Click on the Interactions tab in the interactions pane (near the
bottom of the window). This method doesnt take any parameters so just finish
with the open and close parenthesis and the semicolon. When you compile the
interactions pane will be reset, meaning that all the variables that we have defined
main
2005/1/11
page 60
i
60
Chapter 3
Introduction to Programming
main
2005/1/11
page 61
Section 3.5
Creating Methods
61
that it is the second version. We can copy the first method and paste it and rename
it and then change it to declare and use the width variable.
Program 3: Draw Square Using a Variable for Width
Compile and run this method and check that you get the same results as with
drawSquare().
> World world = new World();
> Turtle turtle1 = new Turtle(world);
> turtle1.drawSquare2();
3.5.1
main
2005/1/11
page 62
i
62
Chapter 3
Introduction to Programming
/
Method t o draw a s q u a r e w i t h a w i d t h and h e i g h t
o f some p a s s e d amount .
@param w i d t h t h e w i d t h and h e i g h t t o u s e
/
public void drawSquare ( i n t width )
{
this . turnRight ( ) ;
t h i s . f o r w a r d ( width ) ;
this . turnRight ( ) ;
t h i s . f o r w a r d ( width ) ;
this . turnRight ( ) ;
t h i s . f o r w a r d ( width ) ;
this . turnRight ( ) ;
t h i s . f o r w a r d ( width ) ;
}
Type in the new method declaration and compile. Lets try this new method
out.
When you send the message drawSquare(200) you are telling it to substitute
200 for the parameter width everywhere it appears in the method drawSquare.
The parameter name width is known throughout the body of the method. This is
very similar to drawSquare2() but has the advantage that we dont need to change
the method and recompile to use a different width.
An important reason for using parameters is to make a method more general.
Consider method drawSquare(int width). That method handles the general case
of drawing a square. We call that kind of generalization abstraction. Abstraction
main
2005/1/11
page 63
i
Section 3.5
Creating Methods
63
FIGURE 3.10: Showing the result of sending the width as a parameter to drawSquare
&
%
Defining a method that takes input is very easy. It continues to be a matter
of substitution and evaluation. Well put a type and name inside those parentheses
after the method name. The names given inside the parentheses are called the
parameters or input variables.
When you evaluate the function, by specifying its name with input values (also
main
2005/1/11
page 64
i
64
Chapter 3
Introduction to Programming
3.6.1
main
2005/1/11
page 65
i
Section 3.6
65
the default is that all of the pixels in the picture are white. How can we create a
picture from data in a file from a digital camera? We can use new Picture(String
fileName) which takes an object of the String class which is the full file name of
a file to read the picture information from.
What is the full file name of a file? The full or complete name of a file is
the path to the file as well as the base file name and extension. How can we get
the full file name for a file? One way is to use another class we have created for
you. The FileChooser class has a class method pickAFile() which will display a
dialog window that will help you pick a file.
> System.out.println(FileChooser.pickAFile());
Youre probably already familiar with how to use a file chooser or file dialog
like this:
Double-click on folders/directories to open them.
Click on the top right iconic button to see the details about the files such as
the types of files they are (if you put the cursor over the button and leave it
there it will show Details). To create a picture we want to pick a file with
a type of JPEG Image. To create a sound we would pick a file with a type
of Wave Sound.
main
2005/1/11
page 66
i
66
Chapter 3
Introduction to Programming
Click on the file name to select it and then click Open, or double-click, to
select a file.
Once you select a file, what gets returned is the full file name as a string (a
sequence of characters). (If you click Cancel, pickAFile() returns null which is
a predefined value in Java that means nothing. Try it, type the code below after
the > in the interactions pane and select a file by clicking the mouse button when
the cursor points to the desired file name, then click on the Open button.
> System.out.println(FileChooser.pickAFile());
C:\intro-prog-java\mediasources\flower1.jpg
What you get when you finally select a file will depend on your operating
system. On Windows, your file name will probably start with C: and will have
backslashes in it (e.g., \). There are really two parts to this file name:
The character between words (e.g., the \ between intro-prog-java and mediasources) is called the path separator . Everything from the beginning of
the file name to the last path separator is called the path to the file. That
describes exactly where on the hard disk (in which directory) a file exists.
A directory is like a drawer of a file cabinet and it can hold many files. A
directory can even hold other directories.
The last part of the file (e.g. cat.jpg) is called the base file name. When you
look at the file in the Finder/Explorer/Directory window (depending on your
operating system), thats the part that you see. Those last three characters
(after the period) is called the file extension. It identifies the encoding of the
file. You may not see the extension depending on the settings you have. But,
if you show the detail view (top right iconic button on the file chooser) you
will see the file types. Look for files of type JPEG Image.
Files that have an extension of .jpg or a type of JPEG Image are JPEG
files. They contain pictures. (To be picky, they contain data that can be interpreted to be a representation of a picture but thats close enough to they contain
pictures.) JPEG is a standard encoding (a representation) for any kind of image.
The other kind of media files that well be using frequently are .wav files (Figure 3.13). The .wav extension means that these are WAV files. They contain
sounds. WAV is a standard encoding for sounds. There are many other kinds of
extensions for files, and there are even many other kinds of media extensions. For
example, there are also GIF (.gif) files for images and AIFF (.aif or .aiff)
files for sounds. Well stick to JPEG and WAV in this text, just to avoid too much
complexity.
3.6.2
Showing a Picture
So now we know how to get a complete file name: Path and base name. This
doesnt mean that we have the file itself loaded into memory. To get the file into
memory, we have to tell Java how to interpret this file. We know that JPEG files
are pictures, but we have to tell Java explicitly to read the file and make a Picture
object from it (an object of the Picture class).
main
2005/1/11
page 67
i
Section 3.6
67
The way we create and initialize new objects in Java is to ask the class to
create a new object using new ClassName (parameterList ). The class contains
the description of the data each object of the class needs to have so it is the thing
that knows how to create objects of that class. You can think of a class as a factory
for making objects of that class. So, to create a new object of the Picture class
from a file name use new Picture(fileName). The fileName is the name of a file
as a string. We know how to get a file name using FileChooser.pickAFile().
> System.out.println(new Picture(FileChooser.pickAFile()));
Picture, filename
C:\intro-prog-java\mediasources\partFlagSmall.jpg height 217 width
139
The result from System.out.println suggests that we did in fact make a
Picture object, from a given filename and with a given height and width. Success!
Oh, you wanted to actually see the picture? Well need another method! (Did I
mention somewhere that computers are stupid?) The method to show the picture
is named show().
You ask a Picture object to show itself using the method show(). It may
seem strange to say that a picture knows how to show itself but in object-oriented
programming we treat objects as intelligent beings that know how to do the things
that we would expect an object to be able to do, or that someone would want to
do to it. We typically show pictures, so in object-oriented programming Picture
objects know how to show themselves (make themselves visible).
main
2005/1/11
page 68
i
68
3.6.3
Chapter 3
Introduction to Programming
Variable Substitution
We can now pick a file, make a picture, and show it in a couple of different ways.
We can do it all at once because the result from one method can be used in the
next method: new Picture(FileChooser.pickAFile()).show(). Thats
what we see in figure 3.14. This code will first invoke the pickAFile() class
method of the class FileChooser because it is inside the parentheses. The
pickAFile() method will return the name of the selected file as a string.
Next it will create a new Picture object with the selected file name. And
finally it will ask the created Picture object to show itself.
FIGURE 3.14: Picking, making, and showing a picture, using the result of each
method in the next method. The picture used is matt-spaceman.jpg.
The second way is to name each of the pieces by declaring variables. To declare a variable (a name for data) use type name; or type name=expression ;.
main
2005/1/11
page 69
i
Section 3.6
69
'
&
Try the following in the interactions pane. Pick a file name that ends in .jpg.
As you can see we can name the file that we get from FileChooser.pickAFile()
by using using ( String fileName =). This says that the variable named fileName
will be of type String (will refer to an object of the String class) and that the
String object that it will refer to will be returned from FileChooser.pickAFile().
In a similar fashion we can create a variable named pictureObj that will refer to
an object of the Picture class that we get from creating a new Picture object
with the fileName using Picture pictureObj = new Picture(fileName). We
can then ask that Picture object to show itself by sending it the show() message
main
2005/1/11
page 70
i
70
Chapter 3
Introduction to Programming
%
Debugging Tip: Methods names must be followed
by parentheses!
In Java all methods (functions) have to have parentheses
after the method name both when you declare the method
and when you invoke it. You cant leave off the parentheses
even if the method doesnt take any parameters. So, you
must type picture.show() not picture.show.
If you try picture.show(), youll notice that there is no output from this
method. Methods in Java dont have to return a value, unlike real mathematical
functions. A method may just do something (like display a picture).
3.6.4
Object references
When the type of a variable is int or double or boolean we call that a primitive
variable. As you have seen when a primitive variable is declared space is reserved
to represent that variables value and the name is used to find the address of that
reserved space.
When the type of a variable is the name of a class (like String) then this
is called an object variable or object reference. Unlike primitive variables, object
variables do not reserve space for the value of the variable. How could they? How
much space do you need for an object? How about an object of the class String?
How about an object of the class Picture? The amount of space you need for an
object depends on the number and types of fields (data) each object of that class
has.
Object variables (references) reserve space for a reference to an object of the
given class. A reference allows the computer to determine the address of the actual
object (it isnt just the address of the object). If the object variable is declared but
not assigned to an object the reference is set to null which means that it doesnt
main
2005/1/11
page 71
i
Section 3.6
71
FIGURE 3.15: Picking, making, and showing a picture, when naming the pieces. The
picture shown is katie.jpg.
Playing a Sound
We can replicate this entire process with sounds.
We still use FileChooser.pickAFile() to find the file we want and get its
file name.
We use new Sound(fileName ) to make a Sound object using the passed
fileName as the file to read the sound information from.
We will use play() to play the sound. The method play() is an object
method (invoked on a Sound object). It plays the sound one time. It doesnt
return anything.
Here are the same steps we saw previously with pictures:
> System.out.println(FileChooser.pickAFile());
C:\intro-prog-java\mediasources\croak.wav
> System.out.println(new Sound(FileChooser.pickAFile()));
Sound file: croak.wav length: 17616
> new Sound(FileChooser.pickAFile()).play();
main
2005/1/11
page 72
i
72
Chapter 3
Introduction to Programming
3.6.7
main
2005/1/11
page 73
i
Section 3.6
73
Notice that the algebraic notions of subsitution and evaluation work here
as well. Picture myPicture = new Picture(myFileName) causes the exact same
picture to be created as if we had executed
Picture myPicture = new Picture(FileChooser.pickAFile())1 , because we set
myFileName to be equal to the result of FileChooser.pickAFile();. The values
get substituted for the names when the expression is evaluated. new Picture(myFileName)
is an expression which, at evaluation time, gets expanded into
new Picture (C:\intro-prog-java\mediasources\katie.jpg)
because C:\intro-prog-java\mediasources\katie.jpg is the name of the file
that was picked when FileChooser.pickAFile() was evaluated and the returned
value was named myFileName.
We can also replace the method (function) invocations (function calls) with
the value returned. FileChooser.pickAFile() returns a String objecta bunch
of characters enclosed inside of double quotes. We can make the last example work
like this, too.
'
$
Common Bug: Backslashes and Slashes
You
have
seen
the
names
of
files
displayed
with
backslashes
in
them,
such
as
C:\intro-prog-java\mediasources\beach.jpg. However, when you create an object of the String class in
Java you might not want to use backslashes because they
are used to create special characters in strings like tab or
newline. You can use slashes / instead as a path separator C:/intro-prog-java/mediasources/beach.jpg.
Java can still figure out the path name when you
use slashes.
You can still use backslashes in the
full path name, but you need to double each one
C:\\intro-prog-java\\mediasources\\beach.jpg.
&
%
> String myFileName =
"C:/intro-prog-java/mediasources/katie.jpg";
> System.out.println(myFileName);
C:/intro-prog-java/mediasources/katie.jpg
> Picture myPicture = new Picture(myFileName);
> System.out.println(myPicture);
Picture, filename C:/intro-prog-java/mediasources/katie.jpg height
360 width 381
Or even substitute for the name.
> Picture aPicture = new
Picture("C:/intro-prog-java/mediasources/katie.jpg");
> System.out.println(aPicture);
Picture, filename C:/intro-prog-java/mediasources/katie.jpg height
360 width 381
1 Assuming,
main
2005/1/11
page 74
i
74
Chapter 3
Introduction to Programming
CONCEPTS SUMMARY
This chapter introduced many concepts: invoking object and class methods, creating objects, and how to create new methods.
3.7.1
3.7.2
3.7.3
Creating Objects
To create an object ask the class to create and initialize a new object.
new ClassName(parameterList)
main
2005/1/11
page 75
i
Section 3.7
3.7.4
Concepts Summary
75
If the method doesnt return a value use the keyword void as the return
type. Each parameter in the parameter list has a type and name. Parameters are
separated by commas. Method and parameter names start with a lowercase letter,
but the first letter of each additional word is capitalized.
OBJECTS AND METHODS SUMMARY
In this chapter, we talk about several kinds of encodings of data (or objects).
Pictures
Sounds
Strings
Turtles
Worlds
main
2005/1/11
page 76
i
76
Chapter 3
Introduction to Programming
Character.getNumericValue(Character character)
FileChooser.pickAFile()
Math.abs(int number)
picture.show()
sound.play()
turtle.forward(int numberOfSteps)
turtle.setPenDown(boolean value)
turtle.hide()
turtle.moveTo(int x, int y)
turtle.penDown()
turtle.penUp()
turtle.show()
turtle.turn(int angle)
turtle.turnLeft()
turtle.turnRight()
main
2005/1/11
page 77
i
Section 3.7
Concepts Summary
77
PROBLEMS
3.1. Some computer science concept questions:
What is an algorithm?
What is a program?
What is Moores Law?
What is a method?
What creates new objects?
What does pass by value mean?
3.2. Test your understanding of Java with the following:
What does picture.show() do?
What does FileChooser.pickAFile() do?
What does turtle.turnLeft() do?
What does turtle.forward() do?
What does turtle.turn(-45) do?
What does turtle.turn(45) do?
What does turtle.penUp() do?
What does turtle.hide() do?
3.3. Which of the following are class methods and which are object methods? How
do you tell which are which?
Math.abs(-3);
sound.play();
FileChooser.pickAFile();
picture.show();
ColorChooser.pickAColor();
turtle.turnLeft();
3.4. How many and what kind of variables (primitive or object) are created in the
code below? Draw what the variables look like.
> String fileName = FileChooser.pickAFile();
> Picture p = new Picture(fileName);
> p.show();
3.5. How many and what kind of variables (primitive or object) are created in the
code below? Draw what the variables look like.
>
>
>
>
>
>
house.
3.8. Create a World object and a Turtle object and use the Turtle object to draw a
star.
main
2005/1/11
page 78
i
78
Chapter 3
Introduction to Programming
TO DIG DEEPER
The best (deepest, most material, most elegant) computer science textbook is Structure and Interpretation of Computer Programs [2], by Abelson, Sussman, and Sussman. Its a hard book to get through, though. Somewhat easier, but in the same
spirit is the new book How to Design Programs [9].
Neither of these books are really aimed at students who want to program
because its fun or because they have something small that they want to do. Theyre
really aimed at future professional software developers. The best books aimed at
the less hardcore user are by Brian Harvey. His book Simply Scheme uses the
same programming language as the earlier two, Scheme, but is more approachable.
My favorite of this class of books, though, is Brians three volume set Computer
Science Logo Style [16] which combine good computer science with creative and fun
projects.
main
2005/1/11
page 79
i
P A R T
T W O
PICTURES
Chapter 4
Chapter 5
Chapter 6
Chapter 7
Drawing
79
main
2005/1/11
page 80
i
C H A P T E R
main
2005/1/11
page 81
i
Section 4.1
81
59
39
16
10
-1
...
Every time you join a line (queue) of people you are in something like an
array. All you usually care about is how far you are from the front of the line. If
you are at the front of the line then that is index 0 (you are next). If you are the
second one in line then you are at index 1 (there is one person in front of you). If
you are the third person in line then you are at index 2 (there are two people in
front of you).
Arrays are a great way to store lots of data of the same type. You wouldnt
want to create a different variable for every pixel in a picture when there are thousands of pixels in a picture. Instead you use an array of pixels. You still need a way
to refer to a particular pixel so we use an index for that. You can access elements of
an array in Java using arrayName[index]. For example, to access the first element
in an array variable named pixels use pixels[0]. To access the second element use
pixels[1]. To access the third element use pixels[2]. You can get the number
of items in an array using arrayName.length. So, to access the last element in the
main
2005/1/11
page 82
i
82
Chapter 4
&
%
A two-dimensional array is a matrix . A matrix is a collection of elements
arranged in both a horizontal and vertical sequence. For one dimensional arrays
you would talk about an element at index i, that is array[i]. For two-dimensional
arrays you talk about an element at column i and row j, that is, matrix[i][j].
In Figure 4.2, you see an example matrix. At coordinates (0, 0) (horizontal,
vertical), youll find the matrix element whose value is 15. The element at(1, 1) is
7, (2, 1) is 43, and (3, 1) is 23. We will often refer to these coordinates as (x, y)
((horizontal, vertical).
Have you ever played the game battleship? If you have then you had to specify
both the row and column of your guess (B-3) This means row B and column 3
(Figure 4.3). Have you ever gone to a play? Usually your ticket has a row and
seat number (row E, seat 10). These are both examples of two-dimensional arrays.
Picture data is also represented as a two-dimensional array.
Whats stored at each element in the picture is a pixel . The word pixel is
short for picture element. Its literally a dot, and the overall picture is made up
of lots of these dots. Have you ever taken a magnifying glass to pictures in the
newspaper or magazines, or to a television or even your own computer monitor?
(Figure 4.4 was generated by capturing as an image the of the top left part of the
DrJava window and then magnifying it 600%. Its made up of many, many dots.
When you look at the picture in the magazine or on the television, it doesnt look
like its broken up into millions of discrete spots, but it is.
main
2005/1/11
page 83
i
Section 4.1
83
FIGURE 4.3: The top left corner of the battleship guess board with a miss at B-3.
FIGURE 4.4: Upper left corner of DrJava window with part magnified 600%
You can get a similar view of individual pixels using the picture explorer,
which is discussed later in this chapter. The picture explorer allows you to zoom a
picture up to 500% so that each individual pixel is visible (Figure 4.5).
Our human sensor apparatus cant distinguish (without magnification or other
special equipment) the small bits in the whole. Humans have low visual acuitywe
dont see as much detail as, say, an eagle. We actually have more than one kind
of vision system in use in our brain and our eyes. Our system for processing color
is different than our system for processing black-and-white (or luminance). We
actually pick up luminance detail better with the sides of our eyes than the center
of our eye. Thats an evolutionary advantage since it allows you to pick out the
sabertooth sneaking up on you from the side.
That lack of resolution in human vision is what makes it possible to digitize
pictures. Animals that perceive greater details than humans (e.g., eagles or cats)
may actually see the individual pixels. We break up the picture into smaller elements (pixels), but there are enough of them and they are small enough that the
picture doesnt look choppy when looked at it overall. If you can see the effects
of the digitization (e.g., lines have sharp edges, you see little rectangles in some
spots), we call that pixelizationthe effect when the digitization process becomes
obvious.
Picture encoding is actually more complex than sound encoding. A sound
main
2005/1/11
page 84
i
84
Chapter 4
FIGURE 4.5: Image shown in the picture explorer: 100% image on left and 500% on
right (close-up of the branch over the mountain)
Color Respresentations
Visible light in continuousvisible light is any wavelength between 370 and 730
nanometers (0.00000037 and 0.00000073 meters). But our perception of light is
limited by how our color sensors work. Our eyes have sensors that trigger (peak)
around 425 nanometers (blue), 550 nanometers (green), and 560 nanometers (red).
Our brain determines what a particular color based on the feedback from these
three sensors in our eyes. There are some animals with only two kinds of sensors,
like dogs. Those animals still do perceive color, but not the same colors nor in
the same way as humans do. One of the interesting implications of our limited
visual sensory apparatus is that we actually perceive two kinds of orange. There is
a spectral visiona particular wavelength that is natural orange. There is also a
mixture of red and yellow that hits our color sensors just right that we perceive as
the same orange.
Based on how we perceive color, as long as we encode what hits our three
kinds of color sensors, were recording our human perception of color. Thus, we can
encode each pixel as a triplet of numbers. The first number represents the amount
of red in the pixel. The second is the amount of green, and the third is the amount
of blue. We can make up any human-visible color by combining red, green, and
blue light (Figure 4.6) (replicated at Figure 5.17 (page 176). Combining all three
gives us pure white. Turning off all three gives us black. We call this the RGB
color model .
There are other models for defining and encoding colors besides the RGB color
model. Theres the HSV color model which encodes Hue, Saturation, and Value
(sometimes also called the HSB color model for Hue, Saturation, and Brightness).
The nice thing about the HSV model is that some notions, like making a color
lighter or darker map cleanly to it, e.g., you simply change the saturation
main
2005/1/11
page 85
i
Section 4.1
85
FIGURE 4.6: Merging red, green, and blue to make new colors
(Figure 4.7). Another model is the CMYK color model , which encodes Cyan,
Magenta, Yellow, and blacK (B could be confused with Blue). The CMYK model
is what printers usethose are the inks they combine to make colors. However, the
four elements means more to encode on a computer, so its less popular for media
computation. RGB is the most popular model on computers.
main
2005/1/11
page 86
i
86
Chapter 4
dont cover the entire space of color (nor luminance) that we can perceive. So, the
24 bit RGB model is adequate until technology advances.
There are computer models that use more bits per pixel. For example, there
are 32 bit models which use the extra 8 bits to represent transparencyhow much
of the color below the given image should be blended with this color? These
additional 8 bits are sometimes called the alpha channel . There are other models
that actually use more than 8 bits for the red, green, and blue channels, but they
are uncommon.
We actually perceive borders of objects, motion, and depth through a separate
vision system. We perceive color through one system, and luminance (how light/dark things are) through another system. Luminance is not actually the amount
of light, but our perception of the amount of light. We can measure the amount of
light (e.g., the number of photons reflected off the color) and show that a red and a
blue spot each are reflecting the same amount of light, but well perceive the blue as
darker. Our sense of luminance is based on comparisons with the surroundingsthe
optical illusion in Figure 4.8 highlights how we perceive gray levels. The two end
quarters are actually the same level of gray, but because the two mid quarters end
in a sharp contrast of lightness and darkness, we perceive that one end is darker
than the other.
FIGURE 4.8: The ends of this figure are the same colors of gray, but the middle two
quarters contrast sharply so the left looks darker than the right
Most tools for allowing users to pick out colors let the users specify the color
as RGB components. The Macintosh offers RGB sliders in its basic color picker
(Figure 4.9). The color chooser in Java offers a similar set of sliders (Figure 4.10).
As mentioned a triplet of (0, 0, 0) (red, green, blue components) is black,
and (255, 255, 255) is white. (255, 0, 0) is pure red, but (100, 0, 0) is red, toojust
darker. (0, 100, 0) is a dark green, and (0, 0, 100) is a dark blue.
When the red component is the same as the green and as the blue, the resultant color is gray. (50, 50, 50) would be a fairly dark gray, and (150, 150, 150) is a
lighter gray.
The Figure 4.11 (replicated at Figure 5.18 (page 176) in the color pages) is
a representation of pixel RGB triplets in a matrix representation. Thus, the pixel
at (1, 0) has color (30, 30, 255) which means that it has a red value of 30, a green
value of 30, and a blue value of 255its a mostly blue color, but not pure blue.
Pixel at (2, 1) has pure green but also more red and blue ((150, 255, 150)), so its a
fairly light green.
Images on disk and even in computer memory are usually stored in some kind
main
2005/1/11
page 87
i
Section 4.1
87
of compressed form. The amount of memory needed to represent every pixel of even
small images is pretty large (Table 4.1). A fairly small image of 320 pixels across by
240 pixels wide, with 24-bits per pixel, takes up 230, 400 bytesthats roughly 230
kilobytes (1000 bytes) or 1/4 megabyte (million bytes). A computer monitor with
1024 pixels across and 768 pixels vertically with 32-bits per pixel takes up over 3
main
2005/1/11
page 88
i
88
Chapter 4
24-bit color
32-bit color
320x240 image
230, 400 bytes
307, 200
640x480
921, 600
1, 228, 800
1024x768
2, 359, 296
3, 145, 728
MANIPULATING PICTURES
We manipulate a picture in DrJava by making a picture object out of a JPEG file,
then changing the pixels in that picture. We change the pixels by changing the color
associated with the pixelby manipulating the red, green, and blue components.
We make a picture using new Picture(fileName ). We make the picture
appear with the method show(). We can also explore a picture with the method
main
2005/1/11
page 89
i
Section 4.2
Manipulating Pictures
89
explore(). These are both object methods so they must be called on an object of
the class that understands the method. This means that show() and explore()
must be called on a Picture object (object of the Picture class) using dot notation
as in pictureObject.show().
> String fileName = FileChooser.pickAFile();
> System.out.println(fileName);
c:\intro-prog-java\mediasources\caterpillar.jpg
> Picture pictureObject = new Picture(fileName);
> pictureObject.show();
> System.out.println(pictureObject);
Picture, filename c:\intro-prog-java\mediasources\caterpillar.jpg
height 150 width 329
What new Picture(fileName) does is to scoop up all the bytes in the input
filename, bring them in to memory, reformat them slightly, and place a sign on
them This is a picture object! When you execute Picture pictureObject =
new Picture(fileName), you are saying The name pictureObject is referring
to a Picture object created from the contents of the file.
Picture objects know their width and their height. You can query them with
the methods getWidth() and getHeight().
> System.out.println(pictureObject.getWidth());
329
> System.out.println(pictureObject.getHeight());
150
We can get any particular pixel from a picture using getPixel(x,y)where x
and y are the coordinates of the pixel desired. The x coordinate starts at 0 at the
top left of the picture and increases horizontally. The y coordinate starts at 0 at the
top left of the picture and increases vertically. We can also get a one-dimensional
array containing all the pixels in the picture using the method getPixels(). This
just grabs all the pixels in the first column from top to bottom and then all the
pixels in the second column from top to bottom and so on till it has all of the pixels.
> Pixel pixelObject = pictureObject.getPixel(0,0);
> System.out.println(pixelObject);
Pixel red=252 green=254 blue=251
> Pixel[] pixelArray=pictureObject.getPixels();
> System.out.println(pixelArray[0]);
Pixel red=252 green=254 blue=251
Pixels know where they came from. You can ask them their x and y coordinates with getX() and getY().
> System.out.println(pixelObject.getX());
0
> System.out.println(pixelObject.getY());
0
main
2005/1/11
page 90
i
90
Chapter 4
Each pixel object knows how to get the red value getRed() and set the red
value setRed(redValue). (Green and blue work similarly.)
> System.out.println(pixelObject.getRed());
252
> pixelObject.setRed(0);
> System.out.println(pixelObject.getRed());
0
You can ask a pixel object for its color with getColor(), and you can ask the
pixel object to set the color with setColor(color). Color objects (objects of the
class Color in package java.awt) know their red, green, and blue components. You
can also create new Color objects with new Color(redValue,greenValue,blueValue)
(the color values must be between 0 and 255). The Color class also has several
colors predefined that you can use. If you need a color object that represents the
color black you can use Color.black or Color.BLACK, for yellow use Color.yellow
or Color.YELLOW. Other colors that are predefined are: Color.blue, Color.green,
Color.red, Color.gray, Color.orange, Color.pink, Color.cyan, Color.magenta, and
Color.white (or use all capitals for the color names). Notice that this is accessing
fields on the Color class, not invoking class methods (no parentheses). Public class
main
2005/1/11
page 91
i
Section 4.2
Manipulating Pictures
91
&
main
2005/1/11
page 92
i
92
Chapter 4
changed. However you wont see the change until the picture repaints.
> System.out.println(pictureObject.getPixel(0,0));
Pixel red=0 green=100 blue=0
'
$
Common Bug: Seeing changes in the picture
If you show your picture, and then change the
pixels, you might be wondering, Where are the
changes?!? Picture displays dont automatically update. If you ask the Picture object to repaint using
pictureObject.repaint(), the display of the Picture
object will update.
&
%
You can automatically get a darker or lighter color from a Color object with
colorObj.darker() or colorObj.brighter(). (Remember that this was easy in
HSV, but not so easy in RGB. These functions do it for you.)
> Color testColorObj = new Color(168,131,105);
> System.out.println(testColorObj);
java.awt.Color[r=168,g=131,b=105]
> testColorObj = testColorObj.darker();
> System.out.println(testColorObj);
java.awt.Color[r=117,g=91,b=73]
> testColorObj = testColorObj.brighter();
> System.out.println(testColorObj);
java.awt.Color[r=167,g=130,b=104]
Notice that even though we darken the color and then brighten it the final
color doesnt exactly match the original color. This is due to rounding errors. A
rounding error is when calculations are done in floating point but the answer is
stored in an integer. The floating point result cant fit in the type of the result
(integer) and so some of the detail is lost.
You can also get a color using ColorChooser.pickAColor(), which gives you
a variety of ways of picking a color. ColorChooser is a class that we have created to
make it easy for you to pick colors using the Java class javax.swing.JColorChooser.
> import java.awt.Color;
> Color pickedColorObj = ColorChooser.pickAColor();
> System.out.println(pickedColorObj);
java.awt.Color[r=51,g=255,b=102]
When you have finished manipulating a picture, you can write it out to a file
with write(fileName).
> pictureObject.write("newPicture.jpg");
main
2005/1/11
page 93
i
Section 4.2
Manipulating Pictures
93
'
$
Common Bug: Saving a file quicklyand how to
find it again!
What if you dont know the whole path to a directory of
your choosing? You dont have to specify anything more
than the base name. The problem is finding the file again!
In what directory did it get saved? This is a pretty simple
bug to resolve. The default directory (the one you get if
you dont specify a path) is wherever DrJava is.
&
%
We dont have to write new functions to manipulate pictures. We can do it
from the command area using the methods (functions) just described. Please reset
the interactions pane by clicking the Reset button at the top of DrJava before you
do the following.
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
import java.awt.Color;
String fileName = "C:/intro-prog-java/mediasources/caterpillar.jpg";
Picture picture = new Picture(fileName);
picture.show();
picture.getPixel(10,100).setColor(Color.black);
picture.getPixel(11,100).setColor(Color.black);
picture.getPixel(12,100).setColor(Color.black);
picture.getPixel(13,100).setColor(Color.black);
picture.getPixel(14,100).setColor(Color.black);
picture.getPixel(15,100).setColor(Color.black);
picture.getPixel(16,100).setColor(Color.black);
picture.getPixel(17,100).setColor(Color.black);
picture.getPixel(18,100).setColor(Color.black);
picture.getPixel(19,100).setColor(Color.black);
picture.repaint();
picture.explore();
'
$
Making it Work Tip: Reuse the previous line in
DrJava
You can use the up arrow on the keyboard to bring up
previous lines you have typed in the interactions pane in
DrJava. You can then use the left arrow key to get to
a character to correct or change and then execute it by
pressing the Enter key.
&
%
The result showing a small black line on the left side below the middle of the
main
2005/1/11
page 94
i
94
Chapter 4
leaf appears in Figure 4.12. The black line is 100 pixels down, and the pixels 10
though 19 from the left edge have been turned black.
FIGURE 4.12: Directly modifying the pixel colors via commands: Note the small
black line on the left under the line across the leaf
4.2.1
Exploring Pictures
On your CD, you will find the MediaTools application with documentation for
how to get it started. You can also open a picture explorer in DrJava. Both the
MediaTools application and the picture explorer will let you get pixel information
from a picture. You can see the picture explorer in Figure 4.13 and the MediaTools
application appears in Figure 4.14. Both of these will display the x, y, red, green,
and blue values for a pixel. They will also both let you zoom in or out.
main
2005/1/11
page 95
i
Section 4.3
95
the pixel that you want to examine. You can also type in the x and y values and
press Enter to see the pixel information for a particular pixel.
The MediaTools application works from files on the disk. If you want to check
out a file before loading into DrJava, use the MediaTools application. Click on the
Picture Tools box in MediaTools, and the tools will open. Use the Open button
to bring up a file selection boxyou click on directories you want to explore on the
left, and images you want on the right, then click OK. When the image appears,
you have several different tools available. Move your cursor over the picture and
press down with the mouse button.
The red, green, and blue values will be displayed for the pixel youre pointing
at. This is useful when you want to get a sense of how the colors in your
picture map to numeric red, green, and blue values. Its also helpful if youre
going to be doing some computation on the pixels and want to check the
values.
The x and y position will be display for the pixel youre point at. This is
useful when you want to figure out regions of the screen, e.g., if you want to
process only part of the picture. If you know the range of x and y coordinates
where you want to process, you can tune your program to reach just those
sections.
Finally, a magnifier is available to let you see the pixels magnified. (The
magnifier can be clicked and dragged around.)
4.3
main
2005/1/11
page 96
i
96
Chapter 4
effects by simply tweaking those values. Many of Adobe Photoshops filters do just
what were going to be doing in this section.
The way that were going to be manipulating colors is by computing a percentage of the original color. If we want 50% of the amount of red in the picture,
were going to set the red channel to 0.50 times whatever it is right now. If we want
to increase the red by 25%, were going to set the red to 1.25 times whatever it is
right now. Recall that the asterisk (*) is the operator for multiplication in Java.
4.3.1
'
Making it Work Tip: Variable Names
Earlier we used pictureObject and pixelObject as variable names for a Picture object and Pixel object. These
are fine names but a little long. We were using them to
point out that these are object variables. Above we used
pict and currPixel as variable names for a Picture object and Pixel object. The names dont matter to the
computer. The computer wouldnt care if you named the
objects yellow and blue. However, it is best to use names
that make sense to people and indicate what the variables
represent.
&
%
The above code is pretty tedious to write, especially for all of the pixels in
even a small image. What we need is way of telling the computer to do the same
thing over and over again. Well, not exactly the same thingwe want to change
whats going on in a well-defined way. We want to take one step each time, or
process one additional pixel.
We can do that with a while loop. A while loop executes a statement
(command) or group of commands in a block (inside open and close curly braces).
A while loop continues executing until a continuation test is false. When the
main
2005/1/11
page 97
Section 4.3
97
continuation test is false execution continues with the statement following the while
loop.
The syntax for a while loop is:
while ( t e s t )
{
/ commands t o be done go h e r e /
}
main
2005/1/11
page 98
i
98
Chapter 4
false
while (expression)
true
Statement(s)
change loop
variable(s)
Statement(s)
in t count = 0 ;
while ( count < t a r g e t )
{
// commands t o be done i n s i d e l o o p
count = count + 1 ;
}
What if you want to write out the same sentence 5 times. You know how
to print out a string using System.out.println("some string"); So, put this in
the body of the loop. Start the count at 0 and increment it each time after the
string is printed. When the count is 5 the string will have been printed 5 times so
stop the loop.
> int count = 0;
> while (count < 5)
{
System.out.println("This is a test.");
count = count + 1;
}
This is a test.
This is a test.
This is a test.
This is a test.
main
2005/1/11
page 99
i
Section 4.3
99
This is a test.
Debugging Tip: Stopping an Infinite Loop
If you forget to increment the count in the body of the
while loop, or if you close the body of the while loop
before the count is incremented you will have an infinite
loop. An infinite loop is one that will never stop. You can
tell that you are in an infinite loop in this case because
many more than 5 copies of This is a test. will be printed.
To stop an infinite loop click on the Reset button near
the top of the DrJava window.
What if we want to change the color of all the pixels in a picture? Picture
objects understand the method getPixels() which returns a one dimensional array
of pixel objects. Even though the pixels are really in a two-dimensional array (a
matrix) getPixels() puts the pixels in a one-dimensional array to make them easy
to process if we just want to process all the pixels. We can get a pixel at a position
in the array using pixelArray[index] with the index starting at 0 and changing
each time through the loop by one until it is equal to the length of the array of
pixels. Instead of calling the variable count we will call it index since that is
what we are using it for. It doesnt matter to the computer but it makes the code
easier for people to understand.
Here is the while loop that simply sets each pixels color to black in a picture.
>
>
>
>
>
>
>
>
import java.awt.Color;
String fileName = "C:/intro-prog-java/mediasources/caterpillar.jpg";
Picture pict = new Picture(fileName);
pict.show();
Pixel[] pixelArray = pict.getPixels();
Pixel pixel = null;
int index = 0;
while (index < pixelArray.length)
{
pixel = pixelArray[index];
pixel.setColor(Color.black);
index++;
}
>pict.repaint();
Lets talk through this code.
We will be using the Color class so we need to either use the fully qualified
name (java.awt.Color) or import the Color class using import java.awt.Color;.
Next we declare a variable with the name fileName to refer to the string object that has a particular file name C:/intro-prog-java/mediasources/caterpillar.jpg
stored in it.
The variable pict is created and refers to the new Picture object created
from the picture information in the file named by the variable fileName.
main
2005/1/11
page 100
100
Chapter 4
main
2005/1/11
page 101
i
Section 4.3
101
/
Method t o d e c r e a s e t h e r e d by h a l f i n t h e
current picture
/
public void d e c r e a s e R e d ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt v a l u e = 0 ;
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i n d e x < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
p i x e l = pixelArray [ index ] ;
// g e t t h e v a l u e
v a l u e = p i x e l . getRed ( ) ;
// d e c r e a s e t h e r e d v a l u e by 50% ( 1 / 2 )
v a l u e = ( in t ) ( v a l u e 0 . 5 ) ;
// s e t t h e r e d v a l u e o f t h e c u r r e n t p i x e l t o t h e new v a l u e
p i x e l . setRed ( v a l u e ) ;
// i n c r e m e n t t h e i n d e x
index = index + 1 ;
}
}
Go ahead and type the above into your DrJava definitions pane before the last
curly brace in the Picture.java file. Click Compile All to get DrJava to compile
the new method. Why do we have to compile the file before we can use the new
method? Computers dont understand the Java source code directly. We must
compile it which translates the class definition from something people can read
and understand into something a computer can read and understand. When we
successfully compile a ClassName.java file the compiler outputs a ClassName.class
file which contains the instructions that a computer can understand. If our compile
is not successful we will get error messages that explain what is wrong. We have
main
2005/1/11
page 102
i
102
Chapter 4
to fix the errors and compile again before we can try out our new method.
$
'
&
This program works on a Picture objectthe one that well use to get the
pixels from. To create a Picture object, we pass in the filename. After we ask
the picture to decreaseRed(), well want to repaint the picture to see the effect.
Therefore, the decreaseRed method can be used like this:
>
>
>
>
>
'
$
Common Bug: Patience: loops can take a long time
The most common bug with this kind of code is to give up
and quit because you dont think the loop is working. It
might take a full minute (or two!) for some of the manipulations well doespecially if your source image is large.
&
The original picture and its red-decreased version appear in Figure 4.16 (and
at Figure 5.19 on page 177). 50% is obviously a lot of red to reduce! The picture
looks like it was taken through a blue filter.
main
2005/1/11
page 103
i
Section 4.3
103
FIGURE 4.16: The original picture (left) and red-decreased version (right)
main
2005/1/11
page 104
i
104
Chapter 4
main
2005/1/11
page 105
i
Section 4.3
105
The this is a keyword that represents the current object. Since the method
declaration doesnt have the keyword static in it this is an object method.
Object methods are always implicitly passed the current object (the object
the method was invoked on). In this case the method decreaseRed() was
invoked by picture.decreaseRed(); so the Picture object referenced by
the variable picture is the current object. We could leave off the this and
get the same result. If you dont reference any object when invoking a method
the compiler will assume you mean the current object (referenced by the this
keyword).
The this.getPixels() invokes the method getPixels() on the current object. This method returns a one-dimensional array of Pixel objects which
are the pixels in the current Picture object.
So at the end of the first line we have a variable pixelArray that refers to an
array of Pixel objects. The Pixel objects came from the Picture object which
was referred to as picture in the interaction pane and as this in the method
decreaseRed().
main
2005/1/11
page 106
i
106
Chapter 4
Next in the body of the loop is value = pixel.getRed();. This sets the
variable value to the amount of red in the current pixel. Remember that the
amount of red can vary from a minimum of 0 to a maximum of 255.
main
2005/1/11
page 107
i
Section 4.3
107
Next in the body of the loop is value = (int) (value * 0.5);. This sets
the variable value to the integer amount that you get from multiplying the current
contents of value by 0.5. The (int) is a cast to integer so that the compiler doesnt
complain about losing precision since we are storing a floating point number in
an integer number. Any numbers after the decimal point will be discarded. We
do this because colors are represented as integers. The (int) (value * 0.5) is
needed because the variable value is declared of type int and yet the calculation
of (value * 0.5) contains a floating point number and so will automatically be
done in floating point. However, a floating point result (say of 1.5) wont fit into
a variable of type int. So, the compiler wont let us do this without telling it that
we really want it to by including the (int). This is called casting and is required
whenever a larger value is being placed into a smaller variable. So if the result of
a multiplication has a fractional part that fractional part will just be thrown away
so that the result can fit in an int.
The next step in the body of the loop is pixel.setRed(value);. This changes
the amount of red in the current pixel to be the same as what is stored in variable
value. The current pixel is the first one so we see that the red value has changed
from 252 to 126 after this line of code is executed.
After the statements in the body of the loop are executed the index = index
main
2005/1/11
page 108
i
108
Chapter 4
+ 1; will be executed which will add one to the current value of index. Since index
was initialized to 0 this will result in index holding the value 1.
What happens next is very important. The loop starts over again. The
continuation test will again check that the value in variable index is less than the
length of the array of pixels and since the value of index is less than the length
of the array, the statements in the body of the loop will be executed again. The
variable pixel will be set to the pixel object in the array of pixels at index 1. This
is the second Pixel object in the array pixelArray.
The variable value will be set to the red amount in the current pixel referred
to by the variable pixel, which is 253.
The variable value will be set to the result of casting to integer the result of
multiplying the amount in value by 0.5. This results in (253 * 0.5) = 126.5 and
after we drop the digits after the decimal this is 126. We drop the digits after the
decimal point because of the cast to the type int (integer). We cast to integer
because colors are represented as integer values from 0 to 255.
main
2005/1/11
page 109
i
Section 4.3
109
The red value in the current pixel is set to the same amount as what is stored
in value. So the value of red in the second pixel changes from 253 to 126.
The variable index is set to the result of adding 1 to its current value. This
adds 1 to 1 resulting in 2.
At the end of the loop body we go back to the continuation test. The test
will be evaluated and if the result is true the commands in the loop body will be
executed again. If the continuation test evaluates to false execution will continue
with the first statement after the body of the loop.
Eventually, we get Figure 4.16 (and at Figure 5.19 on page 177). We keep
going through all the pixels in the sequence and changing all the red values.
Testing the program: Did that really work?.
How do we know that that really worked? Sure, something happened to the
main
2005/1/11
page 110
i
110
Chapter 4
FIGURE 4.17: Using the picture explorer to convince ourselves that the red was
decreased
picture, but did we really decrease the red? By 50%?
'
&
%
We can check it several ways. One way is with the picture explorer. Create
two Picture objects: Picture p = new Picture(FileChooser.pickAFile());
and Picture p2 = new Picture(FileChooser.pickAFile()); and pick the same
picture each time. Decrease red in one of them. Then open a picture explorer on
each of the Picture objects using p.explore(); and p2.explore();.
We can also use the functions that we know in the Interactions pane to check
the red values of individual pixels.
> String fileName = "C:/intro-prog-java/mediasources/caterpillar.jpg";
> Picture pict = new Picture(fileName);
> Pixel pixel = pict.getPixel(0,0);
> System.out.println(pixel);
Pixel red=252 green=254 blue=251
> pict.decreaseRed();
> Pixel newPixel = pict.getPixel(0,0);
> System.out.println(newPixel);
Pixel red=126 green=254 blue=251
> System.out.println( 252 * 0.5);
126.0
main
2005/1/11
page 111
i
Section 4.3
111
Increasing red.
Lets increase the red in the picture now. If multiplying the red component
by 0.5 decreased it, multiplying it by something over 1.0 should increase it. Im
going to apply the increase to the exact same picture, to see if we can reduce the
blue (Figure 4.18 and Figure 5.20).
/
Method t o i n c r e a s e t h e amount o f r e d by 30%
/
public void i n c r e a s e R e d ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt v a l u e = 0 ;
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i n d e x < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
p i x e l = pixelArray [ index ] ;
// g e t t h e v a l u e
v a l u e = p i x e l . getRed ( ) ;
// change t h e v a l u e t o 1 . 3 t i m e s what i t was
v a l u e = ( in t ) ( v a l u e 1 . 3 ) ;
// s e t t h e r e d v a l u e t o 1 . 3 t i m e s what i t was
p i x e l . setRed ( v a l u e ) ;
// i n c r e m e n t t h e i n d e x
i n d e x ++;
}
}
This method works much the same was as the method decreaseRed. We set
up some variables that we will need such as the array of pixel objects, the current
pixel, the current value, and the current index. We loop through all the pixels in
the array of pixels and change the red value for each pixel to 1.3 times its original
main
2005/1/11
page 112
i
112
Chapter 4
FIGURE 4.18: Overly blue (left) and red increased by 30% (right)
value.
'
$
Making it Work Tip: Shortcuts for Increment and
Decrement
Adding one or subtracting one from a current value is
something that is done frequently in programs. Programmers have to do lots of typing so they try to reduce the
amount of typing that they have to do for things they do
frequently. Notice the index++; in the increase red program. This has the same result as index = index + 1;
and can also be written as ++index;. You can also use
index--; or --index; which will have the same result as
index = index - 1;. Be careful of using this when you
are also assigning the result to a variable. If you do int
x = index++; x will be assigned the original value of index and then index will be incremented. If you do int x
= ++index; first index will be incremented and then the
value assigned to x.
&
%
Compile the new method increaseRed and first use decreaseRed and then
increaseRed on the same picture. Explore the picture objects to check that
increaseRed worked. Remember that the method explore makes a copy of the
picture and allows you to check the color values of individual pixels.
>
>
>
>
>
>
We can even get rid of a color completely. The method below erases the blue
component from a picture by setting the blue value to 0 in all pixels(Figure 4.19
main
2005/1/11
page 113
i
Section 4.3
113
/
Method t o c l e a r t h e b l u e from t h e p i c t u r e ( s e t
the blue to 0 for a l l p i x e l s )
/
public void c l e a r B l u e ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i n d e x < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
p i x e l = pixelArray [ index ] ;
// s e t t h e b l u e on t h e p i x e l t o 0
pixel . setBlue ( 0 ) ;
// i n c r e m e n t i n d e x
i n d e x ++;
}
}
Compile the new method clearBlue and invoke it on a Picture object. Explore the picture object to check that all the blue values are indeed 0.
>
>
>
>
>
Creating a Sunset
We can certainly do more than one color manipulation at once. Mark wanted to
try to generate a sunset out of a beach scene. His first attempt was to increase the
red, but that doesnt always work. Some of the red values in a given picture are
pretty high. If you go past 255 for a channel value it will keep the value at 255.
main
2005/1/11
page 114
114
Chapter 4
His second thought was that maybe what happens in a sunset is that there is
less blue and green, thus emphasizing the red, without actually increasing it. Here
was the program that we wrote for that:
Program 8: Making a sunset
/
Method t o s i m u l a t e a s u n s e t by d e c r e a s i n g t h e g r e e n
and b l u e
/
public void makeSunset ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt v a l u e = 0 ;
i nt i = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// change t h e b l u e v a l u e
value = p i x e l . getBlue ( ) ;
p i x e l . setBlue ( ( int ) ( value 0 . 7 ) ) ;
// change t h e g r e e n v a l u e
v a l u e = p i x e l . getGreen ( ) ;
p i x e l . s e t G r e e n ( ( in t ) ( v a l u e 0 . 7 ) ) ;
// i n c r e m e n t t h e i n d e x
i ++;
main
2005/1/11
page 115
i
Section 4.3
115
FIGURE 4.20: Original beach scene (left) and at (fake) sunset (right)
}
}
'
$
Making it Work Tip: Using short variable names
for loop counters
Notice that instead of using index as the counter for the
loop we are using i. Again, programmers like to reduce
the amount of typing and so the simple variable name i
is commonly used to represent the counter or index for a
loop.
&
%
Compile the new method makeSunset and invoke it on a Picture object.
Explore the picture object to check that the blue and green values have been decreased.
>
>
>
>
>
What we see happening in Method 8 is that were changing both the blue
and green channelsreducing each by 30%. The effect works pretty well, as seen
in Figure 4.20 (and in the color section at Figure 5.22).
4.3.4
main
2005/1/11
page 116
116
Chapter 4
Since were always picking a file (or typing in a filename) then making a picture, before calling one of our picture manipulation functions, and then showing or
repainting the picture, its a natural question why were not building those in. Why
doesnt every method have String fileName = FileChooser.pickAFile(); and
new Picture(fileName); in it?
We actually want to write the methods to make them more general and
reusable. We want our methods to do one and only one thing, so that we can
use the method again in a new context where we need that one thing done. An example might make that clearer. Consider the program to make a sunset (Method 8).
That works by reducing the green and blue, each by 30%. What if we rewrote that
method so that it called two smaller methods that just did the two pieces of the
manipulation? Wed end up with something like Method 9.
Program 9: Making a sunset as three methods
/
Method t o d e c r e a s e t h e g r e e n i n t h e p i c t u r e by 30%
/
public void d e c r e a s e G r e e n ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt v a l u e = 0 ;
i nt i = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s i n t h e a r r a y
while ( i < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// g e t t h e v a l u e
v a l u e = p i x e l . getGreen ( ) ;
// s e t t h e g r e e n v a l u e t o 70% o f what i t was
p i x e l . s e t G r e e n ( ( in t ) ( v a l u e 0 . 7 ) ) ;
// i n c r e m e n t t h e i n d e x
i ++;
}
}
/
Method t o d e c r e a s e t h e b l u e i n t h e p i c t u r e by 30%
/
public void d e c r e a s e B l u e ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
main
2005/1/11
page 117
i
Section 4.3
117
P i x e l p i x e l = null ;
i nt v a l u e = 0 ;
i nt i = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s i n t h e a r r a y
while ( i < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// g e t t h e v a l u e
value = p i x e l . getBlue ( ) ;
// s e t t h e b l u e v a l u e t o 70% o f what i t was
p i x e l . setBlue ( ( int ) ( value 0 . 7 ) ) ;
}
}
/
Method t o make a p i c t u r e l o o k l i k e i t was t a k e n a t s u n s e t
by r e d u c i n g t h e b l u e and g r e e n t o make i t l o o k more r e d
/
public void makeSunset2 ( )
{
decreaseGreen ( ) ;
decreaseBlue ( ) ;
}
The first thing to note is that this actually does work. makeSunset2()
does the same thing here as in the previous method. Its perfectly okay to have
one method (makeSunset2() in this case) use other methods in the same class
(decreaseBlue() and decreaseGreen()). You use makeSunset2() just as you
had before. Its the same algorithm (it tells the computer to do the same thing),
but with different methods. The earlier program did everything in one method,
and this one does it in three. In fact, you can also use decreaseBlue() and
decreaseGreen() by themselves toomake a picture in the Command Area and
invoke either method on the Picture object. They work just like decreaseRed().
Whats different is that the method makeSunset() is much simpler to read.
Thats very important.
Computer Science Idea: Programs are for people.
Computers dont care about how a program looks. Programs are written to communicate with people. Making
programs easy to read and understand means that they
are more easily changed and reused, and they more effectively communicate process to other humans.
What if we had written decreaseBlue() and decreaseGreen() so that each
asked you to pick a file and created the picture before changing the color. We
would be asked for to pick a file twiceonce in each function. Because we wrote
main
2005/1/11
page 118
i
118
Chapter 4
these functions to only decrease the blue and decrease the green (one and only one
thing) in the implicitly passed Picture object, we can use them in new functions
like makeSunset()
There is an issue that the new makeSunset() will take twice as long to finish
as the original one, since every pixel gets changed twice. We address that issue
in a later chapter on speed and complexity. The important issue is still to write
the code readably first, and worry about efficiency later. However, this could also
be handled by a method that changes each color by some passed in amount. This
would be a very general and reusable method.
Now, lets say that we asked you to pick a picture and created the picture in
makeSunset() before calling the other methods. The methods reduceBlue() and
reduceGreen() are completely flexible and reusable again. But makeSunset() is
now less flexible and reusable. Is that a big deal? No, not if you only care about
having the ability to give a sunset look to a single picked picture. But what if you
later want to build a movie with a few hundred frames of Picture objects, to each
of which you want to add a sunset look? Do you really want to pick out each of
those few hundred frames? Or would you rather write a method to go through
each of the frames (which well learn how to do in a few chapters) and invoke
makeSunset() on each Picture object. Thats why we make methods general and
reusableyou never know when youre going to want to use that method again, in
a larger context.
$
'
Making it Work Tip: Dont start by trying to write
applications
Theres a tendency for new programmers to want to write
complete applications that a non-technical user can use.
You might want to write a makeSunset() application that
goes out and fetches a picture for a user and generates a
sunset for them. Building good user interfaces that anyone
can use is hard work. Start out more slowly. Its hard
enough to make a method just operates on a picture. You
can work on user interfaces later.
&
%
Even larger methods, like makeSunset(), do one and only one thing. makeSunset()
makes a sunset-looking picture. It does that by decreasing green and decreasing
blue. It calls two other methods to do that. What we end up with is a hierarchy of
goalsthe one and only one thing that is being done. makeSunset() does its one
thing, by asking two other methods to do their one thing. We call this hierarchical decomposition (breaking down a problem into smaller parts, and then breaking
down those smaller parts until you get something that you can easily program), and
its very powerful for creating complex programs out of pieces that you understand.
4.3.5
main
2005/1/11
page 119
Section 4.3
119
a method have method scope and only apply inside that method. That is why we
can use the same variable names in several methods. Variables declared inside the
Interactions Pane are known inside the Interactions Pane until it is reset. This is
why you get Error: Redefinition of picture when you declare a variable
that is already declared in the Interactions Pane.
The only way to get any data (pictures, sounds, filenames, numbers) from
the interactions pane into a function is by passing it in as input to the function.
Within the function, you can use any names you wantnames that you first define
within the method (like pixel in the last example) or names that you use to stand
for the input data (like fileName) only exist while the method is running. When
the method is done, those variable names literally do not exist anymore.
This is really an advantage. Earlier, we said that naming is very important
to computer scientists: We name everything, from data to methods to classes. But
if each name could mean one and only one thing ever, wed run out of names. In
natural language, words mean different things in different contexts (e.g., What do
you mean? and You are being mean!.) A method is a different contextnames
can mean something different than they do outside of that method.
Sometimes, you will compute something inside a method that you want to
return to the interactions pane or to a calling method. Weve already seen methods
that output a value, like FileChooser.pickAFile() which outputs a filename. If
you created a Picture object using new Picture(fileName) inside a method, you
should output it so that it can be used. You can do that by using the return
keyword as we did for showNamed(fileName).
The name that you give to a methods input can be thought of as a placeholder .
Whenever the placeholder appears, imagine the input data appearing instead. So,
in a method like:
Program 10: General change red by a passed amount
/
Method t o change t h e r e d by an amount
@param amount t h e amount t o change t h e r e d by
/
public void changeRed ( double amount )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt v a l u e = 0 ;
i nt i = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// g e t t h e v a l u e
main
2005/1/11
page 120
120
Chapter 4
v a l u e = p i x e l . getRed ( ) ;
/ s e t t h e r e d v a l u e t o t h e o r i g i n a l v a l u e
t i m e s t h e p a s s e d amount
/
p i x e l . setRed ( ( i n t ) ( v a l u e amount ) ) ;
// i n c r e m e n t i
i ++;
}
}
When you call (invoke) the method changeRed with a specific amount such as
picture.changeRed(0.7); which would decrease the red by 30%. In the method
changeRed the input parameter amount is set to 0.7. This is similar to declaring a
variable inside the method like this double amount = 0.7;. Just like any variable
declared in the method the parameter amount is known inside the method. It has
method scope.
Call changeRed with an amount less than one to decrease the amount of red
in a picture. Call changeRed with an amount greater than one to increase the
amount of red in a picture. Remember that the amount of red must be between 0
and 255. If you try to set the amount of red less than 0 it will be set to 0. If you
try to set the amount of red greater than 255 it will be set to 255.
Weve now talked about different ways of writing the same methodsome
better, some worse. There are others that are pretty much equivalent, and others
that are much better. Lets consider a few more ways that we can write methods.
We can pass in more than input at a time. Consider the following:
Program 11: Change all pixel colors by the passed amounts
/
Method t o change t h e c o l o r o f each p i x e l i n t h e p i c t u r e
o b j e c t by p a s s e d i n amounts .
@param redAmount t h e amount t o change t h e r e d v a l u e
@param greenAmount t h e amount t o change t h e g r e e n v a l u e
@param blueAmount t h e amount t o change t h e b l u e v a l u e
/
public void c h a n g e C o l o r s ( double redAmount ,
double greenAmount ,
double blueAmount )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt v a l u e = 0 ;
i nt i = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i < p i x e l A r r a y . l e n g t h )
main
2005/1/11
page 121
Section 4.3
121
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// change t h e r e d v a l u e
v a l u e = p i x e l . getRed ( ) ;
p i x e l . setRed ( ( i n t ) ( redAmount v a l u e ) ) ;
// change t h e g r e e n v a l u e
v a l u e = p i x e l . getGreen ( ) ;
p i x e l . s e t G r e e n ( ( in t ) ( greenAmount v a l u e ) ) ;
// change t h e b l u e v a l u e
value = p i x e l . getBlue ( ) ;
p i x e l . s e t B l u e ( ( i n t ) ( blueAmount v a l u e ) ) ;
// i n c r e m e n t i
i ++;
}
}
The above code would have the same result as makeSunset(). It keeps the
red values the same and decreases the green and blue values 30%. Thats a pretty
useful and powerful function.
Recall seeing in Method 7 this code:
/
Method t o c l e a r t h e b l u e from t h e p i c t u r e ( s e t
the blue to 0 for a l l p i x e l s )
/
public void c l e a r B l u e ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i n d e x < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
p i x e l = pixelArray [ index ] ;
// s e t t h e b l u e on t h e p i x e l t o 0
pixel . setBlue ( 0 ) ;
main
2005/1/11
page 122
i
122
Chapter 4
// i n c r e m e n t i n d e x
i n d e x ++;
}
}
Its important to note that this function achieves the exact same thing as the
earlier method did. Both set the blue channel of all pixels to zero. An advantage of
the second method is that it is shorter and doesnt require a variable declaration for
a pixel. However, it may be harder for someone to understand. A shorter method
isnt necessarily better.
4.3.6
main
2005/1/11
page 123
i
Section 4.3
123
Next is the initialization area. You can declare and initialize variables here.
For example, you can have int i=0 which declares a variable i of the primitive
type int and initializes it to 0. You can initialize more than one variable here
by separating the initializations with commas. You are not required to have
any initializations here.
Next comes the required semicolon.
Next is the continuation test. This holds an expression that returns true or
false. When this expression is true the loop will continue to be executed.
When this test is false the loop will finish and the statement following the
body of the loop will be executed.
Next comes the required semicolon.
Next is the change area. Here you usually increment or decrement variables,
such as i++ to increment i. The statements in the change area actually take
place after each execution of the body of the loop.
Next is the required closing parenthesis.
If you just want to execute one statement (command) in the body of the loop
it can just follow on the next line. It is normally indented to show that it is part
of the for loop. If you want to execute more than one statements in the body of
the for loop you will need to enclose the statements in a block (a set of open and
close curly braces).
declare and
init loop
variable(s)
false
for (expression)
for(int i=0;
i < pixelArray.length();
i++)
true
Statement(s)
change loop
variable(s)
Statement(s)
main
2005/1/11
page 124
124
Chapter 4
Compare the flowchart (Figure 4.21) for a for loop with the flowchart for a
while loop (Figure 4.15). They look the same because for loops and while loops
execute in the same way even though the code looks different. Any code can be
written using either. The syntax of the for loop just makes it easier to remember
to declare a variable for use in the loop and to change it each time through the
loop since all of that is written at the same time that you write the test. To change
clearBlue() to use a for loop simply move the declaration and initialization of
the index variable i to be done in the initialization area and the increment of i to
be done in the change area.
Program 12: Another clear blue method
/
Method t o c l e a r t h e b l u e from t h e p i c t u r e ( s e t
the blue to 0 for a l l p i x e l s )
/
public void c l e a r B l u e 3 ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
// l o o p t h r o u g h a l l t h e p i x e l s
f o r ( i n t i =0; i < p i x e l A r r a y . l e n g t h ; i ++)
pixelArray [ i ] . setBlue ( 0 ) ;
}
4.3.7
/
Method t o l i g h t e n t h e c o l o r s i n t h e p i c t u r e
/
public void l i g h t e n ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
C o l o r c o l o r = null ;
P i x e l p i x e l = null ;
// l o o p t h r o u g h a l l t h e p i x e l s
f o r ( i n t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++)
main
2005/1/11
page 125
i
Section 4.3
125
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// g e t t h e c u r r e n t c o l o r
color = pixel . getColor ( ) ;
// g e t a l i g h t e r c o l o r
color = color . brighter ();
// s e t t h e p i x e l c o l o r t o t h e l i g h t e r c o l o r
pixel . setColor ( color ) ;
}
}
/
Method t o darken t h e c o l o r i n t h e p i c t u r e
/
public void darken ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
C o l o r c o l o r = null ;
P i x e l p i x e l = null ;
// l o o p t h r o u g h a l l t h e p i x e l s
f o r ( i n t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++)
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// g e t t h e c u r r e n t c o l o r
color = pixel . getColor ( ) ;
// g e t a d a r k e r c o l o r
c o l o r = c o l o r . darker ( ) ;
// s e t t h e p i x e l c o l o r t o t h e d a r k e r c o l o r
pixel . setColor ( color ) ;
}
}
4.3.8
Creating a Negative
Creating a negative image of a picture is much easier than you might think at first.
Lets think it through. What we want is the opposite of each of the current values
main
2005/1/11
page 126
126
Chapter 4
for red, green, and blue. Its easiest to understand at the extremes. If we have a
red component of 0, we want 255 instead. If we have 255, we want the negative to
have a zero.
Now lets consider the middle ground. If the red component is slightly red
(say, 50), we want something that is almost completely redwhere the almost
is the same amount of redness in the original picture. We want the maximum red
(255), but 50 less than that. We want a red component of 255 50 = 205. In
general, the negative should be 255 original. We need to compute the negative
of each of the red, green, and blue components, then create a new negative color,
and set the pixel to the negative color.
Heres the program that does it, and you can see even from the grayscale
image that it really does work (Figure 4.23 and Figure 5.24).
Program 15: Create the negative of the original picture
/
Method t o n e g a t e t h e p i c t u r e
/
public void n e g a t e ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt redValue , blueValue , g r e e n V a l u e = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
f o r ( i n t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++)
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// g e t t h e c u r r e n t red , green , and b l u e v a l u e s
r e dV a lu e = p i x e l . getRed ( ) ;
g r e e n V a l u e = p i x e l . getGreen ( ) ;
main
2005/1/11
page 127
Section 4.3
127
blueValue = p i x e l . getBlue ( ) ;
// s e t t h e p i x e l s c o l o r t o t h e new c o l o r
p i x e l . s e t C o l o r (new C o l o r ( 2 5 5 redValue ,
255 greenValue ,
255 b l u e V a l u e ) ) ;
}
}
4.3.9
Converting to Grayscale
Converting to grayscale is a fun program. Its short, not hard to understand, and
yet has such a nice visual effect. Its a really nice example of what one can do easily
yet powerfully by manipulating pixel color values.
Recall that the resultant color is gray whenever the red component, green
component, and blue component have the same value. That means that our RGB
encoding supports 256 levels of gray from, (0, 0, 0) (black) to (1, 1, 1) through
(100, 100, 100) and finally (255, 255, 255). The tricky part is figuring out what
the replicated value should be.
What we want is a sense of the intensity of the color. It turns out that its
pretty easy to compute: We average the three component colors. Since there are
three components, the formula for intensity is:
(red+green+blue)
3
This leads us to the following simple program and Figure 4.24 (and Figure 5.25
on page 179).
Program 16: Convert to grayscale
/
Method t o change t h e p i c t u r e t o g r a y s c a l e
/
main
2005/1/11
page 128
128
Chapter 4
public void g r a y s c a l e ( )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
i nt i n t e n s i t y = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
f o r ( i n t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++)
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// compute t h e i n t e n s i t y o f t h e p i x e l ( a v e r a g e v a l u e )
i n t e n s i t y = ( in t ) ( ( p i x e l . getRed ( ) + p i x e l . getGreen ( ) +
p i x e l . getBlue ( ) ) / 3 ) ;
// s e t t h e p i x e l c o l o r t o t h e new c o l o r
p i x e l . s e t C o l o r (new C o l o r ( i n t e n s i t y , i n t e n s i t y , i n t e n s i t y ) ) ;
}
}
/
Method t o change t h e p i c t u r e t o g r a y s c a l e w i t h luminance
/
main
2005/1/11
page 129
i
Section 4.4
Concepts Summary
129
4.4
CONCEPTS SUMMARY
In this chapter we have introduced arrays, while loops, for loops, and comments.
4.4.1
Arrays
Arrays are used to store many pieces of data of the same type. They allow you to
quickly access a particular item in the array using an index. If you couldnt use an
array, you would have to create a separate variable name for each piece of data.
To declare a variable that refers to an array use the type followed by open [
and close ] square brackets and then the variable name.
Pixel [ ] pixelArray ;
This declares an array of Pixel objects. The value stored at each position in
the array is a reference to a Pixel object.
Arrays are objects and you can find out how large an array by getting its
length.
i < pixelArray . length
Notice that this isnt a method call (there are no parentheses). This accesses
a public read-only field.
main
2005/1/11
page 130
i
130
Chapter 4
4.4.2
Loops
Loops are used to execute a block of statements while a boolean expression is true.
Most loops have variables that change during the loop which eventually cause the
boolean expression to be false and the loop to stop. Loops that never stop are
called infinite loops.
We introduced two types of loops in this chapter: while and for. The while
loop is usually used when you dont know how many times a loop needs to execute
and the for loop is usually used when you do know how many times the loop will
execute. We introduced the while loop first because it is easier for beginners to
understand.
The while loop has the keyword while followed by a boolean expression and
then a block of statements between an open and close curly brace. If the boolean
expression is true the body of the loop will be executed. If the boolean expression is
false execution will continue after the body of the loop (after the close curly brace).
If you just want to execute one statement in the body of the loop then you dont
need the open and close curly braces, but you should indent the statement.
while ( boolean e x p r e s s i o n )
{
statement1 ;
statement2 ;
...
}
If you use a while loop to execute a block of statements a set number of times
you will need to declare a variable before the while and that variable will need to
be changed in the body of the loop. You may also need to declare other variables
that you use in the loop before the while. Dont declare variables inside the loop
because you will use more memory that way.
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
while ( i n d e x < p i x e l A r r a y . l e n g t h )
{
// g e t t h e c u r r e n t p i x e l
p i x e l = pixelArray [ index ] ;
// do s o m e t h i n g t o t h e p i x e l
// i n c r e m e n t t h e i n d e x
i n d e x ++;
}
The for loop does the same thing as a while loop but it lets you declare the
variables that you need for the loop, specify the boolean expression to test, and
main
2005/1/11
page 131
i
Section 4.4
Concepts Summary
131
specify how to change the loop variables all in one place. This means you are less
likely to forget to do each of these things.
// l o o p t h r o u g h a l l t h e p i x e l s
f o r ( i n t i n d e x = 0 ; i n d e x < p i x e l A r r a y . l e n g t h ; i n d e x++)
{
// g e t t h e c u r r e n t p i x e l
p i x e l = pixelArray [ index ] ;
// do s o m e t h i n g t o t h e p i x e l
}
4.4.3
Comments
Comments are text that the programmer adds to the code to explain the code. The
compiler ignores the comments when it translates the code into a form that the
computer understands.
There are several types of comments in Java. To tell the compiler to ignore
all text till the end of the current line use //.
// g e t t h e c u r r e n t p i x e l
p i x e l = pixelArray [ index ] ;
To tell the compiler to ignore several lines use a starting /* and ending */.
/ s e t t h e r e d v a l u e t o t h e o r i g i n a l v a l u e
t i m e s t h e p a s s e d amount
/
p i x e l . setRed ( ( in t ) ( v a l u e amount ) ) ;
To put special comments in that can be parsed out by the javadoc utility to
make html documentation use a starting /** followed by an ending */.
/
Method t o change t h e r e d by an amount
@param amount t h e amount t o change t h e r e d by
/
main
2005/1/11
page 132
i
132
Chapter 4
getHeight()
getPixel(int x, int y)
getPixels()
getWidth()
writePictureTo(String fileName)
Pixel methods
getColor()
getRed(), getGreen(), getBlue()
getX(), getY()
setColor(Color color)
Color methods
new Color(int red,int green,int blue)
darker(),brighter()
ColorChooser methods
main
2005/1/11
page 133
Section 4.4
Concepts Summary
133
ColorChooser.pickAColor()
main
2005/1/11
page 134
134
Chapter 4
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
in t r e d = 0 ;
in t g r e e n = 0 ;
in t b l u e = 0 ;
in t newRed = 0 ;
// l o o p t h r o u g h a l l t h e p i x e l s
f o r ( in t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++)
{
// g e t t h e c u r r e n t p i x e l
pixel = pixelArray [ i ] ;
// g e t t h e c o l o r v a l u e s
r e d = p i x e l . getRed ( ) ;
g r e e n = p i x e l . getGreen ( ) ;
blue = p i x e l . getBlue ( ) ;
// c a l c u l a t e t h e new r e d v a l u e
newRed = ( in t ) ( r e d 1 . 3 ) ;
// s e t t h e p i x e l c o l o r t o t h e new c o l o r
p i x e l . s e t C o l o r (new C o l o r ( newRed , green , b l u e ) ) ;
}
}
4.5. Change any of the methods that used a while loop to use a for loop. Compile
and run the changed method and make sure it still works.
4.6. Change a variable name in any of the given methods. Make sure you change all
instances of the original name to the new name. Compile and run the changed
method and make sure it still works.
4.7. Write new methods like Program 7 (page 113) to clear red and green. For
each of these, which would be the most useful in actual practice? How about
combinations of these?
4.8. Write a method to keep just the blue color. This means to set all the green and
red values to zero.
4.9. Write a new method to maximize blue (i.e., setting it to 255) instead of clearing
it use Program 7 (page 113) as a starting point. Is this useful? Would the red
or green versions be useful?
4.10. There is more than one way to compute the right grayscale value for a color
value. The simple method that we use in Program 16 (page 127) may not be
what your grayscale printer uses when printing a color picture. Compare the
color (relatively unconverted by the printer) grayscale image using our simple
algorithm in Figure 5.25 with the original color picture that the printer has
converted to grayscale (left of Figure 4.16). How do the two pictures differ?
4.11. Think about how the grayscale algorithm works. Basically, if you know the
luminance of anything visual (e.g., a small image, a letter), you can replace a
pixel with that visual element in a similar way to create a collage image. Try
implementing that. Youll need 256 visual elements of increasing lightness, all
of the same size. Youll create a collage by replacing each pixel in the original
image with one of these visual elements.
main
2005/1/11
page 135
i
Section 4.4
Concepts Summary
135
TO DIG DEEPER
A wonderful new book on how vision works, and how artists have learned to manipulate it, is Vision and art: The biology of Seeing by Margaret Livingstone [19].
main
2005/1/11
page 136
i
C H A P T E R
COPYING PIXELS
COPYING AND TRANSFORMING PICTURES
CONCEPTS SUMMARY
COLOR FIGURES
COPYING PIXELS
We can only get so far in our image processing with getPixels() before we need
to know where a pixel is. For example, if we want to copy just part of a picture to
another picture we will need to know the x and y values to start with and end at.
136
main
2005/1/11
page 137
i
Section 5.1
5.1.1
Copying Pixels
137
This will process all the y values from top to bottom in the first column and
then all the y values in the next column and so on until all the pixels are processed.
You could also process all the x values in the top row and then all the x values
in the next row and so on using this:
// l o o p t h r o u g h t h e rows ( y d i r e c t i o n )
f o r ( i n t y = 0 ; y < g e t H e i g h t ( ) ; y++)
{
// l o o p t h r o u g h t h e columns ( x d i r e c t i o n )
f o r ( in t x = 0 ; x < getWidth ( ) ; x++)
{
// g e t t h e c u r r e n t p i x e l a t t h i s x and y p o s i t i o n
pixel = getPixel (x , y ) ;
// do s o m e t h i n g t o t h e c o l o r
// s e t t h e new c o l o r
p i x e l . setColor ( aColor ) ;
}
}
Does it matter which way you process the pixels? Not if all you are trying
to do is process all the pixels. Both of these loops will process all the pixels in a
picture.
main
2005/1/11
page 138
i
138
Chapter 5
For example, heres Program 13 (page 124), but using explicit pixel references.
Program 18: Lighten the picture using nested loops
/
Method t o l i g h t e n t h e c o l o r s i n t h e p i c t u r e
/
public void l i g h t e n 2 ( )
{
C o l o r c o l o r = null ;
P i x e l p i x e l = null ;
// l o o p t h r o u g h t h e columns ( x d i r e c t i o n )
f o r ( i n t x = 0 ; x < getWidth ( ) ; x++)
{
// l o o p t h r o u g h t h e rows ( y d i r e c t i o n )
f o r ( in t y = 0 ; y < g e t H e i g h t ( ) ; y++)
{
// g e t p i x e l a t t h e x and y l o c a t i o n
pixel = getPixel (x , y ) ;
// g e t t h e c u r r e n t c o l o r
color = pixel . getColor ( ) ;
// g e t a l i g h t e r c o l o r
color = color . brighter ( );
// s e t t h e p i x e l c o l o r t o t h e l i g h t e r c o l o r
pixel . setColor ( color ) ;
}
}
}
Lets walk through (trace) how it would work. Imagine that we just executed
picture.lighten2().
1. The code picture.lighten2() executes the object method in the Picture
class public void lighten2(). The method is implicitly passed the current
picture object (you can refer to the current picture object using the keyword
this).
2. The code Color color = null; and Pixel pixel = null; declares the variables color (an object of the Color class) and pixel (an object of the Pixel
class). Both of these are initialized to null (not referring to any object yet).
These variables will be needed when we are looping through the pixels. We
could declare these in the for loop but then they would be redeclared each
time through the loop. It is better to declare them once before the loop and
change them each time through the loop.
3. The code for (int x = 0; x < getWidth(); x++) declares a variable x of
main
2005/1/11
page 139
i
Section 5.1
4.
5.
6.
7.
8.
9.
10.
5.1.2
Copying Pixels
139
type int which will be initialized to 0 and then a check will be made to see
if x is less than the width of the current Picture object. If x is less than the
width, then the body of this for loop will be executed. After the body of the
loop has been executed one time the value in x will be incremented and the
continuation condition will be tested again.
The code for (int y = 0; y < getHeight(); y++) declares a variable y
of type int which will be initialized to 0. The test checks that y is less than
the height of the current Picture object. If y is less than the height then the
body of this for loop will be executed. After the body has executed the value
in y will be incremented and the continuation condition will be tested again.
The code pixel = getPixel(x,y); sets the variable pixel to refer to the
Pixel object at the given x and y location in the picture.
The code color = pixel.getColor(); sets the variable color to refer to
the Color object at the current pixel.
Next comes color = color.brighter();. This creates a new lighter (brighter)
Color object based on the original Color object and sets the variable color
to refer to that new Color object.
The code pixel.setColor(color); sets the current pixels color to be the
lighter color.
Each time we reach the end of the inner for loop the y value will be incremented by 1 and then the value of y will be compared to the height of the
picture. If the value of y is less than the height, the statements in the body
of the loop will be executed again. If the value of y is equal or greater than
the height, execution will jump to the next statement (the outer loop).
Each time we reach the end of the outer for loop the x value will be incremented by 1 and then the value of x will be compared to the width of the
picture. If the x value is less than the width of the picture, the commands in
the loop body will be executed. If the value of x is equal or greater than the
width of the picture, execution will continue at the statement following the
body of the loop.
Mirroring a Picture
Lets start out with an interesting effect that is only occasionally useful, but it is
fun. Lets mirror a picture along its vertical axis. In other words, imagine that
you have a mirror, and you place it on a picture so that the left side of the picture
shows up in the mirror. Thats the effect that were going to implement. Well do
it in a couple of different ways.
First, lets think through what were going to do. Well pick a horizontal
mirrorPointhalfway across the picture, (int) (picture.getWidth()/2). (We
want this to be an integer, a whole number, so well cast it using (int).) Well
have the x value increment from 1 to the mirrorPoint. At each value of x, we want
to copy the color at the pixel x pixels to the left of the mirrorPoint to the pixel x
pixels to the right of the mirrorPoint. The left would be mirrorPoint-x and the
right would be mirrorPoint+x. Take a look at Figure 5.1 to convince yourself that
well actually reach every pixel using this scheme. Heres the actual program.
main
2005/1/11
page 140
i
140
Chapter 5
mirrorpoint
mirrorpoint+1
FIGURE 5.1: Once we pick a mirror point, we can just walk x halfway and subtract/add to the mirror point
/
Method t o m i r r o r around a v e r t i c a l l i n e i n t h e m i d d l e
o f t h e p i c t u r e b a s e d on t h e w i d t h
/
public void m i r r o r V e r t i c a l ( )
{
i n t m i r r o r P o i n t = ( in t ) ( getWidth ( ) / 2 ) ;
P i x e l l e f t P i x e l = null ;
P i x e l r i g h t P i x e l = null ;
// l o o p t h r o u g h t h e rows
f o r ( i n t y = 0 ; y < g e t H e i g h t ( ) ; y++)
{
// l o o p from 1 t o j u s t b e f o r e t h e m i r r o r p o i n t
f o r ( in t x = 1 ; x < m i r r o r P o i n t ; x++)
{
l e f t P i x e l = getPixel (( mirrorPoint x ) , y ) ;
rightPixel = getPixel (( mirrorPoint + x ) , y ) ;
rightPixel . setColor ( l e f t P i x e l . getColor ( ) ) ;
}
}
}
Wed use it like this, and the result appears in Figure 5.2.
> String fileName = "C:/intro-prog-java/mediasources/caterpillar.jpg";
> System.out.println(fileName);
C:/intro-prog-java/mediasources/caterpillar.jpg
> Picture picture = new Picture(fileName);
> picture.show();
> picture.mirrorVertical();
> picture.repaint();
Another way to code this would be to copy the colors for the pixels starting
main
2005/1/11
page 141
i
Section 5.1
Copying Pixels
141
FIGURE 5.2: Original picture (left) and mirrored along the vertical axis (right)
with the left-most x (x=0) into the right-most pixel (width - 1). To do this have x
range from 0 to less than the mirrorPoint and copy it to (width - 1 - x).
Can we mirror horizontally? Sure!
Program 20: Mirror pixels horizontally, top-to-bottom
/
Method t o m i r r o r around a h o r i z o n t a l l i n e i n t h e m i d d l e
b a s e d on t h e h e i g h t .
I t c o p i e s the top mirrored to
t h e bottom
/
public void m i r r o r H o r i z o n t a l ( )
{
i n t m i r r o r P o i n t = ( in t ) ( g e t H e i g h t ( ) / 2 ) ;
P i x e l t o p P i x e l = null ;
P i x e l b o t t o m P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t x=0; x < getWidth ( ) ; x++)
{
// l o o p from 1 t o j u s t b e f o r e t h e m i r r o r p o i n t
f o r ( in t y=1; y < m i r r o r P o i n t ; y++)
{
topPixel = getPixel (x , ( mirrorPoint y ) ) ;
bottomPixel = g e t P i x e l (x , ( mirrorPoint + y ) ) ;
bottomPixel . setColor ( topPixel . getColor ( ) ) ;
}
}
}
Wed use it like this, and the result appears in Figure 5.3.
> String fileName = "C:/intro-prog-java/mediasources/redMotorcycle.jpg";
> System.out.println(fileName);
C:/intro-prog-java/mediasources/redMotorcycle.jpg
> Picture picture = new Picture(fileName);
> picture.show();
> picture.mirrorHorizontal();
main
2005/1/11
page 142
i
142
Chapter 5
> picture.repaint();
Now this last method copies from the top of the picture onto the bottom (see
Figure 5.3). You can see that were getting the color from topPixel which is from
mirrorPoint - ythat will always be above mirrorPoint since smaller values of y
are nearer the top of the picture. To copy from the bottom up, simply change the
color at the top pixel to the color of the bottom pixel. (Figure 5.3).
Program 21: Mirror pixels horizontally, bottom-to-top
/
Method t o m i r r o r around a h o r i z t o n a l l i n e i n t h e m i d d l e
b a s e d on t h e h e i g h t o f t h e p i c t u r e .
I t c o p i e s t h e bottom
to the top .
/
public void mirrorHorizontalBottomToTop ( )
{
i nt m i r r o r P o i n t = ( in t ) ( g e t H e i g h t ( ) / 2 ) ;
P i x e l t o p P i x e l = null ;
P i x e l b o t t o m P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t x=0; x < getWidth ( ) ; x++)
{
// l o o p from 1 t o j u s t b e f o r e t h e m i r r o r p o i n t
f o r ( in t y=1; y < m i r r o r P o i n t ; y++)
{
topPixel = getPixel (x , ( mirrorPoint y ) ) ;
bottomPixel = g e t P i x e l (x , ( mirrorPoint + y ) ) ;
topPixel . setColor ( bottomPixel . getColor ( ) ) ;
}
}
}
Wed use it like this, and the result appears in Figure 5.3.
>
>
>
>
>
Mirroring Usefully.
While mirroring is probably mostly used for interesting effects, occasionally it
has some more serious (but still fun!) purposes. Mark took a picture of the Temple
of Hephaistos which is in the ancient agora in Athens, Greece, when traveling to
a conference (Figure 5.4). By sheer luck, Mark got the pediment dead horizontal.
main
2005/1/11
page 143
i
Section 5.1
Copying Pixels
143
FIGURE 5.3: A motorcycle mirrored horizontally, top to bottom (left) and bottom
to top (right)
The Temple of Hephaistos had its pediment damaged. Mark wondered if he could
fix it by mirroring the good part onto the broken part.
This time we dont want to mirror one-half of the picture onto the other half.
We just want to mirror the pixels from the good side of the pediment on the left
onto the bad side on the right. We also dont want to mirror all the pixels in the y
direction. We just need the pixels from the top of the pediment to the bottom of
the pediment. We can use the explorer to find out the value for those pixels (Figure
5.5). The pediment starts at x=13 and the middle is at x=276. The highest part
of the pediment is at y=27 and it ends at y=97.
FIGURE 5.4: Temple of Hephaistos from the ancient agora in Athens, Greece
How do we mirror just a small part of a picture? Well, we still need a point to
mirror around. We will use 276 for that instead of half the width like we did in the
method mirrorVertical(). Lets start by copying the mirrorVertical() method
and changing the name to mirrorPartVertical() and setting the mirror point to the
value 276. We also dont want to stop when x gets all the way to the mirror point.
We just want to copy from x=13 to x276. We can start x at 1 and copy while x
is less than the number of pixels to copy (276-13=263). We can start y at the top
of the pediment 27 (instead of 0) and copy while it is less than 97 (instead of the
main
2005/1/11
page 144
i
144
Chapter 5
/
Method t o m i r r o r p a r t o f t h e p i c t u r e around a v e r t i c a l l i n e
at a mirror po in t
/
public void m i r r o r P a r t V e r t i c a l ( )
{
int mirrorPoint = 276;
P i x e l l e f t P i x e l = null ;
P i x e l r i g h t P i x e l = null ;
// l o o p t h r o u g h t h e rows
f o r ( i n t y = 2 7 ; y < 9 7 ; y++)
{
// l o o p from 1 t o j u s t b e f o r e t h e m i r r o r p o i n t
f o r ( in t x = 1 ; x < 2 6 3 ; x++)
{
l e f t P i x e l = getPixel (( mirrorPoint x ) , y ) ;
rightPixel = getPixel (( mirrorPoint + x ) , y ) ;
rightPixel . setColor ( l e f t P i x e l . getColor ( ) ) ;
}
}
}
main
2005/1/11
page 145
Section 5.1
>
>
>
>
Copying Pixels
145
You may be tired of typing the full path name to each file. You can save the
name of the directory that has your media in it and then use FileChooser.getMediaPath(fileName)
to get the full path name. The method FileChooser.getMediaPath(fileName)
generates a complete path for you by returning a string with the saved directory
name followed by the base file name. The default media directory is c:/intro-progjava/mediasources/. If you wish to use a different media directory you should execute FileChooser.setMediaPath(directory ) first!. FileChooser.setMediaPath(directory)
lets you specify the place (directory) where you store your media.
The temple example is a good one to ask ourselves about. If you really
understand, you can answer questions like Whats the first pixel to be mirrored
in this function? and How many pixels get copied anyway? You should be able
to figure these out by thinking through the programpretend youre the computer
and execute the program in your mind.
If thats too hard, you can insert System.out.println() statements, like
this:
/
Method t o m i r r o r p a r t o f t h e p i c t u r e around a v e r t i c a l l i n e
at a mirror po in t
/
public void m i r r o r P a r t V e r t i c a l ( )
main
2005/1/11
page 146
146
Chapter 5
{
int mirrorPoint = 276;
P i x e l l e f t P i x e l = null ;
P i x e l r i g h t P i x e l = null ;
i n t count = 0 ;
// l o o p t h r o u g h t h e rows
f o r ( i n t y = 2 7 ; y < 9 7 ; y++)
{
// l o o p from 1 t o j u s t b e f o r e t h e m i r r o r p o i n t
f o r ( in t x = 1 ; x < 2 6 3 ; x++)
{
System . out . p r i n t l n ( c o p y i n g c o l o r from +
m i r r o r P o i n t x ) ;
l e f t P i x e l = getPixel (( mirrorPoint x ) , y ) ;
System . out . p r i n t l n ( t o + m i r r o r P o i n t + x ) ;
rightPixel = getPixel (( mirrorPoint + x ) , y ) ;
rightPixel . setColor ( l e f t P i x e l . getColor ( ) ) ;
count = count + 1 ;
}
}
System . out . p r i n t l n ( We c o p i e d + count + p i x e l s ) ;
}
When we run this version, it takes a long time to finish. Hit Reset after a
little bit since we only really care about the first few pixels. Heres what we got:
> Picture.mirrorTemple();
Copying color from 275,27 to 277,27
Copying color from 275,28 to 277,28
Copying color from 275,29 to 277,29
Copying color from 275,30 to 277,30
It copies from just to the left of the mirror point (276), since x is 1 at first,
and we copy from mirrorpoint-x to mirrorpoint+x. Thus, we copy down the
column before the mirror point to the column of pixels to the right of the mirror
point. Then we move back one column to the left, and copy one column further to
the right.
How many pixels did we process? We can have the computer figure that one
out, too.
/
Method t o m i r r o r p a r t o f t h e p i c t u r e around a v e r t i c a l l i n e
at a mirror po in t
/
public void m i r r o r P a r t V e r t i c a l ( )
{
int mirrorPoint = 276;
main
2005/1/11
page 147
i
Section 5.2
147
P i x e l l e f t P i x e l = null ;
P i x e l r i g h t P i x e l = null ;
i n t count = 0 ;
// l o o p t h r o u g h t h e rows
f o r ( i n t y = 2 7 ; y < 9 7 ; y++)
{
// l o o p from 1 t o j u s t b e f o r e t h e m i r r o r p o i n t
f o r ( in t x = 1 ; x < 2 6 3 ; x++)
{
l e f t P i x e l = getPixel (( mirrorPoint x ) , y ) ;
rightPixel = getPixel (( mirrorPoint + x ) , y ) ;
rightPixel . setColor ( l e f t P i x e l . getColor ( ) ) ;
count = count + 1 ;
}
}
System . out . p r i n t l n ( We c o p i e d + count + p i x e l s ) ;
}
This one comes back with We copied 18340 pixels. Where did that number
come from? You can calculate how many times you execute the commands in a for
loop with end - start + 1. We copy 70 rows of pixels (y goes from 27 to 96 (because
of the < 97) which is 96 27 + 1. We copy 262 columns of pixels (x goes from 1 to
< 263 which is 262 1 + 1 = 262). 70 262 is 18, 340.
5.2
main
2005/1/11
page 148
148
Chapter 5
which is 7x9.5 inches, which will fit on a 9x11.5 inch lettersize piece of paper with
one inch margins.
> String paperFile = FileChooser.getMediaPath("7inx95in.jpg");
> Picture paperPicture = new Picture(paperFile);
> paperPicture.show();
> System.out.println(paperPicture.getWidth());
504
> System.out.println(paperPicture.getHeight());
684
5.2.1
Copying
To copy a picture we simply make sure that we increment sourceX and targetX
variables (the source and target index variables for the X axis) together, and the
sourceY and targetY variables together. We can initialize more than one variable
in the initialization area of a for loop and change more than one variable in the
change area.
Heres a program for copying a picture of Katie to the current picture.
Program 23: Copying a picture to the current picture
/
Method t o copy t h e p i c t u r e o f K a t i e t o t h e
upper l e f t c o r n e r o f t h e c u r r e n t p i c t u r e
/
public void c o p y K a t i e ( )
{
String sourceFile =
F i l e C h o o s e r . getMediaPath ( KatieFancy . j p g ) ;
P i c t u r e s o u r c e P i c t u r e = new P i c t u r e ( s o u r c e F i l e ) ;
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t sourceX = 0 , t a r g e t X =0;
sourceX < s o u r c e P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t sourceY = 0 , t a r g e t Y =0;
sourceY < s o u r c e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
// s e t t h e t a r g e t p i x e l c o l o r t o t h e s o u r c e p i x e l c o l o r
s o u r c e P i x e l = s o u r c e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
main
2005/1/11
page 149
i
Section 5.2
149
}
}
To use this method create a picture from the file that has a blank paper-sized
picture. The picture of Katie will be copied to the top left corner of the blank
picture Figure 5.7.
>
>
>
>
This method copies a picture of Katie to the canvas (blank picture) (Figure 5.7). Heres how it works:
The first two lines are just setting up the source (sourcePicture).
Then we have the declaration of variables to keep track of the target and
source pixels.
Next comes the loop for managing the x index variables, sourceX for the
source picture and targetX for the target (current) picture. The for loop
declares both variables and initializes them to 0. You can have more than
one variable declared and initialized in the initialization area of a for loop,
just separate them with commas. Next the continuation test checks if the
sourceX is less than the width of the source picture. Finally in the change
area we increment both the sourceX and targetX variables each time after
main
2005/1/11
page 150
150
Chapter 5
the statements in the body of the loop have been executed. You can change
more than one variable in the change area as long as you separate the changes
with commas. The for loop for looping through the columns is:
f o r ( in t sourceX = 0 , t a r g e t X =0;
sourceX < s o u r c e P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
Inside the loop for the X variables is the loop for the Y variables. It has a
very similar structure, since its goal is to keep targetY and sourceY in synch
in exactly the same way.
f o r ( in t sourceY = 0 , t a r g e t Y =0;
sourceY < s o u r c e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
Its inside the Y loop that we actually get the color from the source pixel and
set the corresponding pixel in the target (current picture) to the same color.
Of course, we dont have to copy from (0, 0) in the source to (0, 0) in the
target. We can easily copy to another location in the target picture. All we have to
do is to change where the target X and Y coordinates start. The rest stays exactly
the same (Figure 5.8).
Program 24: Copy elsewhere into the current picture
main
2005/1/11
page 151
i
Section 5.2
151
/
Method t o copy t h e p i c t u r e o f K a t i e t o ( 1 0 0 , 1 0 0 ) i n t h e
current picture
/
public void copyKatieMidway ( )
{
String sourceFile =
F i l e C h o o s e r . getMediaPath ( KatieFancy . j p g ) ;
P i c t u r e s o u r c e P i c t u r e = new P i c t u r e ( s o u r c e F i l e ) ;
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t sourceX = 0 , t a r g e t X =100;
sourceX < s o u r c e P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t sourceY = 0 , t a r g e t Y =100;
sourceY < s o u r c e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
// s e t t h e t a r g e t p i x e l c o l o r t o t h e s o u r c e p i x e l c o l o r
s o u r c e P i x e l = s o u r c e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
}
To try this method create the target picture from the blank paper-sized picture
file, invoke the method on it, and show the result. The picture of Katie will be
copied with the upper left corner at (100, 100).
main
2005/1/11
page 152
152
Chapter 5
/
Method t o copy j u s t K a t i e s f a c e t o t h e c u r r e n t p i c t u r e
/
public void c o p y K a t i e s F a c e ( )
{
String sourceFile =
F i l e C h o o s e r . getMediaPath ( KatieFancy . j p g ) ;
P i c t u r e s o u r c e P i c t u r e = new P i c t u r e ( s o u r c e F i l e ) ;
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t sourceX = 7 0 , t a r g e t X = 1 0 0 ;
sourceX < 1 3 5 ; sourceX++, t a r g e t X++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t sourceY = 3 , t a r g e t Y = 1 0 0 ;
sourceY < 8 0 ; sourceY++, t a r g e t Y++)
{
// s e t t h e t a r g e t p i x e l c o l o r t o t h e s o u r c e p i x e l c o l o r
s o u r c e P i x e l = s o u r c e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
main
2005/1/11
page 153
i
Section 5.2
153
To try this method create the target picture from the blank paper-sized picture
file, invoke the method on it, and show the result. Just Katies face will be copied
to the target picture with the upper left corner at (100, 100).
We then increment both the sourceY and targetY, and copy again.
When done with that column, we increment the X index variables and move
on to the next column, until we copy every pixel.
main
2005/1/11
page 154
154
5.2.2
Chapter 5
Creating a Collage
In the mediasources folder are a couple images of flowers (Figure 5.10), each 100
pixels wide. Lets make a collage of them, by combining several of our effects to
create different flowers. Well copy them all into the blank image 640x480.jpg. All
we really have to do is to copy the pixel colors to the right places.
/
Method t o copy f l o w e r p i c t u r e s t o c r e a t e a c o l l a g e .
A l l t h e f l o w e r p i c t u r e s w i l l be l i n e d up near t h e
bottom o f t h e c u r r e n t p i c t u r e (5 p i x e l s from t h e bottom )
/
public void c o p y F l o w e r s ( )
{
// c r e a t e t h e f l o w e r p i c t u r e s
Picture flower1Picture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( f l o w e r 1 . j p g ) ) ;
Picture flower2Picture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( f l o w e r 2 . j p g ) ) ;
// d e c l a r e t h e s o u r c e and t a r g e t p i x e l v a r i a b l e s
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// s a v e t h e h e i g h t s o f t h e two p i c t u r e s
i nt f l o w e r 1 H e i g h t = f l o w e r 1 P i c t u r e . g e t H e i g h t ( ) ;
main
2005/1/11
page 155
Section 5.2
155
i nt f l o w e r 2 H e i g h t = f l o w e r 2 P i c t u r e . g e t H e i g h t ( ) ;
/ copy t h e f i r s t f l o w e r p i c t u r e t o 5 p i x e l s from t h e bottom
l e f t corner of the current p i c t u r e
/
f o r ( i n t sourceX = 0 , t a r g e t X = 0 ;
sourceX < f l o w e r 1 P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
f o r ( in t sourceY = 0 ,
targetY = this . getHeight ( ) flower1Height 5 ;
sourceY < f l o w e r 1 P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
s o u r c e P i x e l = f l o w e r 1 P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
// copy t h e f l o w e r 2 p i c t u r e s t a r t i n g w i t h x = 100
f o r ( i n t sourceX = 0 , t a r g e t X = 1 0 0 ;
sourceX < f l o w e r 2 P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
f o r ( in t sourceY = 0 ,
targetY = this . getHeight ( ) flower2Height 5 ;
sourceY < f l o w e r 2 P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
s o u r c e P i x e l = f l o w e r 2 P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
// copy t h e f l o w e r 1 n e g a t e d t o x = 200
flower1Picture . negate ( ) ;
f o r ( i n t sourceX = 0 , t a r g e t X = 2 0 0 ;
sourceX < f l o w e r 1 P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
f o r ( in t sourceY = 0 ,
targetY = this . getHeight ( ) flower1Height 5 ;
sourceY < f l o w e r 1 P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
s o u r c e P i x e l = f l o w e r 1 P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
main
2005/1/11
page 156
156
Chapter 5
}
// c l e a r t h e b l u e i n f l o w e r 2 p i c t u r e and add a t x=300
flower2Picture . clearBlue ( ) ;
f o r ( i n t sourceX = 0 , t a r g e t X = 3 0 0 ;
sourceX < f l o w e r 2 P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
f o r ( in t sourceY = 0 ,
targetY = this . getHeight ( ) flower2Height 5 ;
sourceY < f l o w e r 2 P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
s o u r c e P i x e l = f l o w e r 2 P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
// copy t h e n e g a t e d f l o w e r 1 t o x=400
f o r ( i n t sourceX = 0 , t a r g e t X = 4 0 0 ;
sourceX < f l o w e r 1 P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
f o r ( in t sourceY = 0 ,
targetY = this . getHeight ( ) flower1Height 5 ;
sourceY < f l o w e r 1 P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
s o u r c e P i x e l = f l o w e r 1 P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
}
main
2005/1/11
page 157
Section 5.2
157
/
Method t h a t w i l l copy a l l o f t h e p a s s e d s o u r c e p i c t u r e i n t o
the current p i c t u r e o b j e c t s t a r t i n g with the l e f t corner
g i v e n by x S t a r t .
I t w i l l put the sourcePicture at 5 p i x e l s
from t h e bottom o f t h i s p i c t u r e
@param s o u r c e P i c t u r e t h e p i c t u r e o b j e c t t o copy
@param x S t a r t t h e x p o s i t i o n t o s t a r t t h e copy i n t h e t a r g e t
/
public void co p yP ict ur eTo ( P i c t u r e s o u r c e P i c t u r e ,
in t x S t a r t )
{
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t sourceX = 0 , t a r g e t X = x S t a r t ;
sourceX < s o u r c e P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t sourceY = 0 ,
targetY = this . getHeight ( )
sourcePicture . getHeight () 5;
sourceY < s o u r c e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
s o u r c e P i x e l = s o u r c e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
main
2005/1/11
page 158
158
Chapter 5
}
/
Method t o copy two f l o w e r s i n a p a t t e r n t o t h e
bottom (5 p i x e l s from bottom ) o f t h e c u r r e n t p i c t u r e
/
public void c o p y F l o w e r s B e t t e r ( )
{
// c r e a t e t h e t h r e e p i c t u r e s
Picture flower1Picture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( f l o w e r 1 . j p g ) ) ;
Picture flower2Picture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( f l o w e r 2 . j p g ) ) ;
// copy t h e f i r s t f l o w e r p i c t u r e t o near t h e
// bottom l e f t c o r n e r o f t h e c a n v a s
t h i s . co p yP ict u re T o ( f l o w e r 1 P i c t u r e , 0 ) ;
/ copy t h e f l o w e r 2 p i c t u r e s t a r t i n g w i t h
x = 100 i n t h e c a n v a s
/
t h i s . co p yP ict u re T o ( f l o w e r 2 P i c t u r e , 1 0 0 ) ;
// copy t h e f l o w e r 1 n e g a t e d t o x = 200 i n t h e c a n v a s
flower1Picture . negate ( ) ;
t h i s . co p yP ict u re T o ( f l o w e r 1 P i c t u r e , 2 0 0 ) ;
/ c l e a r t h e b l u e i n f l o w e r 2 p i c t u r e and
add a t x=300 i n t h e c a n v a s
/
flower2Picture . clearBlue ( ) ;
t h i s . co p yP ict u re T o ( f l o w e r 2 P i c t u r e , 3 0 0 ) ;
// copy t h e n e g a t e d f l o w e r 1 t o x=400
t h i s . co p yP ict u re T o ( f l o w e r 1 P i c t u r e , 4 0 0 ) ;
}
Method t h a t w i l l copy a l l o f t h e p a s s e d s o u r c e p i c t u r e i n t o
the current p i c t u r e o b j e c t s t a r t i n g with the l e f t corner
g i v e n by x S t a r t , y S t a r t
@param s o u r c e P i c t u r e t h e p i c t u r e o b j e c t t o copy
@param x S t a r t t h e x p o s i t i o n t o s t a r t t h e copy i n t o on t h e
main
2005/1/11
page 159
i
Section 5.2
159
target
@param y S t a r t t h e y p o s i t i o n t o s t a r t t h e copy i n t o on t h e
target
/
public void co p yP ict ur eTo ( P i c t u r e s o u r c e P i c t u r e ,
in t x S t a r t ,
in t y S t a r t )
{
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t sourceX = 0 , t a r g e t X = x S t a r t ;
sourceX < s o u r c e P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t sourceY = 0 ,
targetY = yStart ;
sourceY < s o u r c e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
s o u r c e P i x e l = s o u r c e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
}
Notice that you can have two methods with the same names (like copyPictureTo)
and you dont have any trouble when you compile. How can that be? Java allows
you to have many methods with the same method name as long as the parameters
are different. The first copyPictureTo method took a Picture object and an int.
The second copyPictureTo method took a Picture object, and two int values.
So the two methods have a different number of parameters. Having more than
one method with the same name but different parameters is called overloading. It
doesnt really matter what you name the parameters. What matters in the types.
Two methods with the same name are allowed if the number of parameters is different, or the types of the parameters are different, or the order of the parameter
types is different.
5.2.3
Blending Pictures
When we create collages by copying, any overlap typically means that one picture
shows over another. The last picture painted on is the one that appears. But it
doesnt have to be that way. We can blend pictures by multiplying their colors and
adding them. This gives us the effect of transparency.
We know that 100% of something is the whole thing. 50% of one and 50% of
another would also add up to 100%. In the program below, we blend a picture of
main
2005/1/11
page 160
160
Chapter 5
the two sisters with an overlap of some 50 (the width of Katie minus 150) columns
of pixels (Figure 5.12) onto the current picture.
Program 27: Blending two pictures
/
Method t o b l e n d two s i s t e r s t o g e t h e r o n t o t h e c u r r e n t
picture
/
public void b l e n d P i c t u r e s ( )
{
// c r e a t e t h e s i s t e r p i c t u r e s
Picture katiePicture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( KatieFancy . j p g ) ) ;
Picture jennyPicture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( JenParty . j p g ) ) ;
// d e c l a r e t h e s o u r c e and t a r g e t p i x e l v a r i a b l e s
P i x e l k a t i e P i x e l = null ;
P i x e l j e n n y P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
/ d e c l a r e t h e t a r g e t x and s o u r c e x s i n c e we w i l l need
the values a f t e r the
/
// f o r l o o p
i nt sourceX = 0 ;
i nt t a r g e t X = 0 ;
// copy t h e f i r s t 150 p i x e l s o f k a t i e t o t h e c a n v a s
f o r ( ; sourceX < 1 5 0 ; sourceX++, t a r g e t X++)
{
f o r ( in t sourceY =0 , t a r g e t Y =0;
sourceY < k a t i e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
k a t i e P i x e l = k a t i e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( katiePixel . getColor ( ) ) ;
}
}
/ copy 50% o f k a t i e and 50% o f j e n n y t i l l
t h e end o f k a t i e s w i d t h
/
f o r ( ; sourceX < k a t i e P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
main
2005/1/11
page 161
i
Section 5.2
161
f o r ( in t sourceY =0 , t a r g e t Y =0;
sourceY < k a t i e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
k a t i e P i x e l = k a t i e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
jennyPixel =
j e n n y P i c t u r e . g e t P i x e l ( sourceX 1 5 0 , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor (
new C o l o r ( ( i n t ) ( k a t i e P i x e l . getRed ( ) 0 . 5 +
j e n n y P i x e l . getRed ( ) 0 . 5 ) ,
( i n t ) ( k a t i e P i x e l . getGreen ( ) 0 . 5 +
j e n n y P i x e l . getGreen ( ) 0 . 5 ) ,
( int ) ( k a t i e P i x e l . getBlue ( ) 0 . 5 +
jennyPixel . getBlue () 0 . 5 ) ) ) ;
}
}
// copy t h e r e s t o f Jenny
sourceX = sourceX 1 5 0 ;
f o r ( ; sourceX < j e n n y P i c t u r e . getWidth ( ) ;
sourceX++, t a r g e t X++)
{
f o r ( in t sourceY = 0 , t a r g e t Y = 0 ;
sourceY < j e n n y P i c t u r e . g e t H e i g h t ( ) ;
sourceY++, t a r g e t Y++)
{
j e n n y P i x e l = j e n n y P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( jennyPixel . getColor ( ) ) ;
}
}
}
To try this out create a picture object using the blank 640 by 480 file and
invoke the method on that. Show the result.
main
2005/1/11
page 162
i
162
Chapter 5
'
&
5.2.4
$
Making it Work Tip: Optional parts of the for
loop
Notice that we are missing the initialization area in the
for loops in the method blendPictures(). Also notice that
we moved the declaration of sourceX and sourceY outside
the loops. This is because we want to keep the values
around after the first loop ends. The initialization area of
a for loop is optional (the ; is not optional). In fact, the
initialization area, continuation test, and change area are
all optional. You could code a for loop as for (;;;) but
that isnt terribly useful. It would execute the body of the
loop forever. This is known as an infinite loop.
Rotation
Transformations to the image occur by using the index variables differently or
incrementing them differently, but otherwise keeping the same program. Lets
rotate Katie 90 degrees to the left. What does that mean? Lets try it with
something simple first. You can write some numbers in a table on a piece of paper
and then rotate it left and then read the new table to see where the old numbers
were moved to (Figure 5.13). Notice that the columns become the rows and the
rows the columns but it isnt as simple as just using the source x for the target y
and the source y for the target x.
Value (0,0) in the source moves to (0,2) in the target. Value (0,1) in the
source moves to (1,2) in the target. Value (1,0) in the source moves to (0,1) in the
target. Value (1,1) in the source moves to (1,1) in the target. Value (2,0) in the
main
2005/1/11
page 163
Section 5.2
163
source moves to (0,0) in the target. Value (2,1) in the source moves to (1,0) in the
target. So the first column values move into the bottom row and the last column
values move into the top row. Also notice that the target x value is the same as
the source y value.
We will do the rotation by looping through the pixels in the usual way and
getting the source pixel in the usual way but the target pixels x value will be the
source y and the target pixels y value will be width of the source picture - 1 - the
source x (Figure 5.14).
/
Method t o copy t h e p i c t u r e o f K a t i e b u t r o t a t e
h e r l e f t 90 d e g r e e s on t h e c u r r e n t p i c t u r e
/
public void c o p y K a t i e L e f t R o t a t i o n ( )
{
String sourceFile =
F i l e C h o o s e r . getMediaPath ( KatieFancy . j p g ) ;
P i c t u r e s o u r c e P i c t u r e = new P i c t u r e ( s o u r c e F i l e ) ;
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t sourceX = 0 ;
sourceX < s o u r c e P i c t u r e . getWidth ( ) ;
sourceX++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t sourceY = 0 ;
sourceY < s o u r c e P i c t u r e . g e t H e i g h t ( ) ;
sourceY++)
{
// s e t t h e t a r g e t p i x e l c o l o r t o t h e s o u r c e p i x e l c o l o r
main
2005/1/11
page 164
i
164
Chapter 5
s o u r c e P i x e l = s o u r c e P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( sourceY ,
s o u r c e P i c t u r e . getWidth ( ) 1 sourceX ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
}
To try this out create a picture from the blank paper-sized file and then invoke
the method on it. Show the result.
FIGURE 5.14: Copying a picture to a blank page rotated to the left 90 degrees
main
2005/1/11
page 165
i
Section 5.2
sourcePixel
0
165
2 1
sourceX = 0
sourceY = 0
targetPixel
targetX = sourceY = 0
targetY = source width - 1 - sourceX
targetY = 3 - 1 - 0 = 2
The source x and source y are both 0. The target x is equal to the source y
so it is also 0. But, the target y is equal to the width of the source picture minus
1 minus the source x. The width of the source picture is 3 so the target y is 3 - 1
- 0 which is 2. So we copy the color of the source pixel at (0,0) to the target pixel
at (0,2).
0
2
sourcePixel
1
sourceX = 0
sourceY = 1
targetX = sourceY = 1
targetY = source width - 1 - sourceX
targetY = 3 - 1 - 0 = 2
targetPixel
The source y is incremented by the inner loop to 1 and tested against the
height of the source picture (2). Since it is less than the height we do the body of
the inner loop. So now the source x is 0 and the source y is 1. The target x is equal
to the source y so it is 1. The target y is equal to the width of the source picture
minus 1 minus the source y. The width of the source picture is 3 so the target y is
3 - 1 - 0 which is 2. So we copy the color of the source pixel at (0,1) to the target
pixel at (1,2).
0
sourcePixel
1
2
targetPixel
sourceX = 1
sourceY = 0
targetX = sourceY = 0
targetY = source width - 1 - sourceX
targetY = 3 - 1 - 1 = 1
main
2005/1/11
page 166
166
Chapter 5
The source y is incremented by the inner loop to 2 and tested against the
height of the source picture (2). Since it is not less than the height the inner loop
finishes and the source x is incremented to 1 by the outer loop. The inner loop
starts and sets the source y to 0. So, the source x is 1 and the source y is 0. The
target x is equal to the source y so it is 0. The target y is equal to the width of the
source picture minus 1 minus the source y. The width of the source picture is 3 so
the target y is 3 - 1 - 1 which is 1. So we copy the color of the source pixel at (1,0)
to the target pixel at (0,1).
The inner loop will increment source y and so the next color will be copied
from (1,1) to (1,1). Then, the inner loop will stop again and source x will be
incremented by 1 to 2. The next color will be copied from (2,0) to (0,0). The inner
loop will increment source y so the next color will be copied from (2,1) to (1,0). At
this point source x will be incremented to 3 which is not less than the width of the
source picture (3) and the nested loop will stop.
5.2.5
Scaling
A very common transformation for pictures is to scale them. Scaling up means to
make them larger, and scaling them down makes them smaller. Its common to
scale a 1-megapixel or 3-megapixel picture down to a smaller size to make it easier
to use on the Web. Smaller pictures require less disk space, and thus less network
bandwidth, and thus are easier and faster to download.
Scaling a picture requires the use of sampling which well also use with sounds
later. To scale a picture smaller we are going to take every other pixel when copying
from the source to the target. To scale a picture larger we are going to take every
pixel twice.
Scaling the picture down is the easier function. We will use the passionFlower.jpg picture which is 640 (width) by 480 (height). Instead of incrementing
the source X and Y variables by 1, we simply increment by 2. We divide the amount
of space by 2, since well fill half as much roomour width will be 640/2 and the
height will be 480/2. The result is a smaller flower on the blank 640 by 480 picture
(Figure 5.15).
Program 29: Scaling a picture down (smaller)
/
Method t o copy t h e f l o w e r b u t s m a l l e r ( h a l f as b i g )
on t o t h e c u r r e n t p i c t u r e
/
public void c o p y F l o w e r S m a l l e r ( )
{
Picture flowerPicture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( p a s s i o n F l o w e r . j p g ) ) ;
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
main
2005/1/11
page 167
Section 5.2
167
f o r ( i n t sourceX = 0 , t a r g e t X =0;
sourceX < f l o w e r P i c t u r e . getWidth ( ) ;
sourceX +=2, t a r g e t X++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t sourceY =0 , t a r g e t Y =0;
sourceY < f l o w e r P i c t u r e . g e t H e i g h t ( ) ;
sourceY +=2, t a r g e t Y++)
{
s o u r c e P i x e l = f l o w e r P i c t u r e . g e t P i x e l ( sourceX , sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( targetX , t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
}
To try this out create a picture object using the blank 640 by 480 file and
invoke the method on that. Show the result.
> Picture picture = new Picture(FileChooser.getMediaPath("640x480.jpg"));
> picture.copyFlowerSmaller();
> picture.show();
main
2005/1/11
page 168
i
168
Chapter 5
/
Method t o copy a f l o w e r b u t s c a l e d t o 2 x normal s i z e
on to t h e c u r r e n t p i c t u r e
/
public void c o p y F l o w e r L a r g e r ( )
{
Picture flowerPicture =
new P i c t u r e ( F i l e C h o o s e r . getMediaPath ( r o s e . j p g ) ) ;
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
f o r ( double sourceX = 0 , t a r g e t X =0;
sourceX < f l o w e r P i c t u r e . getWidth ( ) ;
sourceX = sourceX + 0 . 5 , t a r g e t X++)
{
// l o o p t h r o u g h t h e rows
f o r ( double sourceY =0, t a r g e t Y =0;
sourceY < f l o w e r P i c t u r e . g e t H e i g h t ( ) ;
sourceY = sourceY + 0 . 5 , t a r g e t Y++)
{
sourcePixel =
f l o w e r P i c t u r e . g e t P i x e l ( ( i n t ) sourceX , ( i n t ) sourceY ) ;
t a r g e t P i x e l = t h i s . g e t P i x e l ( ( i n t ) targetX , ( i n t ) t a r g e t Y ) ;
targetPixel . setColor ( sourcePixel . getColor ( ) ) ;
}
}
}
STo try this out create a picture object using the blank 640 by 480 file and
invoke the method on that. Show the result.
> Picture picture = new Picture(FileChooser.getMediaPath("640x480.jpg"));
> picture.copyFlowerLarger();
> picture.show();
How Did That Work?.
We start from the same place as the original code for copying a picture. Say
we are copying from the source picture starting at (0,0) and copying to the target
picture starting at (3,1). First we will copy the color of the pixel at (0,0) in the
source picture to (3,1) in the target picture.
main
2005/1/11
page 169
i
Section 5.2
169
When we increment sourceY by 0.5, the actual value will be 0.5 but the (int)
value is 0 so we end up referring to the same pixel in the source, but the target has
moved on to the next pixel. So we will copy the color of the pixel at (0,0) to (3,2).
When we increment sourceY a second time by 0.5 it will now equal 1.0, so
we now move on to the next pixel in the source. So we will copy the color of the
pixel at (0,1) to (3,3).
Again when the sourceY is incremented by 0.5 the actual value will be 1.5
but the (int) of that is 1 so we will copy from (0,1) to (3,4).
And eventually, we cover every pixel. Notice that the end result is degraded
main
2005/1/11
page 170
170
Chapter 5
its choppier than the original. Each pixel is copied four times: twice in the x
direction and twice in the y direction.
/
Method t o c r e a t e a new p i c t u r e t h a t i s s c a l e d
up by t h e p a s s e d number o f t i m e s .
@return t h e new s c a l e d up p i c t u r e
/
public P i c t u r e s c a l e U p ( i n t numTimes )
{
Picture targetPicture =
new P i c t u r e ( t h i s . getWidth ( ) numTimes ,
t h i s . g e t H e i g h t ( ) numTimes ) ;
double f a c t o r = 1 . 0 / numTimes ;
P i x e l s o u r c e P i x e l = null ;
P i x e l t a r g e t P i x e l = null ;
// l o o p t h r o u g h t h e columns
main
2005/1/11
page 171
i
Section 5.3
Concepts Summary
171
Since the method scaleUp returns the resulting scaled Picture object we had
better save a reference to the Picture object to be able to refer to it again.
> Picture p = new Picture(FileChooser.getMediaPath("flower1.jpg"));
> p = p.scaleUp(2);
> p.explore();
Since this method create a new Picture object and copies the scaled picture
into that new Picture object and then returns the new Picture object if you want
to see the result you will have to save a reference to the resulting picture. You can
reuse variables like p, but realize that you will no longer have a reference to the
original Picture object. Of course, you could have declared a new variable to hold
the scaled picture. It would also be of type Picture.
>
>
>
>
5.3
CONCEPTS SUMMARY
This chapter introduced two-dimensional arrays, nested loops, working with multiple variables in a for loop, returning a value from a method, and method overloading.
5.3.1
Two-dimensional Arrays
Pixels are stored in a two-dimensional array. A two-dimensional array is similar
to seating in an auditorium. You can find your seat based on the row and chair
number. You can access a location in a two-dimensional array by specifying an x
and y. All indices start with 0.
main
2005/1/11
page 172
172
5.3.2
Chapter 5
Nested Loops
To process all of the pixels in a picture and track the x and y location of each pixel
you need to use a nested loop. Nested loops are loops inside of loops. You can
either loop through the rows and then the columns (y and then x) or loop through
the columns and then the rows (x and then y).
// l o o p t h r o u g h t h e rows ( y d i r e c t i o n )
f o r ( i n t y = 0 ; y < t h i s . g e t H e i g h t ( ) ; y++)
{
// l o o p t h r o u g h t h e columns ( x d i r e c t i o n )
f o r ( in t x = 0 ; x < t h i s . getWidth ( ) ; x++)
{
// g e t t h e c u r r e n t p i x e l a t t h i s x and y p o s i t i o n
p i x e l = this . g e t P i x e l (x , y ) ;
// do s o m e t h i n g t o t h e c o l o r
// s e t t h e new c o l o r
p i x e l . setColor ( aColor ) ;
}
}
To restrict the area that you are looping through use different values for
starting and stopping the loop. To loop through a rectangular area starting with
the pixel at (startX, startY) at the upper left corner of the rectangular area and
ending with the pixel at (endX,endY) as the bottom right corner of the rectangular
area use:
// l o o p t h r o u g h t h e rows ( y d i r e c t i o n )
f o r ( i n t y = s t a r t Y ; y <= endY ; y++)
{
// l o o p t h r o u g h t h e columns ( x d i r e c t i o n )
f o r ( in t x = s t a r t X ; x <= endX ; x++)
{
// g e t t h e c u r r e n t p i x e l a t t h i s x and y p o s i t i o n
p i x e l = this . g e t P i x e l (x , y ) ;
// do s o m e t h i n g t o t h e c o l o r
// s e t t h e new c o l o r
p i x e l . setColor ( aColor ) ;
}
}
You can declare more than one variable in a loop. This is useful when you
copy from one picture to another. Use variables to represent the source picture x
and y values and use other variables to represent the target picture x and y values.
You can change how the source and target pixel are used in order to rotate the
picture.
// l o o p t h r o u g h t h e columns
f o r ( i n t sourceX = 0 , t a r g e t X = 0 ;
main
2005/1/11
page 173
i
Section 5.3
Concepts Summary
173
By changing the initial and ending values of sourceX, sourceY, targetX and
targetY you can change what part of the source picture you want to copy and
where you want it to go on the target picture. Using this you can clip and create
collages.
If you change the amount you increment or decrement a loop variable by you
can scale a picture up or down.
5.3.3
Methods that do not return any value use the keyword void as the returnType.
Methods that do return a value use the type of that value for the returnType and
then have a return keyword in them that is followed by the thing to return. Remember that a type is any of the primitive types or the name of a class.
Here is an example public method declaration that doesnt return anything
and the name of the method is mirrorVertical and it doesnt take any parameters.
public void m i r r o r V e r t i c a l ( )
Notice that it gives a return type of Picture. The body of the method must
have the keyword return in it and it must return an object that is an instance of
the class Picture.
main
2005/1/11
page 174
174
5.3.4
Chapter 5
Method Overloading
A class can have more than one method with the same name as long as the parameter list is different. The methods can take a different number of parameters,
or the types of the parameters can be different, or the order of the types can be
different. You cant have two methods with the same name and the same number
of parameters with the same types in the same order.
public void copy Pi c tur eTo ( P i c t u r e s o u r c e P i c t u r e , in t x S t a r t )
public void copy Pi c tur eTo ( P i c t u r e s o u r c e P i c t u r e , in t x S t a r t ,
in t y S t a r t )
Notice that there are two method declarations with the same name but one
takes 2 parameters and one takes 3. The compiler will check that a method exists
that takes the same number and type of parameters. If the compiler cant find a
method with the same number, type, and order of parameters it will report that
the method doesnt exist.
> p.copyPictureTo();
Error: No copyPictureTo method in Picture
OBJECTS AND METHODS SUMMARY
Here are the functions used or introduced in this chapter:
new Picture(int width, int height) Creates a new Picture object with the given
width and height. All pixels are white.
getMediaPath(String fileName)
Returns the full path name with the media directory followed by the passed file name. This
is a class method on the FileChooser class.
The default media directory is c:/intro-progjava/mediasources/.
setMediaPath(String directory)
Sets the media directory to use when getting a full
path using getMediaPath(String fileName). This
is a class method on the FileChooser class.
PROBLEMS
5.1. Write the code to mirror a picture around a horizontal line from (0,height-1) to
(width-1,height-1). Be aware that this will double the height of the picture.
5.2. Try to mirror a picture around a diagonal line from (0,0) to (width-1,height-1).
Try to mirror a picture around a diagonal line from (0,height-1) to (width-1,0).
5.3. Write the code to rotate a picture to the right by 90 degrees.
5.4. Weve seen that if you increment the source picture index by 2 while incrementing
the target picture index by 1 for each copied pixel, you end up with the source
being scaled down onto the target. What happens if you increment the target
picture index by 2 as well? What happens if you increment both the source and
target by 0.5 and use int to get just the integer part?
5.5. Write a method named createCollage to create a collage of the same image
at least four times onto the 7x95in.jpg blank JPEG. (You are welcome to add
additional images, too.) One of those four copies can be the original picture.
The other three should be modified forms. You can do any of scaling, cropping,
main
2005/1/11
page 175
i
Section 5.3
Concepts Summary
175
or rotating the image; creating a negative of the image; shifting or altering colors
on the image; and making it darker or lighter.
After composing your image, mirror it. You can do it vertically or horizontally
(or otherwise), in any directionjust make sure that your four base images are
visible still after mirroring.
Your single method should make all of this happenall of the effects and compositing must occur from the single function createCollage. Of course, it is
perfectly okay to use other functions, but make it so that a tester of your program need only to call setMediaPath() and put all your input pictures in her
mediasources directory, create a Picture object from the blank paper-sized file,
and then execute createCollage()and will expect to have a collage generated
and returned.
*5.6. Think about how the grayscale algorithm works. Basically, if you know the
luminance of anything visual (e.g., a small image, a letter), you can replace a
pixel with that visual element in a similar way to create a collage image. Try
implementing that. Youll need 256 visual elements of increasing lightness, all
of the same size. Youll create a collage by replacing each pixel in the original
image with one of these visual elements.
TO DIG DEEPER
The bible of computer graphics is Introduction to Computer Graphics [11]. Its
highly recommended.
main
2005/1/11
page 176
i
176
5.4
Chapter 5
COLOR FIGURES
FIGURE 5.17: Merging red, green, and blue to make new colors
main
2005/1/11
page 177
i
Section 5.4
Color Figures
177
FIGURE 5.19: Color: The original picture (left) and red-decreased version (right)
FIGURE 5.20: Color: Overly blue (left) and red increased by 30% (right)
main
2005/1/11
page 178
i
178
Chapter 5
FIGURE 5.22: Original beach scene (left) and at (fake) sunset (right)
main
2005/1/11
page 179
i
Section 5.4
Color Figures
179
main
2005/1/11
page 180
i
180
Chapter 5
FIGURE 5.27: Color: Increasing reds in the browns, within a certain range
FIGURE 5.28: Finding the range where Jennys eyes are red, then changing them to
black
main
2005/1/11
page 181
i
Section 5.4
Color Figures
181
main
2005/1/11
page 182
i
182
Chapter 5
main
2005/1/11
page 183
i
Section 5.4
Color Figures
183
FIGURE 5.34: Frames from the original movie with kids crawling in front of a blue
screen
main
2005/1/11
page 184
i
184
Chapter 5
main
2005/1/11
page 185
i
C H A P T E R
main
2005/1/11
page 186
i
186
6.1
Chapter 6
true
Statement
or block
statement
main
2005/1/11
page 187
Section 6.1
187
x is less than 40
> if (x > 40) System.out.println("x is greater than 40");
> System.out.println(x);
30
Notice that since x is less than 40 the string saying so was output. However,
since x is not greater than 40 the string saying x is greater than 40 was not
output. We do see the output from the next statement System.out.println(x)
since execution jumps to the statement following an if when the expression is false.
6.1.1
Comparing Colors
What does it mean to compare two colors? How can the computer tell if the color at
the current pixel is red? The distance between two colors is the Cartesian distance
between the colors as points in a three-dimensional space, where red, green, and
blue are the three dimensions. Recall that the distance between two points (x1 , y1 )
and (x
p2 , y2 ) is:
(x1 x2 )2 + (y1 y2 )2
The similar measure for two colors (red1 , green1 , blue1 ) and (red2 , green2 , blue2 )
is: p
(red1 red2 )2 + (green1 green2 )2 + (blue1 blue2 )2
However, you wont have to code this. The Pixel class has an object method
colorDistance(Color color) which returns the distance between the color in
the current Pixel object and the passed color. The hard part is determining what
close enough is for two colors.
6.1.2
Replacing Colors
Heres a program that tries to replace the brown color with red. Mark used the
picture explorer to figure out roughly what the RGB values were for Katies brown
hair, then wrote a program to look for colors close to that, and then increase the
redness of those pixels. Mark played a lot with the value that he used for distance
(here, 50.0) and the amount of redness increase (here, 100% increase). However, this
approach turned part of the couch and carpet red too. (Figure 6.2 and Figure 5.26).
Program 32: Color replacement: Turn brown into red
/
Method t o t u r n t o t h e brown i n a p i c t u r e
i n t o red
/
public void turnBrownIntoRed ( )
{
C o l o r brown = new C o l o r ( 4 2 , 2 5 , 1 5 ) ;
Pixel [ ] p i x e l s = this . g e t P i x e l s ( ) ;
P i x e l p i x e l = null ;
// l o o p t h r o u g h t h e p i x e l s
main
2005/1/11
page 188
i
188
Chapter 6
To use this method to turn Katie into a redhead first create a Picture object
from the file KatieFancy.jpg. Then invoke the method turnBrownIntoRed on
that Picture object and show the result.
> Picture picture = new Picture(FileChooser.getMediaPath("KatieFancy.jpg"));
> picture.turnBrownIntoRed();
> picture.explore();
Notice that we can use a simple for loop through the one-dimensional array
of pixels for this. We dont care where the pixels are in the two-dimensional array
in this method. Of course, we could have used nested for loops instead to loop
through all the pixels.
main
2005/1/11
page 189
i
Section 6.1
189
What this method is doing is looping through all the pixels in the current
picture and for each pixel checking if the distance between the color in the current
pixel is less than 50 away from the color brown (defined as red=42, green=25,
blue=15). If the distance between the current color and the defined brown is less
than 50, the red value at the current pixel is doubled. If the distance is equal or
greater than 50, the pixel color is not changed.
With the picture explorer we can also figure out the coordinates just around
Katies face, and then just do the browns near her face. The effect isnt too good,
though its clear that it worked. The line of redness is too sharp and rectangular
(Figure 6.3 and Figure 5.27).
Program 33: Color replacement in a rectangular area
/
Method t o t u r n brown t o r e d i n s i d e o f
a r e c t a n g u l a r area
/
public void turnBrownToRedInRectangle ( )
{
C o l o r brown = new C o l o r ( 4 2 , 2 5 , 1 5 ) ;
P i x e l p i x e l = null ;
// l o o p t h r o u g h t h e x v a l u e s
f o r ( i n t x =63; x < 1 2 5 ; x++)
{
f o r ( in t y=6; y < 7 6 ; y++)
{
// g e t t h e c u r r e n t p i x e l
p i x e l = this . g e t P i x e l (x , y ) ;
// c h e c k i f i n d i s t a n c e t o brown and i f so d o u b l e t h e r e d
i f ( p i x e l . c o l o r D i s t a n c e ( brown ) < 5 0 . 0 )
p i x e l . s e t C o l o r (new C o l o r ( ( in t ) ( p i x e l . getRed ( ) 2 . 0 ) ,
p i x e l . getGreen ( ) ,
p i x e l . getBlue ( ) ) ) ;
}
}
}
To use this method to turn Katies hair red first create a Picture object from
the file KatieFancy.jpg. Then invoke the method turnBrownIntoRedInRectangle
on that Picture object and show the result.
> Picture picture = new Picture(FileChooser.getMediaPath("KatieFancy.jpg"));
> picture.turnBrownIntoRedInRectangle();
> picture.explore();
main
2005/1/11
page 190
190
Chapter 6
FIGURE 6.3: On left the couch color changes, on right the couch color doesnt change
We put the values for the range right inside the method turnBrownIntoRedInRectangle().
This meant that we didnt need to pass any parameters to specify the range but it
makes the method less reusable. If we want to use it to change a different picture we
would probably have to edit the method to change the range and then recompile.
The method would be easier to reuse if we specified the range when we invoke the
method.
Program 34: Color replacement with passing in the range
/
Method t o t u r n brown t o r e d i n a r e c t a n g u l a r a r e a
specifed
by s t a r t X , endX1, s t a r t Y , endY1
@param s t a r t X t h e s t a r t i n g l o c a t i o n t o c h e c k i n x
@param endX t h e l a s t p i x e l c h e c k e d i s one l e s s tha n
t h i s in x
@param s t a r t Y t h e s t a r t i n g l o c a t i o n t o c h e c k i n y
@param endY t h e l a s t p i x e l c h e c k e d i s one l e s s tha n
t h i s in y
/
public void turnBrownToRedInRectangle ( in t s t a r t X , i n t endX ,
in t s t a r t Y , i n t endY ,
double d i s t a n c e )
{
main
2005/1/11
page 191
Section 6.1
191
C o l o r brown = new C o l o r ( 4 2 , 2 5 , 1 5 ) ;
P i x e l p i x e l = null ;
// l o o p t h r o u g h t h e x v a l u e s
f o r ( i n t x=s t a r t X ; x < endX ; x++)
{
f o r ( in t y=s t a r t Y ; y < endY ; y++)
{
// g e t t h e c u r r e n t p i x e l
p i x e l = this . g e t P i x e l (x , y ) ;
/ c h e c k i f i n d i s t a n c e t o brown i s l e s s t ha n
t h e p a s s e d d i s t a n c e and i f so d o u b l e t h e r e d
/
i f ( p i x e l . c o l o r D i s t a n c e ( brown ) < d i s t a n c e )
p i x e l . s e t C o l o r (new C o l o r ( ( in t ) ( p i x e l . getRed ( ) 2 . 0 ) ,
p i x e l . getGreen ( ) ,
p i x e l . getBlue ( ) ) ) ;
}
}
}
That certainly is easier to reuse now for other ranges. But, what if we want
to change the distance to use between brown and the current color? We could pull
out the distance and pass that in as well.
Program 35: Color replacement with passing in the range and distance
/
Method t o t u r n brown t o r e d i n a r e c t a n g u l a r a r e a
specified
by s t a r t X , endX1, s t a r t Y , endY1
@param s t a r t X t h e s t a r t i n g l o c a t i o n t o c h e c k i n x
@param endX t h e l a s t p i x e l c h e c k e d i s one l e s s tha n
t h i s in x
@param s t a r t Y t h e s t a r t i n g l o c a t i o n t o c h e c k i n y
@param endY t h e l a s t p i x e l c h e c k e d i s one l e s s tha n
t h i s in y
@param d i s t a n c e i f t h e c u r r e n t c o l o r i s w i t h i n
t h i s d i s t a n c e t o brown t h e n change i t
/
public void turnBrownToRedInRectangle ( in t s t a r t X , i n t endX ,
in t s t a r t Y , i n t endY ,
double d i s t a n c e )
{
C o l o r brown = new C o l o r ( 4 2 , 2 5 , 1 5 ) ;
P i x e l p i x e l = null ;
main
2005/1/11
page 192
192
Chapter 6
// l o o p t h r o u g h t h e x v a l u e s
f o r ( i n t x=s t a r t X ; x < endX ; x++)
{
f o r ( in t y=s t a r t Y ; y < endY ; y++)
{
// g e t t h e c u r r e n t p i x e l
p i x e l = this . g e t P i x e l (x , y ) ;
/ c h e c k i f i n d i s t a n c e t o brown i s l e s s t ha n
t h e p a s s e d d i s t a n c e and i f so d o u b l e t h e r e d
/
i f ( p i x e l . c o l o r D i s t a n c e ( brown ) < d i s t a n c e )
p i x e l . s e t C o l o r (new C o l o r ( ( in t ) ( p i x e l . getRed ( ) 2 . 0 ) ,
p i x e l . getGreen ( ) ,
p i x e l . getBlue ( ) ) ) ;
}
}
}
Can you think of any other things that you could do to make this method
easier to reuse? What if I want to change something other than brown? What if I
want to change the old color by increasing the green?
6.1.3
Reducing Red-Eye
Red-eye is the effect where the flash from the camera bounces off the back of the
subjects eyes. Reducing red-eye is a really simple matter. We find the pixels that
are pretty close (a distance from red of 165 works well) to red, then change those
pixels color to a replacement color.
We probably dont want to change the whole picture. In the Figure 6.4, we
can see that Jenny is wearing a red dresswe dont want to wipe out that red, too.
Well fix that by only changing the area where Jennys eyes are. Using the picture
explorer, we find the upper left and lower right corners of her eyes. Those points
were (109, 91) and (202, 107).
Program 36: Remove red-eye
/
Method t o remove rede y e from t h e c u r r e n t p i c t u r e o b j e c t
i n t h e r e c t a n g l e d e f i n e d by s t a r t X , s t a r t Y , endX , endY .
The r e d w i l l be r e p l a c e d w i t h t h e p a s s e d newColor
@param s t a r t X t h e t o p l e f t c o r n e r x v a l u e o f a r e c t a n g l e
@param s t a r t Y t h e t o p l e f t c o r n e r y v a l u e o f a r e c t a n g l e
@param endX
t h e bottom r i g h t c o r n e r x v a l u e o f a
rectangle
@param endY
t h e bottom r i g h t c o r n e r y v a l u e o f a
rectangle
main
2005/1/11
page 193
i
Section 6.1
193
FIGURE 6.4: Finding the range of where Jennys eyes are red
@param newColor t h e new c o l o r t o u s e
/
public void removeRedEye ( in t s t a r t X , i n t s t a r t Y , i n t endX ,
in t endY , C o l o r newColor )
{
P i x e l p i x e l = null ;
/ l o o p t h r o u g h t h e p i x e l s i n t h e r e c t a n g l e d e f i n e d by t h e
s t a r t X , s t a r t Y , and endX and endY /
f o r ( i n t x = s t a r t X ; x < endX ; x++)
{
f o r ( in t y = s t a r t Y ; y < endY ; y++)
{
// g e t t h e c u r r e n t p i x e l
pixel = getPixel (x , y ) ;
// i f t h e c o l o r i s near r e d t h e n change i t
i f ( p i x e l . c o l o r D i s t a n c e ( Color . red ) < 167)
p i x e l . s e t C o l o r ( newColor ) ;
}
}
}
main
2005/1/11
page 194
i
194
Chapter 6
replacement color. The result was good, and we can check that the eye really does
now have all-black pixels (Figure 6.5). (See also Figure 5.28.)
6.2
6.2.1
Negation
You have seen a way to execute a statement or block of statements if some condition
is true using an if. But, how do you execute a statement or block of statements if
a condition is false? One way is to use an if but negate the condition using the !
operator.
> !true
false
> !false
true
So if we wanted to check if a value was less than 20 and if it is print out Yes,
main
2005/1/11
page 195
i
Section 6.2
195
the value is less than 20 and if it isnt print out No, this value is not less than
20 we could test for the value being less than 20 and also test for the value being
not less than 20 using the ! operator.
> int x = 30;
> if (x < 20) System.out.println("Yes, the value is less than 20");
> if (! (x < 20)) System.out.println("No, this value is not less than 20");
No, this value is not less than 20
6.2.2
6.2.3
Statement
or block
statement
main
2005/1/11
page 196
196
Chapter 6
/
Method t o do a s i m p l e e d g e d e t e c t i o n by comparing t h e
a b s o l u t e va lu e of the d i f f e r e n c e between the c o l o r
i n t e n s i t i e s ( average of the c o l o r v a l u e s ) between a
p i x e l and t h e p i x e l b e l o w i t . I f t h e a b s o l u t e v a l u e
of the d i f f e r e n c e between the c o l o r i n t e n s i t i e s i s
l e s s t han a p a s s e d amount t h e t o p p i x e l c o l o r
w i l l be s e t t o w h i t e . O t h e r w i s e i t i s s e t t o b l a c k .
@param amount i f t h e a b s o l u t e v a l u e o f t h e d i f f e r e n c e s
i n t h e c o l o r a v e r a g e i s l e s s th a n t h i s
s e t the c o l o r to white , e l s e b l a c k
/
public void e d g e D e t e c t i o n ( double amount ) {
P i x e l t o p P i x e l = null ;
main
2005/1/11
page 197
i
Section 6.2
197
P i x e l b o t t o m P i x e l = null ;
double topAverage = 0 . 0 ;
double bottomAverage = 0 . 0 ;
i nt endY = t h i s . g e t H e i g h t ( ) 1 ;
/ l o o p t h r o u g h y v a l u e s from 0 t o h e i g h t 1
( s i n c e compare t o b e l o w p i x e l ) /
f o r ( i n t y = 0 ; y < endY ; y++) {
// l o o p t h r o u g h t h e x v a l u e s from 0 t o w i d t h
f o r ( in t x = 0 ; x < t h i s . getWidth ( ) ; x++) {
// g e t t h e t o p and bottom p i x e l s
topPixel = this . g e t P i x e l (x , y ) ;
b o t t o m P i x e l = t h i s . g e t P i x e l ( x , y +1);
// g e t t h e c o l o r a v e r a g e s f o r t h e two p i x e l s
topAverage = t o p P i x e l . g e t A v e r a g e ( ) ;
bottomAverage = b o t t o m P i x e l . g e t A v e r a g e ( ) ;
/ c h e c k i f t h e a b s o l u t e v a l u e o f t h e d i f f e r e n c e
i s l e s s than t h e amount /
i f ( Math . abs ( topAverage bottomAverage ) < amount ) {
t o p P i x e l . s e t C o l o r ( C o l o r .WHITE) ;
// e l s e s e t t h e c o l o r t o b l a c k
} else {
t o p P i x e l . s e t C o l o r ( C o l o r .BLACK) ;
}
}
}
}
>
>
>
>
main
2005/1/11
page 198
i
198
Chapter 6
'
&
6.3
$
Making it Work Tip: Using Curly Braces
You may have noticed that the method edgeDetection
shows the starting curly braces at the end of the line instead of on a new line as shown in the previous methods.
Java doesnt care if the curly braces are at the end of a
line or on a new line. Some programmers prefer one to
another. It is often easier to see that you forgot a curly
brace if it is by itself on a new line. However, the Java
guidelines say to put the opening curly brace at the end of
a line and the closing one on a new line.
You may also notice that we are using curly braces after
the if and else even though there is only one statement
to be executed, so they arent really needed. It is good
practice to have them even if they arent needed because
the code is easier to read and change.
SEPIA-TONED AND POSTERIZED PICTURES: USING MULTIPLE CONDITIONALS TO CHOOSE THE COLOR
We handled the case of having two different ways to process the pixels using an
if and else. What if we have more than two ways that we want to process some
pixels? For example, what if we wanted to do one thing if a value is less than some
number, another thing if it is equal and yet a third if it is greater than the number?
main
2005/1/11
page 199
i
Section 6.3
Sepia-Toned and Posterized Pictures: Using multiple conditionals to choose the color
199
int y = 10;
if (y < 10) System.out.println("y is less than 10");
if (y == 10) System.out.println("y is equal to 10");
is equal 10
if (y > 10) System.out.println("y is greater than 10");
This works but results in some unnecessary checking. Notice that y was equal
to 10 and so that was printed out but it still executed the next statement which
checked if y was greater than 10. But, can y be equal to 10 and greater than 10?
What would have happened if y was less than 10? It would have printed out a
string saying that y is less than 10 and then still checked if y was equal or greater
than 10. We need something to say if the previous test was true only execute that
and then skip to the end of all the checks. We have seen a way to do this for two
possibilities (true or false) using if and else. One way to handle more three or
more possibilities is with if, else if and finally else. You can use as many else
if statements as needed. You are not required to have a final else.
false
if (expression)
true
Statement
or block
else
if (expression)
false
else
true
Statement
or block
Statement
or block
statement
> int y = 2;
> if (y < 10) System.out.println("Y is less than 10");
else if (y == 10) System.out.println("y is equal to 10");
else System.out.println("y is greater than 10");
main
2005/1/11
page 200
200
Chapter 6
y is less than 10
So far, weve done color modification by simply saying This color replaces
that color. We can be more sophisticated in our color swapping. We can look for
a range of colors, by using if, else if, and else, and replace the color with some
function of the original color or a specific color. The results are quite interesting.
For example, we might want to generate sepia-toned prints. Older prints
sometimes have a yellow-ish tint to them. We could just do an overall color change,
but the end result isnt aesthetically pleasing. By looking for different kinds of
colorhighlights, middle ranges, and shadowsand treating them differently, we can
get a better effect (Figure 6.9).
FIGURE 6.9: Original scene (left) and using our sepia-tone program
The way we do this is to first convert the picture to gray, both because older
prints were in shades of gray, and because it makes it a little easier to work with.
We then look for high, middle, and low ranges of color, and change them separately.
We want to make the shadows (darkest grays) a bit darker. We want to make most
of the picture (middle grays) into a brownish color. We want to the highlights
(lightest grays) a bit yellow. Recall that yellow is a mixture of red and green so
one way to make things yellow is to increase the red and green. Another way is to
reduce the amount of blue. The advantage to reducing the blue is that you dont
have to worry about increasing a value past 255 which is the maximum.
Program 38: Convert a picture to sepia-tones
/
Method t o change t h e c u r r e n t p i c t u r e t o a s e p i a
t i n t ( modify t h e m i d d l e c o l o r s t o a l i g h t brown and
t h e l i g h t c o l o r s t o a l i g h t y e l l o w and make t h e
shadows d a r k e r
main
2005/1/11
page 201
i
Section 6.3
Sepia-Toned and Posterized Pictures: Using multiple conditionals to choose the color
201
/
public void s e p i a T i n t ( )
{
P i x e l p i x e l = null ;
double re d V al u e = 0 ;
double g r e e n V a l u e = 0 ;
double b l u e V a l u e = 0 ;
// f i r s t change t h e c u r r e n t p i c t u r e t o g r a y s c a l e
this . g r a y s c a l e ( ) ;
// l o o p t h r o u g h t h e p i x e l s
f o r ( i n t x = 0 ; x < t h i s . getWidth ( ) ; x++)
{
f o r ( in t y = 0 ; y < t h i s . g e t H e i g h t ( ) ; y++)
{
// g e t t h e c u r r e n t p i x e l and c o l o r v a l u e s
p i x e l = this . g e t P i x e l (x , y ) ;
re d V al ue = p i x e l . getRed ( ) ;
g r e e n V a l u e = p i x e l . getGreen ( ) ;
blueValue = p i x e l . getBlue ( ) ;
// t i n t t h e shadows d a r k e r
i f ( re d Va l u e < 6 0 )
{
re d V a lu e = re dV al ue 0 . 9 ;
greenValue = greenValue 0 . 9 ;
blueValue = blueValue 0 . 9 ;
}
// t i n t t h e m i d t o n e s a l i g h t brown
// by r e d u c i n g t h e b l u e
e l s e i f ( r e dV al u e < 1 9 0 )
{
blueValue = blueValue 0 . 8 ;
}
// t i n t t h e h i g h l i g h t s a l i g h t y e l l o w
// by r e d u c i n g t h e b l u e
else
{
blueValue = blueValue 0 . 9 ;
}
// s e t t h e c o l o r s
p i x e l . setRed ( ( in t ) r ed V al u e ) ;
p i x e l . s e t G r e e n ( ( in t ) g r e e n V a l u e ) ;
p i x e l . s e t B l u e ( ( in t ) b l u e V a l u e ) ;
}
}
}
main
2005/1/11
page 202
202
Chapter 6
FIGURE 6.10: Reducing the colors (right) from the original (left)
/
Method t o p o s t e r i z e ( r e d u c e t h e number o f c o l o r s ) i n
t h e p i c t u r e . The number o f r e d s , g r e e n s , and b l u e s
w i l l be 4 .
/
public void p o s t e r i z e ( )
{
P i x e l p i x e l = null ;
i nt re d V al ue = 0 ;
i nt g r e e n V a l u e = 0 ;
i nt b l u e V a l u e = 0 ;
main
2005/1/11
page 203
i
Section 6.3
Sepia-Toned and Posterized Pictures: Using multiple conditionals to choose the color
203
// l o o p t h r o u g h t h e p i x e l s
f o r ( i n t x = 0 ; x < t h i s . getWidth ( ) ; x++) {
f o r ( in t y = 0 ; y < t h i s . g e t H e i g h t ( ) ; y++) {
// g e t t h e c u r r e n t p i x e l and c o l o r s
p i x e l = this . g e t P i x e l (x , y ) ;
re d V al ue = p i x e l . getRed ( ) ;
g r e e n V a l u e = p i x e l . getGreen ( ) ;
blueValue = p i x e l . getBlue ( ) ;
// c h e c k f o r r e d ra n ge and change c o l o r
i f ( re d Va l u e < 6 4 )
re d V a lu e = 3 1 ;
e l s e i f ( r e dV al u e < 1 2 8 )
re d V a lu e = 9 5 ;
e l s e i f ( r e dV al u e < 1 9 2 )
re d V a lu e = 1 5 9 ;
else
re d V a lu e = 2 2 3 ;
// c h e c k f o r g r e e n r an g e
i f ( greenValue < 64)
greenValue = 31;
else i f ( greenValue < 128)
greenValue = 95;
else i f ( greenValue < 192)
greenValue = 159;
else
greenValue = 223;
// c h e c k f o r b l u e r a ng e
i f ( blueValue < 64)
blueValue = 31;
else i f ( blueValue < 128)
blueValue = 95;
else i f ( blueValue < 192)
blueValue = 159;
else
blueValue = 223;
// s e t t h e c o l o r s
p i x e l . setRed ( re dV al ue ) ;
p i x e l . setGreen ( greenValue ) ;
p i x e l . setBlue ( blueValue ) ;
}
}
}
Whats really going on here, though, is setting up (a) a bunch of levels then
(b) setting the value of red, green, or blue to the midpoint of that level. We can do
main
2005/1/11
page 204
204
Chapter 6
this more generally using mathematics to compute the ranges for a desired number
of levels and picking the midpoint. We need to check if the current value is in the
range and if so set it to the midpoint of the range.
How do we check if a value is in a range? If we call the bottom of the range
bottomValue and the top of the range topValue then we could use this math notation bottomValue <= testValue < topValue. However in Java we need to write
it bottomValue <= testValue && testValue < topValue. The two ampersands
(&&) mean and. If I say you have to set the table and sweep the floor, how
many jobs do you have to do? The answer is two, or both of them. If I say you
can set the table or sweep the floor, how many jobs do you have to do then? The
answer is one, or just one of the two. Similarly if in Java you have if (expression
&& expression) then both expressions must be true for the body of the if to be
executed. And, if you have if (expression k expression) then only one of the
two expressions must be true for the body of the if to be executed. The k means
or.
Below is the program for a flexible number of levels, and Figure 6.11 shows a
couple of examples.
Program 40: Posterize by levels
/
Method t o p o s t e r i z e ( r e d u c e t h e number o f c o l o r s ) i n
the picture
@param numLevels t h e number o f c o l o r l e v e l s t o u s e
/
public void p o s t e r i z e ( in t numLevels )
{
P i x e l p i x e l = null ;
i nt re d V al ue = 0 ;
i nt g r e e n V a l u e = 0 ;
i nt b l u e V a l u e = 0 ;
i nt i n c r e m e n t = ( i n t ) ( 2 5 6 . 0 / numLevels ) ;
i nt bottomValue , topValue , middleValue = 0 ;
// l o o p t h r o u g h t h e p i x e l s
f o r ( i n t x = 0 ; x < t h i s . getWidth ( ) ; x++) {
f o r ( in t y = 0 ; y < t h i s . g e t H e i g h t ( ) ; y++) {
// g e t t h e c u r r e n t p i x e l and c o l o r s
p i x e l = this . g e t P i x e l (x , y ) ;
re d V al ue = p i x e l . getRed ( ) ;
g r e e n V a l u e = p i x e l . getGreen ( ) ;
blueValue = p i x e l . getBlue ( ) ;
// l o o p t h r o u g h t h e number o f l e v e l s
f o r ( in t i = 0 ; i < numLevels ; i ++)
{
// compute t h e bottom , top , and m i d d l e v a l u e s
main
2005/1/11
page 205
i
Section 6.3
Sepia-Toned and Posterized Pictures: Using multiple conditionals to choose the color
205
bottomValue = i i n c r e m e n t ;
topValue = ( i + 1 ) i n c r e m e n t ;
middleValue = ( i n t ) ( ( bottomValue + topValue 1 )
/ 2.0);
/ c h e c k i f c u r r e n t v a l u e s a r e i n c u r r e n t r a n g e and
i f so s e t them t o t h e m i d d l e v a l u e
/
i f ( bottomValue <= re d V a lu e &&
r e d V a lu e < topValue )
p i x e l . setRed ( middleValue ) ;
i f ( bottomValue <= g r e e n V a l u e &&
g r e e n V a l u e < topValue )
p i x e l . s e t G r e e n ( middleValue ) ;
i f ( bottomValue <= b l u e V a l u e &&
b l u e V a l u e < topValue )
p i x e l . s e t B l u e ( middleValue ) ;
}
}
}
}
FIGURE 6.11: Pictures posterized to two levels (left) and four levels (right)
main
2005/1/11
page 206
206
Chapter 6
HIGHLIGHTING EXTREMES
What if we want to highlight the lightest and darkest areas of a picture? Would we
highlight areas that are less than some amount from white and less than the same
amount from black? Is there any color that is both close to white and black? No,
we would want to replace the color at all pixels that have a distance from white or
a distance from black less than some amount. We used && to mean and in the
last program. In this program we will use k to mean or.
Program 41: Highlight extremes
/
Method t o r e p l a c e t h e p i x e l c o l o r s i n t h e c u r r e n t
p i c t u r e o b j e c t t h a t have a c o l o r d i s t a n c e l e s s t han
t h e p a s s e d amount t o w h i t e or b l a c k w i t h t h e p a s s e d
replacement color
@param r e p l a c e m e n t C o l o r t h e new c o l o r t o u s e
/
public void h i g h l i g h t L i g h t A n d D a r k ( double amount ,
Color replacementColor ) {
P i x e l p i x e l = null ;
// l o o p t h r o u g h a l l t h e p i x e l s i n t h e x d i r e c t i o n
f o r ( i n t x = 0 ; x < getWidth ( ) ; x++) {
// l o o p t h r o u g h a l l t h e p i x e l s i n t h e y d i r e c t i o n
f o r ( in t y = 0 ; y < g e t H e i g h t ( ) ; y++) {
// g e t t h e c u r r e n t p i x e l
pixel = getPixel (x , y ) ;
// i f t h e d i s t a n c e from w h i t e or b l a c k i s l e s s th an t h e
main
2005/1/11
page 207
i
Section 6.5
207
// p a s s e d amount u s e t h e r e p l a c e c o l o r i n s t e a d
i f ( p i x e l . c o l o r D i s t a n c e ( C o l o r . w h i t e ) < amount | |
p i x e l . c o l o r D i s t a n c e ( C o l o r . b l a c k ) < amount ) {
p i x e l . setColor ( replacementColor ) ;
}
}
}
}
FIGURE 6.12: Original picture (left) and light or dark areas highlighted (right))
6.5
main
2005/1/11
page 208
208
Chapter 6
/
Method t o b l u r t h e p i x e l s
@param numPixels t h e number o f p i x e l s t o a v e r a g e i n a l l
d i r e c t i o n s so i f t h e numPixels i s 2 t h e n we w i l l a v e r a g e
a l l p i x e l s i n t h e r e c t a n g l e d e f i n e d by 2 b e f o r e t h e
current p i x e l to 2 a f t e r the current p i x e l
/
public void b l u r ( in t numPixels )
{
P i x e l p i x e l = null ;
P i x e l s a m p l e P i x e l = null ;
i nt re d V al ue = 0 ;
i nt g r e e n V a l u e = 0 ;
i nt b l u e V a l u e = 0 ;
i nt count = 0 ;
// l o o p t h r o u g h t h e p i x e l s
f o r ( i n t x=0; x < t h i s . getWidth ( ) ; x++) {
f o r ( in t y=0; y < t h i s . g e t H e i g h t ( ) ; y++) {
main
2005/1/11
page 209
i
Section 6.5
209
// g e t t h e c u r r e n t p i x e l
p i x e l = this . g e t P i x e l (x , y ) ;
// r e s e t t h e c o u n t and red , green , and b l u e v a l u e s
count = 0 ;
re d V al ue = g r e e n V a l u e = b l u e V a l u e = 0 ;
/ l o o p t h r o u g h p i x e l numPixels b e f o r e x t o
numPixels a f t e r x
/
f o r ( in t xSample = x numPixels ;
xSample <= x + numPixels ;
xSample++) {
f o r ( in t ySample = y numPixels ;
ySample <= y + numPixels ;
ySample++) {
/ c h e c k t h a t we a r e i n t h e r a n g e o f a c c e p t a b l e
pixels
/
i f ( xSample >= 0 && xSample < t h i s . getWidth ( ) &&
ySample >= 0 && ySample < t h i s . g e t H e i g h t ( ) ) {
s a m p l e P i x e l = t h i s . g e t P i x e l ( xSample , ySample ) ;
r e d V a lu e = re d V al u e + s a m p l e P i x e l . getRed ( ) ;
g r e e n V a l u e = g r e e n V a l u e + s a m p l e P i x e l . getGreen ( ) ;
blueValue = blueValue + samplePixel . getBlue ( ) ;
count = count + 1 ;
}
}
}
// u s e a v e r a g e c o l o r o f s u r r o u n d i n g p i x e l s
C o l o r newColor = new C o l o r ( r ed Va lu e / count ,
g r e e n V a l u e / count ,
b l u e V a l u e / count ) ;
p i x e l . s e t C o l o r ( newColor ) ;
}
}
}
Figure 6.13 shows the flower from the collage made bigger, then blurred. You
can see the pixelation in the bigger versionthe sharp, blocky edges. With the blur,
main
2005/1/11
page 210
210
Chapter 6
some of that pixelation goes away. More careful blurs take into account regions of
colors (so that edges between colors are kept sharp), and thus are able to reduce
pixelation without removing sharpness.
FIGURE 6.13: Making the flower bigger, then blurring to reduce pixellation
6.6
BACKGROUND SUBTRACTION
Lets imagine that you have a picture of someone, and a picture of where they stood
without them there (Figure 6.14). Could you subtract the background of the person
(i.e., figure out where the colors are close), and then replace another background?
Say, of the moon (Figure 6.15)?
FIGURE 6.14: A picture of a child (Katie), and her background without her
Program 43: Subtract the background and replace it with a new one
/
Method t o r e p l a c e t h e b a c k g r o u n d i n t h e c u r r e n t p i c t u r e
main
2005/1/11
page 211
Section 6.6
Background Subtraction
211
w i t h t h e b a c k g r o u n d from a n o t h e r p i c t u r e
@param o l d B a c k g r o u n d a p i c t u r e w i t h t h e o l d b a c k g r o u n d
to replace
@param newBackground a p i c t u r e w i t h t h e new b a c k g r o u n d
to use
/
public void swapBackground ( P i c t u r e oldBackground ,
P i c t u r e newBackground )
{
P i x e l c u r r P i x e l = null ;
P i x e l o l d P i x e l = null ;
P i x e l newPixel = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t x=0; x<getWidth ( ) ; x++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t y=0; y<g e t H e i g h t ( ) ; y++)
{
// g e t t h e c u r r e n t p i x e l and o l d b a c k g r o u n d p i x e l
c u r r P i x e l = this . g e t P i x e l (x , y ) ;
o l d P i x e l = oldBackground . g e t P i x e l ( x , y ) ;
/ i f t h e d i s t a n c e b e t w e e n t h e c u r r e n t p i x e l c o l o r
and t h e o l d b a c k g r o u n d p i x e l c o l o r i s l e s s
than t h e 15 t h e n swap i n t h e new b a c k g r o u n d p i x e l
/
i f ( currPixel . colorDistance ( oldPixel . getColor ( ) ) < 15.0)
{
main
2005/1/11
page 212
i
212
Chapter 6
newPixel = newBackground . g e t P i x e l ( x , y ) ;
c u r r P i x e l . s e t C o l o r ( newPixel . g e t C o l o r ( ) ) ;
}
}
}
}
We can, but the effect isnt as good as we would like (Figure 6.16). Our
daughters shirt color was too close to the color of the wall. And though the light
was dim, the shadow is definitely having an effect here.
Mark tried the same thing with a picture of two students in front of a tiled
wall. While Mark did use a tripod (really critical to get the pixels to line up), Mark
unfortunately left autofocus on, so the two original pictures (Figure 6.17) werent
all that comparable. The background swap (again with the jungle scene) hardly
did anything at all! We changed the threshold value to 50, and finally got some
swapping (Figure 6.18).
>
>
>
>
>
main
2005/1/11
page 213
Section 6.6
Background Subtraction
213
'
&
$
Making it Work Tip: Add an input parameter to
generalize a method
Notice that we changed the threshold from 15.0 to 50.0
for the second test of the swapBackground(oldBG,newBG)
method. A better thing to do would be to change the
method to take the threshold distance as another input
parameter swapBackground(oldBG,newBG,threshold).
This means we wont have to keep changing the method
each time we want to change the threshold, which means
the method can be used in more situations.
/
Method t o r e p l a c e t h e b a c k g r o u n d i n t h e c u r r e n t p i c t u r e
w i t h t h e b a c k g r o u n d from a n o t h e r p i c t u r e
@param o l d B a c k g r o u n d a p i c t u r e w i t h t h e o l d b a c k g r o u n d
to replace
@param newBackground a p i c t u r e w i t h t h e new b a c k g r o u n d
to use
@param t h r e s h o l d i f t h e d i s t a n c e b e t w e e n t h e c u r r e n t
p i x e l c o l o r and t h e b a c k g r o u n d p i x e l c o l o r i s l e s s
than t h i s amount u s e t h e new b a c k g r o u n d p i x e l c o l o r
/
public void swapBackground ( P i c t u r e oldBackground ,
P i c t u r e newBackground ,
double t h r e s h o l d )
{
P i x e l c u r r P i x e l = null ;
P i x e l o l d P i x e l = null ;
P i x e l newPixel = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t x=0; x<getWidth ( ) ; x++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t y=0; y<g e t H e i g h t ( ) ; y++)
{
// g e t t h e c u r r e n t p i x e l and o l d b a c k g r o u n d p i x e l
c u r r P i x e l = this . g e t P i x e l (x , y ) ;
o l d P i x e l = oldBackground . g e t P i x e l ( x , y ) ;
/ i f t h e d i s t a n c e b e t w e e n t h e c u r r e n t p i x e l c o l o r
main
2005/1/11
page 214
214
Chapter 6
and t h e o l d b a c k g r o u n d p i x e l c o l o r i s l e s s tha n
t h e t h r e s h o l d t h e n swap i n t h e new b a c k g r o u n d
pixel
/
i f ( currPixel . colorDistance ( oldPixel . getColor ( ) ) <
threshold )
{
newPixel = newBackground . g e t P i x e l ( x , y ) ;
c u r r P i x e l . s e t C o l o r ( newPixel . g e t C o l o r ( ) ) ;
}
}
}
}
To make this work pass the threshold too when invoking swapBackground:
>
>
>
>
>
FIGURE 6.17: Two people in front of a wall, and a picture of the wall
6.7
CHROMAKEY
The way that weatherpersons appear to be in front of a weather map that changes,
is that they actuallly stand before a background of a fixed color (usually blue or
green), then subtract that color. This is called chromakey. Mark took our sons
blue sheet, attached it to the entertainment center, then took a picture of himself
in front of it, using a timer on a camera (Figure 6.19).
Mark tried a new way to test for blueness. If the blue value was greater
than the sum of the red and green values then it the color was blue.
Program 45: Chromakey: Replace all blue with the new background
main
2005/1/11
page 215
Section 6.7
Chromakey
215
FIGURE 6.18: Swapping a beach for the wall, using background subtraction, with a
threshold of 50
/
Method t o do chromakey u s i n g a b l u e b a c k g r o u n d
@param newBg t h e new b a c k g r o u n d image t o u s e t o r e p l a c e
t h e b l u e from t h e c u r r e n t p i c t u r e
/
public void chromakey ( P i c t u r e newBg )
{
P i x e l c u r r P i x e l = null ;
P i x e l newPixel = null ;
// l o o p t h r o u g h t h e columns
f o r ( i n t x=0; x<getWidth ( ) ; x++)
{
// l o o p t h r o u g h t h e rows
f o r ( in t y=0; y<g e t H e i g h t ( ) ; y++)
main
2005/1/11
page 216
216
Chapter 6
{
// g e t t h e c u r r e n t p i x e l
c u r r P i x e l = this . g e t P i x e l (x , y ) ;
/ i f t h e c o l o r a t t h e c u r r e n t p i x e l m o s t l y b l u e
( b l u e v a l u e i s g r e a t e r tha n r e d and g r e e n
combined , t h e n u s e new b a c k g r o u n d c o l o r
/
i f ( c u r r P i x e l . getRed ( ) + c u r r P i x e l . getGreen ( ) <
currPixel . getBlue ( ) )
{
newPixel = newBg . g e t P i x e l ( x , y ) ;
c u r r P i x e l . s e t C o l o r ( newPixel . g e t C o l o r ( ) ) ;
}
}
}
}
The effect is really quite striking (Figure 6.20). Do note the folds in the
lunar surface, though. The really cool thing is that this program works for any
background thats the same size as the image (Figure 6.21). To put Mark on the
moon and on the beach try this:
>
>
>
>
>
>
>
>
Theres another way of writing this code, which is shorter but does the same
thing.
Program 46: Chromakey, shorter
/
Method t o do chromakey u s i n g a b l u e b a c k g r o u n d
@param newBg t h e new b a c k g r o u n d image t o u s e t o r e p l a c e
t h e b l u e from t h e c u r r e n t p i c t u r e
/
public void chromakeyBlue ( P i c t u r e newBg )
{
Pixel [ ] pixelArray = this . g e t P i x e l s ( ) ;
P i x e l c u r r P i x e l = null ;
P i x e l newPixel = null ;
main
2005/1/11
page 217
i
Section 6.7
Chromakey
217
// l o o p t h r o u g h t h e p i x e l s
f o r ( i n t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++)
{
// g e t t h e c u r r e n t p i x e l
currPixel = pixelArray [ i ] ;
/ i f t h e c o l o r a t t h e c u r r e n t p i x e l m o s t l y b l u e
( b l u e v a l u e i s g r e a t e r t ha n g r e e n and r e d
combined , t h e n u s e new b a c k g r o u n d c o l o r
/
i f ( c u r r P i x e l . getRed ( ) + c u r r P i x e l . getGreen ( ) <
currPixel . getBlue ( ) )
{
newPixe l = newBg . g e t P i x e l ( c u r r P i x e l . getX ( ) ,
c u r r P i x e l . getY ( ) ) ;
c u r r P i x e l . s e t C o l o r ( newPixel . g e t C o l o r ( ) ) ;
}
}
}
main
2005/1/11
page 218
i
218
Chapter 6
'
Making it Work Tip: When do you need a different
method name?
Notice that we used a different name for the new shorter
method. We couldnt have used the same name and had
both methods in our Picture class since the parameters
are the same. Methods can be overloaded (use the same
name) as long as the parameters are different (in number,
or order, or type).
&
%
You dont really want to do chromakey with a common color, like red
something that theres a lot of in your face. Mark tried it with the two pictures
in Figure 6.22one with the flash on, and one with it off. We changed the test to if
(currPixel.getRed() > currPixel.getGreen() + currPixel.getBlue()). The
one without a flash was terriblethe students face was replaced with the background. The one with the flash was better, but the flash is still clear after the swap
(Figure 6.23). Its clear why moviemakers and weather people use blue or green
backgrounds.
6.8
CONCEPTS SUMMARY
We have covered boolean expressions, conditionally executing code using if and
else, combining boolean expressions using and (&&) and or (||) and method
overloading.
6.8.1
Boolean Expressions
A boolean expression is one that results in true or false. The values true and
false are reserved words in Java. Here are some example boolean expressions.
main
2005/1/11
page 219
i
Section 6.8
Concepts Summary
219
<= 30);
> 30);
== 20);
!= 20);
Notice that to check for a variable having a value we use == not =. The = is
used to assign a value to a variable, not check for equality. To check for inequality
use !=.
6.8.2
main
2005/1/11
page 220
220
Chapter 6
first boolean expression is true the second wont be evaluated. If the first boolean
expression is false the second one will still be evaluated.
> int x = 3;
> int y = 5;
> System.out.println(x
true
> System.out.println(x
false
> System.out.println(x
false
> System.out.println(x
false
> System.out.println(x
true
6.8.3
Conditional Execution
To conditionally execute one statement use the if keyword followed by a boolean
expression inside of an open and close parenthesis. Put the statement that you only
want executed if the boolean expression is true on a new line and indent it. If the
boolean expression is false then execution will continue with the next statement.
i f ( boolean e x p r e s s i o n )
// s t a t e m e n t t o e x e c u t e i f t h e b o o l e a n e x p r e s s i o n i s t r u e
statement
// n e x t s t a t e m e n t
statement
main
2005/1/11
page 221
Section 6.8
Concepts Summary
221
} else {
statements
}
If you have 4 options start with an if (boolean expression) and have two
else if (boolean expression) and a final else. The last else is optional.
PROBLEMS
6.1. Try doing chromakey in a rangegrab something out of its background where
the something is only in one part of a picture. For example, put a halo around
someones head, but dont mess with the rest of their body.
6.2. Write a method to copy all but the white pixels from one picture to another.
Use this to put the robot in robot.jpg on the moon in moon-surface.jpg.
6.3. Start with a picture of someone you know, and make some specific color changes
to it:
Turn the skin green
Turn the eyes red.
Turn the hair orange.
Of course, if youre friends skin is already green, or eyes red, or hair orange
choose a different target color.
6.4. Which of the methods below removes all the blue from every pixel of a picture
that already has a blue value of more than 100?
1.
2.
3.
4.
5.
6.
A only
D only
B and C
C and D
None
All
main
2005/1/11
page 222
222
Chapter 6
}
}
}
B.
public void blueChange ( )
{
Pixel [ ] pixelArray = getPixels ( ) ;
P i x e l p i x e l = null ;
f o r ( in t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++) {
pixel = pixelArray [ i ] ;
i f ( p i x e l . getBlue ( ) > 0) {
pixel . setBlue (100);
}
}
}
C.
public void c l e a r S o m e B l u e ( ) {
Pixel [ ] pixelArray = getPixels ( ) ;
P i x e l p i x e l = null ;
f o r ( in t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++) {
pixel = pixelArray [ i ] ;
i f ( p i x e l . c o l o r D i s t a n c e ( C o l o r .BLUE) > 1 0 0 )
pixel . setBlue ( 0 ) ;
}
}
D.
public void s e t B l u e ( ) {
Pixel [ ] pixelArray = getPixels ( ) ;
P i x e l p i x e l = null ;
f o r ( in t i = 0 ; i < p i x e l A r r a y . l e n g t h ; i ++) {
pixel = pixelArray [ i ] ;
i f ( p i x e l . getBlue ( ) > 100)
pixel . setBlue ( 0 ) ;
}
}
6.5. Convert the method copyFlowerLarger() to a method that can scale any picture
up. It should return a new picture object created using new Picture(this.getWidth()
* 2,this.getHeight() * 2).
6.6. Write the method to turn the lightest areas of a picture gray to simulate a fog.
6.7. Write other edge detection methods. Try comparing the current pixel intensity
with the one on the right. Try comparing the current pixel to the average of the
pixels to the right and below.
6.8. What would the output from the following be:
main
2005/1/11
page 223
i
Section 6.8
Concepts Summary
223
int x = 3 0 ;
f o r ( i n t i=x ; i < 4 0 ; i ++) {
i f ( i < 3 5 ) System . out . p r i n t l n ( i i s l e s s than 35 ) ;
e l s e i f ( i == 3 5 ) System . out . p r i n t l n ( i i s 35 ) ;
e l s e System . out . p r i n t l n ( i i s g r e a t e r than 35 ) ;
}
main
2005/1/11
page 224
i
C H A P T E R
Drawing
7.1
7.2
7.3
7.4
main
2005/1/11
page 225
i
Section 7.1
225
lines on a picture (Figure 7.1). It works by simply setting all the pixels in a line to
black! The gap between the lines is 20 pixels.
Program 47: Draw lines by setting pixels
/
Method t o draw a g r i d on a p i c t u r e
/
public void drawGrid ( )
{
P i x e l p i x e l = null ;
// Draw t h e h o r i z o n t a l l i n e s
f o r ( i n t y = 2 0 ; y < t h i s . g e t H e i g h t ( ) ; y+=20) {
f o r ( in t x = 0 ; x < t h i s . getWidth ( ) ; x++) {
p i x e l = this . g e t P i x e l (x , y ) ;
p i x e l . setColor ( Color . black ) ;
}
}
// draw t h e v e r t i c a l l i n e s
f o r ( i n t x = 2 0 ; x < t h i s . getWidth ( ) ; x+=20) {
f o r ( in t y = 0 ; y < t h i s . g e t H e i g h t ( ) ; y++) {
p i x e l = this . g e t P i x e l (x , y ) ;
p i x e l . setColor ( Color . black ) ;
}
}
}
To test this method create a Picture object and then invoke the method on
the Picture.
>
>
>
>
This method first draws the horizontal lines by setting y to a value of 20 and
incrementing y by 20, while x starts at 0 and is incremented by 1. Next the method
draws the vertical lines by setting x to 20 and incrementing x by 20 while y starts
at 0 and is incremented by 1. To draw more lines decrease the start and increment
values and to draw less lines increase the start and increment values.
main
2005/1/11
page 226
i
226
Chapter 7
Drawing
'
&
$
Making it Work Tip: Working with Color Objects
You may notice that this program is using the predefined Color object (object of the Color class)
Color.black.
Remember that Java pre-defines for
you a bunch of colors: Color.black, Color.white,
Color.blue, Color.red, Color.green, Color.gray,
Color.lightGray, Color.darkGray, Color.yellow,
Color.orange, Color.pink, Color.magenta,
and
Color.cyan. You can use any of these when you need a
color. You can also create a Color object by providing
the red, green, and blue values (between 0 and 255) using
new Color(red,green,blue). For example to create
a pure black color you would use new Color(0,0,0)
and to create a pure white color you would use new
Color(255,255,255).
We can imagine drawing anything we want like this, by simply setting individual pixels to whatever colors we want. We could draw rectangles or circles, simply
by figuring out what pixels need to be what color. We could even draw lettersby
setting the appropriate pixels to the appropriate colors, we could make any letter
we want. While we could do it, it would involve a lot of work to do all the math
for all the different shapes and letters. Thats work that lots of people want done,
so instead, the basic drawing has been built into Java for you.
main
2005/1/11
page 227
i
Section 7.1
7.1.1
227
main
2005/1/11
page 228
228
Chapter 7
Drawing
fillArc(int x1, int y1, int w, int h, int startAngle, int arcAngle)
draws a filled arc that is part of an oval that fits in the enclosing rectangle
at (x1, y1), the width of the enclosing rectangle is w and the height of the
enclosing rectangle is h. The arc starts at the given startAngle and extends
arcAngle degrees (where 0 degrees is at the 3 oclock position on a clock and
45 degrees goes through the upper right corner of the enclosing rectangle).
The ending angle is the startAngle plus arcAngle.
drawPolygon(int[] xArray, int[] yArray, int numPoints) draws the outline of a closed polygon using the x values in xArray and the y values in
yArray using the current color.
fillPolygon(int[] xArray,int[] yArray, int numPoints) draws a filled
closed polygon using the x values in xArray and the y values in yArray using
the current color.
$
'
Making it Work Tip: Use the Java API
Java is a large language and it is nearly impossible to know
every method for every class. Use the application program
interface (API) documentation to see the methods that are
available for a class. If you look at the documentation for
the Graphics class in the java.awt package you will see all
of the methods defined for that class. We are only showing some of the most commonly used methods here. To
view the API documentation go to: http://java.sun.com/.
Find the specification for the version of the language that
you are using. Click on the package in the top left window frame and then click on the class name in the bottom
left window frame. The documentation for that class will
appear on the right. Scroll down to Method Summary
(Figure 7.2). This gives an alphabetical listing of all of the
methods in defined in that class.
&
%
We can use these commands to add simple shapes to existing pictures. What
would it look like if a mysterious red box washed up on the shore of a beach? We
will need to get a Graphics object to use for the drawing. We can get one from
a Picture object using the method getGraphics(). When you draw you set the
color using setColor(Color color) and then do any drawing commands. We can
make a box appear on a picture of a beach with this method (Figure 7.3).
Program 48: Adding a box
/
Method t o add a s o l i d r e d r e c t a n g l e t o t h e c u r r e n t p i c t u r e
/
public void addBox ( )
main
2005/1/11
page 229
i
Section 7.1
229
List of packages
{
// g e t t h e g r a p h i c s c o n t e x t from t h e p i c t u r e
Graphics g = this . getGraphics ( ) ;
// s e t t h e c o l o r t o r e d
g . setColor ( Color . red ) ;
// draw t h e box as a f i l l e d r e c t a n g l e
g . f i l l R e c t (150 ,200 ,50 ,50);
}
main
2005/1/11
page 230
230
Chapter 7
Drawing
> p.addBox();
> p.show();
This method isnt very reusable. The only way to change it to work for other
rectangles is to modify the color and rectangle information and then recompile. If
we want this to work on any rectangle then we will want to pass parameters to
make the method more general.
Program 49: General draw box
/
Method t o draw a f i l l e d box on t h e c u r r e n t p i c t u r e
@param c o l o r t h e c o l o r t o draw t h e box w i t h
@param t o p L e f t X t h e t o p l e f t x c o o r d i n a t e o f t h e box
@param t o p L e f t Y t h e t o p l e f t y c o o r d i n a t e o f t h e box
@param w i d t h t h e w i d t h o f t h e box
@param h e i g h t t h e h e i g h t o f t h e box
/
public void drawBox ( C o l o r c o l o r , i nt topLeftX , in t topLeftY ,
in t width , i n t h e i g h t )
{
// g e t t h e g r a p h i c s c o n t e x t f o r drawing
Graphics g = this . getGraphics ( ) ;
// s e t t h e c u r r e n t c o l o r
g . setColor ( color ) ;
// draw t h e f i l l e d r e c t a n g l e
g . f i l l R e c t ( topLeftX , topLeftY , width , h e i g h t ) ;
}
We could use this more general method to generate the same picture by:
> Picture p = new Picture(FileChooser.getMediaPath("beach-smaller.jpg"));
> p.drawBox(java.awt.Color.red,150,200,50,50);
> p.show();
The advantage of the method drawBox over the method addBox is that it can
be used to draw any rectangle of any color on any picture.
Below is another example of using the simple drawing commands (Figure 7.4).
Program 50: An example of using drawing commands
/
Method t o show d i f f e r e n t drawing c a p a b i l i t i e s .
It
w i l l draw a s t r i n g , a l i n e , a f i l l e d r e c t a n g l e , t h e
o u t l i n e o f a r e c t a n g l e , t h e o u t l i n e o f an o v a l ,
main
2005/1/11
page 231
Section 7.1
231
and a f i l l e d a r c .
/
public void drawExample ( )
{
// g e t t h e g r a p h i c s o b j e c t t o u s e f o r drawing
Graphics g r a p h i c s = this . getGraphics ( ) ;
// s t a r t w i t h a b l a c k c o l o r
graphics . setColor ( Color . black ) ;
/ draw t h e s t r i n g w i t h a upper l e f t c o r n e r
a t x =10 , y=75
/
graphics . drawString (
This i s a t e s t o f drawing a s t r i n g on a p i c t u r e ,
10 ,75);
// draw a l i n e from ( 1 0 , 2 0 ) t o ( 3 0 0 , 5 0 )
g r a p h i c s . drawLine ( 1 0 , 2 0 , 3 0 0 , 5 0 ) ;
// s e t t h e c o l o r t o y e l l o w
graphics . setColor ( Color . yellow ) ;
/ draw a f i l l e d r e c t a n g l e ( f i l l e d w i t h y e l l o w ) a t
upper l e f t ( 0 , 2 0 0 ) w i t h a w i d t h o f 300 and
h e i g h t 250
/
graphics . f i l l R e c t (0 ,200 ,300 ,250);
// s e t t h e c o l o r b a c k t o b l a c k
graphics . setColor ( Color . black ) ;
/ draw t h e o u t l i n e o f a r e c t a n g l e w i t h t h e upper
l e f t a t ( 1 0 , 2 1 0 ) and a w i d t h o f 200 and a h e i g h t
o f 100
/
g r a p h i c s . drawRect ( 1 0 , 2 1 0 , 2 0 0 , 1 0 0 ) ;
/ draw an o v a l e n c l o s e d by a r e c t a n g l e w i t h t h e t o p
l e f t c o r n e r a t ( 4 0 0 , 1 0 ) and a w i d t h o f 200 and a
h e i g h t o f 100
/
g r a p h i c s . drawOval ( 4 0 0 , 1 0 , 2 0 0 , 1 0 0 ) ;
/ draw an a r c which i s p a r t o f an o v a l e n c l o s e d by
a r e c t a n g l e with the top l e f t corner at (400 ,300)
a w i d t h o f 200 , and a h e i g h t o f 1 5 0 . The a r c
s t a r t s a t 0 (3 o c l o c k p o s i t i o n ) and g o e s 180
d e g r e e s c o u n t e r c l o c k w i s e t o t h e 9 o c l o c k p o s i t i o n
/
main
2005/1/11
page 232
232
Chapter 7
Drawing
To try this out create a picture from the blank 640 by 480 file. Then invoke
the method drawExample() on the picture.
> Picture p = new Picture(FileChooser.getMediaPath("640x480.jpg"));
> p.drawExample();
> p.show();
How would you draw a simple face (Figure 7.5)? You could draw an oval for
the head. You could use filled ovals for the eyes. You could use arcs for the mouth
and eyebrows.
Program 51: An example of using oval and arc drawing commands
/
Method t o draw a f a c e t o d e m o n s t r a t e drawing
o v a l s and a r c s
/
public void drawFace ( )
{
// g e t t h e g r a p h i c s o b j e c t t o u s e f o r drawing
Graphics g r a p h i c s = this . getGraphics ( ) ;
// s t a r t w i t h a b l a c k c o l o r
graphics . setColor ( Color . black ) ;
// draw t h e o v a l f o r t h e f a c e
main
2005/1/11
page 233
i
Section 7.1
233
FIGURE 7.5: A drawn face (left) and the face with enclosing rectangles (right)
g r a p h i c s . drawOval ( 1 3 0 , 5 0 , 3 8 0 , 3 8 0 ) ;
// draw t h e o v a l s f o r t h e e y e s
graphics . f i l l O v a l (225 ,155 ,40 ,40);
graphics . f i l l O v a l (375 ,155 ,40 ,40);
// draw t h e a r c s f o r t h e e y e b r o w s
g r a p h i c s . drawArc ( 2 2 5 , 1 4 5 , 4 0 , 4 0 , 4 5 , 9 0 ) ;
g r a p h i c s . drawArc ( 3 7 5 , 1 4 5 , 4 0 , 4 0 , 4 5 , 9 0 ) ;
// draw t h e a r c f o r t h e mouth
g r a p h i c s . drawArc ( 1 9 0 , 8 5 , 2 5 5 , 2 5 5 , 4 5 , 9 0 ) ;
}
To try this out create a picture from the blank 640 by 480 file. Then invoke
the method drawFace() on the picture.
> Picture p = new Picture(FileChooser.getMediaPath("640x480.jpg"));
> p.drawFace();
> p.show();
$
'
&
7.1.2
main
2005/1/11
page 234
i
234
Chapter 7
Drawing
the program and not the pixels? Thats what a vector representation for graphics
is about.
Vector-based graphical representations are basically executable programs that
generate the picture when desired. Vector-based representations are used in Postscript,
Flash, and AutoCAD. When you make a change to an image in Flash or AutoCAD,
you are actually making a change to the underlying representationessentially,
youre changing the program, like the one in Program 7.4 (page 232). The program
is then executed again to make the image appear. But thanks to Moores Law,
that execution-and-new-display occurs so fast that it feels like youre changing the
picture.
Font definitions languages like Postscript and TrueType actually define miniature programs (or equations) for each and every letter or symbol. When you want
the letter or symbol at a particular size, the program is run to figure out which
pixels should be set to what values. (Some actually specify more than one color to
create the effect of smoother curves.) Because the programs are written to handle
the desired font size as an input, the letters and symbols can be generated at any
size.
Bitmap graphical representations, on the other hand, store every individual
pixel, or some compressed representation of the pixels. Formats like BMP, GIF,
and JPEG are essentially bitmap representations. GIF and JPEG are compressed
representationsthey dont represent each and every pixel with 24 bits. Instead,
they use some techniques to represent the same information but with fewer bits.
What does compression mean? It means that various techniques have been
used to make the file smaller. Some compression techniques are lossy compression
some detail is lost, but hopefully the least significant (perhaps even invisible to the
human eye, or ear) detail. Other techniques, lossless compression lose no detail, but
still scrunch the file. One of the lossless techniques is run length encoding (RLE).
Imagine that youve got a long line of yellow pixels in a picture, surrounded
by some blue pixels. Something like this:
B B Y Y Y Y Y Y Y Y Y B B
What if you encoded this, not as a long line of pixels, but as something like:
B B 9 Y B B
In words, you encode blue, blue, then 9 yellows, then blue and blue. Since
each of those yellow pixels takes 24 bits (3 bytes for red, green and blue), but
recording 9 takes just a single byte, theres a huge savings. We say that were
encoding the length of the run of yellowsthus, run length encoding. Thats just
one of the compression methods that gets used to make pictures smaller.
There are several benefits to vector-based representations over bitmap representations. If you can represent the picture you want to send (say, over the Internet)
using a vector-based representation, its much smaller than sending all the pixels
in a sense, vector notation is already compressed. Essentially, youre sending the
instructions for how to make the picture, rather than sending the picture itself.
For very complex images, however, the instructions can be as long as the image
itself (imagine sending all the directions on how to paint the Mona Lisa!), so there
main
2005/1/11
page 235
i
Section 7.1
235
is no benefit. But when the images are simple enough, representations like those
used in Flash make for faster upload and download times than sending the same
information as JPEG images.
The real benefit of vector-based notations come when you want to change the
image. Lets say that youre working on an architectural drawing, and you extend
a line in your drawing tool. If your drawing tool is only working with bitmapped
images (sometimes called a painting tool ) then all you have are more pixels on the
screen that are adjacent to the other pixels on the screen representing the line.
Theres nothing in the computer that says that all those pixels represent a line of
any kindtheyre just pixels. But if your drawing tool is working with vector-based
representations (sometimes called a drawing tool ) then extending a line means that
youre changing an underlying representation of a line.
Why is that important? The underlying representation is actually a specification of the drawing, and it can be used anywhere that a specification is needed.
Imagine taking the drawing of a part, then actually running the cutting and stamping machines based on that drawing. This happens regularly in many shops, and its
possible because the drawing isnt just pixelsits a specification of the lines and
their relationships, which can then be scaled and used to determine the behavior
of machines.
You might be wondering, But how could we change the program? Can
we write a program that would essentially re-type the program or parts of the
program? Yes, we can, and well do that in the chapter on text.
7.1.3
main
2005/1/11
page 236
i
236
Chapter 7
Drawing
DialogInput, Monospaced, Serif, or SansSerif for font names. You can get an array
of all of the available font names using:
>
>
>
>
import java.awt.*;
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] nameArray = env.getAvailableFontFamilyNames();
for (int i=0; i < nameArray.length; i++)
System.out.println(nameArray[i]);
The method to draw a passed string on the current picture at the passed x
and y is:
Program 52: Draw a string on a picture
/
Method t o draw a s t r i n g on t h e c u r r e n t p i c t u r e
@param t e x t t h e s t r i n g t o draw
@x t h e x l o c a t i o n t o s t a r t a t
@y t h e y l o c a t i o n o f t h e b a s e l i n e
/
public void d r a w S t r i n g ( S t r i n g t e x t , i n t x , in t y )
{
// g e t t h e g r a p h i c s o b j e c t
Graphics g = getGraphics ( ) ;
// s e t t h e c o l o r
g . setColor ( Color . black ) ;
// s e t t h e f o n t
g . s e t F o n t (new Font ( A r i a l , Font .BOLD, 2 4 ) ) ;
// draw t h e s t r i n g
g . drawString ( text , x , y ) ;
}
To use this program you can use the picture explorer to determine where you
want the baseline of the string to be and then use the drawString method to draw
the string on the picture. Our son, Matthew, took a picture of a kitten on a trip to
Greece. Lets add a string that explains the picture near the bottom of the picture
(Figure 7.7).
>
>
>
>
main
2005/1/11
page 237
Section 7.1
237
The string isnt quite in the center of the picture. What if we want it to be in
the center? How could we calculate the starting x position for the string such that
the resulting string is centered? We know that the center of the picture horizontally
is at the half the width of the picture (int) (picture.getWidth() * 0.5). If we
subtract half the length of the string in pixels from the center of the picture that
would center the string. How do we calculate the length of the string in pixels?
The length of the string depends on the number of characters in the string but also
on the font used to draw the string.
To get information about the length of the string in the number of pixels
drawn we can use the FontMetrics class which is in package java.awt. To get a
FontMetrics object use g.getFontMetrics() where g is a Graphics object. The
FontMetrics class contains methods for getting information about the display of
a font. For example, we can get the length in pixels of a string using the method
stringWidth(String str). We could get the height in pixels of a string drawn in
the current font using the method getHeight(). We could get the length of the
descent (part of character like p below the baseline) using getDescent().
Program 53: Drawing a string centered horizontally on a picture
/
Method t o draw a h o r i z o n t a l l y c e n t e r e d s t r i n g
on t h e c u r r e n t p i c t u r e
@param t e x t t h e s t r i n g t o draw
@x t h e x l o c a t i o n t o s t a r t a t
@y t h e y l o c a t i o n o f t h e b a s e l i n e
/
public void d r a w H o r i z o n t a l C e n t e r e d S t r i n g ( S t r i n g t e x t ,
int y )
main
2005/1/11
page 238
i
238
Chapter 7
Drawing
{
// g e t t h e g r a p h i c s o b j e c t
Graphics g = getGraphics ( ) ;
// c r e a t e t h e f o n t o b j e c t
Font f o n t = new Font ( A r i a l , Font .BOLD, 2 4 ) ;
// s e t t h e c o l o r
g . setColor ( Color . black ) ;
// s e t t h e f o n t
g . setFont ( font ) ;
// g e t t h e f o n t m e t r i c s
FontMetrics fontMetrics = g . getFontMetrics ( ) ;
// g e t t h e w i d t h o f t h e s t r i n g
i nt strWidth = f o n t M e t r i c s . s t r i n g W i d t h ( t e x t ) ;
// c a l c u l a t e t h e c e n t e r o f t h e
picture
i nt c e n t e r = ( in t ) ( t h i s . getWidth ( ) 0 . 5 ) ;
// draw t h e s t r i n g c e n t e r e d i n x
g . drawString ( text ,
c e n t e r ( in t ) ( strWidth 0 . 5 ) ,
y);
}
main
2005/1/11
page 239
Section 7.2
239
7.2
/
Method t o draw a g r a y e f f e c t p i c t u r e on t h e
current picture
/
public void drawGrayEff ect ( )
{
// c r e a t e a medium g r a y c o l o r t o u s e
C o l o r medGray = new C o l o r ( 1 0 0 , 1 0 0 , 1 0 0 ) ;
// Do 100 columns o f medium g r a y
f o r ( i n t x = 0 ; x < 1 0 0 ; x++)
f o r ( in t y = 0 ; y < 1 0 0 ; y++)
t h i s . g e t P i x e l ( x , y ) . s e t C o l o r ( medGray ) ;
/ Do 100 columns o f g r a y s t a r t i n g a t medium
g r a y and g e t t i n g l i g h t e r
/
f o r ( i n t x =100 , g r a y L e v e l =100;
x < 200;
x++, g r a y L e v e l++)
f o r ( in t y=0; y < 1 0 0 ; y++)
main
2005/1/11
page 240
i
240
Chapter 7
Drawing
this . g e t P i x e l (x , y ) . setColor (
new C o l o r ( g r a y L e v e l , g r a y L e v e l , g r a y L e v e l ) ) ;
// Do 100 columns s t a r t i n g a t b l a c k and g e t t i n g l i g h t e r
f o r ( i n t x =200 , g r a y L e v e l =0; x < 3 0 0 ; x++, g r a y L e v e l++)
f o r ( in t y=0; y < 1 0 0 ; y++)
this . ge t P i x e l (x , y ) . setColor (
new C o l o r ( g r a y L e v e l , g r a y L e v e l , g r a y L e v e l ) ) ;
// Do 100 columns o f medium g r a y
f o r ( i n t x =300; x < 4 0 0 ; x++)
f o r ( in t y=0; y < 1 0 0 ; y++)
t h i s . g e t P i x e l ( x , y ) . s e t C o l o r ( medGray ) ;
}
To use this method create a picture of the file that has a blank 640 by 480
picture in it. Invoke the method on that picture.
> Picture p = new Picture(FileChooser.getMediaPath("640x480.jpg"));
> p.drawGrayEffect();
> p.show();
Graphics functions are particularly good at drawings that are repeated where
the positions of lines and shapes and the selection of colors can be made by mathematical relationships.
Program 55: Draw the picture in Figure 7.10
/
Method t o draw a p i c t u r e w i t h a s u c c e s s i o n o f
f i l l e d r e c t a n g l e s with the top l e f t corner the
d a r k e s t and t h e bottom r i g h t t h e l i g h t e s t on
the current picture
/
public void d r a w F i l l e d R e c t a n g l e s ( )
{
Graphics g = this . getGraphics ( ) ;
C o l o r c o l o r = null ;
// l o o p 25 t i m e s
f o r ( i n t i = 2 5 ; i > 0 ; i )
{
c o l o r = new C o l o r ( i 1 0 , i 5 , i ) ;
g . setColor ( color ) ;
g . f i l l R e c t (0 ,0 , i 10 , i 1 0 ) ;
}
}
To use this method create a picture of the file that has a blank 640 by 480
picture in it. Invoke the method on that picture.
main
2005/1/11
page 241
i
Section 7.2
241
/
Method t o draw a p i c t u r e w i t h a s u c c e s s i o n o f
r e c t a n g l e s on t h e c u r r e n t p i c t u r e
/
public void d r a w R e c t a n g l e s ( )
{
Graphics g = this . getGraphics ( ) ;
C o l o r c o l o r = null ;
// l o o p 25 t i m e s
f o r ( i n t i = 2 5 ; i > 0 ; i )
{
g . setColor ( Color . black ) ;
g . drawRect ( i , i , i 3 , i 4 ) ;
g . drawRect (100+ i 4 ,100+ i 3 , i 8 , i 1 0 ) ;
}
}
To use this method create a picture of the file that has a blank 640 by 480
picture in it. Invoke the method on that picture.
> Picture p = new Picture(FileChooser.getMediaPath("640x480.jpg"));
> p.drawRectangles();
> p.show();
main
2005/1/11
page 242
i
242
Chapter 7
Drawing
7.2.1
7.3
main
2005/1/11
page 243
Section 7.3
243
The java.awt.Graphics class is okay for simple drawing but lacks many
advanced features. However, the class java.awt.Graphics2D which is part of the
Java 2D API can be used for more advanced drawing. Some of the capabilities of
a Graphics2D object are:
You can set the width of the brush (pen). You can also set the style of the
brush to do different types of dashed lines.
You can rotate, translate, scale, or shear what you are drawing.
You can fill a shape with more than just a solid color. You can fill a shape
with a gradient or a texture.
You can change what happens when objects overlap.
You can clip objects so that only the part visible inside the clipping area is
drawn. This is like using a stencil.
You can set rendering hints to make your curves smoother using anti-aliasing
if it is available.
7.3.1
/
Method t o add two c r o s s e d l i n e s t o a p i c t u r e .
One l i n e w i l l go from t h e t o p l e f t c o r n e r t o t h e
bottom r i g h t c o r n e r . The o t h e r w i l l go from t h e
bottom l e f t c o r n e r t o t h e t o p r i g h t c o r n e r .
/
public void drawWideX ( C o l o r c o l o r , f l o a t width )
{
// g e t t h e Graphics2D o b j e c t
Graphics g r a p h i c s = getGraphics ( ) ;
Graphics2D g2 = ( Graphics2D ) g r a p h i c s ;
// s e t t h e c o l o r and b r u s h w i d t h
main
2005/1/11
page 244
i
244
Chapter 7
Drawing
g2 . s e t P a i n t ( c o l o r ) ;
g2 . s e t S t r o k e (new B a s i c S t r o k e ( width ) ) ;
// g e t t h e max x and y v a l u e s
i nt maxX = getWidth ( ) 1 ;
i nt maxY = g e t H e i g h t ( ) 1 ;
// draw t h e l i n e s
g2 . draw (new Line2D . Double ( 0 , 0 ,maxX, maxY ) ) ;
g2 . draw (new Line2D . Double ( 0 ,maxY, maxX , 0 ) ) ;
}
You can use this program to add a wide red X to a picture as shown in
Figure 7.12.
> Picture p = new Picture(FileChooser.getMediaPath("grayMotorcycle.jpg"));
> p.drawWideX(java.awt.Color.red,5);
> p.show();
'
&
7.3.2
$
Making it Work Tip: Creating and Drawing
Shapes with Graphics2D
Notice that we created a Line2D.Double object and then
asked the Graphics2D object named g2 to draw this object. This is different from how we drew shapes using the
Graphics class. With the Graphics2D class you create
geometric objects and either draw or fill them.
The Line2D.Double probably looks strange to you. This
is actually the name of a class in the java.awt.geom package. Even though java.awt.geom and java.awt both
start the same they are different packages. If you imported all classes in the package java.awt using the wildcard * you still wouldnt have imported the classes in
the java.awt.geom package. You need at least two import statements if you are using classes from both of these
packages.
main
2005/1/11
page 245
Section 7.3
245
Notice that the drawImage methods take an Image object not a Picture
object. The Picture class is one we created, but it contains a method that will get
you an Image object: getImage().
So, to copy a picture to the current picture object you need to get the
Graphics object to use to draw on the current picture. You can do this using
the method getGraphics(). Next draw the Image that you get from the passed
Picture (using the method getImage()) at the passed x and y location using
the method drawImage(Image img, int x, int y, ImageObserver observer).
What is an ImageObserver? It is an object that wants to be notified as the image
is changed. It can be null to say that no object wants to be notified.
Program 58: Copy a picture to this picture
/
Method t o copy t h e p a s s e d p i c t u r e i n t o t h e c u r r e n t
p i c t u r e a t t h e g i v e n x and y p o s i t i o n i n t h e
current picture
@param s o u r c e t h e p i c t u r e t o copy
@param x t h e x o f t h e uppper l e f t c o r n e r t o copy t o
@param y t h e y t o t h e upper l e f t c o r n e r t o copy t o
/
public void copy ( P i c t u r e s o u r c e , i nt x , in t y )
{
// g e t t h e g r a p h i c s o b j e c t
Graphics g = this . getGraphics ( ) ;
// copy t h e image
g . drawImage ( s o u r c e . getImage ( ) , x , y , null ) ;
main
2005/1/11
page 246
246
Chapter 7
Drawing
You can use this program to copy a turtle to the beach using:
>
>
>
>
>
Now change the copy method to use a Graphics2D object. Will it still compile? Try it and see. Does the Graphics2D class have the drawImage method?
Program 59: Copy a picture to this picture using Graphics2D
/
Method t o copy t h e p a s s e d p i c t u r e i n t o t h e c u r r e n t
p i c t u r e a t t h e g i v e n x and y p o s i t i o n i n t h e c u r r e n t
picture
@param s o u r c e t h e p i c t u r e t o copy
@param x t h e x o f t h e uppper l e f t c o r n e r t o copy t o
@param y t h e y t o t h e upper l e f t c o r n e r t o copy t o
/
public void copy2D ( P i c t u r e s o u r c e , i n t x , i n t y )
{
// g e t t h e g r a p h i c s o b j e c t
Graphics g = this . getGraphics ( ) ;
G r a p h i c s g2 = ( Graphics2D ) g ;
main
2005/1/11
page 247
i
Section 7.3
247
// copy t h e image
g2 . drawImage ( s o u r c e . getImage ( ) , x , y , null ) ;
}
Why does this work? The class Graphics2D inherits from the class Graphics.
What does that mean? Have you heard of children inheriting features or abilities
from their parents like eye color, hair color, or musical ability? A child class inherits
from a parent class. What can it inherit? Classes dont have eye color, but they
do define data (fields) and behavior (methods). A child class inherits data (fields)
and behavior (methods).
Unlike people, classes inherit all of the fields and methods of the parent class
and they can only have one parent. An object of a child class can invoke public
methods that are defined in a parent class just as if they were defined in the child
class. An object of a child class can also access any public fields in a parent class
as if they were defined in the child class.
What about private fields and methods? While these are inherited they can
not be directly accessed by a child object, nor should they be. The standard way to
work with private fields is to provide public methods that can modify or access the
private fields, but keep the data in a valid state. Private methods are only meant
to be used inside the class they are defined in so you wouldnt want a child directly
using those.
How can we tell that the class Graphics2D inherits from the class Graphics?
If you look at the API for Graphics2D you will see that the API starts with the
package name. On the next line is the class name. Following that is the ancestor tree
of the class. It shows all the ancestors of the class starting with java.lang.Object.
The class java.awt.Graphics2D class inherits from the class java.awt.Graphics
which inherits from the class java.lang.Object. You can also tell who the parent
class is by finding the class name given after the extends keyword in the class
declaration.
main
2005/1/11
page 248
248
Chapter 7
Drawing
'
&
7.3.3
$
Making it Work Tip: Object is the Top Ancestor
All classes inherit from the class java.lang.Object, and
it is the only class that doesnt inherit from another class.
Even if a class doesnt specify in the class definition the
class it inherits from using the extends keyword it will
inherit from Object.
General Scaling
We have shown how to scale an image up by copying the same pixel more than
once. We have shown how to scale an image down by skipping every other pixel.
The methods that we wrote would double the size of the original image or reduce
it by half. But, what if we want to scale up in x and down in y? What if we
want to scale to a specific size? The Graphics2D class provides ways to handling
scaling using an AffineTransform object in package java.awt.geom to handle the
transformation. Here is a program that will scale a picture by the passed x and y
factors.
Program 60: General Scale Method
/
Method t o c r e a t e a new p i c t u r e by s c a l i n g t h e c u r r e n t
p i c t u r e by t h e g i v e n x and y f a c t o r s
@param x F a c t o r t h e amount t o s c a l e i n x
@param y F a c t o r t h e amount t o s c a l e i n y
@return t h e r e s u l t i n g p i c t u r e
/
public P i c t u r e s c a l e ( double xFactor , double y F a c t o r )
{
// s e t up t h e s c a l e t r a n f o r m
A f f i n e T r a n s f o r m s c a l e T r a n s f o r m = new A f f i n e T r a n s f o r m ( ) ;
s c a l e T r a n s f o r m . s c a l e ( xFactor , y F a c t o r ) ;
// c r e a t e a new p i c t u r e o b j e c t t h a t i s t h e r i g h t s i z e
P i c t u r e r e s u l t = new P i c t u r e ( ( i n t ) ( getWidth ( ) x F a c t o r ) ,
( int ) ( getHeight ( ) yFactor ) ) ;
// g e t t h e g r a p h i c s 2d o b j e c t t o draw on t h e r e s u l t
Graphics g r a p h i c s = r e s u l t . getGraphics ( ) ;
Graphics2D g2 = ( Graphics2D ) g r a p h i c s ;
// draw t h e c u r r e n t image o nto t h e r e s u l t image s c a l e d
g2 . drawImage ( t h i s . getImage ( ) , s c a l e T r a n s f o r m , null ) ;
return r e s u l t ;
main
2005/1/11
page 249
i
Section 7.3
249
You can use the scale method to create a new picture from the original
picture by scaling up or down by any amount in x and/or y.
> Picture p = new Picture(FileChooser.getMediaPath("mattDoor.jpg"));
> Picture p1 = p.scale(2.0,0.5);
> p1.show();
FIGURE 7.15: Original picture and picture scaled up 2 times in x and down by half
in y
'
&
7.3.4
$
Making it Work Tip: Build from Working Parts
Earlier we wrote a method to scale a picture by copying
colors from a source picture to a target picture pixel by
pixel. Here we are using classes defined as part of the
Java language to do that work for us. There are many
classes in Java available for you to use. When you start
solving a problem you dont have to create everything you
need from scratch. Instead you can assemble your solution
from classes that already exist. This is one of the goals
of object-oriented programming: make things reusable by
encapsulating them in objects defined by classes.
Shearing
One of the effects that is easy to do with an java.awt.geom.AffineTransform
object is to shear the image. Shearing by 1.0 in x moves each row over by the y
index amount. So the first row, with a y index of 0, starts at (0, 0). The second row,
with a y index of 1, starts at (1, 1). The third row, with a y index of 2, starts at
(2, 2). The last rows x will start at (height 1, height 1). To figure out how big
our new picture will need to be no matter what the amounts are we are shearing by
use the getTranslationEnclosingRectangle(AffineTransform trans) method
which takes an AffineTransform object and returns a rectangle that will enclose
main
2005/1/11
page 250
i
250
Chapter 7
Drawing
the picture that results from applying that AffineTransform object to the current
Picture object.
Program 61: General Shear Method
/
Method t o c r e a t e a new p i c t u r e by s h e a r i n g t h e c u r r e n t
p i c t u r e by t h e g i v e n x and y f a c t o r s
@param x F a c t o r m u l t i p l i e r t o u s e t o s h i f t i n x
d i r e c t i o n b a s e d on y i n d e x
@param y F a c t o r m u l t i p l i e r t o u s e t o s h i f t i n y
d i r e c t i o n b a s e d on x i n d e x
@return t h e r e s u l t i n g p i c t u r e
/
public P i c t u r e s h e a r ( double xFactor , double y F a c t o r )
{
// s e t up t h e s h e a r t r a n f o r m
A f f i n e T r a n s f o r m s h e a r T r a n s f o r m = new A f f i n e T r a n s f o r m ( ) ;
s h e a r T r a n s f o r m . s h e a r ( xFactor , y F a c t o r ) ;
Rectangle2D r e c t =
getTranslationEnclosingRectangle ( shearTransform ) ;
/ c r e a t e a new p i c t u r e o b j e c t b i g enough t o h o l d t h e
result
/
P i c t u r e r e s u l t = new P i c t u r e (
( in t ) ( Math . c e i l ( r e c t . getWidth ( ) ) ) ,
( in t ) ( Math . c e i l ( r e c t . g e t H e i g h t ( ) ) ) ) ;
// g e t t h e g r a p h i c s 2d o b j e c t from t h e r e s u l t
Graphics g r a p h i c s = r e s u l t . getGraphics ( ) ;
Graphics2D g2 = ( Graphics2D ) g r a p h i c s ;
/ s a v e t h e c u r r e n t t r a n s f o r m a t i o n and s e t up t o
c e n t e r t h e new image
/
A f f i n e T r a n s f o r m savedTrans = g2 . g e t T r a ns f or m ( ) ;
A f f i n e T r a n s f o r m c e n t e r T r a n s = new A f f i n e T r a n s f o r m ( ) ;
c e n t e r T r a n s . t r a n s l a t e ( 0 r e c t . getX ( ) , 0 r e c t . getY ( ) ) ;
g2 . s e t T r a n s f o r m ( c e n t e r T r a n s ) ;
// draw t h e c u r r e n t image o nto t h e r e s u l t image s h e a r e d
g2 . drawImage ( t h i s . getImage ( ) , shearTransform , null ) ;
// r e s e t g2 t r a n s f o r m a t i o n t o t h e s a v e d one
g2 . s e t T r a n s f o r m ( savedTrans ) ;
return r e s u l t ;
}
main
2005/1/11
page 251
i
Section 7.3
251
You can use the shear method to create a new picture from the original
picture by shearing by any amount in x and/or y.
7.3.5
7.3.6
Interfaces
What is an interface? An interface in Java is a special kind of class that only
has abstract methods (methods with just a method declaration and no body) and
perhaps some constants defined in it. Even though a class can only inherit from
one parent class a class can implement many interfaces. An object of a class that
main
2005/1/11
page 252
252
Chapter 7
Drawing
/
Method t o add a g r a d i e n t p a i n t e d sun t o t h e c u r r e n t p i c t u r e
@param x t h e x l o c a t i o n f o r t h e upper l e f t c o r n e r o f t h e
r e c t a n g l e e n c l o s i n g t h e sun
@param y t h e y l o c a t i o n f o r t h e upper l e f t c o r n e r o f t h e
r e c t a n g l e e n c l o s i n g t h e sun
@param w i d t h t h e w i d t h o f t h e e n c l o s i n g r e c t a n g l e
@param h e i g h t t h e h e i g h t o f t h e e n c l o s i n g r e c t a n g l e
/
public void drawSun ( in t x , i n t y , i n t width , i n t h e i g h t )
{
// g e t t h e g r a p h i c s 2 D o b j e c t f o r t h i s p i c t u r e
Graphics g = this . getGraphics ( ) ;
Graphics2D g2 = ( Graphics2D ) g ;
main
2005/1/11
page 253
i
Section 7.3
253
// c r e a t e t h e g r a d i e n t f o r p a i n t i n g from y e l l o w t o r e d w i t h
// y e l l o w a t t h e t o p o f t h e sun and r e d a t t h e bottom
f l o a t xMid = ( f l o a t ) ( width / 0 . 5 + x ) ;
G r a d i e n t P a i n t g P a i n t = new G r a d i e n t P a i n t ( xMid , y ,
Color . yellow ,
xMid , y + h e i g h t ,
Color . red ) ;
// s e t t h e g r a d i e n t and draw t h e e l l i p s e
g2 . s e t P a i n t ( g P a i n t ) ;
g2 . f i l l (new E l l i p s e 2 D . Double ( x , y , width , h e i g h t ) ) ;
}
FIGURE 7.17: A beach with a sun that is filled with a gradient from yellow to red
7.3.7
main
2005/1/11
page 254
254
Chapter 7
Drawing
predefined constants such as SRC_OVER which draws the new pixels on top of the old
pixels. The alpha value can be between 0.0 and 1.0 where 0.0 is fully transparent
(invisible) and 1.0 is fully opaque. If we use an alpha value of 0.5 we will achieve
the same effect as the method blendPictures without having to loop through all
the pixels and calculate the resulting color.
To overlap two pictures we will draw the part of the first picture before a given
overlap point using one of the drawImage methods inherited from Graphics that
specifies the rectangular area to draw to and the rectangular area to draw from.
We will draw the first part using an AlphaComposite that uses an alpha value of
1.0 (opaque) so that it replaces any previous pixel color. Next we will draw the
parts of the two pictures that overlap using an alpha value of 0.5 so that it uses half
the color from the image being drawn and half the color from the current image.
Finally we will draw the part of the second picture that is after the overlapping
area using an alpha value of 1.0 (opaque).
Program 63: Overlap Pictures Using AlphaComposite
/
Method t o o v e r l a p one p i c t u r e w i t h a n o t h e r
h o r i z o n t a l l y on t o p o f t h e c u r r e n t p i c t u r e .
First
the part of the f i r s t picture before the overlap
w i l l be d i s p l a y e d , n e x t t o t h a t w i l l be t h e o v e r l a p p i n g
r e g i o n up t o t h e end o f t h e f i r s t p i c t u r e , a f t e r t h a t
i s t h e remainder o f t h e s e c o n d p i c t u r e
@param p1 t h e f i r s t p i c t u r e t o d i s p l a y
@param p2 t h e s e c o n d p i c t u r e t o d i s p l a y
@param s t a r t O v e r l a p t h e x p o s i t i o n where t h e o v e r l a p b e g i n s
/
public void o v e r l a p P i c t u r e s ( P i c t u r e p1 ,
P i c t u r e p2 ,
int st ar tOv er lap )
{
i nt amountOverlap = p1 . getWidth () s t a r t O v e r l a p ;
// g e t t h e Graphics2D o b j e c t
Graphics g = this . getGraphics ( ) ;
Graphics2D g2 = ( Graphics2D ) g ;
// draw p1 up t o o v e r l a p p o i n t
g2 . s e t C o m p o s i t e (
AlphaComposite . g e t I n s t a n c e ( AlphaComposite . SRC OVER,
( float ) 1.0 f ) ) ;
g2 . drawImage ( p1 . getImage ( ) ,
0 , 0 , s t a r t O v e r l a p , p1 . g e t H e i g h t ( ) ,
0 , 0 , s t a r t O v e r l a p , p1 . g e t H e i g h t ( ) ,
null ) ;
// draw p1 i n t h e o v e r l a p a r e a ( r e p l a c e b a c k g r o u n d )
main
2005/1/11
page 255
i
Section 7.3
255
g2 . drawImage ( p1 . getImage ( ) ,
s t a r t O v e r l a p , 0 , p1 . getWidth ( ) , p1 . g e t H e i g h t ( ) ,
s t a r t O v e r l a p , 0 , p1 . getWidth ( ) , p1 . g e t H e i g h t ( ) ,
null ) ;
// s e t t h e c o m p o s i t e t o b l e n d t h e o l d and new p i x e l s
// 50%
g2 . s e t C o m p o s i t e (
AlphaComposite . g e t I n s t a n c e ( AlphaComposite . SRC OVER,
0.5 f ) ) ;
g2 . drawImage ( p2 . getImage ( ) ,
s t a r t O v e r l a p , 0 , p1 . getWidth ( ) , p2 . g e t H e i g h t ( ) ,
0 , 0 , amountOverlap , p2 . g e t H e i g h t ( ) , null ) ;
// draw p2 a f t e r t h e o v e r l a p
g2 . s e t C o m p o s i t e (
AlphaComposite . g e t I n s t a n c e ( AlphaComposite . SRC OVER,
( float ) 1.0 f ) ) ;
g2 . drawImage ( p2 . getImage ( ) ,
p1 . getWidth ( ) , 0 , p2 . getWidth ( ) + s t a r t O v e r l a p ,
p2 . g e t H e i g h t ( ) , amountOverlap , 0 , p2 . getWidth ( ) ,
p2 . g e t H e i g h t ( ) , null ) ;
}
You can experiment with the alpha amount that you use in the method
getInstance. If you specify 0.5 then half of the source color values will be combined with half of the target color values. If you specify 1.0 then the source color
will replace the target color. If you use 0.25 then only 0.25 of the source color will
be combined with 0.75 of the target color.
7.3.8
Clipping
You can create one shape and then use that shape as a stencil to limit what is
shown when you draw other shapes or images. Only the area inside of the stencil
will be seen. This is called clipping.
Lets create a stencil from an ellipse and then draw the beach using that
stencil to clip the image of the beach. The only part of the beach that will be
main
2005/1/11
page 256
256
Chapter 7
Drawing
/
Method t o c l i p t h e p i c t u r e t o an e l l i p s e
@return a new p i c t u r e w i t h t h e image c l i p p e d
t o an e l l i p s e
/
public P i c t u r e c l i p T o E l l i p s e ( )
{
i nt width = t h i s . getWidth ( ) ;
i nt h e i g h t = t h i s . g e t H e i g h t ( ) ;
P i c t u r e r e s u l t = new P i c t u r e ( width , h e i g h t ) ;
// g e t t h e g r a p h i c s 2 D o b j e c t f o r t h i s p i c t u r e
Graphics g = r e s u l t . getGraphics ( ) ;
Graphics2D g2 = ( Graphics2D ) g ;
// c r e a t e an e l l i p s e t o u s e f o r c l i p p i n g
main
2005/1/11
page 257
i
Section 7.4
Concept Summary
257
E l l i p s e 2 D . Double e l l i p s e =
new E l l i p s e 2 D . Double ( 0 , 0 , width , h e i g h t ) ;
// u s e t h e e l l i p s e f o r c l i p p i n g
g2 . s e t C l i p ( e l l i p s e ) ;
// draw t h e image
g2 . drawImage ( t h i s . getImage ( ) , 0 , 0 , width ,
h e i g h t , null ) ;
// r e t u r n t h e r e s u l t
return r e s u l t ;
}
7.4
CONCEPT SUMMARY
In this chapter we discussed packages, using predefined Java classes, inheritance,
and interfaces.
7.4.1
Packages
A package is a group of related classes. Classes that are basic to the Java language
are in java.lang like the System class. Classes that are used for graphics are in
java.awt like the Color class. Classes that are used for input and output are in
java.io like the File class.
main
2005/1/11
page 258
i
258
Chapter 7
Drawing
The full name for a class is packageName.ClassName . You dont have to use
the full name for any class in the package java.lang. You dont have to use the full
name for classes in packages other than java.lang if you use a import statement.
You can import a class by using the keyword import and then the full name of the
class.
> import java.awt.Color;
You can also import all classes in a package.
> import java.awt.*;
When you use classes in packages other than java.lang in methods you will
either need to use the full class name or add import statements before the class
definition.
import
import
import
import
j a v a . awt . ;
j a v a . awt . f o n t . ;
j a v a . awt . geom . ;
java . text . ;
/
A c l a s s t h a t r e p r e s e n t s a p i c t u r e . This c l a s s i n h e r i t s from
S i m p l e P i c t u r e and a l l o w s t h e s t u d e n t t o add f u n c t i o n a l i t y t o
the Picture class .
7.4.2
7.4.3
Inheritance
When one class inherits from another it gets all the data (fields) and behavior
(methods) from that class. This means that if Graphics2D inherits from Graphics
it understands the same public messages as Graphics. The API for a class shows
what class it inherits from as well as all methods that are inherited and what class
they are inherited from.
main
2005/1/11
page 259
i
Section 7.4
7.4.4
Concept Summary
259
Interfaces
A Java class can inherit from only one class but it can implement several interfaces.
An interface defines how classes will communicate without worrying about what
types they actually are. An object of a class that implements an interface can
be declared with the interface name as the type. So if a GradientPaint object
implements the Paint interface it can be passed as a parameter to methods that
take objects of the type Paint. The API for a class shows what interfaces it
implements.
OBJECTS AND METHODS SUMMARY
In this chapter we have talked about several kinds of objects.
AffineTransform
java.awt.geom
AlphaComposite
java.awt
BasicStroke
java.awt
Ellipse2D.Double
java.awt.geom
FontRenderContext
java.awt.font
Graphics
java.awt
Graphics2D
java.awt
Line2D.Double
java.awt.geom
main
2005/1/11
page 260
i
260
Chapter 7
Drawing
setClip(Shape clip)
setColor(Color colorObj)
setFont(Font fontObj)
main
2005/1/11
page 261
Section 7.4
draw(Object obj)
drawImage(Image image, AffineTransform xform, ImageObserver
observer)
getFontRenderContext()
getTransform()
fill(Object obj)
setComposite(Composite comp)
setPaint(Paint paint)
setStroke(Stroke s)
setTransform(AffineTransform
tranform)
Concept Summary
261
PROBLEMS
7.1. Using the drawing tools presented here, draw a housejust go for the simple
childs house with one door, two windows, walls, and a roof.
7.2. Put a cabana on that beach. Draw the house from the previous exercise on the
beach where we put the mysterious box previously.
7.3. Now use your house to draw a town with dozens of houses at different sizes. Youll
probably want to modify your house function to draw at an input coordinate,
then change the coordinate where each house is drawn.
7.4. Draw a rainbowuse what you know about colors, pixels, and drawing operations to draw a rainbow. Is this easier to do with our drawing functions or by
manipulating individual pixels? Why?
7.5. Modify the method drawFace to take the width and height of the desired face
and calculate the positions based on the desired width and height.
7.6. Draw a string on a picture at the top of the picture and centered horizontally.
You will need to use the FontMetrics class to get the height of the string in
pixels in order to determine where the baseline should be so that the string is
visible. You should also subtract the descent from the height.
7.7. Create another method that takes the text, x, y, font and color to use when you
draw a string. Rewrite the old drawString() method to call this new method.
7.8. Create a method that draws an X across the current picture using dashed lines.
7.9. Write a general rotate method that takes the degrees to rotate the current
Picture object and returns a new Picture object. Use the AffineTransform.rotate(double
radians) method to do this. You will need to use the general Math.toRadians(int
degrees) method to translate the degrees to radians.
7.10. Write a method to draw a text string rotated 90 degrees to the right down the
right side of the picture.
main
2005/1/11
page 262
i
262
Chapter 7
Drawing
7.11. Write a method to overlap two pictures vertically using an AlphaComposite object.
7.12. Write a method to clip an image to a triangle or star shape.
main
2005/1/11
page 263
i
P A R T
T H R E E
SOUNDS
Chapter 8
Chapter 9
263
main
2005/1/11
page 264
i
C H A P T E R
main
2005/1/11
page 265
i
Section 8.1
8.1.1
265
FIGURE 8.1: Raindrops causing ripples in the surface of the water, just as sound
causes ripples in the air
main
2005/1/11
page 266
i
266
Chapter 8
the square of the amplitude. For example, if the amplitude doubles, intensity
quadruples.
Human perception of sound is not a direct mapping from the physical reality.
The study of the human perception of sound is called psychoacoustics. One of
the odd facts about psychoacoustics is that most of our perception of sound is
logarithmically related to the actual phenomena. Intensity is an example of this.
A change in intensity from 0.1W/m2 to 0.01W/m2 sounds the same to us (as in
the same amount of volume change) as a change in intensity of 0.001W/m2 to
0.0001W/m2 .
We measure the change in intensity in decibels (dB). Thats probably the unit
that you most often associate with volume. A decibel is a logarithmic measure, so
it does match the way we perceive volume. Its always a ratio, a comparison of two
values. 10 log10 (I1 /I2 ) is change in intensity in decibels between I1 and I2 . If
two amplitudes are measured under the same conditions, we can express the same
definition as amplitudes: 20 log10 (A1 /A2 ). If A2 = 2 A1 (i.e., the amplitude
doubles), the difference is roughly 6dB.
When decibel is used as an absolute measurement, its in reference to the
threshold of audibility at sound pressure level (SPL): 0 dB SPL. Normal speech
has intensity about 60 dB SPL. Shouted speech is about 80 dB SPL.
How often a cycle occurs is called the frequency. If a cycle is short, then there
can be lots of them per second. If a cycle is long, then there are fewer of them. As
the frequency increases we perceive the pitch to increase. We measure frequency in
cycles per second (cps) or Hertz (Hz).
All sounds are periodic: there is always some pattern of rarefaction and compression that leads to cycles, In a sine wave, the notion of a cycle is easy. In
natural waves, its not so clear where a pattern repeats. Even in the ripples in a
pond, the waves arent as regular as you might think. The time between peaks in
waves isnt always the same: it varies. This means that a cycle may involve several
peaks-and-valleys until it repeats.
Humans hear between 2 Hz and 20,000 Hz (or 20 kilohertz, abbreviated 20
main
2005/1/11
page 267
i
Section 8.1
267
kHz). Again, as with amplitudes, thats an enormous range! To give you a sense
of where music fits into that spectrum, the note A above middle C is 440 Hz in
traditional, equal temperament tuning (Figure 8.3).
main
2005/1/11
page 268
i
268
Chapter 8
wind instruments), while others hit the frequency and amplitude very quickly
and then the volume fades while the frequency remains pretty constant (like
a piano).
Not all sound waves are represented well by sine waves. Real sounds have
funny bumps and sharp edges. Our ears can pick these up, at least in the
first few waves. We can do a reasonable job synthesizing with sine waves,
but synthesizers sometimes also use other kinds of wave forms to get different
kinds of sounds (Figure 8.4).
FIGURE 8.4: Some synthesizers using triangular (or sawtooth) or square waves.
8.1.2
Exploring Sounds
On your CD, you will find the MediaTools application with documentation for how
to get it started. The MediaTools application contains tools for sound, graphics,
and video. Using the sound tools, you can actually observe sounds as theyre coming
into your computers microphone to get a sense of what louder and softer sounds
look like, and what higher and lower pitched sounds look like.
The basic sound editor looks like Figure 8.5. You can record sounds, open
WAV files on your disk, and view the sounds in a variety of ways. (Of course,
assuming that you have a microphone on your computer!)
To view sounds, click the View button, then the Record button. (Hit the
Stop button to stop recording.) There are three kinds of views that you can make
of the sound.
The first is the signal view (Figure 8.6). In the signal view, youre looking at
the sound raweach increase in air pressure results in an rise in the graph, and
each decrease in sound pressure results in a drop in the graph. Note how rapidly
main
2005/1/11
page 269
i
Section 8.1
269
the wave changes! Try some softer and louder sounds so that you can see how the
sounds look changes. You can always get back to the signal view from another
view by clicking the Signal button.
The second view is the spectrum view (Figure 8.7). The spectrum view is a
completely different perspective on the sound. In the previous section, you read
that natural sounds are often actually composed of several different frequencies at
once. The spectrum view shows these individual frequencies. This view is also
called the frequency domain.
Frequencies increase in the spectrum view from left to right. The height of a
column indicates the amount of energy (roughly, the volume) of that frequency in
the sound. Natural sounds look like Figure 8.8 with more than one spike (rise in
the graph). (The smaller rises around a spike are often seen as noise.)
The technical term for how a spectrum view is generated is called a Fourier
transform. A Fourier transform takes the sound from the time domain (rises and
falls in the sound over time) into the frequency domain (identifying which frequencies are in a sound, and the energy of those frequencies, over time). The specific
technique being used in the MediaTools signal view is a Fast Fourier Transform
(or FFT ), a very common way to do Fourier transforms quickly on a computer so
that we can get a real time view of the changing spectra.
The third view is the sonogram view (Figure 8.9). The sonogram view is very
much like the spectrum view in that its describing the frequency domain, but it
presents these frequencies over time. Each column in the sonogram view, sometimes
called a slice or window (of time), represents all the frequencies at a given moment
in time. The frequencies increase in the slice from lower (bottom) to higher (top).
The darkness of the spot in the column indicates the amount of energy of that
frequency in the input sound at the given moment. The sonogram view is great for
studying how sounds change over time, e.g., how the sound of a piano key being
struck changes as the note fades, or how different instruments differ in their sounds,
or in how different vocal sounds differ.
main
2005/1/11
page 270
i
270
Chapter 8
"
8.1.3
Encoding Sounds
You just read about how sounds work physically and how we perceive them. To
manipulate these sounds on a computer and to play them back on a computer, we
have to digitize them. To digitize sound means to take this flow of waves and turn
it into numbers. We want to be able to capture a sound, perhaps manipulate it, and
then play it back (through the computers speakers) and hear what we capturedas
exactly as possible.
The first part of the process of digitizing a sound is handled by the computers hardwarethe physical machinery of the computer. If a computer has a
microphone and the appropriate sound equipment (like a SoundBlaster sound card
on Wintel computers), then its possible, at any moment, to measure the amount
of air pressure against that microphone as a single number. Positive numbers correspond to rises in pressure, and negative numbers correspond to rarefactions. We
call this an analog-to-digital conversion (ADC)weve moved from an analog signal
(a continuously changing sound wave) to a digital value. This means that we can
get an instantaneous measure of the sound pressure, but its only one step along
the way. Sound is a continuous changing pressure wave. How do we store that in
our computer?
By the way, playback systems on computers work essentially the same in
reverse. The sound hardware does a digital-to-analog conversion (DAC), and the
analog signal is then sent to the speakers. The DAC process also requires numbers
main
2005/1/11
page 271
i
Section 8.1
271
representing pressure.
If youve had some calculus, youve got some idea of how we might do that.
You know that we can get close to measuring the area under a curve with more and
more rectangles whose height matches the curve (Figure 8.10). With that idea, its
pretty clear that if we capture enough of those microphone pressure readings, we
capture the wave. We call each of those pressure readings a samplewe are literally
sampling the sound at that moment. But how many samples do we need? In
integral calculus, you compute the area under the curve by (conceptually) having
an infinite number of rectangles. While computer memories are growing larger and
larger all the time, we still cant capture an infinite number of samples per sound.
Mathematicians and physicists wondered about these kinds of questions long
before there were computers, and the answer to how many samples we need was
actually computed long ago. The answer depends on the highest frequency you
want to capture. Lets say that you dont care about any sounds higher than 8,000
Hz. The Nyquist theorem says that we would need to capture 16,000 samples per
second to completely capture and define a wave whose frequency is less than 8,000
cycles per second.
Computer Science Idea: Nyquist Theorem
To capture a sound of at most n cycles per second, you
need to capture 2n samples per second.
This isnt just a theoretical result. The Nyquist theorem influences applications in our daily life. It turns out that human voices dont typically get over 4,000
Hz. Thats why our telephone system is designed around capturing 8,000 samples
per second. Thats why playing music through the telephone doesnt really work
main
2005/1/11
page 272
i
272
Chapter 8
very well. The limits of (most) human hearing is around 22,000 Hz. If we were to
capture 44,000 samples per second, we would be able to capture any sound that we
could actually hear. CDs are created by capturing sound at 44,100 samples per
secondjust a little bit more than 44 kHz for technical reasons and for a fudge
factor.
We call the rate at which samples are collected the sampling rate. Most
sounds that we hear in daily life are well within the range of the limits of our
hearing. You can capture and manipulate sounds in this class at a sampling rate
of 22 kHz (22,000 samples per second), and it will sound quite reasonable. If you
use too low of a sampling rate to capture a high-pitched sound, youll still hear
something when you play the sound back, but the pitch will sound strange.
Typically, each of these samples are encoded in two bytes(16 bits). Though
there are larger sample sizes, 16 bits works perfectly well for most applications.
CD-quality sound uses 16 bit samples.
main
2005/1/11
page 273
i
Section 8.1
273
In 16 bits, the numbers that can be encoded range from -32,768 to 32,767.
These arent magic numbersthey make perfect sense when you understand the
encoding. These numbers are encoded in 16 bits using a technique called twos
complement notation, but we can understand it without knowing the details of
that technique. Weve got 16 bits to represent positive and negative numbers.
Lets set aside one of those bits (remember, its just 0 or 1) to represent whether
were talking about a positive (0) or negative (1) number. We call that the sign
bit. That leaves 15 bits to represent the actual value. How many different patterns
of 15 bits are there? We could start counting:
000000000000000
000000000000001
000000000000010
000000000000011
...
111111111111110
111111111111111
That looks forbidding. Lets see if we can figure out a pattern. If weve got
two bits, there are four patterns: 00, 01, 10, 11. If weve got three bits, there are
eight patterns: 000, 001, 010, 011, 100, 101, 110, 111. It turns out that 22 is four,
and 23 is eight. Play with four bits. How many patterns are there? 24 = 16 It
turns out that we can state this as a general principle.
Computer Science Idea: 2n patterns in n bits
If you have n bits, there are 2n possible patterns in those
n bits.
215 = 32, 768. Why is there one more value in the negative range than the
positive? Zero is neither negative nor positive, but if we want to represent it as bits,
we need to define some pattern as zero. We use one of the positive range values
(where the sign bit is zero) to represent zero, so that takes up one of the 32,768
patterns.
The sample size is a limitation on the amplitude of the sound that can be
captured. If you have a sound that generates a pressure greater than 32,767 (or a
rarefaction greater than -32,768), youll only capture up to the limits of the 16 bits.
If you were to look at the wave in the signal view, it would look like somebody took
some scissors and clipped off the peaks of the waves. We call that effect clipping
for that very reason. If you play (or generate) a sound thats clipped, it sounds
badit sounds like your speakers are breaking.
There are other ways of digitizing sound, but this is by far the most common.
The technical term for this way of encoding sound is pulse coded modulation (PCM).
You may encounter that term if you read further in audio or play with audio
software.
What this means is that a sound in a computer is a long list of numbers, each
of which is a sample in time. There is an ordering in these samples: If you played
the samples out of order, you wouldnt get the same sound at all. The most efficient
main
2005/1/11
page 274
i
274
Chapter 8
way to store an ordered list of data items on a computer is with an array. An array
is literally a sequence of bytes right next to one another in memory. We call each
value in an array an element.
We can easily store the samples that make up a sound in an array. Think of
each two bytes as storing a single sample. The array will be largefor CD-quality
sounds, there will be 44,100 elements for every second of recording. A minute long
recording will result in an array with 26,460,000 elements.
Each array element has a number associated with it, called its index . The
index numbers start at 0 and increase sequentially. The first one is 0, the second
one is 1, and so on. It may sound strange to say the the index for the first array
element is 0 but this is basically a measure of the distance from the first element
in the array. Since the distance from the first element to itself is 0, the index is 0.
You can think about an array as a long line of boxes, each one holding a value and
each box having an index number on it (Figure 8.11).
59
39
16
10
-1
...
FIGURE 8.11: A depiction of the first five elements in a real sound array
Using the MediaTools, you can graph a sound file (Figure 8.12) and get a
sense of where the sound is quiet (small amplitudes), and loud (large amplitudes).
This is actually important if you want to manipulate the sound. For example, the
gaps between recorded words tend to be quietat least quieter than the words
themselves. You can pick out where words end by looking for these gaps, as in
Figure 8.12.
You will soon read about how to read a file containing a recording of a sound
into a sound object, view the samples in that sound, and change the values of the
sound array elements. By changing the values in the array, you change the sound.
Manipulating a sound is simply a matter of manipulating elements in an array.
8.2
MANIPULATING SOUNDS
Now that we know how sounds are encoded, we can manipulate sounds using Java
programs. Heres what well need to do.
main
2005/1/11
page 275
i
Section 8.2
Manipulating Sounds
275
1. Well need to get a filename of a WAV file, and make a Sound object from it.
You already saw how to do that in a previous chapter.
2. You will often get the samples of the sound as an array. Sample objects are
easy to manipulate, and they know that when you change them, they should
automatically change the original sound. Youll read first about manipulating
the samples to start with, then about how to manipulate the sound samples
from within the sound itself.
3. Whether you get the sample objects out of a sound, or just deal with the
samples in the sound object, you will then want to do something to the value
at the sample.
4. You may then want to write the sound back out to a new file, to use elsewhere.
(Most sound editing programs know how to deal with audio.)
8.2.1
main
2005/1/11
page 276
i
276
Chapter 8
value of a SoundSample object by using getValue(), and you set the value of a
SoundSample object with setValue(value).
But before we get to the manipulations, lets look at some other ways to get
and set samples. We can ask the sound to give us the value of a specific sample at a
specific index, by using the method getSampleValueAt(index) on a Sound object.
> System.out.println(sound1.getSampleValueAt(0));
36
> System.out.println(sound1.getSampleValueAt(1));
29
What numbers can we use as index values? Anything between 0 and the
number of samples minus 1. We can get the number of samples using getLength().
Notice the error that we get below if we try to get a sample past the end of the
array.
> System.out.println(sound1.getLength());
421110
> sound1.getSampleValueAt(500000);
You are trying to access the sample at index: 500000, but the last
valid index is at 421109
We can similarly change sample values in a Sound object with setSampleValueAt(index).
This method changes the value of the sample at the passed index. We can then
check it again with getSampleValueAt().
> System.out.println(sound1.getSampleValueAt(0));
36
> sound1.setSampleValueAt(0,12);
> System.out.println(sound1.getSampleValueAt(0));
12
'
$
Common Bug: Mistyping a name
You just saw a whole bunch of method names, and some
of them are pretty long. What happens if you type one of
them wrong? DrJava will complain that it doesnt know
what you mean, like this:
> sound1.setSampleVAlueAt(0,12);
Error: No setSampleVAlueAt method in Sound
Its no big deal. Use the up arrow on the keyboard to bring
up the last thing you typed into DrJava and then use the
left arrow to get to the place with the error and then fix it.
You can use the up arrow to get to any of the commands
you have typed in the interactions pane since you started
DrJava.
&
%
What do you think would happen if we then played this sound? Would it really
sound different than it did before, now that weve turned the first sample from the
main
2005/1/11
page 277
i
Section 8.2
Manipulating Sounds
277
number 36 to the number 12? Not really. To explain why not, lets find out what
the sampling rate is for this sound, by using the method getSamplingRate().
> Sound aSound = new Sound(FileChooser.getMediaPath("preamble.wav"));
> System.out.println(aSound.getSamplingRate());
22050.0
To make some of our manipulations easier, were going to be using
FileChooser.setMediaPath(String directory)
and
FileChooser.getMediaPath(String baseFileName)
Using setMediaPath(String directory) will set a media directory (folder),
and then getMediaPath(String baseFileName) will reference media files within
that directory. This makes it much easier to reference media filesyou dont have
to spell out the whole path. The method getMediaPath takes a base file name as an
argument, and will return the directory set by setMediaPath with the passed file
name added to the end of the directory name. The default for the media directory is
c:/intro-prog-java/mediasources/. If you have your media in another directory
you will need to use setMediaPath before you can use getMediaPath.
> FileChooser.setMediaPath("c:/intro-prog-java/mediasources/");
The media directory is now c:/intro-prog-java/mediasources/
> System.out.println(FileChooser.getMediaPath("barbara.jpg"));
c:/intro-prog-java/mediasources/barbara.jpg
> System.out.println(FileChooser.getMediaPath("croak.wav"));
c:/intro-prog-java/mediasources/croak.wav
'
$
Common Bug: Its not a file, its a string
Just because getMediaPath returns something that looks
like a path doesnt mean that a file really exists there.
You have to know the right base name, but if you do, its
easier to use in your code. But if you put in a non-existent
file, youll get a path to a non-existent file. The method
getMediaPath will warn you if the file doesnt exist.
> FileChooser.getMediaPath("blahblah.wav");
There is no file named blahblah.wav in directory
c:/intro-prog-java/mediasources/
>
&
%
The sound that were manipulating in this example (a recording of Mark
reading part of the U.S. Constitutions preamble) has a sampling rate of 22,050
samples per second. Changing one sample changes 1/22050 of the first second of
that sound. If you can hear that, you have amazingly good hearingand I will
have my doubts about your truthfulness!
Obviously, to make a significant manipulation to the sound, we have to manipulate hundreds if not thousands of samples. Were certainly not going to do that
by typing thousands of lines like this:
main
2005/1/11
page 278
i
278
>
>
>
>
>
Chapter 8
aSound.setSampleValueAt(0,12);
aSound.setSampleValueAt(1,24);
aSound.setSampleValueAt(2,100);
aSound.setSampleValueAt(3,99);
aSound.setSampleValueAt(4,-1);
&
'
$
Making it Work Tip: Slashes and Backslashes in
Java
Even though the file name is output with backslashes (\)
in it, a Java string that wants to have a backslash in it
must double it since the backslash character is used in Java
strings to indicate special characters like tab. Or, you can
use a forward slash (/) instead of double backslashes.
%
$
main
2005/1/11
page 279
i
Section 8.2
Manipulating Sounds
279
You use something called blockingPlay(). That works the same as play(), but
it waits for the sound to end so that no other sound can interfere while its playing.
8.2.2
You will then be shown the file in the sound editor view (Figure 8.15). The
sound editor lets you explore a sound in many ways (Figure 8.16). As you scroll
through the sound and change the sound cursor (the red/blue line in the graph)
position, the index changes to show you which sound array element youre currently
looking at, and the value shows you the value at that index. You can also fit
the whole sound into the graph to get an overall view (but currently breaks the
index/value displays). You can even play your recorded sound as if it were an
instrumenttry pressing the piano keys across the bottom of the editor. You can
also set the cursor (via the scrollbar or by dragging in the graph window) and play
the sound before the cursora good way to hear what part of the sound corresponds
to what index positions. Clicking the <> button provides a menu of options which
includes getting an FFT view of the sound.
main
2005/1/11
page 280
i
280
Chapter 8
8.2.3
Introducing Loops
The problem of wanting to do something similar a great many times is a common
one in computing: How do we get the computer to do something over-and-over
again? We need to get the computer to loop or iterate. Java has commands
especially for looping (iterating).
Starting with Java version 1.5 there is a new way of looping through all
members of an array using a for each loop. The syntax is
for (Type variableName : array)
You can read this as, for each element in the array execute the body of the
loop. The first time through the loop the variableName will refer to the first
element of the array (the one at index 0). The second time through the loop the
variableName will refer to the second element of the array (the one at index 1).
The last time through the loop the variableName will refer to the last element
of the array (the one at index (length - 1)). The code to loop through all the
SoundSample objects in an array of SoundSample objects and set the value of each
main
2005/1/11
page 281
Section 8.2
Manipulating Sounds
281
As you can see the for each loop makes it easy to loop through all the
elements of an array. You can use the for each loop whenever you want to process
all the elements of an array and you dont need to know the current index in the
body of the loop.
However, not everyone is using Java 1.5 yet. If you are not using Java 1.5
you may want to start with a while loop. A while loop executes some commands
while a test returns true. In order for the loop to stop there but be some way for
the test to end up false.
The way that we will manipulate a sound is to change the values in the samples
that make up the sound. We want to loop through all the samples in a sound and
do something to each value. One way to do that is to loop through all the elements
of the array of samples. We are going to use the getSamples() function we saw
earlier to provide our array.
For example, here is the while loop that simply sets each sample to its own
value (a particularly useless exercise, but itll get more interesting in just a couple
pages).
public void doNothing ( )
{
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt i n d e x = 0 ;
main
2005/1/11
page 282
282
Chapter 8
i nt v a l u e = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
while ( i n d e x < sampleArray . l e n g t h )
{
sample = sampleArray [ i n d e x ] ;
v a l u e = sample . g e t V a l u e ( ) ;
sample . s e t V a l u e ( v a l u e ) ;
i n d e x ++;
}
}
main
2005/1/11
page 283
i
Section 8.2
Manipulating Sounds
283
SoundSample [ ] a = t h i s . g e t S a m p l e s ( ) ;
SoundSample s = null ;
i nt i = 0 ;
i nt v = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
while ( i < a . l e n g t h )
{
s = a[ i ];
v = s . getValue ( ) ;
s . setValue (v ) ;
i ++;
}
Whats the difference? These are slightly easier to confuse variable names. a and s
are not as obvious as to what they are naming as sampleArray and sample. Java
doesnt care which we use, and the single character variable names are clearly easier
to type. But the longer variable names make it easier to understand your code. It
is best to try to make your code easier for humans to understand.
You may have wondered do we need the variable v? We could combine the
two statements into one.
SoundSample [ ] a = t h i s . g e t S a m p l e s ( ) ;
SoundSample s = null ;
i nt i = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
while ( i < a . l e n g t h )
{
s = a[ i ];
s . setValue ( s . getValue ( ) ) ;
i ++;
}
main
2005/1/11
page 284
284
Chapter 8
without writing thousands of individual lines, lets do something useful with this.
'
&
8.3
$
Common Bug: Windows and WAV files
The world of WAV files isnt as compatible and smooth as
one might like. WAV files created with other applications
(such as Windows Recorder) may not play in DrJava, and
DrJava WAV files may not play in all other applications
(e.g., WinAmp 2). Some tools like Apple QuickTime
Player Pro (http://www.apple.com/quicktime) are
good at reading any WAV file and being able to export
a new one that most any other application can read.
Some WAV files are encoded using MP3 which means
they are really MP3 files. You can convert these using
Sound.convert(origFileName, convertedFileName)
where origFileName and convertedFileName are the full
names (include path information).
8.3.1
Increasing Volume
Heres a function that doubles the amplitude of an input sound.
Program 65: Increase an input sounds volume
/
Method t o d o u b l e t h e volume ( a m p l i t u d e ) o f t h e sound
/
main
2005/1/11
page 285
i
Section 8.3
285
public void i n c r e a s e V o l u m e ( )
{
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt v a l u e = 0 ;
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
while ( i n d e x < sampleArray . l e n g t h )
{
sample = sampleArray [ i n d e x ] ;
v a l u e = sample . g e t V a l u e ( ) ;
sample . s e t V a l u e ( v a l u e 2 ) ;
i n d e x ++;
}
}
Go ahead and type the above into your DrJava definitions pane before the last
curly brace in the Sound.java class. Click Compile All to get DrJava to compile
it. Follow along the example below to get a better idea of how this all works.
To use this program, you have to create a sound first and invoke this method
on it. Dont forget that you cant type this code in and have it work as-is: Your
path names may be different than what is shown here!
>
>
>
>
>
>
>
String f = "c:/intro-prog-java/mediasources/gettysburg10.wav";
Sound s = new Sound(f);
s.play();
s.explore();
s.increaseVolume();
s.play();
s.explore();
main
2005/1/11
page 286
i
286
Chapter 8
from the implicitly passed Sound object (the one referred to by variable s).
Computer Science Idea: Changing memory doesnt
change the file
If you create another Sound object from the same file will
you get the original sound or the sound with volume increased? You will get the original sound. The Sound
object s was created by reading the file data into memory. The change to the Sound object was done in memory, but the file wasnt changed. If you want to save
your changes write them out to a file using the method
soundObj.write(String fileName); where soundObj is
the name of the Sound object and fileName is the full
path name of the file. So to save the changed Sound object
above use s.write("gettyLouder.wav");.
8.3.2
FIGURE 8.17: Comparing the graphs of the original sound (left) and the louder one
(right)
Maybe youre unsure that youre really seeing a larger wave in the second
picture. You can use a sound explorer to check the individual sample values. You
can actually already see that in Figure 8.17see that the first value (index number
0) is 59 in the original sound and 118 in the second sound. You can also check
the value at any index using the sound explorer. Just click on a location and the
value will be displayed for that location. To check the same location in the second
explorer just type in the desired current index and it will show the value at that
index. Youll see that the louder sound really does have double the value of the
main
2005/1/11
page 287
i
Section 8.3
287
FIGURE 8.18: Comparing specific samples in the original sound (left) and the louder
one (right)
same sample in the original sound.
Finally, you can always check for yourself from within DrJava. If youve been
following along with the example1 , then the variable s is the now louder sound.
f should still be the filename of the original sound. Go ahead and make a new
sound object which is the original soundthat is named below as sOrig (for sound
original ). Check any sample that you wantits always true that the louder sound
has twice the value than the the original sound.
> System.out.println(s);
Sound file:
c:/intro-prog-java/mediasources/gettysburg10-louder.wav number of
samples: 220568
> System.out.println(f);
c:/intro-prog-java/mediasources/gettysburg10.wav
> Sound sOrig = new Sound(f);
> System.out.println(s.getSampleValueAt(0));
118
> System.out.println(sOrig.getSampleValueAt(0));
59
> System.out.println(s.getSampleValueAt(1));
78
> System.out.println(sOrig.getSampleValueAt(1));
39
> System.out.println(s.getSampleValueAt(999));
-80
> System.out.println(sOrig.getSampleValueAt(999));
-40
You can see from the last value that even negative values become more negative. Thats whats meant by increasing the amplitude. The amplitude of the
wave goes in both directions. We have to make the wave larger in both the positive
and negative dimensions.
1 What?
You havent? You should! Itll make much more sense if you try it yourself!
main
2005/1/11
page 288
i
288
Chapter 8
Its important to do what you just read in this chapter: Doubt your programs.
Did that really do what I wanted it to do? The way you check is by testing. Thats
what this section is about. You just saw several ways to test:
By looking at the result overall (like with the graphs created by the explorer),
By checking pieces of the results (like with the explorer or MediaTools), and
By writing additional code statements that check the results of the original
program.
Figuring out how it Worked.
Lets walk through the code, slowly, and consider how this program worked.
/
Method t o d o u b l e t h e volume ( a m p l i t u d e ) o f t h e sound
/
public void i n c r e a s e V o l u m e ( )
{
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt v a l u e = 0 ;
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
while ( i n d e x < sampleArray . l e n g t h )
{
sample = sampleArray [ i n d e x ] ;
v a l u e = sample . g e t V a l u e ( ) ;
sample . s e t V a l u e ( v a l u e 2 ) ;
i n d e x ++;
}
}
59
39
16
10
-1
...
main
2005/1/11
page 289
i
Section 8.3
289
sample
59
39
16
10
-1
...
index = 0
value = 0
The variable value will take on the value of 59 when value=sample.getValue()
is executed. The value stored at that SoundSample object will be set to value times
2 (59 2 = 118).
sample
118
39
16
10
-1
...
index = 0
value = 59
The value in variable index will be incremented by 1 (0 + 1 = 1). Thats the
end of the first pass through the body of the while loop. The loop will then start
over. The test that index is less than the length of the array of samples will happen
again. Since, it is still less the body of the loop will be executed (statements inside
the open and close curly braces). The variable sample will be changed to refer to
the second item in the array (the one at index 1).
sample
118
39
16
10
-1
...
index = 1
value = 59
Again, the variable value is set to the value of the SoundSample object. The
value of the SoundSample object is set to twice the amount held in the variable
value.
main
2005/1/11
page 290
290
Chapter 8
sample
118
78
16
10
-1
...
index = 1
value = 39
This is what it will look like after 5 times through the loop.
sample
118
78
32
20
-2
...
index = 4
value = -1
But really, the while loop keeps going through all the samplestens of thousands of them! Thank goodness its the computer executing this program!
What you have just read in this section is called tracing the program. We
slowly went through how each step in the program was executed. We drew pictures
to describe the data in the program. We used numbers, arrows, equations, and even
plain English to explain what was going on in the program. This is the single most
important technique in programming. Its part of debugging. Your program will not
always work. Absolutely, guaranteed, without a shadow of a doubtyou will write
code that does not do what you want. But the computer will do SOMETHING.
How do you figure out what it is doing? You debug, and the most significant way
to do that is by tracing the program.
8.3.3
Decreasing Volume
Decreasing volume, then, is the reverse of the previous process.
Program 66: Decrease an input sounds volume
/
Method t o h a l v e t h e volume ( a m p l i t u d e ) o f t h e sound .
/
public void decreaseVolume ( )
{
main
2005/1/11
page 291
i
Section 8.3
291
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt v a l u e = 0 ;
i nt i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
while ( i n d e x < sampleArray . l e n g t h )
{
sample = sampleArray [ i n d e x ] ;
v a l u e = sample . g e t V a l u e ( ) ;
sample . s e t V a l u e ( ( in t ) ( v a l u e 0 . 5 ) ) ;
i n d e x ++;
}
}
main
2005/1/11
page 292
i
292
8.3.4
Chapter 8
/
Method t o h a l v e t h e volume ( a m p l i t u d e ) o f t h e sound .
/
public void decreaseVolume2 ( )
{
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt v a l u e = 0 ;
// i n t i n d e x = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
// w h i l e ( i n d e x < sampleArray . l e n g t h )
f o r ( i n t i n d e x = 0 ; i n d e x < sampleArray . l e n g t h ; i n d e x++)
{
sample = sampleArray [ i n d e x ] ;
v a l u e = sample . g e t V a l u e ( ) ;
sample . s e t V a l u e ( ( in t ) ( v a l u e 0 . 5 ) ) ;
// i n d e x ++;
}
}
main
2005/1/11
page 293
i
Section 8.3
293
initialization part of the for loop will actually take place before the first test. The
change of the loop variables will actually take place after each execution of the loop
body and before the next test.
8.3.5
/
Method t o change t h e volume ( a m p l i t u d e ) o f t h e sound
by m u l t i p l y i n g t h e c u r r e n t v a l u e s i n t h e sound by
the passed f a c t o r .
@param f a c t o r t h e f a c t o r t o m u l t i p l y by
/
public void changeVolume ( double f a c t o r )
{
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt v a l u e = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
f o r ( i n t i = 0 ; i < sampleArray . l e n g t h ; i ++)
{
sample = sampleArray [ i ] ;
v a l u e = sample . g e t V a l u e ( ) ;
sample . s e t V a l u e ( ( in t ) ( v a l u e f a c t o r ) ) ;
}
}
main
2005/1/11
page 294
i
294
Chapter 8
even use the same variable names that you use in your methods in the interactions
pane. This is a different context. If you create a variable in a method context (like
value in Method 68 above), then that variable wont exist when you get back out
to the interactions pane. We can return values from a method context back out to
the interactions pane (or a calling method) by using return, which well talk more
about later.
8.4
NORMALIZING SOUNDS
If you think about it some, it seems strange that the last two methods work! We
can just multiply these numbers representing a soundand the sound seems (essentially) the same to our ears just louder? The way we experience a sound depends
less on the specific numbers than on the relationship between them. Remember
that the overall shape of the sound waveform is dependent on many samples. In
general, if we multiply all the samples by the same multiplier, we only effect our
sense of volume (intensity), not the sound itself. (Well work to change the sound
itself in future sections.)
A common operation that people want to do with sounds is to make them as
LOUD AS POSSIBLE. Thats called normalizing. Its not really hard to do, but
it takes more lines of code than weve used previously and a few more variables, but
we can do it. Heres the algorithm, in English, that we need to tell the computer
to do.
We have to figure out what the largest sample in the sound is. If its already
at the maximum value (the allowed range is -32768 to 32767 so the maximum
allowed positive value is 32767), then we cant really increase the volume and
still get what seems like the same sound. Remember that we have to multiply
all the samples by the same multiplier.
Its an easy (algorithm) to find the largest valuesort of a sub-program within
the overall normalizing program. Define a name (say, largest) and assign it
a small value (0 works). Now, check all the samples. If you find a sample with
an absolute value larger than the largest, save that as the value for largest.
Keep checking the samples, comparing to the new largest. Eventually, the
very largest value in the array will be in the variable largest.
To do this, well need a way of figuring out the maximum value of two values.
We can use an if (value > largest) to check if the current value is greater
than the current largest and if so set that value to the largest. We can also
save the index of that value so that we can check it with the sound explorer.
Next, we need to figure out what value to multiply all the samples by. We want
the largest value to become 32767. Thus, we want to figure out a multiplier
such that
(multiplier)(largest) = 32767.
Solve for the multiplier:
multiplier = 32767/largest. The multiplier will need to be a floating point
number (have a decimal component), so we need to convince Java that not
main
2005/1/11
page 295
Section 8.4
Normalizing Sounds
295
everything here is an integer. Turns out that thats easyuse 32767.0. Simply
stick on .0.
Now, loop through all the samples, as we did for increaseVolume, and multiply the sample by the multiplier.
Heres a program to normalize sounds.
Program 69: Normalize the sound to a maximum amplitude
/
Method t o n o r m a l i z e ( make as l o u d as p o s s i b l e ) a sound .
/
public void n o r m a l i z e ( )
{
i nt l a r g e s t = 0 ;
i nt maxIndex = 0 ;
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt v a l u e = 0 ;
// l o o p comparing t h e a b s o l u t e v a l u e o f t h e c u r r e n t v a l u e
// t o t h e c u r r e n t l a r g e s t
f o r ( i n t i = 0 ; i < sampleArray . l e n g t h ; i ++)
{
sample = sampleArray [ i ] ;
v a l u e = Math . abs ( sample . g e t V a l u e ( ) ) ;
i f ( value > l a r g e s t )
{
l a r g e s t = value ;
maxIndex = i ;
}
}
// now c a l c u l a t e t h e m u l t i p l i e r t o m u l t i p l y by
double m u l t i p l i e r = 3 2 7 6 7 . 0 / l a r g e s t ;
// p r i n t o u t t h e l a r g e s t v a l u e and t h e m u l t i p l i e r
System . out . p r i n t l n ( The l a r g e s t v a l u e was + l a r g e s t +
a t i n d e x + maxIndex ) ;
System . out . p r i n t l n ( The m u l t i p l i e r i s + m u l t i p l i e r ) ;
/ l o o p t h r o u g h a l l t h e s a m p l e s and m u l t i p l y by t h e
multiplier
/
f o r ( i n t i = 0 ; i < sampleArray . l e n g t h ; i ++)
{
sample = sampleArray [ i ] ;
sample . s e t V a l u e ( ( in t ) ( sample . g e t V a l u e ( ) m u l t i p l i e r ) ) ;
main
2005/1/11
page 296
i
296
Chapter 8
}
}
FIGURE 8.19: Comparing the original sound with the normalized one
8.4.1
Generating Clipping
Earlier, we talked about clipping, what happens when the normal curves of the
sound are broken by the limitations of the sample size. One way of generating
clipping is to keep increasing the volume. Another way is to explicitly force clipping.
main
2005/1/11
page 297
i
Section 8.4
Normalizing Sounds
297
What if you only had the largest and smallest possible sample values? What
if all positive values (including zero), were the maximum value (32767) and all
negative values were the minimum value (-32768)? Try this program, particularly
on sounds with words in them.
Program 70: Set all samples to extreme values
/
Method t o s e t a l l t h e sample v a l u e s t o t h e
maximum p o s i t i v e v a l u e i f t h e y were p o s i t i v e
( i n c l u d i n g 0) and t h e minimum n e g a t i v e
v a l u e i f t h e y were n e g a t i v e .
/
public void f o r c e T o E x t r e m e s ( )
{
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
// l o o p t h r o u g h t h e sample v a l u e s
f o r ( i n t i = 0 ; i < sampleArray . l e n g t h ; i ++)
{
// g e t t h e c u r r e n t sample
sample = sampleArray [ i ] ;
// i f t h e v a l u e was p o s i t i v e ( or z e r o ) s e t i t t o t h e
// maximum p o s i t i v e v a l u e
i f ( sample . g e t V a l u e ( ) >= 0 )
sample . s e t V a l u e ( 3 2 7 6 7 ) ;
// e l s e f o r c e t o max n e g a t i v e v a l u e
else
sample . s e t V a l u e ( 32768);
}
}
Look at Figure 8.20 and see that all the values have been set to extremes.
When you play the sound back, youll hear a bunch of awful noises. Thats clipping.
The really amazing thing is that you can still make out the words in sounds that
main
2005/1/11
page 298
298
Chapter 8
you manipulate with this method. Our ability to decipher words from noise is
incredibly powerful.
FIGURE 8.20: Comparing the original sound with one with all values set to extremes.
8.5
CONCEPTS SUMMARY
In this chapter we worked with one-dimensional arrays, while loops, for loops,
and conditionals.
8.5.1
Arrays
Arrays are used to store many pieces of data of the same type. They allow you to
quickly access a particular item in the array using an index. If you couldnt use an
array, you would have to create a separate variable name for each piece of data.
To declare a variable that refers to an array use the type followed by open [
and close ] square brackets and then the variable name.
SoundSample [ ] sampleArray ;
Loops
Loops are used to execute a block of statements while a boolean expression is true.
Most loops have variables that change during the loop which eventually cause the
boolean expression to be false and the loop to stop. Loops that never stop are
called infinite loops.
We used two types of loops in this chapter: while and for. The while loop is
usually used when you dont know how many times a loop needs to execute and the
for loop is usually used when you do know how many times the loop will execute.
We introduced a special kind of for loop in this chapter which is often called
a for each loop. This new loop was introduced in Java 1.5 and loops through all
the elements of an array one at a time.
main
2005/1/11
page 299
Section 8.5
Concepts Summary
299
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
i nt v a l u e = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
f o r ( SoundSample sample : sampleArray )
{
v a l u e = sample . g e t V a l u e ( ) ;
// do s o m e t h i n g t o t h e v a l u e
sample . s e t V a l u e ( v a l u e ) ;
}
This declares a variable sample that is of the type SoundSample and each
time through the loop the sample variable will refer to a different element of the
array until all the elements have been processed.
The while loop has the keyword while followed by a boolean expression and
then a block of statements between an open and close curly brace. If the boolean
expression is true the body of the loop will be executed. If the boolean expression is
false execution will continue after the body of the loop (after the close curly brace).
If you just want to execute one statement in the body of the loop then you dont
need the open and close curly braces, but you should indent the statement.
while ( boolean e x p r e s s i o n ) {
statement1 ;
statement2 ;
...
}
If you use a while loop to execute a block of statements a set number of times
you will need to declare a variable before the while and that variable will need to
be changed in the body of the loop. You may also need to declare other variables
that you use in the loop before the while. Dont declare variables inside the loop
because you will use more memory that way.
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt i n d e x = 0 ;
i nt v a l u e = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
while ( i n d e x < sampleArray . l e n g t h )
{
sample = sampleArray [ i n d e x ] ;
v a l u e = sample . g e t V a l u e ( ) ;
// do s o m e t h i n g t o t h e v a l u e
sample . s e t V a l u e ( v a l u e ) ;
i n d e x ++;
}
The for loop does the same thing as a while loop but it lets you declare the
variables that you need for the loop, specify the boolean expression to test, and
specify how to change the loop variables all in one place. This means you are less
likely to forget to do each of these things.
main
2005/1/11
page 300
i
300
Chapter 8
SoundSample [ ] sampleArray = t h i s . g e t S a m p l e s ( ) ;
SoundSample sample = null ;
i nt v a l u e = 0 ;
// l o o p t h r o u g h a l l t h e s a m p l e s i n t h e a r r a y
f o r ( i n t i n d e x = 0 ; i n d e x < sampleArray . l e n g t h ; i n d e x++)
{
sample = sampleArray [ i n d e x ] ;
v a l u e = sample . g e t V a l u e ( ) ;
// do s o m e t h i n g t o t h e v a l u e
sample . s e t V a l u e ( v a l u e ) ;
}
8.5.3
Conditional Execution
To conditionally execute one statement use the if keyword followed by a boolean
expression inside of an open and close parenthesis. Put the statement that you only
want executed if the boolean expression is true on a new line and indent it. If the
boolean expression is false then execution will continue with the next statement.
i f ( boolean e x p r e s s i o n )
// s t a t e m e n t t o e x e c u t e i f t h e b o o l e a n e x p r e s s i o n i s t r u e
statement
// n e x t s t a t e m e n t
statement
main
2005/1/11
page 301
i
Section 8.5
Concepts Summary
301
In this chapter, we talk about several kinds of encodings of data (or objects).
Sound
SoundSample
FileChooser methods
FileChooser.getMediaPath(String fileName)
FileChooser.pickAFile()
FileChooser.setMediaPath(String directory)
Takes a filename as input, and returns the full path name of the
file with the media directory before the filename.
Lets the user pick a file and returns the complete path name as
a string.
Takes a directory as input and
sets the directory name to be
what is added to the passed filename using getMediaPath.
Sound methods
main
2005/1/11
page 302
302
Chapter 8
blockingPlay()
getLength()
getSamples()
getSampleValueAt(int index)
getSamplingRate()
play()
setSampleValueAt(int index, int value)
write(String fileName)
Plays the Sound object it is invoked on, and makes sure that
no other sound plays at the exact same time. (Compare two
blockingPlays with two plays
right after each other.)
Returns the number of samples in
the Sound object it is invoked on.
Returns an array of SoundSample
objects for the Sound object it is
invoked on.
Takes an index (an integer value),
and returns the value of the sample at that index for the Sound
object it is invoked on.
Returns the number representing
the number of samples in each
second for the Sound object it is
invoked on.
Plays the Sound object it is invoked on.
Takes an index, and a value, and
sets the value of the sample at the
given index in the Sound object it
was invoked on to the given value.
Takes a filename (a string) and
writes the sound in the Sound
object it is invoked on to that
file as a WAV file. (Make sure
that the filename ends in .wav
if you want the operating system
to treat it right.)
SoundSample methods
getValue()
Returns the value for the SoundSample object it
is invoked on.
setValue(int value) Sets the value for the SoundSample object it is
invoked on to be the passed value.
PROBLEMS
8.1. Open up the Sonogram view and say some vowel sounds. Is there a distinctive
pattern? Do Ohs always sound the same? Do Ahs? Does it matter if you
switch speakersare the patterns the same?
8.2. Get a couple of different instruments and play the same note on them into MediaTool applications sound editor with the sonogram view open. Are all Cs
made equal? Can you see some of why one sound is different than another?
main
2005/1/11
page 303
i
Section 8.5
Concepts Summary
303
8.3. Try out a variety of WAV files as instruments, using the piano keyboard in the
MediaTools application sound editor. What kinds of recordings work best as
instruments?
8.4. The increase volume Method 65 (page 284) uses a while loop. Convert it to a
for loop.
8.5. The decrease volume Method 66 (page 290) uses a while loop. Convert it to a
for loop.
8.6. In section 8.3.2, we walked through how Program 65 (page 284) worked. Draw
the pictures to show how Program 66 (page 290) works, in the same way.
8.7. What happens if you increase a volume too far? Explore that by creating a Sound
object, then increase the volume once, and again, and again. Does it always keep
getting louder? Or does something else happen? Can you explain why?
8.8. Instead of multiplying samples by a multiplier (like 2 or 0.5), try adding a value
to them. What happens to a sound if you add 100 to every sample? 1000?
8.9. Try sprinkling in some specific values into your sounds. What happens if you set
the value of a few hundred samples in the middle of a sound to 32767? Or a few
hundred -32768? Or a bunch of zeroes? What happens to the sound?
TO DIG DEEPER
There are many wonderful books on psychoacoustics and computer music. One
of my favorites for understandability is Computer Music: Synthesis, Composition,
and Performance by Dodge and Jerse [8]. The bible of computer music is Curtis
Roads massive The Computer Music Tutorial [22].
main
2005/1/11
page 304
i
C H A P T E R
main
2005/1/11
page 305
Section 9.1
305
Recall that each sample has an index number, and that we can get each
individual sample value with getSampleValue(int index) (with an index number
as input). We can set any sample with setSampleValue(int index, int value)
(with inputs of an index number, and a new value). Thats how we can manipulate
samples without using getSamples() and SoundSample objects. But we still dont
want to have to write code like:
sound . s e t S a m p l e V a l u e ( 0 , 1 2 ) ;
sound . s e t S a m p l e V a l u e ( 1 , 2 8 ) ;
Not for tens of thousands of samples! So, we will continue to use a for loop.
However, if we are not processing the entire sound in the same way the index value
that we start at wont necessarily be 0 and the last index value wont necessarily
be the length of the sound minus 1.
What if we want to increase the sound for the first half of the sound, then
decrease it in the second half. How could we do that? First we will need to calculate
the half-way point. We can determine that by dividing the length of the sound by
2. Since the length and 2 are both integers the result will also be an integer so no
casting is needed (any values after the decimal point will be thrown away). We will
need two loops. One loop will start at the beginning of sound (0) and loop till the
half-way point. The second loop will start at the half-way point and loop to the
end of the sound (length - 1).
Program 71: Increase the volume then decrease
/
Method t o i n c r e a s e t h e f i r s t h a l f o f t h e sound
( d o u b l e i t ) and t h e n d e c r e a s e t h e
second h a l f ( h a l f i t ) .
/
public void i n c r e a s e A n d D e c r e a s e ( )
{
i nt h a l f = t h i s . g e t L e n g t h ( ) / 2 ;
i nt v a l u e = 0 ;
// l o o p t h r o u g h t h e f i r s t h a l f o f t h e sound
f o r ( i n t i = 0 ; i < h a l f ; i ++)
{
// g e t t h e c u r r e n t v a l u e
v a l u e = t h i s . getSampleValueAt ( i ) ;
// s e t t h e v a l u e t o 2 x t h e o r i g i n a l
t h i s . setSampleValueAt ( i , v a l u e 2 ) ;
}
// l o o p t h r o u g h t h e s e c o n d h a l f o f t h e sound
f o r ( i n t i = h a l f ; i < t h i s . g e t L e n g t h ( ) ; i ++) {
// g e t t h e c u r r e n t v a l u e
main
2005/1/11
page 306
i
306
Chapter 9
v a l u e = t h i s . getSampleValueAt ( i ) ;
// s e t t h e v a l u e t o h a l f t h e o r i g i n a l
t h i s . setSampleValueAt ( i , ( in t ) ( v a l u e 0 . 5 ) ) ;
}
}
main
2005/1/11
page 307
Section 9.2
307
FIGURE 9.1: Exploring the This is a test to find the end of the first word
many samples it will have. To calculate the number of samples we can subtract the
ending value from the starting value and add 1.
We can then create a new sound and loop copying from the start to the end
from the source Sound object into the target Sound object starting at the beginning
of the target sound. We have to make sure to increment both the index in the source
and the index in the target. If we forget to increment the source index we will copy
the same source sample over and over and if we fail to increment the target index
we will copy to the same place in the target over and over.
We need to return our new sound object in order to be able to refer to it
again. To return something from a method we need to specify the type of the thing
that will be returned in the method declaration (replacing the void keyword). We
also need to use the keyword return followed by what we want to return.
Program 72: Create a sound clip
/
Method t o c r e a t e a new sound by c o p y i n g j u s t p a r t o f
t h e c u r r e n t sound t o a new sound
@param s t a r t t h e i n d e x t o s t a r t t h e copy a t ( i n c l u s i v e )
@param end t h e i n d e x t o s t o p t h e copy a t ( i n c l u s i v e )
@return a new sound w i t h j u s t t h e s a m p l e s from s t a r t t o
end i n i t
/
public Sound c l i p ( i n t s t a r t , in t end )
{
// c a l c u l a t e t h e number o f s a m p l e s i n t h e c l i p
main
2005/1/11
page 308
308
Chapter 9
i nt l e n g t h I n S a m p l e s = end s t a r t + 1 ;
Sound t a r g e t = new Sound ( l e n g t h I n S a m p l e s ) ; // h o l d c l i p
i nt v a l u e = 0 ;
// h o l d s t h e c u r r e n t sample v a l u e
i nt t a r g e t I n d e x = 0 ; // i n d e x i n t a r g e t sound
// copy from s t a r t t o end from s o u r c e i n t o t a r g e t
f o r ( i n t i = s t a r t ; i <= end ; i ++, t a r g e t I n d e x++) {
v a l u e = t h i s . getSampleValueAt ( i ) ;
t a r g e t . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
return t a r g e t ;
}
Notice that we said that we would return a Sound object from this method by
saying that the type of thing returned is from the class Sound. At the end of the
method we use the keyword return followed by the variable that refers to the new
Sound object. So, in order to refer to this new Sound object again we will need to
declare a variable and set the value of that variable to refer to the returned Sound
object.
> Sound test = new
Sound(FileChooser.getMediaPath("thisisatest.wav"));
> test.explore();
> Sound clip = test.clip(0,8500);
> clip.play();
> clip.explore();
Use the explorer on the original sound and the clip. Change the number of
samples between pixels to be 100 in the clip. Then compare the sample values.
Convince yourself that the clip does have the same values as the original. (Figure 9.2)
9.3
SPLICING SOUNDS
Splicing sounds is a term that dates back to when sounds were recorded on tape,
so juggling the order of things on the tape involved literally cutting the tape into
segments and then gluing it back together in the right order. Thats splicing.
When everything is digital, its much easier.
To splice sounds, we simply have to copy elements around in the array. Its
easiest to do this with two (or more) arrays, rather than copying within the same
array. Splicing lets you create all kinds of sounds, speeches, non-sense, and art.
The easiest kind of splice to do is when the sounds are in separate files. All
that you need to do is to copy each sound, in order, into a target sound. You need
to keep track of the next index in the target sound. Heres a recipe that creates the
start of a sentence Guzdial is.... (Readers are welcome to complete the sentence.)
Program 73: Splice words into a single sentence
main
2005/1/11
page 309
Section 9.3
Splicing Sounds
309
/
Method t o s p l i c e two sounds t o g e t h e r w i t h some s i l e n c e b e t w e e n them
i n t o t h e c u r r e n t sound
/
public void s p l i c e ( ) {
Sound sound1 =
new Sound ( F i l e C h o o s e r . getMediaPath ( g u z d i a l . wav ) ) ;
Sound sound2 =
new Sound ( F i l e C h o o s e r . getMediaPath ( i s . wav ) ) ;
i nt t a r g e t I n d e x = 0 ; // t h e s t a r t i n g p l a c e on t h e t a r g e t
i nt v a l u e = 0 ;
// copy a l l o f sound 1 i n t o t h e c u r r e n t sound ( t a r g e t )
for ( int i = 0 ;
i < sound1 . g e t L e n g t h ( ) ;
i ++, t a r g e t I n d e x++) {
v a l u e = sound1 . getSampleValueAt ( i ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
// c r e a t e s i l e n c e b e t w e e n words by s e t t i n g v a l u e s t o 0
for ( int i = 0 ;
i < ( in t ) ( t h i s . getSamplingRate ( ) 0 . 1 ) ;
i ++, t a r g e t I n d e x++) {
t h i s . setSampleValueAt ( t a r g e t I n d e x , 0 ) ;
}
// copy a l l o f sound 2 i n t o t h e c u r r e n t sound ( t a r g e t )
for ( int i = 0 ;
main
2005/1/11
page 310
i
310
Chapter 9
i < sound2 . g e t L e n g t h ( ) ;
i ++, t a r g e t I n d e x++) {
v a l u e = sound2 . getSampleValueAt ( i ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
}
To test this we need a blank sound that the two sounds will be copied to. You can
use the file sec3silence.wav for this. It holds 3 seconds of silence, which should
be more than enough.
>
>
>
>
>
There are three loops in this method splice, each of which copies one segment
into the current (target) sounda segment being either a word or a silence between
words.
The function starts by creating sound objects for the word Guzdial (s1)
and the word is (s2).
Notice that we set targetIndex (the index for the target sound) equal to 0
before the first loop. We then increment it in every loop, but we never again
set it to a specific value. Thats because targetIndex is always the index
for the next empty sample in the target sound. Because each loop follows the
previous, we just keep tacking samples onto the end of the current sound (the
target).
In the first loop, we copy each and every sample from s1 into the current
sound this. We have the index i go from 0 to one before the length of s1.
We get the sample value at index i from s1, then set the sample value at
targetIndex in the current sound to that value. We then increment both i
and targetIndex.
In the second loop, we create 0.1 seconds of silence. Since this.getSamplingRate()
gives us the number of samples in one second of the current sound, 0.1 times
that tells us the number of samples in 0.1 seconds. We dont get any source
value herewe simply set the targetIndex-th sample to 0 (for silence), then
increment the targetIndex.
Finally, we copy in all the samples from s2, just like the first loop where we
copied in s1.
The more common kind of splicing is when the words are in the middle of an
existing sound, and you need to pull them out from there. The first thing to do in
splicing like that is to figure out the index numbers that delimit the pieces youre
interested in. Using the explorer, thats pretty easy to do.
main
2005/1/11
page 311
Section 9.3
Splicing Sounds
311
main
2005/1/11
page 312
i
312
Chapter 9
v a l u e = s o u r c e . getSampleValueAt ( s o u r c e I n d e x ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
// l o o p c o p y i n g t h e u n i t e d i n t o t h e c u r r e n t sound
for ( int sourceIndex = 33414;
sourceIndex < 40052;
s o u r c e I n d e x ++, t a r g e t I n d e x++) {
v a l u e = s o u r c e . getSampleValueAt ( s o u r c e I n d e x ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
// copy t h e p e o p l e o f t h e U n i t e d S t a t e s
for ( int sourceIndex = 17408;
sourceIndex < 55510;
s o u r c e I n d e x ++, t a r g e t I n d e x++) {
v a l u e = s o u r c e . getSampleValueAt ( s o u r c e I n d e x ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
}
>
>
>
>
>
The first loop copies the words We the into the current sound. The second
loop is copies the word united into the current sound. The last loop copies the
words people of the United States into the current sound. Notice that the value
of targetIndex is set to 0 at the beginning so we start copying at the beginning
of the current sound. In each loop we increment targetIndex but we never reset
its value so it always points to the next place in the current sound to copy to.
Figure 9.3 shows the original preamble10.wav file in the left sound explorer,
and the new spliced one (saved with write(String fileName)) on the right.
Lets see if we can figure out whats going on mathematically. Recall the
table back on page 311. First we copy the range from 0 to 17406 to the target
sound. This means we copied 17407 pixels (17406 - 0 + 1 = 17407). After the
first loop the value of targetIndex will be 17407. Next we copy the range from
33414 to 40051 which means we copy (40051 - 33414 + 1 = 6,638) 6638 pixels.
After the second loop the value of targetIndex will be 24045 (17407 + 6638 =
24045). Next we copy the range from 17408 to 55509 which is (55509 - 17408 + 1
= 38102) pixels. The total number of copied pixels is (17407 + 6638 + 38102 =
62,147). The value of targetIndex will be 62147 after the last loop. You can add
System.out.println("Target index is " + targetIndex); after each loop to
main
2005/1/11
page 313
i
Section 9.3
Splicing Sounds
313
/
Method t o s p l i c e We t h e t h e n U n i t e d t h e n
p eop le o f the United S t a t e s i n t o the current
sound
/
public void s p l i c e P r e a m b l e ( )
{
S t r i n g f i l e = F i l e C h o o s e r . getMediaPath ( preamble10 . wav ) ;
Sound s o u r c e = new Sound ( f i l e ) ;
i nt t a r g e t I n d e x = 0 ; // s t a r t c o p y i n g t o f i r s t sample v a l u e
i nt v a l u e = 0 ;
// l o o p c o p y i n g t h e We t h e i n t o t h e c u r r e n t sound
for ( int sourceIndex = 0 ;
sourceIndex < 17407;
s o u r c e I n d e x ++, t a r g e t I n d e x++) {
v a l u e = s o u r c e . getSampleValueAt ( s o u r c e I n d e x ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
// p r i n t t h e v a l u e o f t h e t a r g e t i n d e x
System . out . p r i n t l n ( Target i n d e x i s + t a r g e t I n d e x ) ;
// l o o p c o p y i n g t h e u n i t e d i n t o t h e c u r r e n t sound
for ( int sourceIndex = 33414;
sourceIndex < 40052;
s o u r c e I n d e x ++, t a r g e t I n d e x++) {
v a l u e = s o u r c e . getSampleValueAt ( s o u r c e I n d e x ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
// p r i n t t h e v a l u e o f t h e t a r g e t i n d e x
System . out . p r i n t l n ( Target i n d e x i s + t a r g e t I n d e x ) ;
// copy t h e p e o p l e o f t h e U n i t e d S t a t e s
for ( int sourceIndex = 17408;
sourceIndex < 55510;
s o u r c e I n d e x ++, t a r g e t I n d e x++) {
v a l u e = s o u r c e . getSampleValueAt ( s o u r c e I n d e x ) ;
t h i s . setSampleValueAt ( t a r g e t I n d e x , v a l u e ) ;
}
// p r i n t t h e v a l u e o f t h e t a r g e t i n d e x
System . out . p r i n t l n ( Target i n d e x i s + t a r g e t I n d e x ) ;
}
main
2005/1/11
page 314
i
314
Chapter 9
FIGURE 9.3: Comparing the original sound (left) to the spliced sound (right)
We can also use the explorer to check that the last copied pixel is at 62146
by checking the value at 62147. It should still be 0, as should all the values from
that index to the end of the sound.
Each of the loops that copies part of the preamble sound into the current
sound is very similar. To make a general splice method we will pass in the Sound
object to copy from, the starting index to use in that passed sound, the index to
stop before in the passed sound, and the place to start the copy to in current sound.
Program 76: General splice method
/
Method t o copy p a r t o f t h e p a s s e d sound i n t o t h i s sound a t
the given s t a r t index
@param s o u r c e t h e s o u r c e sound t o copy from
@param s o u r c e S t a r t t h e s t a r t i n g i n d e x t o copy from i n t h e
s o u r c e ( t h e copy w i l l i n c l u d e t h i s )
@param s o u r c e S t o p t h e e n d i n g i n d e x ( t h e copy won t i n c l u d e
this )
@param t a r g e t S t a r t t h e i n d e x t o s t a r t c o p y i n g i n t o
/
public void s p l i c e ( Sound s o u r c e ,
int sourceStart ,
int sourceStop ,
int t a r g e t S t a r t )
{
// l o o p c o p y i n g from s o u r c e t o t a r g e t
for ( int sourceIndex = sourc e Start ,
targetIndex = targetStart ;
s o u r c e I n d e x < s o u r c e S t o p &&
targetIndex < this . getLength ( ) ;
s o u r c e I n d e x ++, t a r g e t I n d e x++)
t h i s . setSampleValueAt ( t a r g e t I n d e x ,
s o u r c e . getSampleValueAt ( s o u r c e I n d e x ) ) ;
}
main
2005/1/11
page 315
i
Section 9.4
Reversing a Sound
315
This new object method can be used to splice united in the phrase We the
people of the United States as shown below:
Program 77: Using the General Splice Method
/
Method t o s p l i c e t h e p r e a m b l e i n t o t h e c u r r e n t sound so t h a t
i t s a y s We t h e U n i t e d p e o p l e o f t h e U n i t e d S t a t e s
/
public void s p l i c e P r e a m b l e 2 ( )
{
Sound preamble =
new Sound ( F i l e C h o o s e r . getMediaPath ( preamble10 . wav ) ) ;
// f i r s t s p l i c e t h e we t h e i n t o t h e c u r r e n t sound
t h i s . s p l i c e ( preamble , 0 , 1 7 4 0 7 , 0 ) ;
// now s p l i c e t h e u n i t e d i n t o t h e c u r r e n t sound
t h i s . s p l i c e ( preamble , 3 3 4 1 4 , 4 0 0 5 2 , 1 7 4 0 7 ) ;
/ now s p l i c e t h e p e o p l e o f t h e U n i t e d S t a t e s i n t o
t h e c u r r e n t sound
/
t h i s . s p l i c e ( preamble , 1 7 4 0 8 , 5 5 5 1 0 , 2 4 0 4 5 ) ;
}
REVERSING A SOUND
In the splicing example, we copied the samples from the words just as they were in
the original sound. We dont have to always go in the same order. We can reverse
the wordsor make them faster, slower, louder, or softer. For an example, heres
main
2005/1/11
page 316
i
316
Chapter 9
/
Method t o r e v e r s e t h e c u r r e n t sound .
/
public void r e v e r s e ( )
{
Sound o r i g = new Sound ( t h i s . getFileName ( ) ) ;
i nt l e n g t h = t h i s . g e t L e n g t h ( ) ;
// l o o p t h r o u g h t h e s a m p l e s
for ( int t a r g e t I n d e x = 0 , sourceIndex = length 1 ;
t a r g e t I n d e x < l e n g t h && s o u r c e I n d e x > 0 ;
t a r g e t I n d e x ++, s o u r c e I n d e x )
t h i s . setSampleValueAt ( t a r g e t I n d e x ,
o r i g . getSampleValueAt ( s o u r c e I n d e x ) ) ;
}
This method firsts creates another Sound object from the same file as the
current Sound object. This will make a copy of the original sound. Next the
method saves the length of the current Sound object. Then it loops.
The loop initializes the value of targetIndex to 0, and the value of sourceIndex
to the length of the sound minus 1. It loops while targetIndex is less than
the length of the sound and the sourceIndex is greater than 0. It increments
targetIndex by 1 after the body of the loop and it decrements the value of
sourceIndex by one each time through the loop.
Why does is start sourceIndex at the length of the sound minus 1 and decrement it each time through the loop? Remember that the last valid index is at the
length minus 1, which is why the sourceIndex starts with this value. So we copy
from the end of the source sound (length - 1) to the beginning of the target sound
(0) during the first execution of the loop. The second time through the loop we
copy from the next to last sound sample in the source (length - 2) to the second
position in the target (1). We will keep looping until the targetIndex equals the
length of the sound.
To use this method to reverse a sound try:
>
>
>
>
>
>
main
2005/1/11
page 317
i
Section 9.5
Mirroring a Sound
317
FIGURE 9.4: Comparing the original sound (left) to the reversed sound (right)
9.5
MIRRORING A SOUND
Once we know how to play sounds forwards and backwards, mirroring the sound
is the exact same process as mirroring pictures! Compare this to Program 19
(page 140). Do you see that this is the same algorithm, though were dealing with
a different medium?
Program 79: Mirror a sound, front to back
/
Method t o m i r r o r a sound f r o n t t o b a c k
/
public void mirrorFrontToBack ( )
{
i nt l e n g t h = g e t L e n g t h ( ) ; // s a v e t h e l e n g t h
i nt l a s t I n d e x = l e n g t h 1 ; // l a s t v a l i d i n d e x
i nt m i r r o r P o i n t = l e n g t h / 2 ; // m i r r o r around t h i s
i nt v a l u e = 0 ; // h o l d t h e c u r r e n t v a l u e
// l o o p from 0 t o m i r r o r P o i n t
f o r ( i n t i = 0 ; i < m i r r o r P o i n t ; i ++) {
v a l u e = t h i s . getSampleValueAt ( m i r r o r P o i n t i ) ;
t h i s . setSampleValueAt ( m i r r o r P o i n t+i ) ;
}
}
The length of the sound in the file croak.wav is 8808, so the mirror point is
at 4404. Use the explorer to check the values on either side of the mirror point.
main
2005/1/11
page 318
i
318
Chapter 9
FIGURE 9.5: Comparing the mirror point in the original sound (left) to the mirrored
sound (right)
9.6
CONCEPTS SUMMARY
This chapter covered working with ranges in loops and how to return a value from
a method.
9.6.1
Ranges in Loops
To limit the range of a loop change the starting value and/or ending value. For
example to mirror the front of the sound to the back start with an index value of
1 and end before the mirror point (midpoint).
public
{
i nt
i nt
i nt
void mirrorFrontToBack ( )
l e n g t h = g e t L e n g t h ( ) ; // s a v e t h e l e n g t h
m i r r o r P o i n t = l e n g t h / 2 ; // m i r r o r around t h i s
v a l u e = 0 ; // h o l d t h e c u r r e n t v a l u e
// l o o p from 1 t o m i r r o r P o i n t
f o r ( i n t i = 1 ; i < m i r r o r P o i n t ; i ++) {
v a l u e = t h i s . getSampleValueAt ( m i r r o r P o i n t i ) ;
t h i s . setSampleValueAt ( m i r r o r P o i n t+i , v a l u e ) ;
}
}
9.6.2
main
2005/1/11
page 319
Section 9.6
Concepts Summary
319
Methods that do not return any value use the keyword void as the returnType.
Methods that do return a value use the type of that value for the returnType and
then have a return keyword in them that is followed by the thing to return. Remember that a type is any of the primitive types or the name of a class.
Here is an example public method declaration that doesnt return anything
and the name of the method is mirrorFrontToBack and it doesnt take any parameters.
public void mirrorFrontToBack ( )
Notice that it gives a return type of Sound. The body of the method must
have the keyword return in it and it must return an object that is an instance of
the class Sound.
PROBLEMS
9.1. Rewrite Program 71 (page 305) so that two input values are provided to the
function: The sound, and a percentage of how far into the sound to go before
dropping the volume.
9.2. Rewrite Program 71 (page 305) so that you normalize the first second of a sound,
then slowly decrease the sound in steps of 1/5 for each following second. (How
many samples are in a second? getSamplingRate() is the number of samples per
second for the given sound.)
9.3. Try rewriting Program 71 (page 305) so that you have a linear increase in volume
to halfway through the sound, then linearly decrease the volume down to zero in
the second half.
9.4. I think that if were going to say We the UNITED people in the splice (Program 74 (page 311)), the UNITED should be really emphasizedreally loud.
Change the recipe so that the word united is maximally loud (normalized) in
the phrase united people.
9.5. Try using a stopwatch to time the execution of the recipes in this chapter. Time
from hitting return on the command, until the next prompt appears. What is the
relationship between execution time and the length of the sound? Is it a linear
relationship, i.e., longer sounds take longer to process and shorter sounds take
less time to process? Or is it something else? Compare the individual recipes.
Does normalizing a sound take longer than raising (or lowering) the amplitude
a constant amount? How much longer? Does it matter if the sound is longer or
shorter?
9.6. Make an audio collage. Make it at least five seconds long, and include at least
two different sounds (e.g., come from different files). Make a copy of one of those
different sounds and modify it using any of the techniques described in this
chapter (e.g., mirroring, splicing, and volume manipulations). Splice together
the original two sounds and the modified sound to make the complete collage.
9.7. Compose a sentence that no one ever said, by combining words from other sounds
into a grammatically correct new sound. Write a method named audioSentence
to generate a sentence out of individual words. Use at least three words in your
sentence! You can use the words in the mediasources folder on your CD or record
main
2005/1/11
page 320
i
320
Chapter 9
your own words. Be sure to include a tenth (1/10) of a second pause between the
words. (Hint 1: Remember that zeroes for the sample values generate silence or
pause.) (Hint 2: Remember that the sampling rate is the number of samples per
second. From there, you should be able to figure out how many samples need
to be made zero to generate a 1/10 of a second pause.) Be sure to access your
sounds in your Media Folder using getMediaPath so that it will work for users of
your program will work as long as they first execute setMediaPath.
9.8. Write a method called erasePart to set all the samples in the 2nd second of
thisisatest.wav to 0s-essentially, making the 2nd second go silent. (Hint: Remember that getSamplingRate() tells you the number of samples in a single
second in a sound.) Play and return the partially-erased sound.
9.9. Weve seen a function that reverses a sound and a function that can process
samples by index number.
Write a function called reverseLastHalf that reverses just the second half of the
current sound. For example, if the sound said MarkBark the returned sound
should say MarkkraB.
9.10. Write a method similar to Program 79 (page 317) that mirrors from back to
front.
TO DIG DEEPER
When you are using the MediaTools application, you are actually using a programming language called Squeak , developed initially and primarily by Alan Kay,
Dan Ingalls, Ted Kaehler, John Maloney, and Scott Wallace [17]. Squeak is now
open-source1 , and is an excellent cross-platform multimedia tool. There is a book
that introduces Squeak including its sound capabilities [13], and another book on
Squeak [14] that includes a chapter on Siren, a variation of Squeak by Stephen
Pope especially designed for computer music exploration and composition.
1 http://www.squeak.org
main
2005/1/11
page 321
i
C H A P T E R
10
main
2005/1/11
page 322
i
322
Chapter 10
In physics, adding sounds involves issues of canceling waves out and enforcing
other factors. In math, its about matrices. In computer science, its the easiest
process in the world! Lets say that youve got a sound, source, that you want
to add in to the current Sound object. Simply add the values at the same index
numbers! Thats it!
// l o o p t h r o u g h a l l o f t h e s o u r c e
f o r ( i n t i = 0 ; i < s o u r c e . g e t L e n g t h ( ) ; i ++) {
// add s o u r c e sound v a l u e and t h i s sound v a l u e
v a l u e = t h i s . getSampleValueAt ( i ) +
s o u r c e . getSampleValueAt ( i ) ;
// s e t t h e v a l u e i n t h i s sound t o t h e new v a l u e
t h i s . setSampleValueAt ( i , v a l u e ) ;
}
FIGURE 10.1: The top and middle waves are added together to create the bottom
wave
main
2005/1/11
page 323
i
Section 10.2
10.2
Blending Sounds
323
BLENDING SOUNDS
In this example, we take two soundssomeone saying Aah! and an bassoon instrument sound of C in the fourth octaveand blend the two sounds. The way we do
this is to first copy part of the first sound, Aah!, then copy 50% of each sound,
and then copying the second sound. This is very much like mixing 50% of each
sound at a mixing board. Its also very much like the way that we blended pictures
in Program 27 (page 160)!
/
Method t o o v e r l a p or b l e n d two sounds .
Start
by c o p y i n g t h e f i r s t 20 ,000 s a m p l e s from sound1 i n t o
t h e c u r r e n t sound t h e n copy t h e sum o f h a l f o f sound1
and h a l f o f sound2 f o r t h e n e x t 20 ,000 s a m p l e s and
end w i t h t h e n e x t 20 ,000 s a m p l e s from sound2 .
/
public void blendSounds ( ) {
Sound sound1 =
new Sound ( F i l e C h o o s e r . getMediaPath ( aah . wav ) ) ;
Sound sound2 =
new Sound ( F i l e C h o o s e r . getMediaPath ( bassoon c4 . wav ) ) ;
i nt v a l u e = 0 ;
// copy t h e f i r s t 20 ,000 s a m p l e s from sound1 i n t o t a r g e t
f o r ( i n t i n d e x =0; i n d e x < 2 0 0 0 0 ; i n d e x++)
t h i s . setSampleValueAt ( index ,
sound1 . getSampleValueAt ( i n d e x ) ) ;
// copy t h e n e x t 20 ,000 s a m p l e s from sound1 and b l e n d t h a t
// w i t h t h e f i r s t 20 ,000 s a m p l e s from sound2
f o r ( i n t i n d e x = 0 ; i n d e x < 2 0 0 0 0 ; i n d e x++) {
v a l u e = ( in t ) ( ( sound1 . getSampleValueAt ( i n d e x + 2 0 0 0 0 )
0.5) +
( sound2 . getSampleValueAt ( i n d e x ) 0 . 5 ) ) ;
t h i s . setSampleValueAt ( i n d e x + 2 0 0 0 0 , v a l u e ) ;
}
// copy t h e n e x t 20 ,000 s a m p l e s from sound2 i n t o t h e t a r g e t
f o r ( i n t i n d e x =20000; i n d e x < 4 0 0 0 0 ; i n d e x++)
t h i s . setSampleValueAt ( i n d e x + 2 0 0 0 0 ,
sound2 . getSampleValueAt ( i n d e x ) ) ;
}
main
2005/1/11
page 324
i
324
Chapter 10
Like blending the picture (Program 27 (page 160)), there are loops in this
function for each segment of the blended sound.
We start by creating the sound1 and sound2 sounds for blending. The length
of these sounds is over 40,000 samples, but were just going to use the first
40,000 as an example.
In the first loop, we simply get 20,000 samples from sound1 and copy them
into the current sound this. Notice that were not using a separate index
variable for the targetinstead, were using the same index variable, index,
for both sounds since we are copying from 0 to 19,999 from sound1 into 0 to
19,999 in the current sound.
In the next loop, we copy 20,000 samples from both sound1 and sound2
blended into the current sound. We get a sample from each of sound1 and
sound2, then multiply each by 0.5 and add the results together. The result
is a sample that represents 50% of each. Notice that we are using one index
variable here as well but adding 20,000 to the value of that for determining
the index of sound1 and the current sound. So we blend values from sound1
starting at index 20,000 and from sound2 starting at index 0 and the blended
values go into the current sound starting at index 20,000.
Finally, we copy another 20,000 samples from sound2. The result sounds like
Aah, first, then half of each, then just a bassoon note. Notice that we start
the index at 20,000 for the next place to copy from sound2. This means we
need to add 20,000 to that value for the index in the current sound (since
there are already 40,000 values in the current sound).
To create the blended sound first create a sound using the file that has 3
seconds of silence. Then explore it to see what it looks like before the blending.
Next, blend the two sounds into the silent sound. Finally, explore the new sound.
>
>
>
>
10.3
CREATING AN ECHO
Creating an echo effect is similar to the splicing recipe (Program 74 (page 311)) that
we saw in the last chapter, but involves actually creating sounds that didnt exist
before. We do that by actually adding wave forms. What were doing here is adding
main
2005/1/11
page 325
Section 10.3
Creating an Echo
325
FIGURE 10.2: The original ahh sound, the original bassoon note, and the blended
sound
samples from a delay number of samples away into the sound, but multiplied by
0.6 so that theyre fainter.
Program 81: Make a sound and a single echo of it
/
Method t o add an echo t o a sound
@param d e l a y t h e number o f s a m p l e s b e f o r e t h e echo s t a r t s
/
public void echo ( in t d e l a y )
{
// make a copy o f t h e o r i g i n a l sound
Sound s = new Sound ( t h i s . getFileName ( ) ) ;
i nt v a l u e = 0 ;
// l o o p from d e l a y t o end o f sound
f o r ( i n t i = d e l a y ; i < t h i s . g e t L e n g t h ( ) ; i ++) {
/ g e t t h e v a l u e b a c k by d e l a y s a m p l e s from t h e
copy o f t h e sound and make i t f a i n t e r
/
v a l u e = ( in t ) ( s . getSampleValueAt ( i d e l a y ) 0 . 6 ) ;
/ s e t t h e v a l u e a t t h e c u r r e n t i n d e x t o t h e sum
o f t h e c u r r e n t v a l u e and t h e echo
/
main
2005/1/11
page 326
i
326
Chapter 10
t h i s . setSampleValueAt ( i ,
t h i s . getSampleValueAt ( i ) +
value ) ;
}
}
How it works: The echo function takes a delay: the number of samples before
the echo starts. Try this with different amounts of delay. With low values of delay,
the echo will sound more like vibrato. Higher values (try 10,000 or 20,000) will give
you a real echo.
This method creates a copy of the current sound s. This is where well get
the original, unadulterated samples for creating the echo. (You could try this
without creating a copy to get some interesting layered echoes.)
Next we declare a variable value to hold a value of a sample.
Our loop starts with the index i being set to the passed delay and continues
through the rest of the sound.
The echoed sound is delay samples back, so i-delay is the sample we need.
We multiply it by 0.6 to make it softer in volume.
We then add the echoed sample to the current sample at i and set it in the
current Sound object.
Try this method on sounds with words in them.
>
>
>
>
FIGURE 10.3: The original This is a test sound (left), and the sound with an echo
(right)
main
2005/1/11
page 327
i
Section 10.3
10.3.1
Creating an Echo
327
/
Method t o c r e a t e m u l t i p l e e c h o e s o f t h e c u r r e n t sound
@param d e l a y t h e number o f s a m p l e s b e f o r e t h e echo s t a r t s
@param numEchoes t h e number o f e c h o e s d e s i r e d
@return a new sound w i t h t h e e c h o e s i n i t
/
public Sound echo ( i n t d e l a y , in t numEchoes )
{
i nt soundLength = t h i s . g e t L e n g t h ( ) ;
Sound echoSound = new Sound ( numEchoes d e l a y + soundLength ) ;
i nt v a l u e = 0 ;
i nt e c h o I n d e x = 0 ;
i nt echoValue = 0 ;
double echoAmplitude = 1 ; // t o s t a r t
// copy t h e o r i g i n a l sound
echoSound . s p l i c e ( this , 0 , soundLength , 0 ) ;
/ l o o p s t a r t i n g w i t h 1 t o c r e a t e t h e f i r s t echo a t t h e
r i g h t p l a c e and end t h e n when = t h e number o f e c h o e s
/
f o r ( i n t echoCount = 1 ; echoCount <= numEchoes ; echoCount++)
{
// d e c r e a s e t h e volume ( a m p l i t u d e ) o f t h e echo
echoAmplitude = echoAmplitude 0 . 6 ;
// echo t h e w h o l e sound
f o r ( in t i = 0 ; i < soundLength ; i ++)
{
e c h o I n d e x = i + ( d e l a y echoCount ) ;
echoValue = ( i n t ) ( t h i s . getSampleValueAt ( i )
echoAmplitude ) ;
echoSound . setSampleValueAt ( echoIndex , echoValue +
echoSound . getSampleValueAt ( e c h o I n d e x ) ) ;
}
}
return echoSound ;
}
To try out this recipe create a Sound object and then invoke this method on
the Sound object. Be sure to save the resulting Sound.
> Sound sound = new Sound(FileChooser.getMediaPath("croak.wav"));
main
2005/1/11
page 328
328
Chapter 10
/
Method t o d o u b l e t h e f r e q u e n c y o f a sound by t a k i n g
e v e r y s e c o n d sample . The r e s u l t w i l l be a h i g h e r
sound .
/
public void d o u b l e F r e q ( )
{
// make a copy o f t h e o r i g i n a l sound
Sound s = new Sound ( t h i s . getFileName ( ) ) ;
/ l o o p t h r o u g h t h e sound and i n c r e m e n t t a r g e t i n d e x
by one b u t s o u r c e i n d e x by 2 and s e t t a r g e t v a l u e
t o t h e copy o f t h e o r i g i n a l sound
/
f o r ( i n t s o u r c e I n d e x =0 , t a r g e t I n d e x = 0 ;
sourceIndex < this . getLength ( ) ;
s o u r c e I n d e x=s o u r c e I n d e x +2 , t a r g e t I n d e x++)
t h i s . setSampleValueAt ( t a r g e t I n d e x ,
s . getSampleValueAt ( s o u r c e I n d e x ) ) ;
// c l e a r o u t t h e r e s t o f t h i s sound
for ( int i = this . getLength ( ) / 2 ;
i < this . getLength ( ) ;
main
2005/1/11
page 329
i
Section 10.4
329
i ++)
t h i s . setSampleValueAt ( i , 0 ) ;
}
FIGURE 10.4: The original sound (left), and the sound with the frequency doubled
(right)
This method starts like the other ones in this chapter by making a copy of
the sound. Then it loops through the sound but it increments the index that keeps
the position in the source sound sourceIndex by 2 and the index that keeps the
position in the target sound targetIndex by 1. This will copy the sample value
at sourceIndex 0 to targetIndex 0, then sourceIndex 2 to targetIndex 1, then
sourceIndex 4 to targetIndex 2, and so on. Since the resulting sound will be half
as long as it was the second loop just fills the rest of the sound with zeroes.
Try it1 ! Youll see that the sound really does double in frequency with the
result that it sounds higher!
How did that happen? Its not really all that complicated. Think of it this
wayThe frequency of the original sound is really the number of cycles that pass
by in a certain amount of time. If you skip every other sample, the new sound has
just as many cycles, but has them in half the amount of time!
Now lets try the other way: Lets take every sample twice! What happens
then?
To do this, we need to use a cast to throw away the fractional part of a
floating point number using a cast to integer. To cast a floating point number to
an integer number use (int).
> System.out.println((int)0.5)
0
1 You
main
2005/1/11
page 330
i
330
Chapter 10
> System.out.println((int)1.5)
1
Heres the recipe that halves the frequency. The for loop moves the targetIndex
along the length of the sound. The sourceIndex is now being incrementedbut
only by 0.5! The effect is that well take every sample in the source twice. The
sourceIndex will be 1, 1.5, 2, 2.5, and so on, but because were using the (int) of
that value, well take samples 1, 1, 2, 2, and so on.
Program 84: Half the frequency
/
Method t o h a l v e t h e f r e q u e n c y o f a sound by t a k i n g
each sample t w i c e . The r e s u l t w i l l be a l o w e r
sound .
/
public void h a l v e F r e q ( )
{
// make a copy o f t h e o r i g i n a l sound
Sound s = new Sound ( t h i s . getFileName ( ) ) ;
/ l o o p t h r o u g h t h e sound and i n c r e m e n t t a r g e t i n d e x
by one b u t s o u r c e i n d e x by 0 . 5 and s e t t a r g e t v a l u e
t o t h e copy o f t h e o r i g i n a l sound
/
f o r ( double s o u r c e I n d e x =0, t a r g e t I n d e x = 0 ;
targe tIndex < this . getLength ( ) ;
s o u r c e I n d e x=s o u r c e I n d e x +0.5 , t a r g e t I n d e x++)
t h i s . setSampleValueAt ( ( i n t ) t a r g e t I n d e x ,
s . getSampleValueAt ( ( i n t ) s o u r c e I n d e x ) ) ;
}
This method first creates a copy of the sound. Then it loops through the
sound incrementing the sourceIndex by 0.5 and the targetIndex by 1. We get a
sample value from source at the integer value using ((int)) of the sourceIndex.
We set the target at the integer value using ((int)) of the targetIndex to the
sample value that we got from the copy of the sound. We then add 0.5 to the
sourceIndex. This means that the sourceIndex, each time through the loop, will
take on the values 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, and so on. But the integer part of this
sequence is 0, 0, 1, 1, 2, 2, and so on. The result is that we take each sample from
the source sound twice.
Think about what were doing here. Imagine that the 0.5 above were actually
0.75 or 3.0. Would this work? The for loop would have to change, but essentially
the idea is the same in all these cases. We are sampling the source data to create
the target data. Using a sample index of 0.5 slows down the sound and halves the
frequency. A sample index larger than one speeds up the sound and increases the
frequency.
main
2005/1/11
page 331
i
Section 10.4
331
Lets try to generalize this sampling with the below recipe. (Note that this
one wont work right!)
Program 85: Changing the frequency of a sound: BROKEN!
/
Method t o change t h e f r e q u e n c y o f a sound by t h e
passed f a c t o r
@param f a c t o r t h e amount t o i n c r e m e n t t h e s o u r c e
i n d e x by . A number g r e a t e r th an 1 w i l l i n c r e a s e t h e
f r e q u e n c y and make t h e sound h i g h e r
w h i l e a number l e s s than one w i l l d e c r e a s e t h e
f r e q u e n c y and make t h e sound l o w e r .
/
public void changeFreq1 ( double f a c t o r )
{
// make a copy o f t h e o r i g i n a l sound
Sound s = new Sound ( t h i s . getFileName ( ) ) ;
/ l o o p t h r o u g h t h e sound and i n c r e m e n t t h e t a r g e t i n d e x
by one b u t i n c r e m e n t t h e s o u r c e i n d e x by t h e f a c t o r
/
f o r ( double s o u r c e I n d e x =0, t a r g e t I n d e x = 0 ;
targe tIndex < this . getLength ( ) ;
s o u r c e I n d e x=s o u r c e I n d e x+f a c t o r , t a r g e t I n d e x++)
{
t h i s . setSampleValueAt ( ( i n t ) t a r g e t I n d e x ,
s . getSampleValueAt ( ( i n t ) s o u r c e I n d e x ) ) ;
}
}
s = new Sound(FileChooser.getMediaPath("c4.wav"));
s.explore();
s.changeFreq(0.75);
s.explore();
That will work really well! But what if the factor for sampling is MORE
than 1.0?
>Sound hello = new Sound(FileChooser.getMediaPath("Elliot-hello.wav"));
> hello.changeFreq1();
You are trying to access the sample at index: 54759, but the last
valid index is at 54757.
Why? Whats happening? Heres how you could see it: Print out the
sourceIndex just before the setSampleValueAt. Youd see that the sourceIndex
main
2005/1/11
page 332
332
Chapter 10
becomes larger than the source sound! Of course, that makes sense. If each time
through the loop, we increment the targetIndex by 1, but were incrementing the
sourceIndex by more than one, well get past the end of the source sound before
we reach the end of the target sound. But how do we avoid it?
Heres what we want to happen: If the sourceIndex ever gets equal or larger
than length of the source, we want to reset the sourceIndexprobably back to 0.
The key word there is if, or even if.
We can can tell Java to make decisions based on a test. We use if as a
statement to do something if a test is true. In our case, the test is sourceIndex
>= s.getLength(). We can test on < , > , == (for equality), != (for inequality,
not-equals) and even <= and >=. An if statement takes a block , just as while and
for do. The block defines the things to do if the test in the if statement is true.
In this case, our block is simply sourceIndex = 0;. The block of statements is
defined inside of an open curly brace and a close curly brace . If you just have
one statement that you want to execute it doesnt have to be in a block but it is
better to keep it in a block.
The below recipe generalizes this and allows you to specify how much to shift
the samples by.
Program 86: Changing the frequency of a sound
/
Method t o change t h e f r e q u e n c y o f a sound
by t h e p a s s e d f a c t o r
@param f a c t o r t h e amount t o i n c r e m e n t t h e s o u r c e
i n d e x by . A number g r e a t e r th an 1 w i l l i n c r e a s e t h e
f r e q u e n c y and make t h e sound h i g h e r
w h i l e a number l e s s than one w i l l d e c r e a s e t h e f r e q u e n c y
and make t h e sound l o w e r .
/
public void changeFreq ( double f a c t o r )
{
// make a copy o f t h e o r i g i n a l sound
Sound s = new Sound ( t h i s . getFileName ( ) ) ;
/ l o o p t h r o u g h t h e sound and i n c r e m e n t t h e t a r g e t i n d e x
by one b u t i n c r e m e n t t h e s o u r c e i n d e x by t h e f a c t o r
/
f o r ( double s o u r c e I n d e x =0, t a r g e t I n d e x = 0 ;
targe tIndex < this . getLength ( ) ;
s o u r c e I n d e x=s o u r c e I n d e x+f a c t o r , t a r g e t I n d e x++)
{
i f ( s o u r c e I n d e x >= s . g e t L e n g t h ( ) ) {
sourceIndex = 0;
}
t h i s . setSampleValueAt ( ( i n t ) t a r g e t I n d e x ,
s . getSampleValueAt ( ( i n t ) s o u r c e I n d e x ) ) ;
main
2005/1/11
page 333
i
Section 10.4
333
}
}
We can actually set the factor so that we get whatever frequency we want.
We call this factor the sampling interval . For a desired frequency f0 , the sampling
interval should be:
f0
samplingInteval = (sizeOf SourceSound) samplingRate
This is how a keyboard synthesizer works. It has recordings of pianos, voices,
bells, drums, whatever. By sampling those sounds at different sampling intervals,
it can shift the sound to the desired frequency.
The last recipe of this section plays a single sound at its original frequency,
then at two times, three times, four times, and five times the frequency. We need
to use blockingPlay to let one sound finish playing before the next one starts. Try
it with just play and youll hear the sounds collide as theyre generated faster than
the computer can play them.
Program 87: Playing a sound in a range of frequencies
/
Method t o p l a y a sound 5 t i m e s and each t im e i n c r e a s e t h e
frequency .
I t doesn t change t h e o r i g i n a l sound .
/
public void p l a y 5 F r e q ( )
{
Sound s = null ;
// l o o p 5 t i m e s b u t s t a r t w i t h 1 and end a t 5
f o r ( i n t i = 1 ; i < 6 ; i ++)
{
// r e s e t t h e sound
s = new Sound ( t h i s . getFileName ( ) ) ;
// change t h e f r e q u e n c y
s . changeFreq ( i ) ;
// p l a y t h e sound
s . blockingPlay ( ) ;
}
}
main
2005/1/11
page 334
i
334
Chapter 10
a factor of 0 to change the frequency? We would end up will silence for the first
sound.
10.4.1
Sampling as an Algorithm
You should recognize a similarity between the halving recipe Program 84 (page 330)
and the recipe for scaling a picture up (larger) Program 30 (page 167). To halve the
frequency, we take each sample twice by incrementing the source index by 0.5 and
using the casting (int) to get the integer part of that. To make the picture larger,
we take each pixel twice, by adding 0.5 to the source index variable and using the
casting on that. These two methods are using the same algorithm. The details of
pictures vs. sounds arent critical. The point is that the same basic process is being
used in each.
We have seen other algorithms that cross media boundaries. Obviously, our
increasing red and increasing volume functions (and the decreasing versions) are
essentially doing the same things. The way that we blend pictures or sounds is the
same. We take the component color channels (pixels) or samples (sounds) and add
them using percentages to determine the amount from each that we want in the
final product. As long as the percentages total 100%, well get a reasonable output
that reflects the input sounds or pictures at the correct percentages.
Identifying algorithms like these are useful for several reasons. If we understand the algorithm in general (e.g., when its slow and when its fast, what it works
for and what it doesnt, what the limitations are), then the lessons learned apply
in the specific picture or sound instances. The algorithms are also useful to know
for designers. When you are designing a new program, you can keep in mind the
algorithms so that you can use them when they apply.
When we double or half the sound frequency, we are also shrinking and doubling the length of the sound (respectively). You might want a target sound whose
length is exactly the length of the sound, rather than have to clear out extra stuff
from a longer sound. You can do that with new Sound(int lengthInSamples).
new Sound(44000) returns a new empty sound of 44000 samples.
10.5
ADDITIVE SYNTHESIS
Additive synthesis creates sounds by adding sine waves together. We saw earlier
that its really pretty easy to add sounds together. With additive synthesis, you
can shape the waves yourselves, set their frequencies, and create instruments that
never existed.
10.5.1
main
2005/1/11
page 335
i
Section 10.5
Additive Synthesis
335
handle infinity very well, so well actually only take some values between 0 to 2.
To create the below graph, Mark filled 20 rows (a totally arbitrary number)
of a spreadsheet with values from 0 and 2 (about 6.28). Mark added about 0.314
(6.28/20) to each preceding row. In the next column, he took the sine of each value
in the first column, then graphed it.
Now, if we want to create a sound at a given frequency, say 440 Hz. This means
that we have to fit an entire cycle like the above into 1/440 of a second. (440 cycles
per second, means that each cycle fits into 1/440 second, or 0.00227 seconds.) Mark
made the above picture using 20 values. Call it 20 samples. How many samples do
we have to chop up the 440 Hz cycle into? Thats the same question as: How many
samples must go by in 0.00227 seconds? We know the sampling ratethats the
number of samples in one second. Lets say that its 22050 samples per second (our
default sampling rate). Each sample is then (1/22050) 0.0000453 seconds. How
many samples fit into 0.00227? Thats 0.00227/0.0000453, or about 50. What we
just did here mathematically is:
interval = 1/f requency
interval
= (samplingRate)(interval)
samplesP erCyle = 1/samplingRate
Now, lets spell this out in Java code. To get a waveform at a given frequency,
say 440 Hz, we need 440 of these waves in a single second. Each one must fit into
the interval of 1/f requency. The number of samples that needs to be produced
during the interval is the sampling rate divided by the frequency, or interval (1/f )
(samplingrate). Call that the samplesP erCycle.
At each entry of the sound sampleIndex, we want to:
Get the fraction of sampleIndex/samplesP erCycle.
Multiply that fraction by 2. Thats the number of radians we need. Take
the sin of (sampleIndex/samplesP erCycle) 2.
Multiply the result by the desired amplitude, and put that in the sample value
at sampleIndex.
10.5.2
main
2005/1/11
page 336
336
Chapter 10
/
Method t o c r e a t e a one s e c o n d s i n e wave sound w i t h t h e
g i v e n f r e q u e n c y and maximum a m p l i t u d e
@param f r e q t h e d e s i r e d f r e q u e n c y
@param maxAmplitude t h e maximum a m p l i t u d e
@return t h e new sound
/
public s t a t i c Sound createSineWave ( i n t f r e q , i n t maxAmplitude )
{
Sound s =
new Sound ( F i l e C h o o s e r . getMediaPath ( s e c 1 s i l e n c e . wav ) ) ;
double samplingRate = s . getSamplingRate ( ) ;
double rawValue = 0 ;
i nt v a l u e = 0 ;
double i n t e r v a l = 1 . 0 / f r e q ; // l e n g t h o f c y c l e i n s e c o n d s
double s a m p l e s P e r C y c l e = i n t e r v a l samplingRate ;
double maxValue = 2 Math . PI ;
// l o o p t h r o u g h t h e l e n g t h o f t h e sound
f o r ( i n t i = 0 ; i < s . g e t L e n g t h ( ) ; i ++)
{
// c a l c u l a t e t h e v a l u e b e t w e e n 1 and 1
main
2005/1/11
page 337
Section 10.5
Additive Synthesis
337
Notice that this method creates an object of the Sound class and returns it. In
order to be able to refer to this Sound object again be sure to set a variable to refer to
it. We can invoke any class method using ClassName.methodName (parameterList ).
Lets build a sine wave of 880 Hz at an amplitude of 4000.
> Sound s = Sound.createSineWave(880,4000);
> s.explore();
FIGURE 10.5: The sine wave with a frequency of 880 and a maximum amplitude of
4000
10.5.3
/
Method t o add t h e p a s s e d sound t o t h i s sound
main
2005/1/11
page 338
i
338
Chapter 10
How are we going to use this function to add together sine waves? We need
both of them at once? Turns out that its easy:
Lets add together 440 Hz, 880 Hz (twice 440), and 1320 Hz (880+440), and
well increase the amplitudes. Well double the amplitude each time: 2000, then
4000, then 8000. Well add them all up into a sound called sound440. At the end,
we generate a 440 Hz sound so that we can listen to them both and compare.
>
>
>
>
>
>
>
>
&
10.5.4
$
Common Bug: Beware of adding amplitudes past
32767
When you add sounds, you add their amplitudes, too. A
maximum of 2000+4000+8000 will never be greater than
32767, but do worry about that. Remember what happened when the amplitude got too high last chapter. . .
main
2005/1/11
page 339
i
Section 10.5
Additive Synthesis
339
Open up each of these in turn in the sound editor. Right away, youll notice
that the wave forms look very different (Figure 10.6). That tells you that we did
something to the sound, but what?
FIGURE 10.6: The raw 440 Hz signal on top, then the 440+880+1320 Hz signal on
the bottom
The way to really check your additive synthesis is with a fast fourier transform
(FFT). Generate the FFT for each signal. Youll see that the 440 Hz signal has a
single spike (Figure 10.7). Thats what youd expectits supposed to be a single
sine wave. Now, look at the combined wave forms FFT (Figure 10.8). Its what
its supposed to be! You see three spikes there, and each succeeding one is double
the height of the last one.
10.5.5
Square Waves
We dont have to just add sine waves. We can also add square waves. These are
literally square-shaped waves, moving between +1 and 1. The FFT will look very
different, and the sound will be very different. It can actually be a much richer
sound.
main
2005/1/11
page 340
340
Chapter 10
Try this method instead of the sine wave generator and see what you think.
Note the use of an if statement to swap between the positive and negative sides
of the wave half-way through a cycle.
Program 90: Square wave generator for given frequency and amplitude
/
Method t o g e n e r a t e a 1 s e c o n d sound w i t h s q u a r e waves
w i t h t h e p a s s e d f r e q u e n c y and maximum a m p l i t u d e .
@param f r e q t h e d e s i r e d f r e q u e n c y
@param maxAmplitude t h e maximum a m p l i t u d e
@return t h e c r e a t e d sound
/
public s t a t i c Sound createSquareWave ( i n t f r e q ,
i n t maxAmplitude )
{
Sound s =
new Sound ( F i l e C h o o s e r . getMediaPath ( s e c 1 s i l e n c e . wav ) ) ;
double samplingRate = s . getSamplingRate ( ) ;
double rawValue = 0 ;
i nt v a l u e = 0 ;
double i n t e r v a l = 1 . 0 / f r e q ; // l e n g t h o f c y c l e i n s e c o n d s
double s a m p l e s P e r C y c l e = i n t e r v a l samplingRate ;
double s a m p l e s P e r H a l f C y c l e = ( i n t ) ( s a m p l e s P e r C y c l e / 2 ) ;
double maxValue = 2 Math . PI ;
// l o o p t h r o u g h t h e l e n g t h o f t h e sound
f o r ( i n t soundIndex = 0 , sampleCounter = 0 ;
soundIndex < s . g e t L e n g t h ( ) ;
soundIndex++, sampleCounter++)
{
// c h e c k i f i n f i r s t h a l f o f c y c l e
i f ( sampleCounter < s a m p l e s P e r H a l f C y c l e )
v a l u e = maxAmplitude ;
else
{
// make t h e v a l u e n e g a t i v e
v a l u e = maxAmplitude 1;
/ i f t h e sample c o u n t e r i s g r e a t e r t ha n t h e
samples per c y c l e r e s e t i t to 0
/
i f ( sampleCounter > s a m p l e s P e r C y c l e )
sampleCounter = 0 ;
}
// s e t t h e v a l u e
s . setSampleValueAt ( soundIndex , v a l u e ) ;
}
main
2005/1/11
page 341
i
Section 10.5
Additive Synthesis
341
return s ;
}
Youll find that the waves (in the wave editor of MediaTools) really do look
square (Figure 10.9), but the most amazing thing is all the additional spikes in FFT
(Figure 10.10). Square waves really do result in a much more complex sound.
FIGURE 10.9: The 440 Hz square wave (top) and additive combination of square
waves (bottom)
FIGURE 10.10: FFTs of the 440 Hz square wave (top) and additive combination of
square waves (bottom)
main
2005/1/11
page 342
i
342
10.5.6
Chapter 10
Triangle Waves
Try triangle waves instead of square waves with this recipe.
Program 91: Generate triangle waves
/
Method t o c r e a t e a one s e c o n d t r i a n g l e wave sound
w i t h t h e g i v e n f r e q u e n c y and maximum a m p l i t u d e
@param f r e q t h e d e s i r e d f r e q u e n c y
@param maxAmplitude t h e maximum a m p l i t u d e
@return t h e new sound
/
public s t a t i c Sound c r e a t e T r i a n g l e W a v e ( in t f r e q ,
in t maxAmplitude )
{
Sound s =
new Sound ( F i l e C h o o s e r . getMediaPath ( s e c 1 s i l e n c e . wav ) ) ;
double samplingRate = s . getSamplingRate ( ) ;
i nt v a l u e = 0 ;
double i n t e r v a l = 1 . 0 / f r e q ; // l e n g t h o f c y c l e i n s e c o n d s
double s a m p l e s P e r C y c l e = i n t e r v a l samplingRate ;
i nt s a m p l e s P e r Q u a r t e r C y c l e =
( int ) ( samplesPerCycle / 4 ) ;
i nt i n c r e m e n t =
( i n t ) ( maxAmplitude / s a m p l e s P e r Q u a r t e r C y c l e ) ;
// l o o p t h r o u g h t h e l e n g t h o f t h e sound
f o r ( i n t soundIndex = 0 ;
soundIndex < s . g e t L e n g t h ( ) ;
soundIndex++, v a l u e = v a l u e + i n c r e m e n t )
{
// c h e c k i f t h e v a l u e i s e q u a l t o t h e d e s i r e d max
i f ( v a l u e >= maxAmplitude | |
v a l u e <= maxAmplitude 1)
{
i n c r e m e n t = i n c r e m e n t 1;
value = value + increment ;
}
// s e t t h e sample v a l u e
s . setSampleValueAt ( soundIndex , v a l u e ) ;
}
return s ;
}
main
2005/1/11
page 343
i
Section 10.6
343
10.6.1
MP3
Nowadays, the most common kind of audio file that you have on your computer is
probably MP3 files (or perhaps MP4 or one of its related or descendant file types).
MP3 files are sound (and video, in some cases) encodings based on the MPEG-3
standard. They are audio files, but compressed in special ways.
One way in which MP3 files are compressed is called lossless compression. As
we know, there are techniques for storing data that use fewer bits. For example,
we know that every sample is typically two bytes wide. What if we didnt store
every sample, but instead stored the difference from the last sample to the current
sample? The difference between samples is usually much smaller than 32,767 to
-32,768it might be +/- 1000. That takes fewer bits to store.
But MP3 also uses lossy compression. It actually throws away some of the
sound information. For example, if theres a really soft sound immediately after or
main
2005/1/11
page 344
i
344
Chapter 10
simultaneous with a really loud sound, you wont be able to hear the soft sound. A
digital recording keeps all those frequencies. MP3 throws away the ones you cant
actually hear.
WAV files are kind of compressed, but not as much as MP3, and they only
use lossless techniques. Some WAV files use MP3 compression which makes them
really MP3 files. MP3 files tend to be much smaller than the same sound in a WAV
format. AIFF files are similar to WAV files.
10.6.2
MIDI
MIDI is the Musical Instrument Digital Interface. Its really a set of agreements
between manufacturers of computer music devices (sequencers, synthesizers, drum
machines, keyboards, etc.) for how their devices will work together. Using MIDI,
you can control various synthesizers and drum machines from different keyboards.
MIDI doesnt really record what something sounds like, instead it encodes
how it is played. Literally, MIDI encodes information like Press the key down on
synthesized instrument X at pitch Y then later Release the key Y on instrument
X. The quality of MIDI sound depends entirely on the synthesizer, the device
generating the synthesized instrument.
MIDI files tend to be very small. Instructions like Play key #42 on track
7 are only some five bytes long. This makes MIDI attractive in comparison with
large sound files. MIDI has been particularly popular for karaoke machines.
MIDI has an advantage over MP3 or WAV files in that they can specify a
lot of music in very few bytes. But MIDI cant record any particular sound. For
example, if you want to record a particular persons style of playing an instrument,
or record anyone singing, you dont want to use MIDI. To capture actual sounds,
you need to record the actual samples, so youll need MP3 or WAV.
Most modern operating systems have pretty good synthesizers built into them.
We can actually use them from Java. We have created a class MidiPlayer that has
a method playNote that takes as input a note as a number and a duration (how
long to play the sound) in milliseconds. The note numbers correspond to keys, not
to frequencies. C in the first octave is 1, C# is 2. C in the fourth octave is 60, D is
62, and E is 64. See http://www.harmony-central.com/MIDI/Doc/table2.html for
more information on the note numbers. If you dont specify what instrument you
want to play the note on it will simulate a piano.
Heres a simple example of playing some MIDI notes from DrJava.
> MidiPlayer player = new MidiPlayer();
> player.playNote(62,250); // d quarter note
> player.playNote(60,500); // c half note
The 250 and 500 specify the number of milliseconds to play the note. If you
want a measure to take one second (1000 milliseconds) then a quarter note would
be 250 milliseconds and a half note would be 500 milliseconds.
We can write a method to play a song. How about writing a method to play
part of Jingle Bells? Here is a method that plays the first 4 measures from it. Put
main
2005/1/11
page 345
i
Section 10.6
345
this in the MidiPlayer class (before the ending curly brace) and compile it.
Program 92: Playing a song
/
Method t o p l a y t h e f i r s t 4 measures o f J i n g l e B e l l s
w i t h each measure t a k i n g 1000 m i l l i s e c o n d s (1 s e c o n d )
t h i s i s 2/4 time
/
public void p l a y J i n g l e B e l l s 4 ( )
{
// measure 1
playNote ( 5 2 , 2 5 0 ) ; // e e i g h t h n o t e
playNote ( 6 0 , 2 5 0 ) ; // c e i g h t h n o t e
playNote ( 5 8 , 2 5 0 ) ; // b f l a t e i g h t h n o t e
playNote ( 5 6 , 2 5 0 ) ; // a f l a t e i g h t h n o t e
// measure 2
playNote ( 5 2 , 5 0 0 ) ;
rest (250);
playNote ( 5 2 , 1 2 5 ) ;
playNote ( 5 2 , 1 2 5 ) ;
//
//
//
//
e quarter note
rest
e s i x t e e n t h note
e s i x t e e n t h note
// measure 3
playNote ( 5 2 , 5 0 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 4
playNote ( 5 3 , 1 0 0 0 ) ; // f h a l f n o t e
}
This method only plays the first 4 measures of Jingle Bells. This may not
sound like Jingle Bells to you since I am using the original version first published
in 1859 by James Pierpont.
To play this using the default piano sounding instrument do the following:
> MidiPlayer player = new MidiPlayer();
> player.playJingleBells4();
You can change the instrument that you want to use to play the notes using
the method setInstrument(int num) where the num is a number from 0 to 127
that maps to an instrument. We have create constants for some of the instrument
numbers as you can see at the top of the MidiPlayer class definition. To play the
first 4 measures of Jingle Bells on a flute do the following:
> MidiPlayer player = new MidiPlayer();
main
2005/1/11
page 346
346
Chapter 10
> player.setInstrument(MidiPlayer.FLUTE);
> player.playJingleBells4();
10.6.3
Private Methods
Music often repeats. In Jingle Bells the first verse is played and then the refrain.
Next, the second verse is played and then the refrain is played again. The first
verse and second verse are a bit different, but many of the measures in the two
verses are the same. If we want to write a method that plays the first two verses of
Jingle Bells with each verse followed by the refrain, we could put all the measures
in it for both verses and the refrains, but then it would be very long (67 measures).
Another option is to pull out the measures in the refrain and make a method that
just plays the refrain and then call that method from the one that plays Jingle
Bells.
Does this new method that plays just the refrain need to be public? If we
dont think any other class will need access to the new method we can make it
private. Private methods can only be invoked from code in the class that they
are declared in. You will get an error if you try to invoke a private method in code
that is in another class.
To declare a method to be private use the keyword private for the visibility.
Remember that to declare a method you must specify:
visibility returnType methodName( parameterList )
The following method is a private method that will play the refrain of Jingle
Bells.
Program 93: Playing the refrain
/
Method t o p l a y r e f r a i n o f J i n g l e B e l l s
/
private void p l a y J i n g l e B e l l s R e f r a i n ( )
{
// measure 1
playNote ( 6 0 , 2 5 0 ) ; // c e i g h t h n o t e
playNote ( 6 0 , 2 5 0 ) ; // c e i g h t h n o t e
playNote ( 6 0 , 5 0 0 ) ; // c q u a r t e r n o t e
// measure 2
playNote ( 6 3 , 2 5 0 ) ; // e f l a t e i g h t h n o t e
playNote ( 6 3 , 2 5 0 ) ; // e f l a t e i g h t h n o t e
playNote ( 6 3 , 5 0 0 ) ; // e f l a t q u a r t e r n o t e
// measure 3
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 6 5 , 3 7 5 ) ;
playNote ( 6 5 , 1 2 5 ) ;
//
//
//
//
c
c
f
f
e i g h t h note
e i g h t h note
dotted e i g h t h note
s i x t e e n t h note
main
2005/1/11
page 347
Section 10.6
347
// measure 4
playNote ( 6 4 , 1 0 0 0 ) ; // e h a l f n o t e
// measure 5
playNote ( 6 5 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
playNote ( 6 4 , 2 5 0 ) ;
//
//
//
//
f
d
a
f
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
e i g h t h note
// measure 6
playNote ( 6 3 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
playNote ( 5 6 , 1 2 5 ) ;
playNote ( 5 8 , 1 2 5 ) ;
//
//
//
//
//
e
c
a
a
b
f l a t e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t s i x t e e n t h note
f l a t s i x t e e n t h note
// measure 7
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
//
//
//
//
c
b
a
b
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 8
playNote ( 6 0 , 1 0 0 0 ) ; // c h a l f n o t e
// measure 9
playNote ( 6 0 , 2 5 0 ) ; // c e i g h t h n o t e
playNote ( 6 0 , 2 5 0 ) ; // c e i g h t h n o t e
playNote ( 6 0 , 5 0 0 ) ; // c q u a r t e r n o t e
// measure 10
playNote ( 6 3 , 2 5 0 ) ; // e f l a t e i g h t h n o t e
playNote ( 6 3 , 2 5 0 ) ; // e f l a t e i g h t h n o t e
playNote ( 6 3 , 5 0 0 ) ; // e f l a t q u a r t e r n o t e
// measure 11
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 6 5 , 2 5 0 ) ;
playNote ( 6 5 , 2 5 0 ) ;
//
//
//
//
c
c
f
f
eighth
eighth
eighth
eighth
note
note
note
note
// measure 12
playNote ( 6 4 , 1 0 0 0 ) ; // e h a l f n o t e
// measure 13
playNote ( 5 3 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
//
//
//
//
f
d
c
b
e i g h t h note
f l a t e i g h t h note
e i g h t h note
f l a t e i g h t h note
// measure 14
main
2005/1/11
page 348
348
Chapter 10
playNote ( 5 6 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
playNote ( 6 2 , 2 5 0 ) ;
playNote ( 6 3 , 1 2 5 ) ;
playNote ( 6 3 , 1 2 5 ) ;
//
//
//
//
//
a
e
d
e
e
f l a t e i g h t h note
f l a t e i g h t h note
e i g h t h note
f l a t s i x t e e n t h note
f l a t s i x t e e n t h note
// measure 16
playNote ( 6 5 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
//
//
//
//
f
e
d
b
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 17
playNote ( 5 6 , 5 0 0 ) ; // a f l a t q u a r t e r n o t e
r e s t ( 5 0 0 ) ; // r e s t
}
Here is a method that will play the first verse of Jingle Bells.
Program 94: Playing the first verse
/
Method t o p l a y t h e f i r s t v e r s e o f j i n g l e b e l l s
w i t h each measure t a k i n g 1000 m i l l i s e c o n d s (1 s e c o n d )
I t i s i n 2/4 time
/
private void p l a y J i n g l e B e l l s V 1 ( )
{
// measure 1
playNote ( 5 2 , 2 5 0 ) ; // e e i g h t h n o t e
playNote ( 6 0 , 2 5 0 ) ; // c e i g h t h n o t e
playNote ( 5 8 , 2 5 0 ) ; // b f l a t e i g h t h n o t e
playNote ( 5 6 , 2 5 0 ) ; // a f l a t e i g h t h n o t e
// measure 2
playNote ( 5 2 , 5 0 0 ) ;
rest (250);
playNote ( 5 2 , 1 2 5 ) ;
playNote ( 5 2 , 1 2 5 ) ;
//
//
//
//
e quarter note
rest
e s i x t e e n t h note
e s i x t e e n t h note
// measure 3
playNote ( 5 2 , 5 0 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 4
playNote ( 5 3 , 1 0 0 0 ) ; // f h a l f n o t e
main
2005/1/11
page 349
Section 10.6
// measure 5
playNote ( 5 3 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
//
//
//
//
f
d
c
b
349
e i g h t h note
f l a t e i g h t h note
e i g h t h note
f l a t e i g h t h note
// measure 6
playNote ( 5 5 , 1 0 0 0 ) ; // g h a l f n o t e
// measure 7
playNote ( 5 5 , 2 5 0 ) ;
playNote ( 6 5 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
//
//
//
//
g
f
e
d
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 8
playNote ( 6 0 , 1 0 0 0 ) ; // c h a l f n o t e
// measure 9
playNote ( 5 2 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 10
playNote ( 5 2 , 1 0 0 0 ) ; // e h a l f n o t e
// measure 11
playNote ( 5 2 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 12
playNote ( 5 3 , 1 0 0 0 ) ; // f h a l f n o t e
// measure 13
playNote ( 5 3 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
//
//
//
//
f
d
c
b
e i g h t h note
f l a t e i g h t h note
e i g h t h note
f l a t e i g h t h note
// measure 14
playNote ( 5 5 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
playNote ( 6 2 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
//
//
//
//
g
e
d
e
e i g h t h note
f l a t e i g h t h note
e i g h t h note
f l a t e i g h t h note
// measure 15
playNote ( 6 5 , 2 5 0 ) ; // f e i g h t h n o t e
playNote ( 6 3 , 2 5 0 ) ; // e f l a t e i g h t h n o t e
main
2005/1/11
page 350
350
Chapter 10
playNote ( 6 1 , 2 5 0 ) ; // d f l a t e i g h t h n o t e
playNote ( 5 8 , 2 5 0 ) ; // b f l a t e i g h t h n o t e
// measure 16
playNote ( 5 6 , 1 0 0 0 ) ; // a f l a t h a l f n o t e
}
Here is a method that will play the second verse of Jingle Bells.
Program 95: Playing the second verse
/
Method t o p l a y t h e s e c o n d v e r s e o f j i n g l e b e l l s
w i t h each measure t a k i n g 1000 m i l l i s e c o n d s (1 s e c o n d )
I t i s i n 2/4 time
/
private void p l a y J i n g l e B e l l s V 2 ( )
{
// measure 1
rest (750);
playNote ( 5 2 , 2 5 0 ) ; // e e i g h t h n o t e
// measure 2
playNote ( 5 2 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
// measure 3
playNote ( 5 2 , 5 0 0 ) ;
rest (250);
playNote ( 5 2 , 1 2 5 ) ;
playNote ( 5 2 , 1 2 5 ) ;
//
//
//
//
e quarter note
rest
e s i x t e e n t h note
e s i x t e e n t h note
// measure 4
playNote ( 5 2 , 5 0 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 5
playNote ( 5 3 , 7 5 0 ) ; // f d o t t e d q u a r t e r n o t e
playNote ( 5 3 , 2 5 0 ) ; // f e i g h t h n o t e
// measure 6
playNote ( 5 3 , 2 5 0 ) ; // f e i g h t h n o t e
playNote ( 6 1 , 2 5 0 ) ; // d f l a t e i g h t h n o t e
main
2005/1/11
page 351
Section 10.6
351
playNote ( 6 0 , 2 5 0 ) ; // c e i g h t h n o t e
playNote ( 5 8 , 2 5 0 ) ; // b f l a t e i g h t h n o t e
// measure 7
playNote ( 5 5 , 7 5 0 ) ; // g d o t t e d q u a r t e r n o t e
playNote ( 5 5 , 2 5 0 ) ; // g e i g h t h n o t e
// measure 8
playNote ( 5 5 , 2 5 0 ) ;
playNote ( 6 5 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
//
//
//
//
g
f
e
d
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 9
playNote ( 6 0 , 7 5 0 ) ; // c d o t t e d q u a r t e r n o t e
playNote ( 5 2 , 2 5 0 ) ; // e e i g h t h n o t e
// measure 10
playNote ( 5 2 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 11
playNote ( 5 2 , 7 5 0 ) ; // e d o t t e d q u a r t e r n o t e
playNote ( 5 2 , 2 5 0 ) ; // e e i g h t h n o t e
// measure 12
playNote ( 5 2 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
playNote ( 5 6 , 2 5 0 ) ;
//
//
//
//
e
c
b
a
e i g h t h note
e i g h t h note
f l a t e i g h t h note
f l a t e i g h t h note
// measure 13
playNote ( 5 3 , 7 5 0 ) ; // f d o t t e d q u a r t e r n o t e
playNote ( 5 3 , 2 5 0 ) ; // f e i g h t h n o t e
// measure 14
playNote ( 5 3 , 2 5 0 ) ;
playNote ( 6 1 , 2 5 0 ) ;
playNote ( 6 0 , 2 5 0 ) ;
playNote ( 5 8 , 2 5 0 ) ;
//
//
//
//
f
d
c
b
e i g h t h note
f l a t e i g h t h note
e i g h t h note
f l a t e i g h t h note
// measure 15
playNote ( 5 5 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
playNote ( 6 2 , 2 5 0 ) ;
playNote ( 6 3 , 2 5 0 ) ;
//
//
//
//
g
e
d
e
e i g h t h note
f l a t e i g h t h note
e i g h t h note
f l a t e i g h t h note
// measure 16
playNote ( 6 5 , 2 5 0 ) ; // f e i g h t h n o t e
main
2005/1/11
page 352
i
352
Chapter 10
playNote ( 6 3 , 2 5 0 ) ; // e f l a t e i g h t h n o t e
playNote ( 6 1 , 2 5 0 ) ; // d f l a t e i g h t h n o t e
playNote ( 5 8 , 2 5 0 ) ; // b f l a t e i g h t h n o t e
// measure 17
playNote ( 5 6 , 1 0 0 0 ) ; // a f l a t h a l f n o t e
}
Here is a method that will play the first two verses of Jingle Bells with each
verse followed by the refrain write a method that calls the other methods.
Program 96: Playing Jingle Bells
/
Method t o p l a y J i n g l e B e l l s
/
public void p l a y J i n g l e B e l l s ( )
{
// p l a y v e r s e 1
playJingleBellsV1 ( ) ;
// p l a y r e f r a i n
playJingleBellsRefrain ();
// p l a y v e r s e 2
playJingleBellsV2 ( ) ;
// p l a y r e f r a i n
playJingleBellsRefrain ();
}
CONCEPTS SUMMARY
In this chapter we introduced class methods, private methods, and building methods
from other methods.
10.7.1
Class Methods
Class methods are general methods like Math.abs(int num) or methods that create
objects like Sound.createTriangleWave(int freq, int maxAmplitude). Class
main
2005/1/11
page 353
Section 10.7
Concepts Summary
353
methods do not work on object data (no implicit this current object is passed to
the method).
Class methods are defined with the keyword static on the method declaration: visibility static returnType methodName (parameterList ).
public s t a t i c Sound c r e a t e T r i a n g l e W a v e ( i n t f r e q , in t maxAmplitude )
Private Methods
Private methods can only be invoked from code in the same class that they are
declared in. Use private methods to break down long public methods into smaller
reusable parts.
To declare a method to be private use the private keyword for the visibility.
Remember that to declare a method you can use
visibility [static] returnType
methodName (parameterList ). The static keyword is optional and is used for
declaring class methods.
private void p l a y J i n g l e B e l l s R e f r a i n ( )
If you try to access a private method from code outside the class the method
is defined in you will get an error.
> MidiPlayer player = new MidiPlayer();
> player.playJingleBellsRefrain();
java.lang.IllegalAccessException: Class
koala.dynamicjava.interpreter.EvaluationVisitor can not access a
member of class MidiPlayer with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:57)
at java.lang.reflect.Method.invoke(Method.java:317)
>
10.7.3
main
2005/1/11
page 354
i
354
Chapter 10
playJingleBellsV1 ( ) ;
// p l a y r e f r a i n
playJingleBellsRefrain ();
// p l a y v e r s e 2
playJingleBellsV2 ( ) ;
// p l a y r e f r a i n
playJingleBellsRefrain ();
}
Long methods are hard to read and understand. Try to break down methods
into smaller parts.
PROBLEMS
10.1. Rewrite the echo function (Program 81 (page 325)) to generate two echoes back,
each delay samples previous. Hint: Start your index loop at 2*delay + 1, then
access one echo sample at index-delay and another at index - 2*delay.
10.2. How long is a sound compared to the original when its been frequency doubled
(Program 83 (page 328))?
10.3. Hip-hop DJs move turntables so that sections of sound are moved forwards and
backwards quickly. Try combining backwards play (Program 78 (page 316)) and
frequency shifting (Program 83 (page 328)) to get the same effect. Play a second
of a sound quickly forward, then quickly backward, two or three times. (You
might have to move faster than just double the speed.)
10.4. Consider changing the if block in the frequency shift recipe (Program 86 (page 332))
to sourceIndex = sourceIndex - getLength(source). Whats the difference
from just setting the sourceIndex to 1? Is this better or worse? Why?
10.5. If you use the shifting recipe (Program 86 (page 332)) with a factor of 2.0 or 3.0,
youll get the sound repeated or even triplicated. Why? Can you fix it? Write
shiftDur that takes a number of samples (or even seconds) to play the sound.
10.6. Change the shift function in Program 86 (page 332) to shiftFreq which takes a
frequency instead of a factor, then plays the given sound at the desired frequency.
10.7. Using the sound tools, figure out the characteristic pattern of different instruments. For example, pianos tend to have a pattern the opposite of what we
createdthe amplitudes decrease as we get to higher sine waves. Try creating a
variety of patterns and see how they sound and how they look.
10.8. When musicians work with additive synthesis, they will often wrap envelopes
around the sounds, and even around each added sine wave. An envelope changes
the amplitude over time: It might start out small, then grow (rapidly or slowly),
then hold at a certain value during the sound, and then drop before the sound
ends. That kind of pattern is sometimes called the attack-sustain-decay (ASD)
envelope . Pianos tend to attack quickly then decay quickly. Flutes tend to attack
slowly and sustain as long as you want. Try implementing that for the sine and
square wave generators.
10.9. Write a method to play a simple song like Jingle Bells. The song should have
at least one repeating part. Make sure that you create a private method for the
repeating part.
main
2005/1/11
page 355
i
Section 10.7
Concepts Summary
355
TO DIG DEEPER
Good books on computer music will talk a lot about creating sounds from scratch
like in this chapter. One of my favorites for understandability is Computer Music:
Synthesis, Composition, and Performance by Dodge and Jerse [8]. The bible of
computer music is Curtis Roads massive The Computer Music Tutorial [22].
One of the most powerful tools for playing with this level of computer music is CSound . Its a software music synthesis system, free, and totally crossplatform. The book by Richard Boulanger [5] has everything you need for playing
with CSound.
jMusic is a free programming library written for musicians in Java (see http:
//http://jmusic.ci.qut.edu.au/jmtutorial/t1.html). It allows you to compose music. It can also import and export MIDI and audio files.
main
2005/1/11
page 356
i
356
Chapter 10
main
2005/1/11
page 357
Bibliography
1. AAUW, Tech-savvy: Educating girls in the new computer age, American Association
of University Women Education Foundation, New York, 2000.
2. Harold Abelson, Gerald Jay Sussman, and Julie Sussman, Structure and intepretation of computer programs 2nd edition, MIT Press, Cambridge, MA, 1996.
3. Ken Abernethy and Tom Allen, Exploring the digital domain: An introduction to
computing with multimedia and networking, PWS Publishing, Boston, 1998.
4. Beth Adelson and Elliot Soloway, The role of domain experience in software
design., IEEE Transactions on Software Engineering SE-11 (1985), no. 11, 1351
1360.
5. Richard Boulanger (ed.), The csound book: Perspectives in synthesis, sound design,
signal processing, and programming, MIT Press, Cambridge, MA, 2000.
6. Amy Bruckman, Situated support for learning: Storms weekend with rachael, Journal of the Learning Sciences 9 (2000), no. 3, 329372.
7. John T. Bruer, Schools for thought: A science of learning in the classroom, MIT
Press, Cambridge, MA, 1993.
8. Charles Dodge and Thomas A. Jerse, Computer music: Synthesis, composition,
and performance, Schimer:Thomason Learning Inc., 1997.
9. Matthias Felleisen, Robert Bruce Findler, Matthew Flatt, and Shriram
Krishnamurthi, How to design programs: An introduction to programming and computing, MIT Press, Cambridge, MA, 2001.
10. Ann E. Fleury, Encapsulation and reuse as viewed by java students, Proceedings fo
the 32nd SIGCSE technical symposium on computer science education (2001), 189
193.
11. James D. Foley, Andries Van Dam, and Steven K. Feiner, Introduction to
computer graphics, Addison Wesley, Reading, MA, 1993.
12. Martin Greenberger, Computers and the world of the future, Transcribed recordings of lectures held at the Sloan School of Business Administration, April, 1961, MIT
Press, Cambridge, MA, 1962.
13. Mark Guzdial, Squeak: Object-oriented design with multimedia applications,
Prentice-Hall, Englewood, NJ, 2001.
14. Mark Guzdial and Kim Rose (eds.), Squeak, open personal computing for multimedia,
Prentice-Hall, Englewood, NJ, 2001.
15. Idit Harel and Seymour Papert, Software design as a learning environment, Interactive Learning Environments 1 (1990), no. 1, 132.
16. Brian Harvey, Computer science logo style 2/e vol. 1: Symbolic computing, MIT
Press, Cambridge, MA, 1997.
17. Dan Ingalls, Ted Kaehler, John Maloney, Scott Wallace, and Alan Kay,
Back to the future: The story of squeak, a practical smalltalk written in itself, OOPSLA97 Conference Proceedings, ACM, Atlanta, GA, 1997, pp. 318326.
18. Janet Kolodner, Case based reasoning, Morgan Kaufmann Publishers, San Mateo,
CA, 1993.
357
main
2005/1/11
page 358
i
358
BIBLIOGRAPHY
19. Margaret Livingstone, Vision and art: The biology of seeing, Harry N. Abrams,
Inc., New York, 2002.
20. Jane Margolis and Allan Fisher, Unlocking the clubhouse: Women in computing,
MIT Press, Cambridge, MA, 2002.
21. M. Resnick, Turtles, termites, and traffic jams: Explorations in massively parallel
microworlds, MIT Press, Cambridge, MA, 1997.
22. Curtis Roads, The computer music tutorial, MIT Press, Cambridge, MA, 1996.
main
2005/1/11
page 359
i
Index
==, 332
greater-than-or-equals, 332
abstraction, 62, 63
ACM Turing Award, 17
acuity, 83
algebra, 46
algorithm, 9, 12, 294, 317, 334
defintion, 12
mirroring, 317
alpha channel, 86
American Standard Code for Information Interchange (ASCII), 14
amplitude, 265
analog, 16
analog-to-digital conversion, 270
analog-to-digital conversion (ADC), 270
appending, 30
applets, 20
arguments, 64
array, 44, 274
access elements, 81
copying, 311
definition, 81
element, 274
index, 274
notation, 306
artificial intelligence, 9
ASCII, 14
attack-sustain-decay (ASD) envelope, 354
AutoCAD, 234
background subtraction, 210
base file name, 66
binary, 13
binary number system, 13
binding, 37
bit, 13
bitmap, 80
bitmap graphical representations, 234
blend, 159, 323
blending
pictures, 159
359
main
2005/1/11
page 360
i
360
Index
sounds, 323
block, 57, 332
blue, 15
blurring, 207
BMP, 234
body of method, 57
boxing, 46
Brando, Marlon, 284
byte, 13, 44
definition, 44
C, 11
calculus, 17
casting, 27, 107
changeVolume, 293
channel, 85
chromakey, 214
class, 20, 43
fully qualified name, 91
class fields
accessing, 91
class method, 335
class methods, 46
defintion, 46
class variables
accessing, 91
clipped, 273
clipping, 255, 273, 296
CMYK color model, 85
code, 44
collage, 154
color
sepia, 200
color replacement, 187
ColorChooser.pickAColor(), 92
colorObj.brighter(), 92
colorObj.darker(), 92
commands, 74
compile, 23, 101
compiling
definition, 23
compressed, 45, 81, 87
compression, 81, 234
compressions, 265
computational recipe, 8
computer, 13
computer hardware, 270
main
2005/1/11
page 361
i
Index
361
computer music, 10
constant, 252
constants, 10, 133
context, 58
coordinates, 82
copying, 147
cropping, 151
CSound, 355
cycle, 265
cycles per second, 266
data, 44
data representation, 15
data structures, 9, 15
databases, 9
debuggging
using print statements, 145
debugging, 290, 296
using print statements, 145
decibels, 266
decimal number, 13
definitions pane, 23
Digital, 16
Digital media, 16
digital-to-analog conversion, 271
digital-to-analog conversion (DAC), 270
digitization, 16
digitizing media
sounds, 270
digitizing media
pictures, 83
why?, 16
directory, 45, 66
disk, 45
double quote, 29
drawing tool, 235
DrJava, 21
definitions pane, 23
interactions pane, 23
files pane, 22
installing, 21
running slowly, 22
starting, 21
Dynabook, 18
edge detection, 194
editor, 21
main
2005/1/11
page 362
i
362
Index
emergent properties, 10
encoding, 8, 13, 66
envelopes, 354
equal temperament, 267
equality, 332
equals-to, 332
error
illegal start of expression, 57
semicolon expected, 57
evaluation, 63, 73
explore(), 89
expression, 25
Fast Fourier Transform, 269
FFT, 269
file, 45
file extension, 66
FileChooser.getMediaPath(fileName), 145, 147
files pane, 22
fillPolygon(int[] xArray,int[] yArray, int numPoints), 228
filters, 343
Flash, 234
floating point
typing, 27
floating point number, 18, 44
FM synthesis, 343
font, 235
how defined, 234
for loop, 122
Fourier transform, 269
frequency, 265, 266, 271
frequency domain, 269
frequency modulation synthesis, 343
full file name, 65, 66
fully qualified name, 91
function
arguments, 63
input values, 63
input variables, 63
parameters, 63
function calls, 73
functions, 46
fundamental, 267
garbage collection, 36
general, 116
getColor(), 90
main
2005/1/11
page 363
i
Index
363
getHeight(), 89
getPixel(x,y), 89
getPixels(), 89
getRed(), 90
getWidth(), 89
getX(), 89
getY(), 89
GIF, 234
going digital, 16
graphics, 10
grayscale, 127
greater-than, 332
green, 15
hard disk, 45
hardware, 270
Hertz, 266
hierarchical decomposition, 118
hierarchy, 118
HSB, 84
HSV color model, 84
HTML, 14
human-computer interface, 9
if, 332, 340
immutable, 48
implicitly, 58
import, 91
how to, 91
why, 91
infinite loop, 99, 100, 162
stopping one, 99
Ingalls, Dan, 320
inherit, 34, 247
inheritance, 34
inherits, 247
input, 46
input values, 63
input variables, 63
integer, 44
typing, 27
Intel, 15
intelligence, 9
intelligent systems, 9
intensity, 127, 265
interactions pane, 23
interface, 9, 251, 259
main
2005/1/11
page 364
i
364
Index
iterate, 280
Java, 11, 19
installing, 21
Javadoc comments, 24
JPEG, 66, 81, 234
Kaehler, Ted, 320
Kay, Alan, 320
kilobyte, 233
kilobytes, 87
knots, 9
less-than, 332
less-than-or-equals, 332
liberal education, 17
Lisp, 11
literal, 47
loop, 280
nested, 137
lossless compression, 234, 343
lossy compression, 81, 234, 343
luminance, 83, 86, 128
makeSound
what it does, 275
Maloney, John, 320
matrix, 82
definition, 82
max, 294
maximum, finding, 294
media computation, 15
MediaTools, 94, 268
application, 279
picture tools, 94
sound tools, 268
megabyte, 87
memory, 13, 22
memory leak, 36
method, 43
definition, 46, 56
method body, 57
method overloading, 61
methods, 46
methods vs. recipes, 117
MIDI, 344
mirroring, 317
mixing sounds, 323
main
2005/1/11
page 365
i
Index
365
Moores Law, 15
Moore, Gordon, 15
MP3, 343
MP4, 343
MPEG-3, 343
Musical Instrument Digital Interface, 344
negative image, 125
nested, 137
nested loop, 172
nested loops, 137
networking, 10
new Color(redValue,greenValue,blueValue), 90
new Picture(fileName), 88
new Picture(width,height), 170
new Sound(fileName), 71
new Sound(int lengthInSamples), 334
noise, 269, 343
normalizing, 294
not-equals, 332
Nyquist theorem, 271
applications, 271
object, 275
definition, 20
object methods, 46
definition, 46
object reference, 70
object variable, 70
object-oriented, 19
objects, 275
operating system, 45
operators, 25
overloading, 159, 206
overtones, 267
package
definition, 91
packages, 227
painting tool, 235
parameters, 63
pass by value, 64
path, 66
path separator, 66
PCM, 273
percentage, 96
Perlis, Alan, 17
main
2005/1/11
page 366
i
366
Index
physics
color, 15
picture
show(), 67
picture element, 15
picture objects, 80
picture tool, 94
pictureObject.repaint(), 92
pitch, 266
pitch interval, 267
pixel, 15, 80, 82
definition, 82
pixelation, 207
pixelization, 83
pixels, 80
placeholder, 119
play(), 71
playNote, 344
posterizing, 202
Postscript, 234
precedence, 25
primitive, 69, 70
primitive type, 69
private visibility
definition, 56
process, 17
program, 9, 12, 43, 44
defined, 44
definition, 12
programming language, 44
programming languages, 10
psychoacoustics, 266
public visibility
definition, 56
pulse coded modulation (PCM), 273
ranges, 10
rarefactions, 265
recipe, 8
recipes vs. methods, 117
red, 15
reference, 70
reserved words, 29
return, 119, 294
reusable, 116
RGB color model, 84
Roads, Curtis, 303, 355
main
2005/1/11
page 367
i
Index
367
rounding errors, 92
run length encoding, 234
sample, 15, 271
sample index, 330
sample objects, 275
sample size, 296
sample sizes, 272
sampling, 166, 330, 333
sampling rate, 277
sampling interval, 333
sampling rate, 272
in additive synthesis, 335
sawtooth, 268
scope, 106, 118
definition, 118
sepia-toned, 200
setColor(color), 90
setRed(redValue), 90
short-circuit evaluation, 219
show(), 67, 88
sign bit, 273
signal view, 268
sine wave, 265
Siren, 320
slice, 269
software engineering, 9, 17
sonogram view, 269
sound
amplitude, 265
blockingPlay, 279
clipping, 273
decibels, 266
decreasing volume, 290
frequency, 265
frequency domain, 269
fundamental, 267
getLength(), 276
getSamples, 275
getSampleValueAt, 276
getSamplingRate, 277
getValue, 276
how to manipulate, 274
increasing volume, 284
intensity, 265
new Sound(fileName), 71
overtones, 267
main
2005/1/11
page 368
i
368
Index
pitch, 266
play(), 71
pulse coded modulation, 273
sampling rate, 272
setSampleValueAt, 276
setValue, 276
sonogram view, 269
splicing, 308
volume, 265, 284, 290
sound cursor, 279
sound editor, 268
sound object, 274
sound pressure level, 266
source, 147
specification, 235
spectrum view, 269
spike, 269
splicing, 308
square brackets, 306
square waves, 339
Squeak, 320
static method, 335
stepping, 103
String, 44, 73
strings, 29
appending, 30
strongly typed, 45
sub-program, 294
sub-recipe
sampling, 330
subscript, 306
subsitution, 73
substitution, 63
subtractive synthesis, 343
Sun, 21
symbols, 43
syntax, 56
synthesizers, 328
systems, 9
target, 147
test, 332
testing, 288
using print statements, 145
testing methods, 288, 296
The Godfather, 284
theory, 9
main
2005/1/11
page 369
i
Index
369