001/* 002 * $Id: ImagePainter.java 4147 2012-02-01 17:13:24Z 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 022package org.jdesktop.swingx.painter; 023 024import java.awt.BasicStroke; 025import java.awt.Graphics2D; 026import java.awt.Image; 027import java.awt.Insets; 028import java.awt.Paint; 029import java.awt.Rectangle; 030import java.awt.Shape; 031import java.awt.TexturePaint; 032import java.awt.geom.Area; 033import java.awt.image.BufferedImage; 034import java.util.logging.Logger; 035 036import org.jdesktop.beans.JavaBean; 037import org.jdesktop.swingx.painter.effects.AreaEffect; 038 039/** 040 * <p>A Painter instance that paints an image. Any Image is acceptable. This 041 * Painter also allows the developer to specify a "Style" -- CENTERED, TILED, 042 * SCALED, POSITIONED, and CSS_POSITIONED; with the following meanings:</p> 043 * 044 * <ul> 045 * <li><b>CENTERED</b>: draws the image unscaled and positioned in the center of 046 * the component</li> 047 * <li><b>TILED</b>: draws the image repeatedly across the component, filling the 048 * entire background.</li> 049 * <li><b>SCALED</b>: draws the image stretched large enough (or small enough) to 050 * cover the entire component. The stretch may not preserve the aspect ratio of the 051 * original image.</li> 052 * <li><b>POSITIONED</b>: draws the image at the location specified by the imageLocation 053 * property. This style of drawing will respect the imageScale property.</li> 054 * <li><b>CSS_POSITIONED</b>: draws the image using CSS style background positioning. 055 *It will use the location specified by the imageLocation property. This property should 056 *contain a point with the x and y values between 0 and 1. 0,0 will put the image in the 057 *upper left hand corner, 1,1 in the lower right, and 0.5,0.5 in the center. All other values 058 *will be interpolated accordingly. For a more 059 * complete definition of the positioning algorithm see the 060 * <a href="http://www.w3.org/TR/CSS21/colors.html#propdef-background-position">CSS 2.1 spec</a>. 061 * </li> 062 * </ul> 063 * 064 * @author Richard 065 */ 066@JavaBean 067@SuppressWarnings("nls") 068public class ImagePainter extends AbstractAreaPainter<Object> { 069 public enum ScaleType { InsideFit, OutsideFit, Distort } 070 071 /** 072 * Logger to use 073 */ 074 @SuppressWarnings("unused") 075 private static final Logger LOG = Logger.getLogger(ImagePainter.class.getName()); 076 077 /** 078 * The image to draw 079 */ 080 private transient BufferedImage img; 081 082 private boolean horizontalRepeat; 083 private boolean verticalRepeat; 084 085 private boolean scaleToFit = false; 086 private ScaleType scaleType = ScaleType.InsideFit; 087 088 private double imageScale = 1.0; 089 090 /** 091 * Create a new ImagePainter. By default there is no image, and the alignment 092 * is centered. 093 */ 094 public ImagePainter() { 095 this((BufferedImage)null); 096 } 097 098 /** 099 * Create a new ImagePainter with the specified image and the Style 100 * Style.CENTERED 101 * 102 * @param image the image to be painted 103 */ 104 public ImagePainter(BufferedImage image) { 105 this(image,HorizontalAlignment.CENTER, VerticalAlignment.CENTER); 106 } 107 108 /** 109 * Create a new ImagePainter with the specified image and alignment. 110 * @param horizontal the horizontal alignment 111 * @param vertical the vertical alignment 112 * @param image the image to be painted 113 */ 114 public ImagePainter(BufferedImage image, HorizontalAlignment horizontal, VerticalAlignment vertical) { 115 super(); 116 setCacheable(true); 117 this.img = image; 118 this.setVerticalAlignment(vertical); 119 this.setHorizontalAlignment(horizontal); 120 this.setFillPaint(null); 121 this.setBorderPaint(null); 122 this.setDirty(false); 123 } 124 125 /** 126 * Sets the image to paint with. 127 * @param image if null, clears the image. Otherwise, this will set the 128 * image to be painted. 129 */ 130 public void setImage(BufferedImage image) { 131 if (image != img) { 132 Image oldImage = img; 133 img = image; 134 setDirty(true); 135 firePropertyChange("image", oldImage, img); 136 } 137 } 138 139 /** 140 * Gets the current image used for painting. 141 * @return the image used for painting 142 */ 143 public BufferedImage getImage() { 144 return img; 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 @Override 151 protected void doPaint(Graphics2D g, Object component, int width, int height) { 152 Shape shape = provideShape(g, component,width,height); 153 154 switch (getStyle()) { 155 case BOTH: 156 drawBackground(g,shape,width,height); 157 drawBorder(g,shape,width,height); 158 break; 159 case FILLED: 160 drawBackground(g,shape,width,height); 161 break; 162 case OUTLINE: 163 drawBorder(g,shape,width,height); 164 break; 165 } 166 } 167 168 private void drawBackground(Graphics2D g, Shape shape, int width, int height) { 169 Paint p = getFillPaint(); 170 171 if(p != null) { 172 if(isPaintStretched()) { 173 p = calculateSnappedPaint(p, width, height); 174 } 175 g.setPaint(p); 176 g.fill(shape); 177 } 178 179 if(getAreaEffects() != null) { 180 for(AreaEffect ef : getAreaEffects()) { 181 ef.apply(g, shape, width, height); 182 } 183 } 184 185 186 if (img != null) { 187 int imgWidth = img.getWidth(null); 188 int imgHeight = img.getHeight(null); 189 if (imgWidth == -1 || imgHeight == -1) { 190 //image hasn't completed loading, do nothing 191 } else { 192 Rectangle rect = shape.getBounds(); 193 194 if(verticalRepeat || horizontalRepeat) { 195 Shape oldClip = g.getClip(); 196 Shape clip = g.getClip(); 197 if(clip == null) { 198 clip = new Rectangle(0,0,width,height); 199 } 200 Area area = new Area(clip); 201 Insets insets = getInsets(); 202 area.intersect(new Area(new Rectangle(insets.left, insets.top, width - insets.left - insets.right, height - insets.top - insets.bottom))); 203 204 if (verticalRepeat && horizontalRepeat) { 205 area.intersect(new Area(new Rectangle(0, 0, width, height))); 206 g.setClip(area); 207 } else if (verticalRepeat) { 208 area.intersect(new Area(new Rectangle(rect.x, 0, rect.width, height))); 209 g.setClip(area); 210 } else { 211 area.intersect(new Area(new Rectangle(0, rect.y, width, rect.height))); 212 g.setClip(area); 213 } 214 215 TexturePaint tp = new TexturePaint(img, rect); 216 g.setPaint(tp); 217 g.fillRect(0,0,width,height); 218 g.setClip(oldClip); 219 } else { 220 if(scaleToFit) { 221 int sw = imgWidth; 222 int sh = imgHeight; 223 if(scaleType == ScaleType.InsideFit) { 224 if(sw > width) { 225 float scale = (float)width/(float)sw; 226 sw = (int)(sw * scale); 227 sh = (int)(sh * scale); 228 } 229 if(sh > height) { 230 float scale = (float)height/(float)sh; 231 sw = (int)(sw * scale); 232 sh = (int)(sh * scale); 233 } 234 } 235 if(scaleType == ScaleType.OutsideFit) { 236 if(sw > width) { 237 float scale = (float)width/(float)sw; 238 sw = (int)(sw * scale); 239 sh = (int)(sh * scale); 240 } 241 if(sh < height) { 242 float scale = (float)height/(float)sh; 243 sw = (int)(sw * scale); 244 sh = (int)(sh * scale); 245 } 246 } 247 if(scaleType == ScaleType.Distort) { 248 sw = width; 249 sh = height; 250 } 251 int x=0; 252 int y=0; 253 switch(getHorizontalAlignment()) { 254 case CENTER: 255 x=(width/2)-(sw/2); 256 break; 257 case RIGHT: 258 x=width-sw; 259 break; 260 } 261 switch(getVerticalAlignment()) { 262 case CENTER: 263 y=(height/2)-(sh/2); 264 break; 265 case BOTTOM: 266 y=height-sh; 267 break; 268 } 269 g.drawImage(img, x, y, sw, sh, null); 270 } else { 271 int sw = rect.width; 272 int sh = rect.height; 273 if(imageScale != 1.0) { 274 sw = (int)(sw * imageScale); 275 sh = (int)(sh * imageScale); 276 } 277 g.drawImage(img, rect.x, rect.y, sw, sh, null); 278 } 279 } 280 } 281 } 282 283 } 284 285 private void drawBorder(Graphics2D g, Shape shape, int width, int height) { 286 if(getBorderPaint() != null) { 287 g.setPaint(getBorderPaint()); 288 g.setStroke(new BasicStroke(getBorderWidth())); 289 g.draw(shape); 290 } 291 } 292 293 public boolean isScaleToFit() { 294 return scaleToFit; 295 } 296 297 public void setScaleToFit(boolean scaleToFit) { 298 boolean old = isScaleToFit(); 299 this.scaleToFit = scaleToFit; 300 setDirty(true); 301 firePropertyChange("scaleToFit", old, isScaleToFit()); 302 } 303 304 public ScaleType getScaleType() { 305 return scaleType; 306 } 307 308 public void setScaleType(ScaleType scaleType) { 309 ScaleType old = getScaleType(); 310 this.scaleType = scaleType; 311 setDirty(true); 312 firePropertyChange("scaleType", old, getScaleType()); 313 } 314 315 /** 316 * Gets the current scaling factor used when drawing an image. 317 * @return the current scaling factor 318 */ 319 public double getImageScale() { 320 return imageScale; 321 } 322 323 /** 324 * Sets the scaling factor used when drawing the image 325 * @param imageScale the new image scaling factor 326 */ 327 public void setImageScale(double imageScale) { 328 double old = getImageScale(); 329 this.imageScale = imageScale; 330 setDirty(true); 331 firePropertyChange("imageScale",old,this.imageScale); 332 } 333 334 /** 335 * Indicates if the image will be repeated horizontally. 336 * @return if the image will be repeated horizontally 337 */ 338 public boolean isHorizontalRepeat() { 339 return horizontalRepeat; 340 } 341 342 /** 343 * Sets if the image should be repeated horizontally. 344 * @param horizontalRepeat the new horizontal repeat value 345 */ 346 public void setHorizontalRepeat(boolean horizontalRepeat) { 347 boolean old = this.isHorizontalRepeat(); 348 this.horizontalRepeat = horizontalRepeat; 349 setDirty(true); 350 firePropertyChange("horizontalRepeat",old,this.horizontalRepeat); 351 } 352 353 /** 354 * Indicates if the image will be repeated vertically. 355 * @return if the image will be repeated vertically 356 */ 357 public boolean isVerticalRepeat() { 358 return verticalRepeat; 359 } 360 361 /** 362 * Sets if the image should be repeated vertically. 363 * @param verticalRepeat new value for the vertical repeat 364 */ 365 public void setVerticalRepeat(boolean verticalRepeat) { 366 boolean old = this.isVerticalRepeat(); 367 this.verticalRepeat = verticalRepeat; 368 setDirty(true); 369 firePropertyChange("verticalRepeat",old,this.verticalRepeat); 370 } 371 372 /** 373 * 374 */ 375 @Override 376 protected Shape provideShape(Graphics2D g, Object comp, int width, int height) { 377 if(getImage() != null) { 378 BufferedImage bi = getImage(); 379 int imgWidth = bi.getWidth(); 380 int imgHeight = bi.getHeight(); 381 382 return calculateLayout(imgWidth, imgHeight, width, height); 383 } 384 return new Rectangle(0,0,0,0); 385 386 } 387}