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}