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    }