/* * Copyright (c) 2005, romain guy (romain.guy@jext.org) and craig wickesser (craig@codecraig.com) * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.java.swingfx.waitwithstyle; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import javax.swing.ImageIcon; import javax.swing.JPanel; /** * Display an animated panel. The panel contains a picture and a text message. * As soon as <code>start()</code> is called, the pictures and the text glow in * cycles. The animation can be stopped at anytime by calling * <code>stop()</code>. You can set the font and its color by calling * <code>setFont()</code> and <code>setForeground()</code>. * * @author Romain Guy, 17/02/2005 * @since 1.0 <br> * $Revision: 1.2 $ */ public class AnimatedPanel extends JPanel { private static final long serialVersionUID = 3257288036894324529L; protected float gradient; protected String message; protected Thread animator; protected BufferedImage convolvedImage; protected BufferedImage originalImage; protected static AlphaComposite composite = AlphaComposite .getInstance(AlphaComposite.SRC_OVER); protected RenderingHints brightnessHints; /** * Creates an animated panel with a message and a picture. * * @param message * The message to display, can not be null nor empty. * @param icon * The picture to display, can not be null */ public AnimatedPanel(final String message, final ImageIcon icon) { // since the message can not be null or empty, validate it AnimatedPanel.validateMessage(message); // the icon can't be null either if (icon == null) { throw new IllegalArgumentException("Icon can not be null."); } this.message = message; final Image image = icon.getImage(); this.originalImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); this.convolvedImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); final Graphics g = this.originalImage.createGraphics(); g.drawImage(image, 0, 0, this); g.dispose(); this.brightnessHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); this.setBrightness(1.0f); this.setOpaque(false); } /** * Performs basic validation on the given <code>msg</code> * * @param msg * the message to validate * @throws IllegalArgumentException * if <code>msg</code> is <code>null</code> or empty */ private static void validateMessage(final String msg) throws IllegalArgumentException { final boolean isNullOrEmpty = (msg == null) || msg.equals(""); if (isNullOrEmpty) { throw new IllegalArgumentException( "Invalid message. Message can not be null or empty."); } } /** * Changes the displayed message at runtime. * * @param text * The message to be displayed. Can not be null or empty. */ public void setText(final String text) { // since the message can not be null or empty, validate it AnimatedPanel.validateMessage(text); this.message = text; this.repaint(); } /** * Returns the current message. */ public String getText() { return this.message; } @Override public void paintComponent(final Graphics g) { super.paintComponent(g); if (this.convolvedImage != null) { final int width = this.getWidth(); final int height = this.getHeight(); synchronized (this.convolvedImage) { final Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); final FontRenderContext context = g2.getFontRenderContext(); final TextLayout layout = new TextLayout(this.message, this.getFont(), context); final Rectangle2D bounds = layout.getBounds(); final int x = (width - this.convolvedImage.getWidth(null)) / 2; final int y = (int) (height - (this.convolvedImage .getHeight(null) + bounds.getHeight() + layout .getAscent())) / 2; g2.drawImage(this.convolvedImage, x, y, this); final Color foreground = this.getForeground(); g2.setColor(new Color(foreground.getRed(), foreground .getGreen(), foreground.getBlue(), (int) (this.gradient * 255))); layout.draw(g2, (float) (width - bounds.getWidth()) / 2, (float) (y + this.convolvedImage.getHeight(null) + bounds.getHeight() + layout.getAscent())); } } } /** * Changes the image luminosity. */ void setBrightness(final float multiple) { final float[] brightKernel = { multiple }; final BufferedImageOp bright = new ConvolveOp(new Kernel(1, 1, brightKernel), ConvolveOp.EDGE_NO_OP, this.brightnessHints); bright.filter(this.originalImage, this.convolvedImage); this.repaint(); } /** * Changes the text gradient control value. */ void setGradientFactor(final float gradient) { this.gradient = gradient; } /** * Starts the animation. A thread called "Highlighter" is spawned and can be * interrupted at anytime by invoking <code>stop()</code>. */ public void start() { this.animator = new Thread(new HighlightCycler(), "Highlighter"); this.animator.start(); } /** * Safely stops the animation. */ public void stop() { if (this.animator != null) { this.animator.interrupt(); } this.animator = null; } /** * Makes the image luminosity and the text gradient to cycle. */ class HighlightCycler implements Runnable { private int way = 1; private final int LOWER_BOUND = 10; private final int UPPER_BOUND = 35; private int value = this.LOWER_BOUND; @Override public void run() { while (true) { try { Thread.sleep(1000 / (this.UPPER_BOUND - this.LOWER_BOUND)); } catch (final InterruptedException e) { return; } this.value += this.way; if (this.value > this.UPPER_BOUND) { this.value = this.UPPER_BOUND; this.way = -1; } else if (this.value < this.LOWER_BOUND) { this.value = this.LOWER_BOUND; this.way = 1; } synchronized (AnimatedPanel.this.convolvedImage) { AnimatedPanel.this.setBrightness((float) this.value / 10); AnimatedPanel.this.setGradientFactor((float) this.value / this.UPPER_BOUND); } } } } }