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