001/*
002 * SVG Salamander
003 * Copyright (c) 2004, Mark McKay
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or 
007 * without modification, are permitted provided that the following
008 * conditions are met:
009 *
010 *   - Redistributions of source code must retain the above 
011 *     copyright notice, this list of conditions and the following
012 *     disclaimer.
013 *   - Redistributions in binary form must reproduce the above
014 *     copyright notice, this list of conditions and the following
015 *     disclaimer in the documentation and/or other materials 
016 *     provided with the distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
029 * OF THE POSSIBILITY OF SUCH DAMAGE. 
030 * 
031 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
032 * projects can be found at http://www.kitfox.com
033 *
034 * Created on April 21, 2005, 10:45 AM
035 */
036
037package com.kitfox.svg.app.beans;
038
039import com.kitfox.svg.SVGCache;
040import com.kitfox.svg.SVGDiagram;
041import com.kitfox.svg.SVGException;
042import com.kitfox.svg.SVGUniverse;
043import java.awt.Component;
044import java.awt.Dimension;
045import java.awt.Graphics;
046import java.awt.Graphics2D;
047import java.awt.Image;
048import java.awt.Rectangle;
049import java.awt.RenderingHints;
050import java.awt.geom.AffineTransform;
051import java.awt.geom.Rectangle2D;
052import java.awt.image.BufferedImage;
053import java.beans.PropertyChangeListener;
054import java.beans.PropertyChangeSupport;
055import java.net.URI;
056import javax.swing.ImageIcon;
057
058
059/**
060 *
061 * @author kitfox
062 */
063public class SVGIcon extends ImageIcon
064{
065    public static final long serialVersionUID = 1;
066
067    public static final String PROP_AUTOSIZE = "PROP_AUTOSIZE";
068    
069    private final PropertyChangeSupport changes = new PropertyChangeSupport(this);
070    
071    SVGUniverse svgUniverse = SVGCache.getSVGUniverse();
072    public static final int INTERP_NEAREST_NEIGHBOR = 0;
073    public static final int INTERP_BILINEAR = 1;
074    public static final int INTERP_BICUBIC = 2;
075    
076    private boolean antiAlias;
077    private int interpolation = INTERP_NEAREST_NEIGHBOR;
078    private boolean clipToViewbox;
079    
080    URI svgURI;
081    
082//    private boolean scaleToFit;
083    AffineTransform scaleXform = new AffineTransform();
084
085    public static final int AUTOSIZE_NONE = 0;
086    public static final int AUTOSIZE_HORIZ = 1;
087    public static final int AUTOSIZE_VERT = 2;
088    public static final int AUTOSIZE_BESTFIT = 3;
089    public static final int AUTOSIZE_STRETCH = 4;
090    private int autosize = AUTOSIZE_NONE;
091    
092    Dimension preferredSize;
093    
094    /** Creates a new instance of SVGIcon */
095    public SVGIcon()
096    {
097    }
098    
099    public void addPropertyChangeListener(PropertyChangeListener p)
100    {
101        changes.addPropertyChangeListener(p);
102    }
103    
104    public void removePropertyChangeListener(PropertyChangeListener p)
105    {
106        changes.removePropertyChangeListener(p);
107    }
108    
109    public Image getImage()
110    {
111        BufferedImage bi = new BufferedImage(getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB);
112        paintIcon(null, bi.getGraphics(), 0, 0);
113        return bi;
114    }
115    
116    /**
117     * @return height of this icon
118     */
119    public int getIconHeight()
120    {
121        if (preferredSize != null &&
122                (autosize == AUTOSIZE_VERT || autosize == AUTOSIZE_STRETCH 
123                || autosize == AUTOSIZE_BESTFIT))
124        {
125            return preferredSize.height;
126        }
127        
128        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
129        if (diagram == null)
130        {
131            return 0;
132        }
133        return (int)diagram.getHeight();
134    }
135    
136    /**
137     * @return width of this icon
138     */
139    public int getIconWidth()
140    {
141        if (preferredSize != null &&
142                (autosize == AUTOSIZE_HORIZ || autosize == AUTOSIZE_STRETCH 
143                || autosize == AUTOSIZE_BESTFIT))
144        {
145            return preferredSize.width;
146        }
147        
148        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
149        if (diagram == null)
150        {
151            return 0;
152        }
153        return (int)diagram.getWidth();
154    }
155    
156    /**
157     * Draws the icon to the specified component.
158     * @param comp - Component to draw icon to.  This is ignored by SVGIcon, and can be set to null; only gg is used for drawing the icon
159     * @param gg - Graphics context to render SVG content to
160     * @param x - X coordinate to draw icon
161     * @param y - Y coordinate to draw icon
162     */
163    public void paintIcon(Component comp, Graphics gg, int x, int y)
164    {
165        //Copy graphics object so that 
166        Graphics2D g = (Graphics2D)gg.create();
167        paintIcon(comp, g, x, y);
168        g.dispose();
169    }
170    
171    private void paintIcon(Component comp, Graphics2D g, int x, int y)
172    {
173        Object oldAliasHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
174        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
175        
176        Object oldInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
177        switch (interpolation)
178        {
179            case INTERP_NEAREST_NEIGHBOR:
180                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
181                break;
182            case INTERP_BILINEAR:
183                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
184                break;
185            case INTERP_BICUBIC:
186                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
187                break;
188        }
189        
190        
191        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
192        if (diagram == null)
193        {
194            return;
195        }
196        
197        g.translate(x, y);
198        diagram.setIgnoringClipHeuristic(!clipToViewbox);
199        if (clipToViewbox)
200        {
201            g.setClip(new Rectangle2D.Float(0, 0, diagram.getWidth(), diagram.getHeight()));
202        }
203        
204        
205        if (autosize == AUTOSIZE_NONE)
206        {
207            try
208            {
209                diagram.render(g);
210                g.translate(-x, -y);
211                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
212            }
213            catch (Exception e)
214            {
215                throw new RuntimeException(e);
216            }
217            return;
218        }
219        
220        final int width = getIconWidth();
221        final int height = getIconHeight();
222//        int width = getWidth();
223//        int height = getHeight();
224        
225        if (width == 0 || height == 0)
226        {
227            return;
228        }
229        
230//        if (width == 0 || height == 0)
231//        {
232//           //Chances are we're rendering offscreen
233//            Dimension dim = getSize();
234//            width = dim.width;
235//            height = dim.height;
236//            return;
237//        }
238        
239//        g.setClip(0, 0, width, height);
240        
241        
242//        final Rectangle2D.Double rect = new Rectangle2D.Double();
243//        diagram.getViewRect(rect);
244//        
245//        scaleXform.setToScale(width / rect.width, height / rect.height);
246        double diaWidth = diagram.getWidth();
247        double diaHeight = diagram.getHeight();
248        
249        double scaleW = 1;
250        double scaleH = 1;
251        if (autosize == AUTOSIZE_BESTFIT)
252        {
253            scaleW = scaleH = (height / diaHeight < width / diaWidth) 
254                    ? height / diaHeight : width / diaWidth;
255        }
256        else if (autosize == AUTOSIZE_HORIZ)
257        {
258            scaleW = scaleH = width / diaWidth;
259        }
260        else if (autosize == AUTOSIZE_VERT)
261        {
262            scaleW = scaleH = height / diaHeight;
263        }
264        else if (autosize == AUTOSIZE_STRETCH)
265        {
266            scaleW = width / diaWidth;
267            scaleH = height / diaHeight;
268        }
269        scaleXform.setToScale(scaleW, scaleH);
270        
271        AffineTransform oldXform = g.getTransform();
272        g.transform(scaleXform);
273        
274        try
275        {
276            diagram.render(g);
277        }
278        catch (SVGException e)
279        {
280            throw new RuntimeException(e);
281        }
282        
283        g.setTransform(oldXform);
284        
285        
286        g.translate(-x, -y);
287        
288        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
289        if (oldInterpolationHint != null)
290        {
291            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldInterpolationHint);
292        }
293    }
294    
295    /**
296     * @return the universe this icon draws it's SVGDiagrams from
297     */
298    public SVGUniverse getSvgUniverse()
299    {
300        return svgUniverse;
301    }
302    
303    public void setSvgUniverse(SVGUniverse svgUniverse)
304    {
305        SVGUniverse old = this.svgUniverse;
306        this.svgUniverse = svgUniverse;
307        changes.firePropertyChange("svgUniverse", old, svgUniverse);
308    }
309    
310    /**
311     * @return the uni of the document being displayed by this icon
312     */
313    public URI getSvgURI()
314    {
315        return svgURI;
316    }
317    
318    /**
319     * Loads an SVG document from a URI.
320     * @param svgURI - URI to load document from
321     */
322    public void setSvgURI(URI svgURI)
323    {
324        URI old = this.svgURI;
325        this.svgURI = svgURI;
326        
327        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
328        if (diagram != null)
329        {
330            Dimension size = getPreferredSize();
331            if (size == null)
332            {
333                size = new Dimension((int)diagram.getRoot().getDeviceWidth(), (int)diagram.getRoot().getDeviceHeight());
334            }
335            diagram.setDeviceViewport(new Rectangle(0, 0, size.width, size.height));
336        }
337        
338        changes.firePropertyChange("svgURI", old, svgURI);
339    }
340    
341    /**
342     * Loads an SVG document from the classpath.  This function is equivilant to
343     * setSvgURI(new URI(getClass().getResource(resourcePath).toString());
344     * @param resourcePath - resource to load
345     */
346    public void setSvgResourcePath(String resourcePath)
347    {
348        URI old = this.svgURI;
349        
350        try
351        {
352            svgURI = new URI(getClass().getResource(resourcePath).toString());
353            changes.firePropertyChange("svgURI", old, svgURI);
354            
355            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
356            if (diagram != null)
357            {
358                diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
359            }
360            
361        }
362        catch (Exception e)
363        {
364            svgURI = old;
365        }
366    }
367    
368    /**
369     * If this SVG document has a viewbox, if scaleToFit is set, will scale the viewbox to match the
370     * preferred size of this icon
371     * @deprecated 
372     * @return 
373     */
374    public boolean isScaleToFit()
375    {
376        return autosize == AUTOSIZE_STRETCH;
377    }
378    
379    /**
380     * @deprecated 
381     * @return 
382     */
383    public void setScaleToFit(boolean scaleToFit)
384    {
385        setAutosize(AUTOSIZE_STRETCH);
386//        boolean old = this.scaleToFit;
387//        this.scaleToFit = scaleToFit;
388//        firePropertyChange("scaleToFit", old, scaleToFit);
389    }
390    
391    public Dimension getPreferredSize()
392    {
393        if (preferredSize == null)
394        {
395            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
396            if (diagram != null)
397            {
398                //preferredSize = new Dimension((int)diagram.getWidth(), (int)diagram.getHeight());
399                setPreferredSize(new Dimension((int)diagram.getWidth(), (int)diagram.getHeight()));
400            }
401        }
402        
403        return new Dimension(preferredSize);
404    }
405    
406    public void setPreferredSize(Dimension preferredSize)
407    {
408        Dimension old = this.preferredSize;
409        this.preferredSize = preferredSize;
410        
411        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
412        if (diagram != null)
413        {
414            diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
415        }
416        
417        changes.firePropertyChange("preferredSize", old, preferredSize);
418    }
419    
420    
421    /**
422     * @return true if antiAliasing is turned on.
423     * @deprecated
424     */
425    public boolean getUseAntiAlias()
426    {
427        return getAntiAlias();
428    }
429    
430    /**
431     * @param antiAlias true to use antiAliasing.
432     * @deprecated
433     */
434    public void setUseAntiAlias(boolean antiAlias)
435    {
436        setAntiAlias(antiAlias);
437    }
438    
439    /**
440     * @return true if antiAliasing is turned on.
441     */
442    public boolean getAntiAlias()
443    {
444        return antiAlias;
445    }
446    
447    /**
448     * @param antiAlias true to use antiAliasing.
449     */
450    public void setAntiAlias(boolean antiAlias)
451    {
452        boolean old = this.antiAlias;
453        this.antiAlias = antiAlias;
454        changes.firePropertyChange("antiAlias", old, antiAlias);
455    }
456    
457    /**
458     * @return interpolation used in rescaling images
459     */
460    public int getInterpolation()
461    {
462        return interpolation;
463    }
464    
465    /**
466     * @param interpolation Interpolation value used in rescaling images.
467     * Should be one of
468     *    INTERP_NEAREST_NEIGHBOR - Fastest, one pixel resampling, poor quality
469     *    INTERP_BILINEAR - four pixel resampling
470     *    INTERP_BICUBIC - Slowest, nine pixel resampling, best quality
471     */
472    public void setInterpolation(int interpolation)
473    {
474        int old = this.interpolation;
475        this.interpolation = interpolation;
476        changes.firePropertyChange("interpolation", old, interpolation);
477    }
478    
479    /**
480     * clipToViewbox will set a clip box equivilant to the SVG's viewbox before
481     * rendering.
482     */
483    public boolean isClipToViewbox()
484    {
485        return clipToViewbox;
486    }
487    
488    public void setClipToViewbox(boolean clipToViewbox)
489    {
490        this.clipToViewbox = clipToViewbox;
491    }
492
493    /**
494     * @return the autosize
495     */
496    public int getAutosize()
497    {
498        return autosize;
499    }
500
501    /**
502     * @param autosize the autosize to set
503     */
504    public void setAutosize(int autosize)
505    {
506        int oldAutosize = this.autosize;
507        this.autosize = autosize;
508        changes.firePropertyChange(PROP_AUTOSIZE, oldAutosize, autosize);
509    }
510        
511}