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.Rectangle;
018    import java.awt.image.BufferedImage;
019    
020    import railo.runtime.engine.ThreadLocalPageContext;
021    import railo.runtime.exp.ExpressionException;
022    import railo.runtime.exp.FunctionException;
023    import railo.runtime.exp.PageException;
024    import railo.runtime.img.ImageUtil;
025    import railo.runtime.type.KeyImpl;
026    import railo.runtime.type.Struct;
027    import railo.runtime.type.util.CollectionUtil;
028    
029    /**
030     * An abstract superclass for filters which distort images in some way. The subclass only needs to override
031     * two methods to provide the mapping between source and destination pixels.
032     */
033    public abstract class TransformFilter extends AbstractBufferedImageOp  implements DynFiltering {
034            
035        
036    
037        /**
038         * Use nearest-neighbour interpolation.
039         */
040            public final static int NEAREST_NEIGHBOUR = 0;
041    
042        /**
043         * Use bilinear interpolation.
044         */
045            public final static int BILINEAR = 1;
046    
047        /**
048         * The action to take for pixels off the image edge.
049         */
050            protected int edgeAction = ConvolveFilter.ZERO_EDGES;
051    
052        /**
053         * The type of interpolation to use.
054         */
055            protected int interpolation = BILINEAR;
056    
057        /**
058         * The output image rectangle.
059         */
060            protected Rectangle transformedSpace;
061    
062        /**
063         * The input image rectangle.
064         */
065            protected Rectangle originalSpace;
066    
067    
068            public TransformFilter(){
069            }
070            public TransformFilter(int edgeAction){
071                    this.edgeAction=edgeAction;
072            }
073            
074        /**
075         * Set the action to perfomr for pixels off the image edges.
076         * valid values are:
077         * - clamp (default): Clamp pixels off the edge to the nearest edge.
078         * - wrap: Wrap pixels off the edge to the opposite edge.
079         * - zero: Treat pixels off the edge as zero
080         * 
081         * @param edgeAction the action
082         * @throws ExpressionException 
083         */
084            public void setEdgeAction(String edgeAction) throws ExpressionException {
085                    String str=edgeAction.trim().toUpperCase();
086                    if("ZERO".equals(str)) this.edgeAction = ConvolveFilter.ZERO_EDGES;
087                    else if("CLAMP".equals(str)) this.edgeAction = ConvolveFilter.CLAMP_EDGES;
088                    else if("WRAP".equals(str)) this.edgeAction = ConvolveFilter.WRAP_EDGES;
089                    else 
090                            throw new ExpressionException("invalid value ["+edgeAction+"] for edgeAction, valid values are [clamp,wrap,zero]");
091            }
092    
093    
094        /**
095         * Get the action to perform for pixels off the edge of the image.
096         * @return one of ZERO, CLAMP or WRAP
097         * @see #setEdgeAction
098         */
099            public int getEdgeAction() {
100                    return edgeAction;
101            }
102            
103        /**
104         * Set the type of interpolation to perform.
105         * valid values are:
106         * - bilinear (default): Use bilinear interpolation.
107         * - nearest_neighbour: Use nearest-neighbour interpolation.
108         * 
109         * @param interpolation one of NEAREST_NEIGHBOUR or BILINEAR
110         * @see #getInterpolation
111         */
112            public void setInterpolation(String interpolation) throws ExpressionException {
113                    String str=interpolation.trim().toUpperCase();
114                    if("NEAREST_NEIGHBOUR".equals(str)) this.interpolation = NEAREST_NEIGHBOUR;
115                    else if("BILINEAR".equals(str)) this.interpolation = BILINEAR;
116                    else 
117                            throw new ExpressionException("invalid value ["+interpolation+"] for interpolation, valid values are [bilinear,nearest_neighbour]");
118            }
119    
120        /**
121         * Get the type of interpolation to perform.
122         * @return one of NEAREST_NEIGHBOUR or BILINEAR
123         * @see #setInterpolation
124         */
125            public int getInterpolation() {
126                    return interpolation;
127            }
128            
129        /**
130         * Inverse transform a point. This method needs to be overriden by all subclasses.
131         * @param x the X position of the pixel in the output image
132         * @param y the Y position of the pixel in the output image
133         * @param out the position of the pixel in the input image
134         */
135            protected abstract void transformInverse(int x, int y, float[] out);
136    
137        /**
138         * Forward transform a rectangle. Used to determine the size of the output image.
139         * @param rect the rectangle to transform
140         */
141            protected void transformSpace(Rectangle rect) {
142            }
143    
144        public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
145            
146            
147            
148            int width = src.getWidth();
149            int height = src.getHeight();
150            
151            
152            
153            
154            
155                    //int type = src.getType();
156                    //WritableRaster srcRaster = 
157            src.getRaster();
158    
159                    originalSpace = new Rectangle(0, 0, width, height);
160                    transformedSpace = new Rectangle(0, 0, width, height);
161                    transformSpace(transformedSpace);
162                    
163                    
164    
165            if (dst == null ) {
166                    dst=ImageUtil.createBufferedImage(src,transformedSpace.width,transformedSpace.height);
167            }
168            
169            //WritableRaster dstRaster = 
170            dst.getRaster();
171    
172                    int[] inPixels = getRGB( src, 0, 0, width, height, null );
173    
174                    if ( interpolation == NEAREST_NEIGHBOUR )
175                            return filterPixelsNN( dst, width, height, inPixels, transformedSpace );
176    
177                    int srcWidth = width;
178                    int srcHeight = height;
179                    int srcWidth1 = width-1;
180                    int srcHeight1 = height-1;
181                    int outWidth = transformedSpace.width;
182                    int outHeight = transformedSpace.height;
183                    int outX, outY;
184                    //int index = 0;
185                    int[] outPixels = new int[outWidth];
186    
187                    outX = transformedSpace.x;
188                    outY = transformedSpace.y;
189                    float[] out = new float[2];
190    
191                    for (int y = 0; y < outHeight; y++) {
192                            for (int x = 0; x < outWidth; x++) {
193                                    transformInverse(outX+x, outY+y, out);
194                                    int srcX = (int)Math.floor( out[0] );
195                                    int srcY = (int)Math.floor( out[1] );
196                                    float xWeight = out[0]-srcX;
197                                    float yWeight = out[1]-srcY;
198                                    int nw, ne, sw, se;
199    
200                                    if ( srcX >= 0 && srcX < srcWidth1 && srcY >= 0 && srcY < srcHeight1) {
201                                            // Easy case, all corners are in the image
202                                            int i = srcWidth*srcY + srcX;
203                                            nw = inPixels[i];
204                                            ne = inPixels[i+1];
205                                            sw = inPixels[i+srcWidth];
206                                            se = inPixels[i+srcWidth+1];
207                                    } else {
208                                            // Some of the corners are off the image
209                                            nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight );
210                                            ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight );
211                                            sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight );
212                                            se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight );
213                                    }
214                                    outPixels[x] = ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se);
215                            }
216                            setRGB( dst, 0, y, transformedSpace.width, 1, outPixels );
217                    }
218                    return dst;
219            }
220    
221            final private int getPixel( int[] pixels, int x, int y, int width, int height ) {
222                    if (x < 0 || x >= width || y < 0 || y >= height) {
223                            switch (edgeAction) {
224                            case ConvolveFilter.ZERO_EDGES:
225                            default:
226                                    return 0;
227                            case ConvolveFilter.WRAP_EDGES:
228                                    return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)];
229                            case ConvolveFilter.CLAMP_EDGES:
230                                    return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)];
231                            }
232                    }
233                    return pixels[ y*width+x ];
234            }
235    
236            protected BufferedImage filterPixelsNN( BufferedImage dst, int width, int height, int[] inPixels, Rectangle transformedSpace ) {
237                    int srcWidth = width;
238                    int srcHeight = height;
239                    int outWidth = transformedSpace.width;
240                    int outHeight = transformedSpace.height;
241                    int outX, outY, srcX, srcY;
242                    int[] outPixels = new int[outWidth];
243    
244                    outX = transformedSpace.x;
245                    outY = transformedSpace.y;
246                    int[] rgb = new int[4];
247                    float[] out = new float[2];
248    
249                    for (int y = 0; y < outHeight; y++) {
250                            for (int x = 0; x < outWidth; x++) {
251                                    transformInverse(outX+x, outY+y, out);
252                                    srcX = (int)out[0];
253                                    srcY = (int)out[1];
254                                    // int casting rounds towards zero, so we check out[0] < 0, not srcX < 0
255                                    if (out[0] < 0 || srcX >= srcWidth || out[1] < 0 || srcY >= srcHeight) {
256                                            int p;
257                                            switch (edgeAction) {
258                                            case ConvolveFilter.ZERO_EDGES:
259                                            default:
260                                                    p = 0;
261                                                    break;
262                                            case ConvolveFilter.WRAP_EDGES:
263                                                    p = inPixels[(ImageMath.mod(srcY, srcHeight) * srcWidth) + ImageMath.mod(srcX, srcWidth)];
264                                                    break;
265                                            case ConvolveFilter.CLAMP_EDGES:
266                                                    p = inPixels[(ImageMath.clamp(srcY, 0, srcHeight-1) * srcWidth) + ImageMath.clamp(srcX, 0, srcWidth-1)];
267                                                    break;
268                                            }
269                                            outPixels[x] = p;
270                                    } else {
271                                            int i = srcWidth*srcY + srcX;
272                                            rgb[0] = inPixels[i];
273                                            outPixels[x] = inPixels[i];
274                                    }
275                            }
276                            setRGB( dst, 0, y, transformedSpace.width, 1, outPixels );
277                    }
278                    return dst;
279            }
280            
281            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
282                    Object o;
283                    if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
284                    if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));
285    
286                    // check for arguments not supported
287                    if(parameters.size()>0) {
288                            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 [EdgeAction, Interpolation]");
289                    }
290    
291                    return filter(src, dst);
292            }
293    }
294