001    /*
002     * $Id: ImagePainter.java 3288 2009-03-10 14:36:28Z 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    
022    package org.jdesktop.swingx.painter;
023    
024    //import org.jdesktop.swingx.editors.PainterUtil;
025    import org.jdesktop.swingx.painter.effects.AreaEffect;
026    
027    import javax.imageio.ImageIO;
028    import java.awt.*;
029    import java.awt.geom.Area;
030    import java.awt.image.BufferedImage;
031    import java.io.IOException;
032    import java.net.URL;
033    import java.util.logging.Logger;
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 definition 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 AbstractAreaPainter<Object> {
063        /**
064         * Logger to use
065         */
066        private static final Logger LOG = Logger.getLogger(ImagePainter.class.getName());
067        
068        /**
069         * The image to draw
070         */
071        private transient BufferedImage img;
072        
073        private URL imageURL;
074        
075        private boolean horizontalRepeat;
076        private boolean verticalRepeat;
077        
078        private boolean scaleToFit = false;
079        private ScaleType scaleType = ScaleType.InsideFit;
080        
081        public enum ScaleType { InsideFit, OutsideFit, Distort }
082        
083        /**
084         * Create a new ImagePainter. By default there is no image, and the alignment
085         * is centered.
086         */
087        public ImagePainter() {
088            this((BufferedImage)null);
089        }
090        
091        /**
092         * Create a new ImagePainter with the specified image and the Style
093         * Style.CENTERED
094         *
095         * @param image the image to be painted
096         */
097        public ImagePainter(BufferedImage image) {
098            this(image,HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
099        }
100        
101        /**
102         * Create a new ImagePainter with the specified image and alignment.
103         * @param horizontal the horizontal alignment
104         * @param vertical the vertical alignment
105         * @param image the image to be painted
106         */
107        public ImagePainter(BufferedImage image, HorizontalAlignment horizontal, VerticalAlignment vertical) {
108            super();
109            setCacheable(true);
110            this.img = image;
111            this.setVerticalAlignment(vertical);
112            this.setHorizontalAlignment(horizontal);
113            this.setFillPaint(null);
114            this.setBorderPaint(null);
115        }
116        
117        public ImagePainter(URL url) throws IOException {
118            this(ImageIO.read(url));
119        }
120        public ImagePainter(URL url, HorizontalAlignment horizontal, VerticalAlignment vertical) throws IOException {
121            this(ImageIO.read(url),horizontal,vertical);
122        }
123        
124        /**
125         * Sets the image to paint with.
126         * @param image if null, clears the image. Otherwise, this will set the
127         * image to be painted.
128         */
129        public void setImage(BufferedImage image) {
130            if (image != img) {
131                Image oldImage = img;
132                img = image;
133                setDirty(true);
134                firePropertyChange("image", oldImage, img);
135            }
136        }
137        
138        /**
139         * Gets the current image used for painting.
140         * @return the image used for painting
141         */
142        public BufferedImage getImage() {
143            if(img == null && imageURL != null) {
144                loadImage();
145            }
146            return img;
147        }
148        
149        /**
150         * {@inheritDoc}
151         */
152        protected void doPaint(Graphics2D g, Object component, int width, int height) {
153            if (img == null && imageURL != null) {
154                loadImage();
155            }
156            
157            Shape shape = provideShape(g, component,width,height);
158            switch (getStyle()) {
159                case BOTH:
160                    drawBackground(g,shape,width,height);
161                    drawBorder(g,shape,width,height);
162                    break;
163                case FILLED:
164                    drawBackground(g,shape,width,height);
165                    break;
166                case OUTLINE:
167                    drawBorder(g,shape,width,height);
168                    break;
169                case NONE:
170                    break;
171            }
172        }
173        
174        private void drawBackground(Graphics2D g, Shape shape, int width, int height) {
175            Paint p = getFillPaint();
176            
177            if(p != null) {
178                if(isPaintStretched()) {
179                    p = calculateSnappedPaint(p, width, height);
180                }
181                g.setPaint(p);
182                g.fill(shape);
183            }
184            
185            if(getAreaEffects() != null) {
186                for(AreaEffect ef : getAreaEffects()) {
187                    ef.apply(g, shape, width, height);
188                }
189            }
190            
191            
192            if (img != null) {
193                int imgWidth = img.getWidth(null);
194                int imgHeight = img.getHeight(null);
195                if (imgWidth == -1 || imgHeight == -1) {
196                    //image hasn't completed loading, do nothing
197                } else {
198                    Rectangle rect = calculateLayout(imgWidth, imgHeight, width, height);
199                    if(verticalRepeat || horizontalRepeat) {
200                        Shape oldClip = g.getClip();
201                        Shape clip = g.getClip();
202                        if(clip == null) {
203                            clip = new Rectangle(0,0,width,height);
204                        }
205                        Area area = new Area(clip);
206                        TexturePaint tp = new TexturePaint(img,rect);
207                        if(verticalRepeat && horizontalRepeat) {
208                            area.intersect(new Area(new Rectangle(0,0,width,height)));
209                            g.setClip(area);
210                        } else if (verticalRepeat) {
211                            area.intersect(new Area(new Rectangle(rect.x,0,rect.width,height)));
212                            g.setClip(area);
213                        } else {
214                            area.intersect(new Area(new Rectangle(0,rect.y,width,rect.height)));
215                            g.setClip(area);
216                        }
217                        g.setPaint(tp);
218                        g.fillRect(0,0,width,height);
219                        g.setClip(oldClip);
220                    } else {
221                        if(scaleToFit) {
222                            int sw = imgWidth;
223                            int sh = imgHeight;
224                            if(scaleType == ScaleType.InsideFit) {
225                                if(sw > width) {
226                                    float scale = (float)width/(float)sw;
227                                    sw = (int)(sw * scale);
228                                    sh = (int)(sh * scale);
229                                }
230                                if(sh > height) {
231                                    float scale = (float)height/(float)sh;
232                                    sw = (int)(sw * scale);
233                                    sh = (int)(sh * scale);
234                                }
235                            }
236                            if(scaleType == ScaleType.OutsideFit) {
237                                if(sw > width) {
238                                    float scale = (float)width/(float)sw;
239                                    sw = (int)(sw * scale);
240                                    sh = (int)(sh * scale);
241                                }
242                                if(sh < height) {
243                                    float scale = (float)height/(float)sh;
244                                    sw = (int)(sw * scale);
245                                    sh = (int)(sh * scale);
246                                }
247                            }
248                            if(scaleType == ScaleType.Distort) {
249                                sw = width;
250                                sh = height;
251                            }
252                            int x=0;
253                            int y=0;
254                            switch(getHorizontalAlignment()) {
255                                case CENTER:
256                                    x=(width/2)-(sw/2);
257                                    break;
258                                case RIGHT:
259                                    x=width-sw;
260                                    break;
261                            }
262                            switch(getVerticalAlignment()) {
263                                case CENTER:
264                                    y=(height/2)-(sh/2);
265                                    break;
266                                case BOTTOM:
267                                    y=height-sh;
268                                    break;
269                            }
270                            g.drawImage(img, x, y, sw, sh, null);
271                        } else {
272                            int sw = rect.width;
273                            int sh = rect.height;
274                            if(imageScale != 1.0) {
275                                sw = (int)((double)sw * imageScale);
276                                sh = (int)((double)sh * imageScale);
277                            }
278                            g.drawImage(img, rect.x, rect.y, sw, sh, null);
279                        }
280                    }
281                }
282            }
283            
284        }
285        
286        private void drawBorder(Graphics2D g, Shape shape, int width, int height) {
287            if(getBorderPaint() != null) {
288                g.setPaint(getBorderPaint());
289                g.setStroke(new BasicStroke(getBorderWidth()));
290                g.draw(shape);
291            }
292        }
293        
294        public void setScaleToFit(boolean scaleToFit) {
295            boolean old = isScaleToFit(); 
296            this.scaleToFit = scaleToFit;
297            setDirty(true);
298            firePropertyChange("scaleToFit", old, isScaleToFit());
299        }
300        
301        
302        public boolean isScaleToFit() {
303            return scaleToFit;
304        }
305    
306    
307        private double imageScale = 1.0;
308    
309        private Logger log = Logger.getLogger(ImagePainter.class.getName());
310        
311        /**
312         * Sets the scaling factor used when drawing the image
313         * @param imageScale the new image scaling factor
314         */
315        public void setImageScale(double imageScale) {
316            double old = getImageScale();
317            this.imageScale = imageScale;
318            setDirty(true);
319            firePropertyChange("imageScale",old,this.imageScale);
320        }
321        /**
322         * Gets the current scaling factor used when drawing an image.
323         * @return the current scaling factor
324         */
325        public double getImageScale() {
326            return imageScale;
327        }
328        
329        private void loadImage() {
330            try {
331                String img = getImageString();
332                // use the resolver if it's there
333                if(img != null) {
334                    URL url = new URL(img);
335                    setImage(ImageIO.read(url));
336                }
337            } catch (IOException ex) {
338                log.severe("ex: " + ex.getMessage());
339                ex.printStackTrace();
340            }
341        }
342        
343        private String imageString;
344        
345        /**
346         * Used by the persistence mechanism.
347         */
348        public String getImageString() {
349            return imageString;
350        }
351        
352        /**
353         * Used by the persistence mechanism.
354         */
355        public void setImageString(String imageString) {
356            log.fine("setting image string to: " + imageString);
357            String old = this.getImageString();
358            this.imageString = imageString;
359            loadImage();
360            setDirty(true);
361            firePropertyChange("imageString",old,imageString);
362        }
363        /*
364        public String getBaseURL() {
365            return baseURL;
366        }
367         
368        private String baseURL;
369         
370        public void setBaseURL(String baseURL) {
371            this.baseURL = baseURL;
372        }*/
373        
374        /**
375         * Indicates if the image will be repeated horizontally.
376         * @return if the image will be repeated horizontally
377         */
378        public boolean isHorizontalRepeat() {
379            return horizontalRepeat;
380        }
381        
382        /**
383         * Sets if the image should be repeated horizontally.
384         * @param horizontalRepeat the new horizontal repeat value
385         */
386        public void setHorizontalRepeat(boolean horizontalRepeat) {
387            boolean old = this.isHorizontalRepeat();
388            this.horizontalRepeat = horizontalRepeat;
389            setDirty(true);
390            firePropertyChange("horizontalRepeat",old,this.horizontalRepeat);
391        }
392        
393        /**
394         * Indicates if the image will be repeated vertically.
395         * @return if the image will be repeated vertically
396         */
397        public boolean isVerticalRepeat() {
398            return verticalRepeat;
399        }
400        
401        /**
402         * Sets if the image should be repeated vertically.
403         * @param verticalRepeat new value for the vertical repeat
404         */
405        public void setVerticalRepeat(boolean verticalRepeat) {
406            boolean old = this.isVerticalRepeat();
407            this.verticalRepeat = verticalRepeat;
408            setDirty(true);
409            firePropertyChange("verticalRepeat",old,this.verticalRepeat);
410        }
411        
412        /**
413         *
414         */
415        protected Shape provideShape(Graphics2D g, Object comp, int width, int height) {
416            if(getImage() != null) {
417                BufferedImage img = getImage();
418                int imgWidth = img.getWidth();
419                int imgHeight = img.getHeight();
420                
421                return calculateLayout(imgWidth, imgHeight, width, height);
422            }
423            return new Rectangle(0,0,0,0);
424            
425        }
426        
427        public ScaleType getScaleType() {
428            return scaleType;
429        }
430        
431        public void setScaleType(ScaleType scaleType) {
432            ScaleType old = getScaleType();
433            this.scaleType = scaleType;
434            setDirty(true);
435            firePropertyChange("scaleType", old, getScaleType());
436        }
437        
438    }