// CrazyLabel.java, 2.3, Patrick Taylor import java.awt.Canvas; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Rectangle; import java.awt.image.ImageObserver; /** * An animated multiline text label. * This is the guts of my CrazyText applet. * Based on Daniel Wyszynski's NervousText applet from JDK 1.0 beta1. * * @version 2.3, 26 April 1996, works with JDK 1.0 * @author Patrick Taylor */ public class CrazyTextBGLabel_fadingray extends Canvas implements Runnable { // parameter variables /** * The concatenated text lines separated by '|' characters. * Default: "CrazyText" */ public String text = "CrazyText"; /** * Milliseconds between animation updates. * Default: 100 */ public int delay = 100; /** * "Craziness" factor; the max offset of a char from its normal position. * Default: 5 */ public int delta = 5; /** * Horizontal spacing between characters. * Default: 0 */ public int hgap = 1; /** * Vertical spacing between characters. * Default: 0 */ public int vgap = 0; /** * Extra horizontal spacing between text and border. * Default: 0 */ public int hspace = 0; /** * Extra vertical spacing between text and border. * Default: 0 */ public int vspace = 0; /** * Clear the background on each update. * Default: false */ public boolean clear = true; /** * Color change style: "whole" | "line" | "char" | "none". * Default: "whole" */ public String cycle = "char"; /** * Depth of drop shadow on text. * Default: 0 */ public int shadowDepth = 2; /** * Color of drop shadow on text. * Default: gray */ public Color shadowColor = Color.gray; /** * Second color for background gradient fill; null for no gradient fill. * Default: null */ public Color background2 = null; /** * Direction of background gradient fill: "vertical" | "horizontal". * Default: "vertical" */ public String bgGradient = "vertical"; /** * Image with which to tile background; null for none. * Default: null */ public Image bgImage = null; /** * ImageObserver to use for image operations; typically, the applet. * Default: null */ public ImageObserver observer = null; /** * Output debugging information to console. * Default: false */ public boolean debug = false; // implementation variables String lines[]; // individual lines in 'text' char chars[][]; // individual chars in 'text' int xPositions[][]; // base horizontal position for each char int baseline; // base vertical position for each char Rectangle lineRect[]; // region occupied by each line Dimension textSize; // total size occupied by text lines boolean cycleChar; // cycle == "char" boolean cycleLine; // cycle == "line" boolean cycleWhole; // cycle == "whole" boolean bgGradVert; // bgGradient == "vertical" Image imageBuffer[]; // off-screen image used for double buffering Graphics imageBufferG[]; // off-screen image graphics context Thread thread; // animation thread MediaTracker tracker; // background image monitor // constructors /** * Constructs a new label with the specified String of text. * @param text the concatenated text lines, each separated by a '|' character */ public CrazyTextBGLabel_fadingray(String text) { if (text != null) { this.text = text; } } // redefined methods public void addNotify() { dbg("addNotify"); super.addNotify(); init(); } public void removeNotify() { dbg("removeNotify"); stop(); destroy(); super.removeNotify(); } public Dimension preferredSize() { return minimumSize(); } public Dimension minimumSize() { return new Dimension(hspace*2 + textSize.width, vspace*2 + textSize.height); } public void reshape(int x, int y, int width, int height) { dbg("reshape: width="+width+", height="+height); super.reshape(x, y, width, height); // set x,y of lineRects int yNew = (height - textSize.height) / 2; for (int l = 0; l < lines.length; l++) { lineRect[l].x = (width - lineRect[l].width ) / 2; lineRect[l].y = yNew; yNew += lineRect[l].height + vgap; dbg("lineRect["+l+"]="+lineRect[l]); // paint buffer backgrounds, since gradient/tiling depends on position paintBg(imageBufferG[l], lineRect[l]); } } public void update(Graphics g) { dbg("update"); paint(g); } public void paint(Graphics g) { dbg("paint"); paintBg(g, new Rectangle(0, 0, size().width, size().height)); for (int l = 0; l < lines.length; l++) { g.drawImage(imageBuffer[l], lineRect[l].x, lineRect[l].y, this); } // when used as a Component, first call to paint() starts thread start(); } /** * Runs the animation. */ public void run() { dbg("run"); Color c = getForeground(); while (thread != null) { try { Thread.sleep(delay); } catch (InterruptedException e) { break; } if (cycleWhole) c = getRandomColor(); for (int l = 0; l < lines.length; l++) { Graphics g = imageBufferG[l]; if (clear) paintBg(g, lineRect[l]); if (cycleLine) c = getRandomColor(); int lineLen = lines[l].length(); for (int i = 0; i < lineLen; i++) { int x = (int)(Math.random() * delta * 2) + xPositions[l][i]; int y = (int)(Math.random() * delta * 2) + baseline; if (shadowDepth > 0) { g.setColor(shadowColor); g.drawChars(chars[l], i, 1, x+shadowDepth, y+shadowDepth); } if (cycleChar) c = getRandomColor(); g.setColor(c); g.drawChars(chars[l], i, 1, x, y); } getGraphics().drawImage(imageBuffer[l], lineRect[l].x, lineRect[l].y, this); } } } // other methods /** * Initializes the instance based on specified parameters. */ public void init() { dbg("init"); // if there is a background image, start loading it if (bgImage != null) { tracker = new MediaTracker(this); tracker.addImage(bgImage, 0); tracker.checkID(0, true); } lines = parseSubstrings(text,'|'); int nLines = lines.length; FontMetrics fm = getFontMetrics(getFont()); int lineHeight = fm.getHeight() + shadowDepth + delta*2; textSize = new Dimension(); textSize.height = lineHeight*nLines + vgap*(nLines-1); // width set below baseline = fm.getAscent() - 1; cycleChar = cycle.equals("char"); cycleLine = cycle.equals("line"); cycleWhole = cycle.equals("whole"); bgGradVert = bgGradient.equals("vertical"); chars = new char[nLines][]; xPositions = new int[nLines][]; lineRect = new Rectangle[nLines]; imageBuffer = new Image[nLines]; imageBufferG = new Graphics[nLines]; for (int l = 0; l < nLines; l++) { int lineLen = lines[l].length(); chars[l] = new char[lineLen]; lines[l].getChars(0, lineLen, chars[l], 0); xPositions[l] = new int[lineLen]; for (int i = 0; i < lineLen; i++) { xPositions[l][i] = fm.charsWidth(chars[l],0,i) + (i*hgap); } // set width,height of lineRect; x,y is set in reshape() lineRect[l] = new Rectangle( fm.stringWidth(lines[l])+shadowDepth+delta*2+hgap*(lineLen-1), lineHeight); textSize.width = Math.max(textSize.width, lineRect[l].width); // create image buffer for line imageBuffer[l] = createImage(lineRect[l].width, lineRect[l].height); imageBufferG[l] = imageBuffer[l].getGraphics(); imageBufferG[l].setFont(getGraphics().getFont()); } // make sure background image is completely loaded before proceeding if (bgImage != null) { try { tracker.waitForID(0); } catch (InterruptedException e) { System.err.println(e); } if (tracker.statusID(0, false) != MediaTracker.COMPLETE) { bgImage = null; // if image didn't completely load, forget it } tracker = null; // we're finished with the tracker } } /** * Cleans up the system resources used by the instance. */ public void destroy() { dbg("destroy"); // unfortunately, in JDK 1.0 you have to remember to clean up some things for (int l = 0; l < lines.length; l++) { imageBuffer[l].flush(); imageBufferG[l].dispose(); } } /** * Starts the animation thread. */ public void start() { if (thread == null) { dbg("start"); thread = new Thread(this); thread.start(); } } /** * Stops the animation thread. */ public void stop() { if (thread != null) { dbg("stop"); thread.stop(); thread = null; } } /** * Prints a debugging message if this.debug is true. */ protected void dbg(String msg) { if (debug) System.out.println(msg); } /** * Paints the background of the canvas or an image buffer, including * any specified gradient and tiled background image. * @param g the graphic context of the canvas or image buffer * @param rect the region of the canvas occupied by the canvas or buffer */ protected void paintBg(Graphics g, Rectangle rect) { if (background2 == null) { // no background gradient; fill with background color g.setColor(getBackground()); g.fillRect(0, 0, rect.width, rect.height); } else { // do gradient fill gradientFill(g, rect, size(), getBackground(), background2, bgGradVert); } // bg image should be drawn after fill, since pixels may be transparent if (bgImage != null) { tileImage(g, bgImage, rect, observer); } } /** * Tiles an image to fill the canvas or an image buffer representing a * region of the canvas. Does nothing if the image size is not yet known. * @param g the graphic context of the canvas or image buffer * @param img the image * @param rect the region of the canvas occupied by the canvas or buffer * @param observer an ImageObserver, typically, the containing Applet */ protected static void tileImage(Graphics g, Image img, Rectangle rect, ImageObserver observer) { int imgW = img.getWidth(observer); int imgH = img.getHeight(observer); if (imgW <= 0 || imgH <= 0) return; int initX = -(rect.x % imgW); int initY = -(rect.y % imgH); for (int x = initX; x < rect.width; x += imgW) { for (int y = initY; y < rect.height; y += imgH) { g.drawImage(img, x, y, observer); }} } /** * Does a gradient fill on the canvas or an image buffer representing a * region of the canvas. The fill is done by drawing a line for each row * or column of the region. The color of each line depends on its position * relative to the start/end edge of the canvas. * @param g the graphic context of the canvas or image buffer * @param rect the region of the canvas occupied by the canvas or buffer * @param size the size of the canvas * @param color1 gradient starting color (top/left edge of canvas) * @param color2 gradient ending color (bottom/right edge of canvas) * @param vertical if true, gradient goes top to bottom; else left to right */ protected static void gradientFill(Graphics g, Rectangle rect, Dimension size, Color color1, Color color2, boolean vertical) { // The fill is done by drawing a line for each row or column of the area. // The color of each line depends on position relative to start/end edge. int r1 = color1.getRed(); int g1 = color1.getGreen(); int b1 = color1.getBlue(); int r2 = color2.getRed(); int g2 = color2.getGreen(); int b2 = color2.getBlue(); // f = fraction of gradient range represented by one line float f = 1.0f / (vertical ? size.height : size.width); // dr,dg,db = change in red/green/blue across one line float dr = (r2 - r1) * f; float dg = (g2 - g1) * f; float db = (b2 - b1) * f; // end = end coord of each line int end = (vertical ? rect.width : rect.height) - 1; // ic = index of line relative to canvas int ic = vertical ? rect.y : rect.x; // i = index of line relative to g int imax = vertical ? rect.height : rect.width; for (int i = 0; i < imax; i++, ic++) { g.setColor(new Color(r1 + (int)(ic * dr), g1 + (int)(ic * dg), b1 + (int)(ic * db))); if (vertical) { g.drawLine(0, i, end, i); } else { g.drawLine(i, 0, i, end); } } } /** * Returns a random color. */ protected static Color getRandomColor() { return new Color((float)Math.random(), (float)Math.random(), (float)Math.random()); } /** * Returns substrings of a string that are separated by a delimiting char. * @param s the string * @param delim the delimiting character */ static protected String[] parseSubstrings(String s, char delim) { int nLines = 1; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == delim) nLines++; } String[] lines = new String[nLines]; int iLine = 0; int first = 0; do { int last = s.indexOf(delim, first); lines[iLine++] = s.substring(first, last == -1 ? s.length() : last); first = last + 1; } while (iLine < nLines); return lines; } }