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