001/*
002 * $Id: ImagePainter.java 4147 2012-02-01 17:13:24Z kschaefe $
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
022package org.jdesktop.swingx.painter;
023
024import java.awt.BasicStroke;
025import java.awt.Graphics2D;
026import java.awt.Image;
027import java.awt.Insets;
028import java.awt.Paint;
029import java.awt.Rectangle;
030import java.awt.Shape;
031import java.awt.TexturePaint;
032import java.awt.geom.Area;
033import java.awt.image.BufferedImage;
034import java.util.logging.Logger;
035
036import org.jdesktop.beans.JavaBean;
037import org.jdesktop.swingx.painter.effects.AreaEffect;
038
039/**
040 * <p>A Painter instance that paints an image. Any Image is acceptable. This
041 * Painter also allows the developer to specify a "Style" -- CENTERED, TILED,
042 * SCALED, POSITIONED, and CSS_POSITIONED; with the following meanings:</p>
043 *
044 * <ul>
045 *  <li><b>CENTERED</b>: draws the image unscaled and positioned in the center of
046 * the component</li>
047 *  <li><b>TILED</b>: draws the image repeatedly across the component, filling the
048 * entire background.</li>
049 *  <li><b>SCALED</b>: draws the image stretched large enough (or small enough) to
050 * cover the entire component. The stretch may not preserve the aspect ratio of the
051 * original image.</li>
052 *  <li><b>POSITIONED</b>: draws the image at the location specified by the imageLocation
053 * property. This style of drawing will respect the imageScale property.</li>
054 *  <li><b>CSS_POSITIONED</b>: draws the image using CSS style background positioning.
055 *It will use the location specified by the imageLocation property. This property should
056 *contain a point with the x and y values between 0 and 1. 0,0 will put the image in the
057 *upper left hand corner, 1,1 in the lower right, and 0.5,0.5 in the center. All other values
058 *will be interpolated accordingly. For a more
059 * complete definition of the positioning algorithm see the
060 * <a href="http://www.w3.org/TR/CSS21/colors.html#propdef-background-position">CSS 2.1 spec</a>.
061 * </li>
062 * </ul>
063 *
064 * @author Richard
065 */
066@JavaBean
067@SuppressWarnings("nls")
068public class ImagePainter extends AbstractAreaPainter<Object> {
069    public enum ScaleType { InsideFit, OutsideFit, Distort }
070    
071    /**
072     * Logger to use
073     */
074    @SuppressWarnings("unused")
075    private static final Logger LOG = Logger.getLogger(ImagePainter.class.getName());
076    
077    /**
078     * The image to draw
079     */
080    private transient BufferedImage img;
081    
082    private boolean horizontalRepeat;
083    private boolean verticalRepeat;
084    
085    private boolean scaleToFit = false;
086    private ScaleType scaleType = ScaleType.InsideFit;
087
088    private double imageScale = 1.0;
089
090    /**
091     * Create a new ImagePainter. By default there is no image, and the alignment
092     * is centered.
093     */
094    public ImagePainter() {
095        this((BufferedImage)null);
096    }
097    
098    /**
099     * Create a new ImagePainter with the specified image and the Style
100     * Style.CENTERED
101     *
102     * @param image the image to be painted
103     */
104    public ImagePainter(BufferedImage image) {
105        this(image,HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
106    }
107    
108    /**
109     * Create a new ImagePainter with the specified image and alignment.
110     * @param horizontal the horizontal alignment
111     * @param vertical the vertical alignment
112     * @param image the image to be painted
113     */
114    public ImagePainter(BufferedImage image, HorizontalAlignment horizontal, VerticalAlignment vertical) {
115        super();
116        setCacheable(true);
117        this.img = image;
118        this.setVerticalAlignment(vertical);
119        this.setHorizontalAlignment(horizontal);
120        this.setFillPaint(null);
121        this.setBorderPaint(null);
122        this.setDirty(false);
123    }
124    
125    /**
126     * Sets the image to paint with.
127     * @param image if null, clears the image. Otherwise, this will set the
128     * image to be painted.
129     */
130    public void setImage(BufferedImage image) {
131        if (image != img) {
132            Image oldImage = img;
133            img = image;
134            setDirty(true);
135            firePropertyChange("image", oldImage, img);
136        }
137    }
138    
139    /**
140     * Gets the current image used for painting.
141     * @return the image used for painting
142     */
143    public BufferedImage getImage() {
144        return img;
145    }
146    
147    /**
148     * {@inheritDoc}
149     */
150    @Override
151    protected void doPaint(Graphics2D g, Object component, int width, int height) {
152        Shape shape = provideShape(g, component,width,height);
153        
154        switch (getStyle()) {
155            case BOTH:
156                drawBackground(g,shape,width,height);
157                drawBorder(g,shape,width,height);
158                break;
159            case FILLED:
160                drawBackground(g,shape,width,height);
161                break;
162            case OUTLINE:
163                drawBorder(g,shape,width,height);
164                break;
165        }
166    }
167    
168    private void drawBackground(Graphics2D g, Shape shape, int width, int height) {
169        Paint p = getFillPaint();
170        
171        if(p != null) {
172            if(isPaintStretched()) {
173                p = calculateSnappedPaint(p, width, height);
174            }
175            g.setPaint(p);
176            g.fill(shape);
177        }
178        
179        if(getAreaEffects() != null) {
180            for(AreaEffect ef : getAreaEffects()) {
181                ef.apply(g, shape, width, height);
182            }
183        }
184        
185        
186        if (img != null) {
187            int imgWidth = img.getWidth(null);
188            int imgHeight = img.getHeight(null);
189            if (imgWidth == -1 || imgHeight == -1) {
190                //image hasn't completed loading, do nothing
191            } else {
192                Rectangle rect = shape.getBounds();
193                
194                if(verticalRepeat || horizontalRepeat) {
195                    Shape oldClip = g.getClip();
196                    Shape clip = g.getClip();
197                    if(clip == null) {
198                        clip = new Rectangle(0,0,width,height);
199                    }
200                    Area area = new Area(clip);
201                    Insets insets = getInsets();
202                    area.intersect(new Area(new Rectangle(insets.left, insets.top, width - insets.left - insets.right, height - insets.top - insets.bottom)));
203                    
204                    if (verticalRepeat && horizontalRepeat) {
205                        area.intersect(new Area(new Rectangle(0, 0, width, height)));
206                        g.setClip(area);
207                    } else if (verticalRepeat) {
208                        area.intersect(new Area(new Rectangle(rect.x, 0, rect.width, height)));
209                        g.setClip(area);
210                    } else {
211                        area.intersect(new Area(new Rectangle(0, rect.y, width, rect.height)));
212                        g.setClip(area);
213                    }
214                    
215                    TexturePaint tp = new TexturePaint(img, rect);
216                    g.setPaint(tp);
217                    g.fillRect(0,0,width,height);
218                    g.setClip(oldClip);
219                } else {
220                    if(scaleToFit) {
221                        int sw = imgWidth;
222                        int sh = imgHeight;
223                        if(scaleType == ScaleType.InsideFit) {
224                            if(sw > width) {
225                                float scale = (float)width/(float)sw;
226                                sw = (int)(sw * scale);
227                                sh = (int)(sh * scale);
228                            }
229                            if(sh > height) {
230                                float scale = (float)height/(float)sh;
231                                sw = (int)(sw * scale);
232                                sh = (int)(sh * scale);
233                            }
234                        }
235                        if(scaleType == ScaleType.OutsideFit) {
236                            if(sw > width) {
237                                float scale = (float)width/(float)sw;
238                                sw = (int)(sw * scale);
239                                sh = (int)(sh * scale);
240                            }
241                            if(sh < height) {
242                                float scale = (float)height/(float)sh;
243                                sw = (int)(sw * scale);
244                                sh = (int)(sh * scale);
245                            }
246                        }
247                        if(scaleType == ScaleType.Distort) {
248                            sw = width;
249                            sh = height;
250                        }
251                        int x=0;
252                        int y=0;
253                        switch(getHorizontalAlignment()) {
254                            case CENTER:
255                                x=(width/2)-(sw/2);
256                                break;
257                            case RIGHT:
258                                x=width-sw;
259                                break;
260                        }
261                        switch(getVerticalAlignment()) {
262                            case CENTER:
263                                y=(height/2)-(sh/2);
264                                break;
265                            case BOTTOM:
266                                y=height-sh;
267                                break;
268                        }
269                        g.drawImage(img, x, y, sw, sh, null);
270                    } else {
271                        int sw = rect.width;
272                        int sh = rect.height;
273                        if(imageScale != 1.0) {
274                            sw = (int)(sw * imageScale);
275                            sh = (int)(sh * imageScale);
276                        }
277                        g.drawImage(img, rect.x, rect.y, sw, sh, null);
278                    }
279                }
280            }
281        }
282        
283    }
284    
285    private void drawBorder(Graphics2D g, Shape shape, int width, int height) {
286        if(getBorderPaint() != null) {
287            g.setPaint(getBorderPaint());
288            g.setStroke(new BasicStroke(getBorderWidth()));
289            g.draw(shape);
290        }
291    }
292    
293    public boolean isScaleToFit() {
294        return scaleToFit;
295    }
296    
297    public void setScaleToFit(boolean scaleToFit) {
298        boolean old = isScaleToFit(); 
299        this.scaleToFit = scaleToFit;
300        setDirty(true);
301        firePropertyChange("scaleToFit", old, isScaleToFit());
302    }
303    
304    public ScaleType getScaleType() {
305        return scaleType;
306    }
307    
308    public void setScaleType(ScaleType scaleType) {
309        ScaleType old = getScaleType();
310        this.scaleType = scaleType;
311        setDirty(true);
312        firePropertyChange("scaleType", old, getScaleType());
313    }
314
315    /**
316     * Gets the current scaling factor used when drawing an image.
317     * @return the current scaling factor
318     */
319    public double getImageScale() {
320        return imageScale;
321    }
322    
323    /**
324     * Sets the scaling factor used when drawing the image
325     * @param imageScale the new image scaling factor
326     */
327    public void setImageScale(double imageScale) {
328        double old = getImageScale();
329        this.imageScale = imageScale;
330        setDirty(true);
331        firePropertyChange("imageScale",old,this.imageScale);
332    }
333    
334    /**
335     * Indicates if the image will be repeated horizontally.
336     * @return if the image will be repeated horizontally
337     */
338    public boolean isHorizontalRepeat() {
339        return horizontalRepeat;
340    }
341    
342    /**
343     * Sets if the image should be repeated horizontally.
344     * @param horizontalRepeat the new horizontal repeat value
345     */
346    public void setHorizontalRepeat(boolean horizontalRepeat) {
347        boolean old = this.isHorizontalRepeat();
348        this.horizontalRepeat = horizontalRepeat;
349        setDirty(true);
350        firePropertyChange("horizontalRepeat",old,this.horizontalRepeat);
351    }
352
353    /**
354     * Indicates if the image will be repeated vertically.
355     * @return if the image will be repeated vertically
356     */
357    public boolean isVerticalRepeat() {
358        return verticalRepeat;
359    }
360    
361    /**
362     * Sets if the image should be repeated vertically.
363     * @param verticalRepeat new value for the vertical repeat
364     */
365    public void setVerticalRepeat(boolean verticalRepeat) {
366        boolean old = this.isVerticalRepeat();
367        this.verticalRepeat = verticalRepeat;
368        setDirty(true);
369        firePropertyChange("verticalRepeat",old,this.verticalRepeat);
370    }
371    
372    /**
373     *
374     */
375    @Override
376    protected Shape provideShape(Graphics2D g, Object comp, int width, int height) {
377        if(getImage() != null) {
378            BufferedImage bi = getImage();
379            int imgWidth = bi.getWidth();
380            int imgHeight = bi.getHeight();
381            
382            return calculateLayout(imgWidth, imgHeight, width, height);
383        }
384        return new Rectangle(0,0,0,0);
385        
386    }
387}