Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
ShadowRenderer |
|
| 4.1;4.1 |
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 < 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 < 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 | } |