001 package org.apache.tapestry.contrib.services.impl; 002 003 import org.apache.hivemind.util.Defense; 004 005 import javax.imageio.ImageIO; 006 import java.awt.*; 007 import java.awt.geom.Arc2D; 008 import java.awt.geom.Rectangle2D; 009 import java.awt.geom.RoundRectangle2D; 010 import java.awt.image.BufferedImage; 011 import java.util.HashMap; 012 import java.util.Map; 013 014 /** 015 * Class responsible for bulk of java2d manipulation work when used in the {@link RoundedCornerService}. 016 */ 017 public class RoundedCornerGenerator { 018 019 public static final String TOP_LEFT = "tl"; 020 public static final String TOP_RIGHT = "tr"; 021 public static final String BOTTOM_LEFT = "bl"; 022 public static final String BOTTOM_RIGHT = "br"; 023 024 public static final String LEFT = "left"; 025 public static final String RIGHT = "right"; 026 public static final String TOP = "top"; 027 public static final String BOTTOM = "bottom"; 028 029 // css2 color spec - http://www.w3.org/TR/REC-CSS2/syndata.html#color-units 030 private static final Map _cssSpecMap = new HashMap(); 031 032 static { 033 _cssSpecMap.put("aqua", new Color(0,255,255)); 034 _cssSpecMap.put("black", Color.black); 035 _cssSpecMap.put("blue", Color.blue); 036 _cssSpecMap.put("fuchsia", new Color(255,0,255)); 037 _cssSpecMap.put("gray", Color.gray); 038 _cssSpecMap.put("green", Color.green); 039 _cssSpecMap.put("lime", new Color(0,255,0)); 040 _cssSpecMap.put("maroon", new Color(128,0,0)); 041 _cssSpecMap.put("navy", new Color(0,0,128)); 042 _cssSpecMap.put("olive", new Color(128,128,0)); 043 _cssSpecMap.put("purple", new Color(128,0,128)); 044 _cssSpecMap.put("red", Color.red); 045 _cssSpecMap.put("silver", new Color(192,192,192)); 046 _cssSpecMap.put("teal", new Color(0,128,128)); 047 _cssSpecMap.put("white", Color.white); 048 _cssSpecMap.put("yellow", Color.yellow); 049 050 ImageIO.setUseCache(false); 051 } 052 053 private static Color SHADOW_COLOR = new Color(0x000000); 054 055 private static final float DEFAULT_OPACITY = 0.5f; 056 057 private static final float ANGLE_TOP_LEFT = 90f; 058 private static final float ANGLE_TOP_RIGHT = 0f; 059 private static final float ANGLE_BOTTOM_LEFT = 180f; 060 private static final float ANGLE_BOTTOM_RIGHT = 270f; 061 062 public BufferedImage buildCorner(String color, String backgroundColor, int width, int height, 063 String angle, int shadowWidth, float endOpacity) 064 throws Exception 065 { 066 width = width * 2; 067 height = height * 2; 068 float startAngle = getStartAngle(angle); 069 Color bgColor = backgroundColor == null ? null : decodeColor(backgroundColor); 070 071 if (shadowWidth <= 0) { 072 073 BufferedImage arc = drawArc(color, width, height, angle, false, -1); 074 BufferedImage ret = arc; 075 076 Arc2D.Float arcArea = new Arc2D.Float(0, 0, width, height, startAngle, 90, Arc2D.PIE); 077 if (bgColor != null) { 078 079 ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 080 Graphics2D g2 = (Graphics2D)ret.createGraphics(); 081 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 082 g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 083 084 g2.setColor(bgColor); 085 g2.fill(arcArea.getBounds2D()); 086 087 g2.drawImage(arc, 0, 0, null); 088 089 g2.dispose(); 090 091 ret = convertType(ret, BufferedImage.TYPE_INT_RGB); 092 } 093 094 return ret.getSubimage((int)arcArea.getBounds2D().getX(), (int)arcArea.getBounds2D().getY(), 095 (int)arcArea.getBounds2D().getWidth(), (int)arcArea.getBounds2D().getHeight()); 096 } 097 098 BufferedImage mask = drawArc(color, width, height, angle, true, shadowWidth); 099 BufferedImage arc = drawArc(color, width, height, angle, false, shadowWidth); 100 101 float startX = 0; 102 float startY = 0; 103 int shadowSize = shadowWidth * 2; 104 float canvasWidth = width + (shadowSize * 2); 105 float canvasHeight = height + (shadowSize * 2); 106 107 if (startAngle == ANGLE_BOTTOM_LEFT) { 108 109 startY -= (shadowSize * 2); 110 111 } else if (startAngle == ANGLE_TOP_RIGHT) { 112 113 startX -= shadowSize * 2; 114 115 } else if (startAngle == ANGLE_BOTTOM_RIGHT) { 116 117 startX -= shadowSize * 2; 118 startY -= shadowSize * 2; 119 } 120 121 BufferedImage ret = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 122 Graphics2D g2 = (Graphics2D)ret.createGraphics(); 123 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 124 125 Arc2D.Float arcArea = new Arc2D.Float(startX, startY, canvasWidth, canvasHeight, startAngle, 90, Arc2D.PIE); 126 127 if (bgColor != null) { 128 129 g2.setColor(bgColor); 130 g2.fill(arcArea.getBounds2D()); 131 } 132 133 BufferedImage shadow = drawArcShadow(mask, color, backgroundColor, width, height, angle, shadowWidth, endOpacity); 134 135 g2.setClip(arcArea); 136 g2.drawImage(shadow, 0, 0, null); 137 138 g2.setClip(null); 139 g2.drawImage(arc, 0, 0, null); 140 141 return convertType(ret, BufferedImage.TYPE_INT_RGB).getSubimage((int)arcArea.getBounds2D().getX(), (int)arcArea.getBounds2D().getY(), 142 (int)arcArea.getBounds2D().getWidth(), (int)arcArea.getBounds2D().getHeight()); 143 } 144 145 static BufferedImage convertType(BufferedImage image, int type) { 146 BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight(), type); 147 Graphics2D g = result.createGraphics(); 148 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 149 g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 150 g.drawRenderedImage(image, null); 151 g.dispose(); 152 return result; 153 } 154 155 BufferedImage drawArc(String color, int width, int height, String angle, boolean masking, int shadowWidth) 156 { 157 Color arcColor = decodeColor(color); 158 float startAngle = getStartAngle(angle); 159 160 int canvasWidth = width; 161 int canvasHeight = height; 162 float startX = 0; 163 float startY = 0; 164 int shadowSize = 0; 165 166 if (shadowWidth > 0 && !masking) { 167 168 shadowSize = shadowWidth * 2; 169 canvasWidth += shadowSize * 2; 170 canvasHeight += shadowSize * 2; 171 172 if (startAngle == ANGLE_TOP_LEFT) { 173 174 startX += shadowSize; 175 startY += shadowSize; 176 177 } else if (startAngle == ANGLE_BOTTOM_LEFT) { 178 179 startX += shadowSize; 180 startY -= shadowSize; 181 182 } else if (startAngle == ANGLE_TOP_RIGHT) { 183 184 startX -= shadowSize; 185 startY += shadowSize; 186 187 } else if (startAngle == ANGLE_BOTTOM_RIGHT) { 188 189 startX -= shadowSize; 190 startY -= shadowSize; 191 } 192 } 193 194 BufferedImage img = new BufferedImage( canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB); 195 Graphics2D g2 = (Graphics2D) img.createGraphics(); 196 197 float extent = 90; 198 if (masking) { 199 200 extent = 120; 201 startAngle -= 20; 202 } 203 204 Arc2D.Float fillArea = new Arc2D.Float(startX, startY, width, height, startAngle, extent, Arc2D.PIE); 205 206 // draw arc 207 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 208 g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 209 210 g2.setColor(arcColor); 211 g2.setComposite(AlphaComposite.Src); 212 g2.fill(fillArea); 213 214 g2.dispose(); 215 216 return img; 217 } 218 219 BufferedImage drawArcShadow(BufferedImage mask, String color, String backgroundColor, int width, int height, 220 String angle, int shadowWidth, float endOpacity) 221 { 222 float startAngle = getStartAngle(angle); 223 int shadowSize = shadowWidth * 2; 224 int sampleY = 0; 225 int sampleX = 0; 226 int sampleWidth = width + shadowSize; 227 int sampleHeight = height + shadowSize; 228 229 if (startAngle == ANGLE_TOP_LEFT) { 230 231 } else if (startAngle == ANGLE_BOTTOM_LEFT) { 232 233 sampleWidth -= shadowSize; 234 sampleHeight = height; 235 236 sampleY += shadowSize; 237 238 } else if (startAngle == ANGLE_TOP_RIGHT) { 239 240 sampleWidth -= shadowSize; 241 sampleHeight -= shadowSize; 242 243 sampleX += shadowSize; 244 } else if (startAngle == ANGLE_BOTTOM_RIGHT) { 245 246 sampleWidth -= shadowSize; 247 sampleHeight -= shadowSize; 248 249 sampleX += shadowSize; 250 sampleY += shadowSize; 251 } 252 253 ShadowRenderer shadowRenderer = new ShadowRenderer(shadowWidth, endOpacity, SHADOW_COLOR); 254 BufferedImage dropShadow = shadowRenderer.createShadow(mask); 255 256 // draw shadow arc 257 258 BufferedImage img = new BufferedImage( (width * 4), (height * 4), BufferedImage.TYPE_INT_ARGB); 259 Graphics2D g2 = (Graphics2D) img.createGraphics(); 260 261 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 262 g2.setComposite(AlphaComposite.Src); 263 g2.drawImage(dropShadow, 0, 0, null); 264 265 g2.dispose(); 266 267 return img.getSubimage(sampleX, sampleY, sampleWidth, sampleHeight); 268 } 269 270 public BufferedImage buildShadow(String color, String backgroundColor, int width, int height, 271 float arcWidth, float arcHeight, 272 int shadowWidth, float endOpacity) 273 { 274 Color fgColor = color == null ? Color.WHITE : decodeColor(color); 275 Color bgColor = backgroundColor == null ? null : decodeColor(backgroundColor); 276 277 BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 278 Graphics2D g2 = mask.createGraphics(); 279 280 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 281 282 RoundRectangle2D.Float fillArea = new RoundRectangle2D.Float(0, 0, width, height, arcHeight, arcWidth); 283 g2.setColor(fgColor); 284 g2.fill(fillArea); 285 g2.dispose(); 286 287 // clip shadow 288 289 ShadowRenderer shadowRenderer = new ShadowRenderer(shadowWidth, endOpacity, SHADOW_COLOR); 290 BufferedImage dropShadow = shadowRenderer.createShadow(mask); 291 292 BufferedImage clipImg = new BufferedImage( width + (shadowWidth * 2), height + (shadowWidth * 2), BufferedImage.TYPE_INT_ARGB); 293 g2 = clipImg.createGraphics(); 294 295 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 296 g2.setComposite(AlphaComposite.Src); 297 298 RoundRectangle2D.Float clip = new RoundRectangle2D.Float(0, 0, width + (shadowWidth * 2), height + (shadowWidth * 2), arcHeight, arcWidth); 299 g2.setClip(clip); 300 g2.drawImage(dropShadow, 0, 0, null); 301 g2.dispose(); 302 303 // draw everything 304 305 BufferedImage img = new BufferedImage( width + (shadowWidth * 2), height + (shadowWidth * 2), BufferedImage.TYPE_INT_ARGB); 306 g2 = img.createGraphics(); 307 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 308 309 if (bgColor != null) 310 { 311 fillArea = new RoundRectangle2D.Float(0, 0, width + (shadowWidth * 2), height + (shadowWidth * 2), arcHeight, arcWidth); 312 g2.setColor(bgColor); 313 g2.fill(fillArea.getBounds2D()); 314 } 315 316 g2.drawImage(clipImg, 0, 0, null); 317 318 if (fgColor != null) 319 { 320 fillArea = new RoundRectangle2D.Float(0, 0, width, height, arcHeight, arcWidth); 321 g2.setColor(fgColor); 322 g2.fill(fillArea); 323 } 324 325 g2.dispose(); 326 327 return convertType(img, BufferedImage.TYPE_INT_RGB); 328 } 329 330 public BufferedImage buildSideShadow(String side, int size, float opacity) 331 throws Exception 332 { 333 Defense.notNull(side, "side"); 334 335 if (opacity <= 0) 336 opacity = DEFAULT_OPACITY; 337 338 int maskWidth = 0; 339 int maskHeight = 0; 340 int sampleY = 0; 341 int sampleX = 0; 342 int sampleWidth = 0; 343 int sampleHeight = 0; 344 345 if (LEFT.equals(side)) { 346 347 maskWidth = size * 4; 348 maskHeight = size * 4; 349 sampleY = maskHeight / 2; 350 sampleWidth = size * 2; 351 sampleHeight = 2; 352 } else if (RIGHT.equals(side)) { 353 354 maskWidth = size * 4; 355 maskHeight = size * 4; 356 sampleY = maskHeight / 2; 357 sampleX = maskWidth; 358 sampleWidth = size * 2; 359 sampleHeight = 2; 360 } else if (BOTTOM.equals(side)) { 361 362 maskWidth = size * 4; 363 maskHeight = size * 4; 364 sampleY = maskHeight; 365 sampleX = maskWidth / 2; 366 sampleWidth = 2; 367 sampleHeight = size * 2; 368 } else if (TOP.equals(side)) { 369 370 maskWidth = size * 4; 371 maskHeight = size * 4; 372 sampleY = 0; 373 sampleX = maskWidth / 2; 374 sampleWidth = 2; 375 sampleHeight = size * 2; 376 } 377 378 BufferedImage mask = new BufferedImage( maskWidth, maskHeight, BufferedImage.TYPE_INT_ARGB); 379 Graphics2D g2 = (Graphics2D) mask.createGraphics(); 380 381 g2.setColor(Color.white); 382 g2.fillRect(0, 0, maskWidth, maskHeight); 383 384 g2.dispose(); 385 386 ShadowRenderer shadowRenderer = new ShadowRenderer(size, opacity, SHADOW_COLOR); 387 BufferedImage dropShadow = shadowRenderer.createShadow(mask); 388 389 BufferedImage render = new BufferedImage(maskWidth * 2, maskHeight * 2, BufferedImage.TYPE_INT_ARGB); 390 g2 = (Graphics2D)render.createGraphics(); 391 392 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 393 394 Rectangle2D.Float clip = new Rectangle2D.Float(sampleX, sampleY, sampleWidth, sampleHeight); 395 396 g2.setColor(Color.white); 397 g2.fill(clip); 398 399 g2.drawImage(dropShadow, 0, 0, null); 400 401 g2.dispose(); 402 403 return render.getSubimage(sampleX, sampleY, sampleWidth, sampleHeight); 404 } 405 406 /** 407 * Matches the incoming string against one of the constants defined; tl, tr, bl, br. 408 * 409 * @param code The code for the angle of the arc to generate, if no match is found the default is 410 * {@link #TOP_RIGHT} - or 0 degrees. 411 * @return The pre-defined 90 degree angle starting degree point. 412 */ 413 public float getStartAngle(String code) 414 { 415 if (TOP_LEFT.equalsIgnoreCase(code)) 416 return ANGLE_TOP_LEFT; 417 if (TOP_RIGHT.equalsIgnoreCase(code)) 418 return ANGLE_TOP_RIGHT; 419 if (BOTTOM_LEFT.equalsIgnoreCase(code)) 420 return ANGLE_BOTTOM_LEFT; 421 if (BOTTOM_RIGHT.equalsIgnoreCase(code)) 422 return ANGLE_BOTTOM_RIGHT; 423 424 return ANGLE_TOP_RIGHT; 425 } 426 427 /** 428 * Decodes the specified input color string into a compatible awt color object. Valid inputs 429 * are any in the css2 color spec or hex strings. 430 * 431 * @param color The color to match. 432 * @return The decoded color object, may be black if decoding fails. 433 */ 434 public Color decodeColor(String color) 435 { 436 Color specColor = (Color) _cssSpecMap.get(color); 437 if (specColor != null) 438 return specColor; 439 440 String hexColor = color.startsWith("0x") ? color : "0x" + color; 441 442 return Color.decode(hexColor); 443 } 444 }