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.Color;
036import java.awt.image.BufferedImage;
037
038import lucee.runtime.engine.ThreadLocalPageContext;
039import lucee.runtime.exp.FunctionException;
040import lucee.runtime.exp.PageException;
041import lucee.runtime.img.ImageUtil;
042import lucee.runtime.type.KeyImpl;
043import lucee.runtime.type.Struct;
044import lucee.runtime.type.util.CollectionUtil;
045
046/**
047 * An experimental filter which can be used for keying against a clean shot. Given a source image, a clean image and a destination image, 
048 * the filter replaces all pixels in the source which nearly equal the equivalent clean pixel by destination pixels.
049 */
050public class KeyFilter extends AbstractBufferedImageOp  implements DynFiltering {
051        
052        private float hTolerance = 0;
053        private float sTolerance = 0;
054        private float bTolerance = 0;
055        private BufferedImage destination;
056        private BufferedImage cleanImage;
057
058        public KeyFilter() {
059        }
060
061        /**
062         * Set the hue tolerance of the image in the range 0..1.
063         * @param hTolerance the tolerance
064     * @see #getHTolerance
065         */
066        public void setHTolerance( float hTolerance ) {
067                this.hTolerance = hTolerance;
068        }
069        
070        /**
071         * Get the hue tolerance.
072         * @return the tolerance
073     * @see #setHTolerance
074         */
075        public float getHTolerance() {
076                return hTolerance;
077        }
078        
079        /**
080         * Set the saturation tolerance of the image in the range 0..1.
081         * @param sTolerance the tolerance
082     * @see #getSTolerance
083         */
084        public void setSTolerance( float sTolerance ) {
085                this.sTolerance = sTolerance;
086        }
087        
088        /**
089         * Get the saturation tolerance.
090         * @return the tolerance
091     * @see #setSTolerance
092         */
093        public float getSTolerance() {
094                return sTolerance;
095        }
096        
097        /**
098         * Set the brightness tolerance of the image in the range 0..1.
099         * @param bTolerance the tolerance
100     * @see #getBTolerance
101         */
102        public void setBTolerance( float bTolerance ) {
103                this.bTolerance = bTolerance;
104        }
105        
106        /**
107         * Get the brightness tolerance.
108         * @return the tolerance
109     * @see #setBTolerance
110         */
111        public float getBTolerance() {
112                return bTolerance;
113        }
114        
115    /**
116     * Set the destination image.
117     * @param destination the destination image
118     * @see #getDestination
119     */
120        public void setDestination( BufferedImage destination ) {
121                this.destination = destination;
122        }
123        
124    /**
125     * Get the destination image.
126     * @return the destination image
127     * @see #setDestination
128     */
129        public BufferedImage getDestination() {
130                return destination;
131        }
132        
133    /**
134     * Get the clean image.
135     * @param cleanImage the clean image
136     * @see #getCleanImage
137     */
138        public void setCleanImage( BufferedImage cleanImage ) {
139                this.cleanImage = cleanImage;
140        }
141        
142    /**
143     * Get the clean image.
144     * @return the clean image
145     * @see #setCleanImage
146     */
147        public BufferedImage getCleanImage() {
148                return cleanImage;
149        }
150                
151    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
152        int width = src.getWidth();
153        int height = src.getHeight();
154                //int type = src.getType();
155                //WritableRaster srcRaster = 
156                src.getRaster();
157
158        if ( dst == null )
159            dst = createCompatibleDestImage( src, null );
160                //WritableRaster dstRaster = 
161        dst.getRaster();
162
163        if ( destination != null && cleanImage != null ) {
164            float[] hsb1 = null;
165            float[] hsb2 = null;
166            int[] inPixels = null;
167            int[] outPixels = null;
168            int[] cleanPixels = null;
169            for ( int y = 0; y < height; y++ ) {
170                inPixels = getRGB( src, 0, y, width, 1, inPixels );
171                outPixels = getRGB( destination, 0, y, width, 1, outPixels );
172                cleanPixels = getRGB( cleanImage, 0, y, width, 1, cleanPixels );
173                for ( int x = 0; x < width; x++ ) {
174                    int rgb1 = inPixels[x];
175                    int out = outPixels[x];
176                    int rgb2 = cleanPixels[x];
177
178                    int r1 = (rgb1 >> 16) & 0xff;
179                    int g1 = (rgb1 >> 8) & 0xff;
180                    int b1 = rgb1 & 0xff;
181                    int r2 = (rgb2 >> 16) & 0xff;
182                    int g2 = (rgb2 >> 8) & 0xff;
183                    int b2 = rgb2 & 0xff;
184                    hsb1 = Color.RGBtoHSB( r1, b1, g1, hsb1 );
185                    hsb2 = Color.RGBtoHSB( r2, b2, g2, hsb2 );
186//                    int tolerance = (int)(255*tolerance);
187//                    return Math.abs(r1-r2) <= tolerance && Math.abs(g1-g2) <= tolerance && Math.abs(b1-b2) <= tolerance;
188
189 //                   if ( PixelUtils.nearColors( in, clean, (int)(255*tolerance) ) )
190                    if ( Math.abs( hsb1[0] - hsb2[0] ) < hTolerance && Math.abs( hsb1[1] - hsb2[1] ) < sTolerance && Math.abs( hsb1[2] - hsb2[2] ) < bTolerance )
191                        inPixels[x] = out;
192                    else
193                        inPixels[x] = rgb1;
194                }
195                setRGB( dst, 0, y, width, 1, inPixels );
196            }
197        }
198
199        return dst;
200    }
201
202        public String toString() {
203                return "Keying/Key...";
204        }
205        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
206                Object o;
207                if((o=parameters.removeEL(KeyImpl.init("HTolerance")))!=null)setHTolerance(ImageFilterUtil.toFloatValue(o,"HTolerance"));
208                if((o=parameters.removeEL(KeyImpl.init("STolerance")))!=null)setSTolerance(ImageFilterUtil.toFloatValue(o,"STolerance"));
209                if((o=parameters.removeEL(KeyImpl.init("BTolerance")))!=null)setBTolerance(ImageFilterUtil.toFloatValue(o,"BTolerance"));
210                if((o=parameters.removeEL(KeyImpl.init("CleanImage")))!=null)setCleanImage(ImageFilterUtil.toBufferedImage(o,"CleanImage"));
211                if((o=parameters.removeEL(KeyImpl.init("destination")))!=null)setDestination(ImageFilterUtil.toBufferedImage(o,"destination"));
212
213                // check for arguments not supported
214                if(parameters.size()>0) {
215                        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 [HTolerance, STolerance, BTolerance, CleanImage]");
216                }
217
218                return filter(src, dst);
219        }
220}