001    /*
002     * $Id: ImagePainter.java,v 1.5 2006/04/26 02:52:48 joshy Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx.painter;
023    
024    import java.awt.Graphics2D;
025    import java.awt.Image;
026    import java.awt.Rectangle;
027    import java.awt.TexturePaint;
028    import java.awt.geom.Point2D;
029    import java.awt.geom.Rectangle2D;
030    import java.awt.image.BufferedImage;
031    import java.util.logging.Logger;
032    import javax.swing.JComponent;
033    import javax.swing.SwingUtilities;
034    
035    /**
036     * <p>A Painter instance that paints an image. Any Image is acceptable. This
037     * Painter also allows the developer to specify a "Style" -- CENTERED, TILED,
038     * SCALED, POSITIONED, and CSS_POSITIONED; with the following meanings:</p>
039     *
040     * <ul>
041     *  <li><b>CENTERED</b>: draws the image unscaled and positioned in the center of
042     * the component</li>
043     *  <li><b>TILED</b>: draws the image repeatedly across the component, filling the
044     * entire background.</li>
045     *  <li><b>SCALED</b>: draws the image stretched large enough (or small enough) to 
046     * cover the entire component. The stretch may not preserve the aspect ratio of the
047     * original image.</li>
048     *  <li><b>POSITIONED</b>: draws the image at the location specified by the imageLocation
049     * property. This style of drawing will respect the imageScale property.</li>
050     *  <li><b>CSS_POSITIONED</b>: draws the image using CSS style background positioning.
051     *It will use the location specified by the imageLocation property. This property should
052     *contain a point with the x and y values between 0 and 1. 0,0 will put the image in the
053     *upper left hand corner, 1,1 in the lower right, and 0.5,0.5 in the center. All other values
054     *will be interpolated accordingly. For a more
055     * complete defintion of the positioning algorithm see the 
056     * <a href="http://www.w3.org/TR/CSS21/colors.html#propdef-background-position">CSS 2.1 spec</a>.
057     * </li>
058     * </ul>
059     *
060     * @author Richard
061     */
062    public class ImagePainter extends AbstractPainter {
063        /**
064         * Logger to use
065         */
066        private static final Logger LOG = Logger.getLogger(ImagePainter.class.getName());
067        
068        /**
069         * <p>An enumeration of Styles supported by the ImagePainter. CENTERED indicates
070         * that the image should be centered. If the image is too large for the
071         * canvas area, it will be clipped, but remain centered</p>
072         *
073         * <p>TILED indicates that the image should be painted multiple times, as
074         * a series of tiles</p>
075         *
076         * <p>SCALED indicates that the image should be scaled to fit the canvas area.
077         * The smallest dimension (Math.min(width, height)) will be used to constrain
078         * the image.
079         */
080        public static enum Style {CENTERED, TILED, SCALED, POSITIONED, CSS_POSITIONED};
081        
082        /**
083         * The image to draw
084         */
085        private Image img;
086        
087        /**
088         * Specifies how to draw the image, i.e. what kind of Style to use
089         * when drawing
090         */
091        private Style style = Style.CENTERED;
092        
093        /**
094         * Create a new ImagePainter. By default there is no image, and the Style
095         * is set to Style.CENTERED
096         */
097        public ImagePainter() {
098            super();
099        }
100        
101        /**
102         * Create a new ImagePainter with the specified image and the Style
103         * Style.CENTERED
104         *
105         * @param image the image to be painted
106         */
107        public ImagePainter(Image image) {
108            super();
109            this.img = image;
110        }
111        
112        /**
113         * Create a new ImagePainter with the specified image and style.
114         *
115         * @param image the image to be painted
116         * @param style the style of the image
117         */
118        public ImagePainter(Image image, Style style) {
119            super();
120            this.img = image;
121            this.style = style;
122        }
123        
124        /**
125         * Sets the image to use for the background of this panel. This image is
126         * painted whether the panel is opaque or translucent.
127         *
128         * @param image if null, clears the image. Otherwise, this will set the
129         * image to be painted. If the preferred size has not been explicitly set,
130         * then the image dimensions will alter the preferred size of the panel.
131         */
132        public void setImage(Image image) {
133            if (image != img) {
134                Image oldImage = img;
135                img = image;
136                firePropertyChange("image", oldImage, img);
137            }
138        }
139        
140        /**
141         * @return the image used for painting the background of this panel
142         */
143        public Image getImage() {
144            return img;
145        }
146        
147        /**
148         * Sets what style to use when painting the image
149         *
150         * @param s The style constant to apply to the image.
151         */
152        public void setStyle(Style s) {
153            if (style != s) {
154                Style oldStyle = style;
155                style = s;
156                firePropertyChange("style", oldStyle, s);
157            }
158        }
159    
160        /**
161         * @return the Style used for drawing the image (CENTERED, TILED, etc).
162         */
163        public Style getStyle() {
164            return style;
165        }
166        
167        /**
168         * @inheritDoc
169         */
170        public void paintBackground(Graphics2D g, JComponent component) {
171            if (img != null) {
172                int imgWidth = img.getWidth(null);
173                int imgHeight = img.getHeight(null);
174                if (imgWidth == -1 || imgHeight == -1) {
175                    //image hasn't completed loading, do nothing
176                } else {
177                    switch (style) {
178                        case CENTERED:
179                            Rectangle clipRect = g.getClipBounds();
180                            int imageX = (component.getWidth() - imgWidth) / 2;
181                            int imageY = (component.getHeight() - imgHeight) / 2;
182                            Rectangle r = SwingUtilities.computeIntersection(imageX, imageY, imgWidth, imgHeight, clipRect);
183                            if (r.x == 0 && r.y == 0 && (r.width == 0 || r.height == 0)) {
184                                return;
185                            }
186                            //I have my new clipping rectangle "r" in clipRect space.
187                            //It is therefore the new clipRect.
188                            clipRect = r;
189                            //since I have the intersection, all I need to do is adjust the
190                            //x & y values for the image
191                            int txClipX = clipRect.x - imageX;
192                            int txClipY = clipRect.y - imageY;
193                            int txClipW = clipRect.width;
194                            int txClipH = clipRect.height;
195    
196                            g.drawImage(img, clipRect.x, clipRect.y, clipRect.x + clipRect.width, clipRect.y + clipRect.height,
197                                    txClipX, txClipY, txClipX + txClipW, txClipY + txClipH, null);
198                            break;
199                        case TILED:
200                            if (img instanceof BufferedImage) {
201                                BufferedImage b = (BufferedImage)img;
202                                TexturePaint paint = new TexturePaint(b,
203                                        new Rectangle2D.Double(0, 0, b.getWidth(), b.getHeight()));
204                                g.setPaint(paint);
205                                g.fillRect(0, 0, component.getWidth(), component.getHeight());
206                            } else {
207                                //TODO!
208                                LOG.fine("unimplemented");
209                            }
210                        case SCALED:
211                              g.drawImage(img, 0, 0, component.getWidth(), component.getHeight(), null);
212                            break;
213                        case POSITIONED:
214                              g.drawImage(img, (int)imagePosition.getX(), (int)imagePosition.getY(), 
215                                      (int)(((double)img.getWidth(null))*imageScale),
216                                      (int)(((double)img.getHeight(null))*imageScale),
217                                      null);
218                            break;
219                        case CSS_POSITIONED:
220                            double x = imagePosition.getX() * (component.getWidth()-img.getWidth(null));
221                            double y = imagePosition.getY() * (component.getHeight()-img.getHeight(null));
222                            g.drawImage(img,(int)x,(int)y,null);
223                            break;
224                        default:
225                            LOG.fine("unimplemented");
226                            g.drawImage(img, 0, 0, null);
227                            break;
228                    }
229                }
230            }
231        }
232        
233        private Point2D imagePosition = new Point2D.Double(0,0);
234        public void setImagePosition(Point2D imagePosition) {
235            this.imagePosition = imagePosition;
236        }
237        public Point2D getImagePosition() {
238            return imagePosition;
239        }
240    
241        private double imageScale = 1.0;
242        public void setImageScale(double imageScale) {
243            this.imageScale = imageScale;
244        }
245    
246    }