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}