001    /*
002    *
003    
004    Licensed under the Apache License, Version 2.0 (the "License");
005    you may not use this file except in compliance with the License.
006    You may obtain a copy of the License at
007    
008       http://www.apache.org/licenses/LICENSE-2.0
009    
010    Unless required by applicable law or agreed to in writing, software
011    distributed under the License is distributed on an "AS IS" BASIS,
012    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013    See the License for the specific language governing permissions and
014    limitations under the License.
015    */
016    
017    package railo.runtime.img.filter;import java.awt.image.BufferedImage;
018    
019    import railo.runtime.engine.ThreadLocalPageContext;
020    import railo.runtime.exp.FunctionException;
021    import railo.runtime.exp.PageException;
022    import railo.runtime.img.ImageUtil;
023    import railo.runtime.type.KeyImpl;
024    import railo.runtime.type.Struct;
025    import railo.runtime.type.util.CollectionUtil;
026    
027    /**
028     * A filter which renders "glints" on bright parts of the image.
029     */
030    public class GlintFilter extends AbstractBufferedImageOp  implements DynFiltering {
031    
032        private float threshold = 1.0f;
033        private int length = 5;
034        private float blur = 0.0f;
035        private float amount = 0.1f;
036            private boolean glintOnly = false;
037            private Colormap colormap = new LinearColormap( 0xffffffff, 0xff000000 );
038    
039        public GlintFilter() {
040            }
041            
042            /**
043         * Set the threshold value.
044         * @param threshold the threshold value
045         * @see #getThreshold
046         */
047            public void setThreshold( float threshold ) {
048                    this.threshold = threshold;
049            }
050            
051            /**
052         * Get the threshold value.
053         * @return the threshold value
054         * @see #setThreshold
055         */
056            public float getThreshold() {
057                    return threshold;
058            }
059            
060            /**
061             * Set the amount of glint.
062             * @param amount the amount
063         * @min-value 0
064         * @max-value 1
065         * @see #getAmount
066             */
067            public void setAmount( float amount ) {
068                    this.amount = amount;
069            }
070            
071            /**
072             * Get the amount of glint.
073             * @return the amount
074         * @see #setAmount
075             */
076            public float getAmount() {
077                    return amount;
078            }
079            
080            /**
081         * Set the length of the stars.
082         * @param length the length
083         * @see #getLength
084         */
085            public void setLength( int length ) {
086                    this.length = length;
087            }
088            
089            /**
090         * Get the length of the stars.
091         * @return the length
092         * @see #setLength
093         */
094            public int getLength() {
095                    return length;
096            }
097            
098            /**
099         * Set the blur that is applied before thresholding.
100         * @param blur the blur radius
101         * @see #getBlur
102         */
103            public void setBlur(float blur) {
104                    this.blur = blur;
105            }
106    
107            /**
108         * Set the blur that is applied before thresholding.
109         * @return the blur radius
110         * @see #setBlur
111         */
112            public float getBlur() {
113                    return blur;
114            }
115            
116            /**
117         * Set whether to render the stars and the image or only the stars.
118         * @param glintOnly true to render only stars
119         * @see #getGlintOnly
120         */
121            public void setGlintOnly(boolean glintOnly) {
122                    this.glintOnly = glintOnly;
123            }
124    
125            /**
126         * Get whether to render the stars and the image or only the stars.
127         * @return true to render only stars
128         * @see #setGlintOnly
129         */
130            public boolean getGlintOnly() {
131                    return glintOnly;
132            }
133            
134        /**
135         * Set the colormap to be used for the filter.
136         * @param colormap the colormap
137         * @see #getColormap
138         */
139            public void setColormap(Colormap colormap) {
140                    this.colormap = colormap;
141            }
142    
143        /**
144         * Get the colormap to be used for the filter.
145         * @return the colormap
146         * @see #setColormap
147         */
148            public Colormap getColormap() {
149                    return colormap;
150            }
151            
152        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
153            int width = src.getWidth();
154            int height = src.getHeight();
155                    int[] pixels = new int[width];
156                    int length2 = (int)(length / 1.414f);
157                    int[] colors = new int[length+1];
158                    int[] colors2 = new int[length2+1];
159    
160                    if ( colormap != null ) {
161                            for (int i = 0; i <= length; i++) {
162                                    int argb = colormap.getColor( (float)i/length );
163                                    int r = (argb >> 16) & 0xff;
164                                    int g = (argb >> 8) & 0xff;
165                                    int b = argb & 0xff;
166                                    argb = (argb & 0xff000000) | ((int)(amount*r) << 16) | ((int)(amount*g) << 8) | (int)(amount*b);
167                                    colors[i] = argb;
168                            }
169                            for (int i = 0; i <= length2; i++) {
170                                    int argb = colormap.getColor( (float)i/length2 );
171                                    int r = (argb >> 16) & 0xff;
172                                    int g = (argb >> 8) & 0xff;
173                                    int b = argb & 0xff;
174                                    argb = (argb & 0xff000000) | ((int)(amount*r) << 16) | ((int)(amount*g) << 8) | (int)(amount*b);
175                                    colors2[i] = argb;
176                            }
177                    }
178    
179            BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
180    
181                    int threshold3 = (int)(threshold*3*255);
182                    for ( int y = 0; y < height; y++ ) {
183                            getRGB( src, 0, y, width, 1, pixels );
184                            for ( int x = 0; x < width; x++ ) {
185                                    int rgb = pixels[x];
186                                    int a = rgb & 0xff000000;
187                                    int r = (rgb >> 16) & 0xff;
188                                    int g = (rgb >> 8) & 0xff;
189                                    int b = rgb & 0xff;
190                                    int l = r + g + b;
191                                    if (l < threshold3)
192                                            pixels[x] = 0xff000000;
193                                    else {
194                                            l /= 3;
195                                            pixels[x] = a | (l << 16) | (l << 8) | l;
196                                    }
197                            }
198                            setRGB( mask, 0, y, width, 1, pixels );
199                    }
200    
201                    if ( blur != 0 )
202                            mask = new GaussianFilter(blur).filter( mask, (BufferedImage)null );
203    
204            if ( dst == null )
205                dst = createCompatibleDestImage( src, null );
206                    int[] dstPixels;
207                    if ( glintOnly )
208                            dstPixels = new int[width*height];
209                    else
210                            dstPixels = getRGB( src, 0, 0, width, height, null );//FIXME - only need 2*length
211    
212                    for ( int y = 0; y < height; y++ ) {
213                            int index = y*width;
214                            getRGB( mask, 0, y, width, 1, pixels );
215                            int ymin = Math.max( y-length, 0 )-y;
216                            int ymax = Math.min( y+length, height-1 )-y;
217                            int ymin2 = Math.max( y-length2, 0 )-y;
218                            int ymax2 = Math.min( y+length2, height-1 )-y;
219                            for ( int x = 0; x < width; x++ ) {
220                                    if ( (pixels[x] & 0xff) > threshold*255 ) {
221                                            int xmin = Math.max( x-length, 0 )-x;
222                                            int xmax = Math.min( x+length, width-1 )-x;
223                                            int xmin2 = Math.max( x-length2, 0 )-x;
224                                            int xmax2 = Math.min( x+length2, width-1 )-x;
225    
226                                            // Horizontal
227                                            for ( int i = 0, k = 0; i <= xmax; i++, k++ )
228                                                    dstPixels[index+i] = PixelUtils.combinePixels( dstPixels[index+i], colors[k], PixelUtils.ADD );
229                                            for ( int i = -1, k = 1; i >= xmin; i--, k++ )
230                                                    dstPixels[index+i] = PixelUtils.combinePixels( dstPixels[index+i], colors[k], PixelUtils.ADD );
231                                            // Vertical
232                                            for ( int i = 1, j = index+width, k = 0; i <= ymax; i++, j += width, k++ )
233                                                    dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors[k], PixelUtils.ADD );
234                                            for ( int i = -1, j = index-width, k = 0; i >= ymin; i--, j -= width, k++ )
235                                                    dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors[k], PixelUtils.ADD );
236    
237                                            // Diagonals
238                                            //int xymin = Math.max( xmin2, ymin2 );
239                                            //int xymax = Math.min( xmax2, ymax2 );
240                                            // SE
241                                            int count = Math.min( xmax2, ymax2 );
242                                            for ( int i = 1, j = index+width+1, k = 0; i <= count; i++, j += width+1, k++ )
243                                                    dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD );
244                                            // NW
245                                            count = Math.min( -xmin2, -ymin2 );
246                                            for ( int i = 1, j = index-width-1, k = 0; i <= count; i++, j -= width+1, k++ )
247                                                    dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD );
248                                            // NE
249                                            count = Math.min( xmax2, -ymin2 );
250                                            for ( int i = 1, j = index-width+1, k = 0; i <= count; i++, j += -width+1, k++ )
251                                                    dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD );
252                                            // SW
253                                            count = Math.min( -xmin2, ymax2 );
254                                            for ( int i = 1, j = index+width-1, k = 0; i <= count; i++, j += width-1, k++ )
255                                                    dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD );
256                                    }
257                                    index++;
258                            }
259                    }
260                    setRGB( dst, 0, 0, width, height, dstPixels );
261    
262            return dst;
263        }
264        
265            public String toString() {
266                    return "Effects/Glint...";
267            }
268            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
269                    Object o;
270                    if((o=parameters.removeEL(KeyImpl.init("Colormap")))!=null)setColormap(ImageFilterUtil.toColormap(o,"Colormap"));
271                    if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount"));
272                    if((o=parameters.removeEL(KeyImpl.init("Blur")))!=null)setBlur(ImageFilterUtil.toFloatValue(o,"Blur"));
273                    if((o=parameters.removeEL(KeyImpl.init("GlintOnly")))!=null)setGlintOnly(ImageFilterUtil.toBooleanValue(o,"GlintOnly"));
274                    if((o=parameters.removeEL(KeyImpl.init("Length")))!=null)setLength(ImageFilterUtil.toIntValue(o,"Length"));
275                    if((o=parameters.removeEL(KeyImpl.init("Threshold")))!=null)setThreshold(ImageFilterUtil.toFloatValue(o,"Threshold"));
276    
277                    // check for arguments not supported
278                    if(parameters.size()>0) {
279                            throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+CollectionUtil.getKeyList(parameters,", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [Colormap, Amount, Blur, GlintOnly, Length, Threshold]");
280                    }
281    
282                    return filter(src, dst);
283            }
284    }