Coverage Report - org.apache.tapestry.contrib.services.impl.ShadowRenderer
 
Classes in this File Line Coverage Branch Coverage Complexity
ShadowRenderer
0%
0/120
0%
0/62
4.6
 
 1  
 package org.apache.tapestry.contrib.services.impl;
 2  
 
 3  
 import java.awt.*;
 4  
 import java.awt.image.BufferedImage;
 5  
 import java.awt.image.Raster;
 6  
 import java.awt.image.WritableRaster;
 7  
 
 8  
 /**
 9  
  *
 10  
  */
 11  
 public class ShadowRenderer {
 12  
 
 13  
     // size of the shadow in pixels (defines the fuzziness)
 14  0
     private int size = 5;
 15  
 
 16  
     // opacity of the shadow
 17  0
     private float opacity = 0.5f;
 18  
     
 19  
     // color of the shadow
 20  0
     private Color color = Color.BLACK;
 21  
 
 22  
     /**
 23  
      * <p>A shadow renderer needs three properties to generate shadows.
 24  
      * These properties are:</p>
 25  
      * <ul>
 26  
      *   <li><i>size</i>: The size, in pixels, of the shadow. This property also
 27  
      *   defines the fuzzyness.</li>
 28  
      *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
 29  
      *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be
 30  
      *   black only.</li>
 31  
      * </ul>
 32  
      * @param size the size of the shadow in pixels. Defines the fuzziness.
 33  
      * @param opacity the opacity of the shadow.
 34  
      * @param color the color of the shadow.
 35  
      */
 36  0
     public ShadowRenderer(final int size, final float opacity, final Color color) {
 37  
 
 38  0
         setSize(size);
 39  0
         setOpacity(opacity);
 40  0
         setColor(color);
 41  0
     }
 42  
 
 43  
     /**
 44  
      * <p>Gets the color used by the renderer to generate shadows.</p>
 45  
      * @return this renderer's shadow color
 46  
      */
 47  
     public Color getColor() {
 48  0
         return color;
 49  
     }
 50  
 
 51  
     /**
 52  
      * <p>Sets the color used by the renderer to generate shadows.</p>
 53  
      * <p>Consecutive calls to {@link #createShadow} will all use this color
 54  
      * until it is set again.</p>
 55  
      * <p>If the color provided is null, the previous color will be retained.</p>
 56  
      * @param shadowColor the generated shadows color
 57  
      */
 58  
     public void setColor(final Color shadowColor) {
 59  0
         if (shadowColor != null) {
 60  0
             Color oldColor = this.color;
 61  0
             this.color = shadowColor;
 62  
         }
 63  0
     }
 64  
 
 65  
     /**
 66  
      * <p>Gets the opacity used by the renderer to generate shadows.</p>
 67  
      * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
 68  
      * transparent and 1.0f fully opaque.</p>
 69  
      * @return this renderer's shadow opacity
 70  
      */
 71  
     public float getOpacity() {
 72  0
         return opacity;
 73  
     }
 74  
 
 75  
     /**
 76  
      * <p>Sets the opacity used by the renderer to generate shadows.</p>
 77  
      * <p>Consecutive calls to {@link #createShadow} will all use this opacity
 78  
      * until it is set again.</p>
 79  
      * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
 80  
      * transparent and 1.0f fully opaque. If you provide a value out of these
 81  
      * boundaries, it will be restrained to the closest boundary.</p>
 82  
      * @param shadowOpacity the generated shadows opacity
 83  
      */
 84  
     public void setOpacity(final float shadowOpacity) {
 85  0
         float oldOpacity = this.opacity;
 86  
 
 87  0
         if (shadowOpacity < 0.0) {
 88  0
             this.opacity = 0.0f;
 89  0
         } else if (shadowOpacity > 1.0f) {
 90  0
             this.opacity = 1.0f;
 91  
         } else {
 92  0
             this.opacity = shadowOpacity;
 93  
         }
 94  0
     }
 95  
 
 96  
     /**
 97  
      * <p>Gets the size in pixel used by the renderer to generate shadows.</p>
 98  
      * @return this renderer's shadow size
 99  
      */
 100  
     public int getSize() {
 101  0
         return size;
 102  
     }
 103  
 
 104  
     /**
 105  
      * <p>Sets the size, in pixels, used by the renderer to generate shadows.</p>
 106  
      * <p>The size defines the blur radius applied to the shadow to create the
 107  
      * fuzziness.</p>
 108  
      * <p>There is virtually no limit to the size. The size cannot be negative.
 109  
      * If you provide a negative value, the size will be 0 instead.</p>
 110  
      * @param shadowSize the generated shadows size in pixels (fuzziness)
 111  
      */
 112  
     public void setSize(final int shadowSize) {
 113  0
         int oldSize = this.size;
 114  
 
 115  0
         if (shadowSize < 0) {
 116  0
             this.size = 0;
 117  
         } else {
 118  0
             this.size = shadowSize;
 119  
         }
 120  0
     }
 121  
 
 122  
     /**
 123  
      * <p>Generates the shadow for a given picture and the current properties
 124  
      * of the renderer.</p>
 125  
      * <p>The generated image dimensions are computed as following:</p>
 126  
      * <pre>
 127  
      * width  = imageWidth  + 2 * shadowSize
 128  
      * height = imageHeight + 2 * shadowSize
 129  
      * </pre>
 130  
      * @param image the picture from which the shadow must be cast
 131  
      * @return the picture containing the shadow of <code>image</code>
 132  
      */
 133  
     public BufferedImage createShadow(final BufferedImage image) {
 134  
         
 135  
         // Written by Sesbastien Petrucci
 136  0
         int shadowSize = size * 2;
 137  
 
 138  0
         int srcWidth = image.getWidth();
 139  0
         int srcHeight = image.getHeight();
 140  
 
 141  0
         int dstWidth = srcWidth + shadowSize;
 142  0
         int dstHeight = srcHeight + shadowSize;
 143  
 
 144  0
         int left = size;
 145  0
         int right = shadowSize - left;
 146  
 
 147  0
         int yStop = dstHeight - right;
 148  
 
 149  0
         int shadowRgb = color.getRGB() & 0x00FFFFFF;
 150  0
         int[] aHistory = new int[shadowSize];
 151  
         int historyIdx;
 152  
 
 153  
         int aSum;
 154  
 
 155  0
         BufferedImage dst = new BufferedImage(dstWidth, dstHeight,
 156  
                                               BufferedImage.TYPE_INT_ARGB);
 157  
 
 158  0
         int[] dstBuffer = new int[dstWidth * dstHeight];
 159  0
         int[] srcBuffer = new int[srcWidth * srcHeight];
 160  
 
 161  0
         getPixels(image, 0, 0, srcWidth, srcHeight, srcBuffer);
 162  
 
 163  0
         int lastPixelOffset = right * dstWidth;
 164  0
         float hSumDivider = 1.0f / shadowSize;
 165  0
         float vSumDivider = opacity / shadowSize;
 166  
 
 167  0
         int[] hSumLookup = new int[256 * shadowSize];
 168  0
         for (int i = 0; i < hSumLookup.length; i++) {
 169  0
             hSumLookup[i] = (int) (i * hSumDivider);
 170  
         }
 171  
 
 172  0
         int[] vSumLookup = new int[256 * shadowSize];
 173  0
         for (int i = 0; i < vSumLookup.length; i++) {
 174  0
             vSumLookup[i] = (int) (i * vSumDivider);
 175  
         }
 176  
 
 177  
         int srcOffset;
 178  
 
 179  
         // horizontal pass : extract the alpha mask from the source picture and
 180  
         // blur it into the destination picture
 181  0
         for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {
 182  
 
 183  
             // first pixels are empty
 184  0
             for (historyIdx = 0; historyIdx < shadowSize; ) {
 185  0
                 aHistory[historyIdx++] = 0;
 186  
             }
 187  
 
 188  0
             aSum = 0;
 189  0
             historyIdx = 0;
 190  0
             srcOffset = srcY * srcWidth;
 191  
 
 192  
             // compute the blur average with pixels from the source image
 193  0
             for (int srcX = 0; srcX < srcWidth; srcX++) {
 194  
 
 195  0
                 int a = hSumLookup[aSum];
 196  0
                 dstBuffer[dstOffset++] = a << 24;   // store the alpha value only
 197  
                                                     // the shadow color will be added in the next pass
 198  
 
 199  0
                 aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum
 200  
 
 201  
                 // extract the new pixel ...
 202  0
                 a = srcBuffer[srcOffset + srcX] >>> 24;
 203  0
                 aHistory[historyIdx] = a;   // ... and store its value into history
 204  0
                 aSum += a;                  // ... and add its value to the sum
 205  
 
 206  0
                 if (++historyIdx >= shadowSize) {
 207  0
                     historyIdx -= shadowSize;
 208  
                 }
 209  
             }
 210  
 
 211  
             // blur the end of the row - no new pixels to grab
 212  0
             for (int i = 0; i < shadowSize; i++) {
 213  
 
 214  0
                 int a = hSumLookup[aSum];
 215  0
                 dstBuffer[dstOffset++] = a << 24;
 216  
 
 217  
                 // substract the oldest pixel from the sum ... and nothing new to add !
 218  0
                 aSum -= aHistory[historyIdx];
 219  
 
 220  0
                 if (++historyIdx >= shadowSize) {
 221  0
                     historyIdx -= shadowSize;
 222  
                 }
 223  
             }
 224  
         }
 225  
 
 226  
         // vertical pass
 227  0
         for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
 228  
 
 229  0
             aSum = 0;
 230  
 
 231  
             // first pixels are empty
 232  0
             for (historyIdx = 0; historyIdx < left;) {
 233  0
                 aHistory[historyIdx++] = 0;
 234  
             }
 235  
 
 236  
             // and then they come from the dstBuffer
 237  0
             for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
 238  0
                 int a = dstBuffer[bufferOffset] >>> 24;         // extract alpha
 239  0
                 aHistory[historyIdx++] = a;                     // store into history
 240  0
                 aSum += a;                                      // and add to sum
 241  
             }
 242  
 
 243  0
             bufferOffset = x;
 244  0
             historyIdx = 0;
 245  
 
 246  
             // compute the blur avera`ge with pixels from the previous pass
 247  0
             for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {
 248  
 
 249  0
                 int a = vSumLookup[aSum];
 250  0
                 dstBuffer[bufferOffset] = a << 24 | shadowRgb;  // store alpha value + shadow color
 251  
 
 252  0
                 aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
 253  
 
 254  0
                 a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;   // extract the new pixel ...
 255  0
                 aHistory[historyIdx] = a;                               // ... and store its value into history
 256  0
                 aSum += a;                                              // ... and add its value to the sum
 257  
 
 258  0
                 if (++historyIdx >= shadowSize) {
 259  0
                     historyIdx -= shadowSize;
 260  
                 }
 261  
             }
 262  
 
 263  
             // blur the end of the column - no pixels to grab anymore
 264  0
             for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {
 265  
 
 266  0
                 int a = vSumLookup[aSum];
 267  0
                 dstBuffer[bufferOffset] = a << 24 | shadowRgb;
 268  
 
 269  0
                 aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
 270  
 
 271  0
                 if (++historyIdx >= shadowSize) {
 272  0
                     historyIdx -= shadowSize;
 273  
                 }
 274  
             }
 275  
         }
 276  
 
 277  0
         setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer);
 278  0
         return dst;
 279  
     }
 280  
 
 281  
     /**
 282  
      * <p>Returns an array of pixels, stored as integers, from a
 283  
      * <code>BufferedImage</code>. The pixels are grabbed from a rectangular
 284  
      * area defined by a location and two dimensions. Calling this method on
 285  
      * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
 286  
      * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
 287  
      *
 288  
      * @param img the source image
 289  
      * @param x the x location at which to start grabbing pixels
 290  
      * @param y the y location at which to start grabbing pixels
 291  
      * @param w the width of the rectangle of pixels to grab
 292  
      * @param h the height of the rectangle of pixels to grab
 293  
      * @param pixels a pre-allocated array of pixels of size w*h; can be null
 294  
      * @return <code>pixels</code> if non-null, a new array of integers
 295  
      *   otherwise
 296  
      * @throws IllegalArgumentException is <code>pixels</code> is non-null and
 297  
      *   of length &lt; w*h
 298  
      */
 299  
     public static int[] getPixels(BufferedImage img,
 300  
                                   int x, int y, int w, int h, int[] pixels) {
 301  0
         if (w == 0 || h == 0) {
 302  0
             return new int[0];
 303  
         }
 304  
 
 305  0
         if (pixels == null) {
 306  0
             pixels = new int[w * h];
 307  0
         } else if (pixels.length < w * h) {
 308  0
             throw new IllegalArgumentException("pixels array must have a length" +
 309  
                                                " >= w*h");
 310  
         }
 311  
 
 312  0
         int imageType = img.getType();
 313  0
         if (imageType == BufferedImage.TYPE_INT_ARGB ||
 314  
             imageType == BufferedImage.TYPE_INT_RGB) {
 315  0
             Raster raster = img.getRaster();
 316  0
             return (int[]) raster.getDataElements(x, y, w, h, pixels);
 317  
         }
 318  
 
 319  
         // Unmanages the image
 320  0
         return img.getRGB(x, y, w, h, pixels, 0, w);
 321  
     }
 322  
 
 323  
     /**
 324  
      * <p>Writes a rectangular area of pixels in the destination
 325  
      * <code>BufferedImage</code>. Calling this method on
 326  
      * an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
 327  
      * and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
 328  
      *
 329  
      * @param img the destination image
 330  
      * @param x the x location at which to start storing pixels
 331  
      * @param y the y location at which to start storing pixels
 332  
      * @param w the width of the rectangle of pixels to store
 333  
      * @param h the height of the rectangle of pixels to store
 334  
      * @param pixels an array of pixels, stored as integers
 335  
      * @throws IllegalArgumentException is <code>pixels</code> is non-null and
 336  
      *   of length &lt; w*h
 337  
      */
 338  
     public static void setPixels(BufferedImage img,
 339  
                                  int x, int y, int w, int h, int[] pixels) {
 340  0
         if (pixels == null || w == 0 || h == 0) {
 341  0
             return;
 342  0
         } else if (pixels.length < w * h) {
 343  0
             throw new IllegalArgumentException("pixels array must have a length" +
 344  
                                                " >= w*h");
 345  
         }
 346  
 
 347  0
         int imageType = img.getType();
 348  0
         if (imageType == BufferedImage.TYPE_INT_ARGB ||
 349  
             imageType == BufferedImage.TYPE_INT_RGB) {
 350  0
             WritableRaster raster = img.getRaster();
 351  0
             raster.setDataElements(x, y, w, h, pixels);
 352  0
         } else {
 353  
             // Unmanages the image
 354  0
             img.setRGB(x, y, w, h, pixels, 0, w);
 355  
         }
 356  0
     }
 357  
 }