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 }