/******************************************
* CelebrityPainter: *
* Applet lets users paint a blank face *
* using facial features of celebrities. *
* *
* Created & Programmed by: *
* Jeff Orkin *
* *
* Copyright 1996 *
******************************************/
//
// To run this applet, you need to create an html page that
// links in the applet, and provides parameter lines for celebrity
// brushes to include.
// The html to link in the applet should look like this:
//
//
// You can add as many celebrities as you want.
// To add celebrities:
// 1. Add a parameter line to the applet tag in the html
// that has the next sequential brush number, and
// gives a name string.
// 2. Provide a 160x200 jpeg of the celebrity, with a
// filename that matches the name string without spaces.
// 3. Provide a 40x50 jpeg of the celebrity, with a
// filename that matches the name string without spaces
// plus the characters "BR".
//
// For example, to add a David Letterman brush:
// 1. Add the parameter line
//
// 2. Provide a 160x200 jpeg called DavidLetterman.jpg
// 3. Provide a 40x50 jpeg called DavidLettermanBR.jpg
//
// I M P O R T S ///////////////////////////////////////////////////////
import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
// C L A S S E S /////////////////////////////////////////////////////
//
// class CelebrityPainter:
// Main applet class for the applet
//
public class CelebrityPainter extends Applet
{
// CONSTANTS //////////////////////////////////////////////////////
static final int SMALL = 1;
static final int MEDIUM = 2;
static final int LARGE = 3;
// MEMBERS /////////////////////////////////////////////
ControlPanel cpControls; // Applet's control panel
Image drawnPic; // Double buffer to draw on
Graphics drawnPicGC;
Graphics screenGC;
Brush bshCeleb; // Brush to paint with
//
// init:
// Set up the applet by setting the background to white and creating
// the control panel, brush, and double buffer.
//
public void init()
{
this.setBackground(Color.white);
setLayout(new BorderLayout());
cpControls = new ControlPanel(this);
add("East", cpControls);
// Set brush to the first Celebrity in the combo box of names.
bshCeleb = new Brush(cpControls.sSelected, this);
// Create a graphics context for the screen so that
// functions other than paint() and update() can draw
// to the screen.
screenGC = this.getGraphics();
// Create a double buffer same size as applet to draw on.
drawnPic = createImage(size().width, size().height);
drawnPicGC = drawnPic.getGraphics();
// Paint the blank face background.
clear();
}
//
// clear:
// Paint a blank face over whatever's in the buffer,
// and repaint the applet.
//
public void clear()
{
// Draw blue background.
drawnPicGC.setColor(Color.blue);
drawnPicGC.fillRect(0, 0, 160, 200);
// Draw pink oval as blank face.
drawnPicGC.setColor(Color.pink);
drawnPicGC.fillOval(26, 12, 108, 176);
// Copy double buffer to the screen.
repaint();
}
//
// paint:
// Copy the double buffer to the screen.
//
public void paint(Graphics g)
{
g.drawImage(drawnPic, 0, 0, this);
}
//
// mouseDrag:
// This is where most of the action takes place.
// Copy the pixels surrounding the mouse from the current
// celebrity picture, held in the Brush, to the double buffer.
// Finally, copy the modified double buffer to the screen.
// The number of pixels affected depends on the current
// brush size held in the control panel.
//
public boolean mouseDrag(Event e, int x, int y) {
int i, j;
Color pixColor;
// Ignore mouse draging in the control panel.
if(x >= 160) return true;
// Number of pixels affected depends on the brush size.
switch (cpControls.iBrushSize)
{
// small brush
case SMALL:
// Copy pixels surrounding mouse from the celebrity brush
// to the double buffer.
for(i=x-1; i<=x+1; i++)
{
for(j=y-3; j<=y+3; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
}
i=x-3;
for(j=y-1; j<=y+1; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x-2;
for(j=y-2; j<=y+2; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x+3;
for(j=y-1; j<=y+1; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x+2;
for(j=y-2; j<=y+2; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
break;
// medium brush
case MEDIUM:
// Copy pixels surrounding mouse from the celebrity brush
// to the double buffer.
for(i=x-2; i<=x+2; i++)
{
for(j=y-4; j<=y+4; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
}
i=x-4;
for(j=y-2; j<=y+2; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x-3;
for(j=y-3; j<=y+3; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x+4;
for(j=y-2; j<=y+2; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x+3;
for(j=y-3; j<=y+3; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
break;
// large brush
case LARGE:
// Copy pixels surrounding mouse from the celebrity brush
// to the double buffer.
for(i=x-3; i<=x+3; i++)
{
for(j=y-5; j<=y+5; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
}
i=x-5;
for(j=y-3; j<=y+3; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x-4;
for(j=y-4; j<=y+4; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x+5;
for(j=y-3; j<=y+3; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
i=x+4;
for(j=y-4; j<=y+4; j++) {
drawnPicGC.setColor(new Color(bshCeleb.iImgPixels[i][j]));
drawnPicGC.drawLine(i, j, i, j);
}
break;
} // end of switch
// Copy double buffer to the screen.
// Done locally rather than calling repaint() for speed.
screenGC.drawImage(drawnPic, 0, 0, this);
return true;
}
} // end of CelebrityPainter
//
// class ControlPanel:
// Class responsible for creating and managing
// the applets GUI.
//
class ControlPanel extends Panel
{
// CONSTANTS //////////////////////////////////////////////////////
static final int SMALL = 1;
static final int MEDIUM = 2;
static final int LARGE = 3;
// MEMBERS /////////////////////////////////////////////
BrushCanvas bcBrushPic; // canvas to paint thumbnail image of brush on
Checkbox optSmall;
Checkbox optMedium;
Checkbox optLarge;
Checkbox optEraser;
int iBrushSize; // currently selected brush size
String sSelected; // currently selected celebrity brush
CelebrityPainter appParent;
//
// ControlPanel:
// Constructor for the Control Panel.
// Creates and places components of the GUI.
//
public ControlPanel(CelebrityPainter ap)
{
Choice cboBrush;
Button btnClear;
CheckboxGroup grpBrushSize;
Label lblBrushSize;
GridBagLayout gblControls = new GridBagLayout();
GridBagConstraints gbcControls = new GridBagConstraints();
appParent = ap;
setFont(new Font("Helvetica", Font.PLAIN, 14));
setLayout(gblControls);
// Set spacing for controls.
gbcControls.insets = new Insets(0, 3, 0, 0);
// cboBrush ///////////////////////////////////////////////////////////////////
// cboBrush is the last control on row of grid.
gbcControls.gridwidth = GridBagConstraints.REMAINDER;
cboBrush = new Choice();
gblControls.setConstraints(cboBrush, gbcControls);
// Fill combo box with names given as parameters to the applet in the html.
fillCombo(cboBrush);
// Set selected to start out holding the filename of the jpg of the first
// celebrity listed in the combo box.
sSelected = convertImageString(cboBrush.getItem(0), "");
add(cboBrush);
// bcBrushPic ///////////////////////////////////////////////////////////////////
// Set grid constraints to allow the bush pic and clear button to share a row.
gbcControls.weightx = 1.0;
gbcControls.gridwidth = 1;
// Create the canvas initially showing the thumbnail of the first celebrity in the list.
bcBrushPic = new BrushCanvas(convertImageString(cboBrush.getItem(0), "BR"), appParent);
bcBrushPic.resize(40, 50);
gblControls.setConstraints(bcBrushPic, gbcControls);
add(bcBrushPic);
// btnClear ///////////////////////////////////////////////////////////////////
// Set grid constraints to make btnClear the last control on the row,
// and weight it so that it takes up less than half of the row.
gbcControls.gridwidth = GridBagConstraints.REMAINDER;
gbcControls.fill = GridBagConstraints.VERTICAL;
gbcControls.weightx = 0.4;
btnClear = new Button("CLEAR");
gblControls.setConstraints(btnClear, gbcControls);
add(btnClear);
// Eraser Option Button ///////////////////////////////////////////////////////////////////
// Set grid constraints that let each control from here on takes up a
// whole line, which makes them left align.
gbcControls.fill = GridBagConstraints.BOTH;
optEraser = new Checkbox("Eraser");
gblControls.setConstraints(optEraser, gbcControls);
add(optEraser);
// Brush Size Option Buttons ///////////////////////////////////////////////////////////////////
lblBrushSize = new Label("Brush Size:");
gblControls.setConstraints(lblBrushSize, gbcControls);
add(lblBrushSize);
grpBrushSize = new CheckboxGroup();
// Set initial brush size to small.
iBrushSize = SMALL;
optSmall = new Checkbox("Small", grpBrushSize, true);
gblControls.setConstraints(optSmall, gbcControls);
add(optSmall);
optMedium = new Checkbox("Medium", grpBrushSize, false);
gblControls.setConstraints(optMedium, gbcControls);
add(optMedium);
optLarge = new Checkbox("Large", grpBrushSize, false);
gblControls.setConstraints(optLarge, gbcControls);
add(optLarge);
}
//
// fillCombo:
// Fills the combo box of celebrity brushes with names
// given as parameters through the html.
// The number of brushes is unlimited.
//
public void fillCombo(Choice cboList)
{
String sListItem; // String to add as a combo choice
int iNum; // Current parameter to get
// Look first for parameter brush1.
iNum = 1;
while(true)
{
sListItem = appParent.getParameter("brush" + iNum);
// Stop looping when there are no more brush parameters.
if(sListItem == null) break;
cboList.addItem(sListItem);
// Look for the next brush parameter incrementally.
iNum++;
}
}
//
// action:
// Act on the afftected control panel component.
//
public boolean action(Event e, Object arg)
{
int iSelectedLen; // Length of selected brush name in combo box
// Used for erasing.
// Process celebrity name combo box event.
if(e.target instanceof Choice)
{
// Tell user that the brush is being loaded.
appParent.screenGC.setColor(Color.white);
appParent.screenGC.fillRect(0, 180, 160, 200);
appParent.screenGC.setColor(Color.black);
appParent.screenGC.drawString("Loading Brush.....", 20, 195);
// Turn off the eraser, in case it was on.
optEraser.setState(false);
// Set the thumbnail celebrity image to one matching the selected name.
bcBrushPic.setImage(convertImageString((String)arg, "BR"));
sSelected = convertImageString((String)arg, "");
bcBrushPic.repaint();
// Load the selected name's celebrity image into the applet's brush.
appParent.bshCeleb.setImage(sSelected);
// Erase the loading message by redrawing the applet's screen.
appParent.screenGC.drawImage(appParent.drawnPic, 0, 0, appParent);
}
// Process option event.
if(e.target instanceof Checkbox)
{
// Process eraser event
if(e.target == optEraser)
{
// Load the eraser
if(optEraser.getState() == true)
{
// Tell user that the eraser is being loaded.
appParent.screenGC.setColor(Color.white);
appParent.screenGC.fillRect(0, 180, 160, 200);
appParent.screenGC.setColor(Color.black);
appParent.screenGC.drawString("Loading Eraser.....", 20, 195);
// Set the thumbnail to image of background.
bcBrushPic.setImage("ERASERBR.jpg");
bcBrushPic.repaint();
// Load the eraser image into the applet's brush.
appParent.bshCeleb.setImage("ERASER.jpg");
// Erase the loading message by redrawing the applet's screen.
appParent.screenGC.drawImage(appParent.drawnPic, 0, 0, appParent);
}
// Reload last selected brush.
else
{
// Tell user that the brush is being loaded.
appParent.screenGC.setColor(Color.white);
appParent.screenGC.fillRect(0, 180, 160, 200);
appParent.screenGC.setColor(Color.black);
appParent.screenGC.drawString("Loading Brush.....", 20, 195);
// Get length of string holding name of selected celeb's image file.
iSelectedLen = sSelected.length();
// Set the thumbnail celebrity image to one matching the selected name.
bcBrushPic.setImage(convertImageString(sSelected.substring(0, iSelectedLen - 4), "BR"));
bcBrushPic.repaint();
// Load the selected name's celebrity image into the applet's brush.
appParent.bshCeleb.setImage(sSelected);
// Erase the loading message by redrawing the applet's screen.
appParent.screenGC.drawImage(appParent.drawnPic, 0, 0, appParent);
}
}
// Process brush size event
else
{
// Set the brush size to correspond to the option button set to true.
if(optSmall.getState() == true) iBrushSize = SMALL;
else if(optMedium.getState() == true) iBrushSize = MEDIUM;
else if(optLarge.getState() == true) iBrushSize = LARGE;
}
}
// Process clear button.
if(e.target instanceof Button)
{
// Clear the applet's screen, setting it back to a blank face.
appParent.clear();
}
return true;
}
//
// convertImageString:
// Convert a celebrity name from the combo box into the name
// of a jpg file to be loaded. Do this by stripping spaces, adding
// any extra characters, and adding the ".jpg" extension.
//
public String convertImageString(String sImageString, String sExt)
{
String sImageName = null; // String to hold the final image name
int iSpace = 0; // Location of current space
int iLastSpace = (-1); // Location of last space
int iNameLength = 0; // Length of given string
iNameLength = sImageString.length();
// Keep stripping spaces until there are no more.
while(iSpace != (-1))
{
iSpace = sImageString.indexOf(" ", iLastSpace + 1);
// If a space was found, add the next string segment to the file name.
if(iSpace != (-1))
{
if(sImageName == null) sImageName = sImageString.substring(iLastSpace + 1, iSpace);
else sImageName += sImageString.substring(iLastSpace + 1, iSpace);
iLastSpace = iSpace;
}
} // end of while
// Add the string segment found after the last space (or no space).
if(sImageName == null) sImageName =
sImageString.substring(iLastSpace + 1, iNameLength) + sExt + ".jpg";
else sImageName += sImageString.substring(iLastSpace + 1, iNameLength) + sExt + ".jpg";
return sImageName;
}
} // end of ControlPanel
//
// class BrushCanvas:
// Canvas to paint thumbnail of currently selected celebrity on.
//
class BrushCanvas extends Canvas
{
// MEMBERS /////////////////////////////////////////////
Applet appParent;
Brush bshCelebPic; // Brush used to hold current celebrity thumbnail
Image imgCanvasImage;
Graphics canvasImageGC;
public BrushCanvas(String sImageName, Applet ap)
{
appParent = ap;
// Create the canvas' image.
imgCanvasImage = appParent.createImage(40, 50);
canvasImageGC = imgCanvasImage.getGraphics();
// Create a new brush from the given image.
bshCelebPic = new Brush(sImageName, appParent);
// Call set image to copy the image to the canvas.
setImage(sImageName);
}
//
// setImage:
// Copies specified image to canvas by setting it in
// the canvas' brush, and copying the pixles.
// This process is slow, but produces a better quality image
// than simply using getImage() and drawImage().
//
public void setImage(String sImageName)
{
// Set the specified image in the canvas' brush.
bshCelebPic.setImage(sImageName);
// Copy pixels of the canvas' brush to the canvas.
for(int x=0; x<40; x++) {
for(int y=0; y<50; y++) {
canvasImageGC.setColor(new Color(bshCelebPic.iImgPixels[x][y]));
canvasImageGC.drawLine(x, y, x, y);
}
}
}
//
// update:
// Call paint without clearing first.
//
public void update(Graphics g) {
paint(g);
}
//
// paint:
// Copy the canvas to the screen.
//
public void paint(Graphics g)
{
g.drawImage(imgCanvasImage, 0, 0, appParent);
}
} // end of BrushCanvas
//
// class Brush:
// Creates an array of pixel values from a given Image.
//
class Brush implements ImageConsumer
{
// MEMBERS /////////////////////////////////////////////
Applet appParent;
boolean bImgReady; // Flag set when image has been consumed
int iImgXSize, iImgYSize;
int iImgPixels[][]; // Actual pizel values of image
//
// Brush:
// Constructor calls setImage to extract pixels frmo the given image.
//
Brush(String sImageName, Applet ap) {
appParent = ap;
// Extract pixels from given image.
setImage(sImageName);
}
//
// setImage:
// Extracts pixels from a given image.
//
public void setImage(String sImageName)
{
int iTicks;
Image imgCopy;
imgCopy = appParent.getImage(appParent.getDocumentBase(), sImageName);
bImgReady = false;
// Get pixles, but give up after 5 minutes.
imgCopy.getSource().startProduction(this);
iTicks = 5*60*1000; // 5 minutes
while(iTicks>0) {
try {
Thread.currentThread().sleep(100);
} catch (Exception e) {}
if(this.bImgReady)
break;
iTicks -= 100;
}
}
// Function required for Image Consumer.
public void setProperties(java.util.Hashtable dummy) {
}
// Function required for Image Consumer.
public void setColorModel(ColorModel model) {
}
// Function required for Image Consumer.
public void setHints(int dummy) {
}
//
// imageComplete:
// Notifies brush that an image has been consumed by
// setting the image ready flag to true.
//
public void imageComplete(int dummy) {
bImgReady = true;
}
//
// setDimensions:
// Sets variables for dimensions of given image,
// and creates an array to hold the pixels.
//
public void setDimensions(int x, int y) {
iImgXSize = x;
iImgYSize = y;
iImgPixels = new int[x][y];
}
//
// setPixels:
// Take pixels from the one dimensional array and put their
// RGB lookup values into the two dimensional array of pixels.
//
public void setPixels(int x1, int y1, int w, int h,
ColorModel cmModel, byte byPixels[], int iOff, int iScansize) {
int x, y, x2, y2, sx, sy;
x1 = x2 = iOff = 0;
x2 = x1+w;
y2 = y1+h;
sy = iOff;
for(y=y1; y