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 }