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    @Override
110    public Image getImage()
111    {
112        BufferedImage bi = new BufferedImage(getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB);
113        paintIcon(null, bi.getGraphics(), 0, 0);
114        return bi;
115    }
116    
117    /**
118     * @return height of this icon
119     */
120    public int getIconHeightIgnoreAutosize()
121    {
122        if (preferredSize != null &&
123                (autosize == AUTOSIZE_VERT || autosize == AUTOSIZE_STRETCH 
124                || autosize == AUTOSIZE_BESTFIT))
125        {
126            return preferredSize.height;
127        }
128        
129        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
130        if (diagram == null)
131        {
132            return 0;
133        }
134        return (int)diagram.getHeight();
135    }
136    
137    /**
138     * @return width of this icon
139     */
140
141    public int getIconWidthIgnoreAutosize()
142    {
143        if (preferredSize != null &&
144                (autosize == AUTOSIZE_HORIZ || autosize == AUTOSIZE_STRETCH 
145                || autosize == AUTOSIZE_BESTFIT))
146        {
147            return preferredSize.width;
148        }
149        
150        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
151        if (diagram == null)
152        {
153            return 0;
154        }
155        return (int)diagram.getWidth();
156    }
157    
158    private boolean isAutoSizeBestFitUseFixedHeight(final int iconWidthIgnoreAutosize, final int iconHeightIgnoreAutosize,
159                final SVGDiagram diagram)
160    {
161        return iconHeightIgnoreAutosize/diagram.getHeight() < iconWidthIgnoreAutosize/diagram.getWidth();
162    }
163    
164    @Override
165    public int getIconWidth()
166    {
167        final int iconWidthIgnoreAutosize = getIconWidthIgnoreAutosize();
168        final int iconHeightIgnoreAutosize = getIconHeightIgnoreAutosize();
169                final SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
170        if (preferredSize != null && (autosize == AUTOSIZE_VERT ||
171                                             (autosize == AUTOSIZE_BESTFIT && isAutoSizeBestFitUseFixedHeight(iconWidthIgnoreAutosize, iconHeightIgnoreAutosize, diagram))))
172        {
173                final double aspectRatio = diagram.getHeight()/diagram.getWidth();
174                return (int)(iconHeightIgnoreAutosize / aspectRatio);
175        }
176        else
177        {
178                return iconWidthIgnoreAutosize;
179        }
180    }
181    
182    @Override
183    public int getIconHeight()
184    {
185        final int iconWidthIgnoreAutosize = getIconWidthIgnoreAutosize();
186        final int iconHeightIgnoreAutosize = getIconHeightIgnoreAutosize();
187                final SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
188        if (preferredSize != null && (autosize == AUTOSIZE_HORIZ ||
189                                      (autosize == AUTOSIZE_BESTFIT && !isAutoSizeBestFitUseFixedHeight(iconWidthIgnoreAutosize, iconHeightIgnoreAutosize, diagram))))
190        {
191                final double aspectRatio = diagram.getHeight()/diagram.getWidth();
192                return (int)(iconWidthIgnoreAutosize * aspectRatio);
193        }
194        else
195        {
196                return iconHeightIgnoreAutosize;
197        }
198    }
199
200    
201    /**
202     * Draws the icon to the specified component.
203     * @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
204     * @param gg - Graphics context to render SVG content to
205     * @param x - X coordinate to draw icon
206     * @param y - Y coordinate to draw icon
207     */
208    @Override
209    public void paintIcon(Component comp, Graphics gg, int x, int y)
210    {
211        //Copy graphics object so that 
212        Graphics2D g = (Graphics2D)gg.create();
213        paintIcon(comp, g, x, y);
214        g.dispose();
215    }
216    
217    private void paintIcon(Component comp, Graphics2D g, int x, int y)
218    {
219        Object oldAliasHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
220        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
221        
222        Object oldInterpolationHint = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
223        switch (interpolation)
224        {
225            case INTERP_NEAREST_NEIGHBOR:
226                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
227                break;
228            case INTERP_BILINEAR:
229                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
230                break;
231            case INTERP_BICUBIC:
232                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
233                break;
234        }
235        
236        
237        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
238        if (diagram == null)
239        {
240            return;
241        }
242        
243        g.translate(x, y);
244        diagram.setIgnoringClipHeuristic(!clipToViewbox);
245        if (clipToViewbox)
246        {
247            g.setClip(new Rectangle2D.Float(0, 0, diagram.getWidth(), diagram.getHeight()));
248        }
249        
250        
251        if (autosize == AUTOSIZE_NONE)
252        {
253            try
254            {
255                diagram.render(g);
256                g.translate(-x, -y);
257                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
258            }
259            catch (Exception e)
260            {
261                throw new RuntimeException(e);
262            }
263            return;
264        }
265        
266        final int width = getIconWidthIgnoreAutosize();
267        final int height = getIconHeightIgnoreAutosize();
268//        int width = getWidth();
269//        int height = getHeight();
270        
271        if (width == 0 || height == 0)
272        {
273            return;
274        }
275        
276//        if (width == 0 || height == 0)
277//        {
278//           //Chances are we're rendering offscreen
279//            Dimension dim = getSize();
280//            width = dim.width;
281//            height = dim.height;
282//            return;
283//        }
284        
285//        g.setClip(0, 0, width, height);
286        
287        
288//        final Rectangle2D.Double rect = new Rectangle2D.Double();
289//        diagram.getViewRect(rect);
290//        
291//        scaleXform.setToScale(width / rect.width, height / rect.height);
292        double diaWidth = diagram.getWidth();
293        double diaHeight = diagram.getHeight();
294        
295        double scaleW = 1;
296        double scaleH = 1;
297        if (autosize == AUTOSIZE_BESTFIT)
298        {
299            scaleW = scaleH = (height / diaHeight < width / diaWidth) 
300                    ? height / diaHeight : width / diaWidth;
301        }
302        else if (autosize == AUTOSIZE_HORIZ)
303        {
304            scaleW = scaleH = width / diaWidth;
305        }
306        else if (autosize == AUTOSIZE_VERT)
307        {
308            scaleW = scaleH = height / diaHeight;
309        }
310        else if (autosize == AUTOSIZE_STRETCH)
311        {
312            scaleW = width / diaWidth;
313            scaleH = height / diaHeight;
314        }
315        scaleXform.setToScale(scaleW, scaleH);
316        
317        AffineTransform oldXform = g.getTransform();
318        g.transform(scaleXform);
319        
320        try
321        {
322            diagram.render(g);
323        }
324        catch (SVGException e)
325        {
326            throw new RuntimeException(e);
327        }
328        
329        g.setTransform(oldXform);
330        
331        
332        g.translate(-x, -y);
333        
334        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAliasHint);
335        if (oldInterpolationHint != null)
336        {
337            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, oldInterpolationHint);
338        }
339    }
340    
341    /**
342     * @return the universe this icon draws it's SVGDiagrams from
343     */
344    public SVGUniverse getSvgUniverse()
345    {
346        return svgUniverse;
347    }
348    
349    public void setSvgUniverse(SVGUniverse svgUniverse)
350    {
351        SVGUniverse old = this.svgUniverse;
352        this.svgUniverse = svgUniverse;
353        changes.firePropertyChange("svgUniverse", old, svgUniverse);
354    }
355    
356    /**
357     * @return the uni of the document being displayed by this icon
358     */
359    public URI getSvgURI()
360    {
361        return svgURI;
362    }
363    
364    /**
365     * Loads an SVG document from a URI.
366     * @param svgURI - URI to load document from
367     */
368    public void setSvgURI(URI svgURI)
369    {
370        URI old = this.svgURI;
371        this.svgURI = svgURI;
372        
373        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
374        if (diagram != null)
375        {
376            Dimension size = getPreferredSize();
377            if (size == null)
378            {
379                size = new Dimension((int)diagram.getRoot().getDeviceWidth(), (int)diagram.getRoot().getDeviceHeight());
380            }
381            diagram.setDeviceViewport(new Rectangle(0, 0, size.width, size.height));
382        }
383        
384        changes.firePropertyChange("svgURI", old, svgURI);
385    }
386    
387    /**
388     * Loads an SVG document from the classpath.  This function is equivilant to
389     * setSvgURI(new URI(getClass().getResource(resourcePath).toString());
390     * @param resourcePath - resource to load
391     */
392    public void setSvgResourcePath(String resourcePath)
393    {
394        URI old = this.svgURI;
395        
396        try
397        {
398            svgURI = new URI(getClass().getResource(resourcePath).toString());
399            changes.firePropertyChange("svgURI", old, svgURI);
400            
401            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
402            if (diagram != null)
403            {
404                diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
405            }
406            
407        }
408        catch (Exception e)
409        {
410            svgURI = old;
411        }
412    }
413    
414    /**
415     * If this SVG document has a viewbox, if scaleToFit is set, will scale the viewbox to match the
416     * preferred size of this icon
417     * @deprecated 
418     * @return 
419     */
420    public boolean isScaleToFit()
421    {
422        return autosize == AUTOSIZE_STRETCH;
423    }
424    
425    /**
426     * @deprecated 
427     */
428    public void setScaleToFit(boolean scaleToFit)
429    {
430        setAutosize(AUTOSIZE_STRETCH);
431//        boolean old = this.scaleToFit;
432//        this.scaleToFit = scaleToFit;
433//        firePropertyChange("scaleToFit", old, scaleToFit);
434    }
435    
436    public Dimension getPreferredSize()
437    {
438        if (preferredSize == null)
439        {
440            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
441            if (diagram != null)
442            {
443                //preferredSize = new Dimension((int)diagram.getWidth(), (int)diagram.getHeight());
444                setPreferredSize(new Dimension((int)diagram.getWidth(), (int)diagram.getHeight()));
445            }
446        }
447        
448        return new Dimension(preferredSize);
449    }
450    
451    public void setPreferredSize(Dimension preferredSize)
452    {
453        Dimension old = this.preferredSize;
454        this.preferredSize = preferredSize;
455        
456        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
457        if (diagram != null)
458        {
459            diagram.setDeviceViewport(new Rectangle(0, 0, preferredSize.width, preferredSize.height));
460        }
461        
462        changes.firePropertyChange("preferredSize", old, preferredSize);
463    }
464    
465    
466    /**
467     * @return true if antiAliasing is turned on.
468     * @deprecated
469     */
470    public boolean getUseAntiAlias()
471    {
472        return getAntiAlias();
473    }
474    
475    /**
476     * @param antiAlias true to use antiAliasing.
477     * @deprecated
478     */
479    public void setUseAntiAlias(boolean antiAlias)
480    {
481        setAntiAlias(antiAlias);
482    }
483    
484    /**
485     * @return true if antiAliasing is turned on.
486     */
487    public boolean getAntiAlias()
488    {
489        return antiAlias;
490    }
491    
492    /**
493     * @param antiAlias true to use antiAliasing.
494     */
495    public void setAntiAlias(boolean antiAlias)
496    {
497        boolean old = this.antiAlias;
498        this.antiAlias = antiAlias;
499        changes.firePropertyChange("antiAlias", old, antiAlias);
500    }
501    
502    /**
503     * @return interpolation used in rescaling images
504     */
505    public int getInterpolation()
506    {
507        return interpolation;
508    }
509    
510    /**
511     * @param interpolation Interpolation value used in rescaling images.
512     * Should be one of
513     *    INTERP_NEAREST_NEIGHBOR - Fastest, one pixel resampling, poor quality
514     *    INTERP_BILINEAR - four pixel resampling
515     *    INTERP_BICUBIC - Slowest, nine pixel resampling, best quality
516     */
517    public void setInterpolation(int interpolation)
518    {
519        int old = this.interpolation;
520        this.interpolation = interpolation;
521        changes.firePropertyChange("interpolation", old, interpolation);
522    }
523    
524    /**
525     * clipToViewbox will set a clip box equivilant to the SVG's viewbox before
526     * rendering.
527     */
528    public boolean isClipToViewbox()
529    {
530        return clipToViewbox;
531    }
532    
533    public void setClipToViewbox(boolean clipToViewbox)
534    {
535        this.clipToViewbox = clipToViewbox;
536    }
537
538    /**
539     * @return the autosize
540     */
541    public int getAutosize()
542    {
543        return autosize;
544    }
545
546    /**
547     * @param autosize the autosize to set
548     */
549    public void setAutosize(int autosize)
550    {
551        int oldAutosize = this.autosize;
552        this.autosize = autosize;
553        changes.firePropertyChange(PROP_AUTOSIZE, oldAutosize, autosize);
554    }
555        
556}