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