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.Rectangle;
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 * A filter for warping images using the gridwarp algorithm.
048 * You need to supply two warp grids, one for the source image and
049 * one for the destination image. The image will be warped so that
050 * a point in the source grid moves to its counterpart in the destination
051 * grid.
052 */
053public class WarpFilter extends WholeImageFilter  implements DynFiltering {
054
055        private WarpGrid sourceGrid;
056        private WarpGrid destGrid;
057        private int frames = 1;
058
059        private BufferedImage morphImage;
060        private float time;
061
062        /**
063         * Create a WarpFilter.
064         */
065        public WarpFilter() {
066                //this(new WarpGrid(),new WarpGrid());
067        }
068        
069        /**
070         * Create a WarpFilter with two warp grids.
071         * @param sourceGrid the source grid
072         * @param destGrid the destination grid
073         */
074        public WarpFilter(WarpGrid sourceGrid, WarpGrid destGrid) {
075                this.sourceGrid = sourceGrid;
076                this.destGrid = destGrid;               
077        }
078        
079        /**
080         * Set the source warp grid.
081         * @param sourceGrid the source grid
082     * @see #getSourceGrid
083         */
084        public void setSourceGrid(WarpGrid sourceGrid) {
085                this.sourceGrid = sourceGrid;
086        }
087
088        /**
089         * Get the source warp grid.
090         * @return the source grid
091     * @see #setSourceGrid
092         */
093        public WarpGrid getSourceGrid() {
094                return sourceGrid;
095        }
096
097        /**
098         * Set the destination warp grid.
099         * @param destGrid the destination grid
100     * @see #getDestGrid
101         */
102        public void setDestGrid(WarpGrid destGrid) {
103                this.destGrid = destGrid;
104        }
105
106        /**
107         * Get the destination warp grid.
108         * @return the destination grid
109     * @see #setDestGrid
110         */
111        public WarpGrid getDestGrid() {
112                return destGrid;
113        }
114
115        public void setFrames(int frames) {
116                this.frames = frames;
117        }
118
119        public int getFrames() {
120                return frames;
121        }
122
123        /**
124         * For morphing, sets the image we're morphing to. If not, set then we're just warping.
125         */
126        public void setMorphImage(BufferedImage morphImage) {
127                this.morphImage = morphImage;
128        }
129
130        public BufferedImage getMorphImage() {
131                return morphImage;
132        }
133
134        public void setTime(float time) {
135                this.time = time;
136        }
137
138        public float getTime() {
139                return time;
140        }
141
142        protected void transformSpace(Rectangle r) {
143                r.width *= frames;
144        }
145
146        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
147                int[] outPixels = new int[width * height];
148                
149                if ( morphImage != null ) {
150                        int[] morphPixels = getRGB( morphImage, 0, 0, width, height, null );
151                        morph( inPixels, morphPixels, outPixels, sourceGrid, destGrid, width, height, time );
152                } else if (frames <= 1) {
153                        sourceGrid.warp(inPixels, width, height, sourceGrid, destGrid, outPixels);
154                } else {
155                        WarpGrid newGrid = new WarpGrid(sourceGrid.rows, sourceGrid.cols, width, height);
156                        for (int i = 0; i < frames; i++) {
157                                float t = (float)i/(frames-1);
158                                sourceGrid.lerp(t, destGrid, newGrid);
159                                sourceGrid.warp(inPixels, width, height, sourceGrid, newGrid, outPixels);
160                        }
161                }
162                return outPixels;
163        }
164
165        public void morph(int[] srcPixels, int[] destPixels, int[] outPixels, WarpGrid srcGrid, WarpGrid destGrid, int width, int height, float t) {
166                WarpGrid newGrid = new WarpGrid(srcGrid.rows, srcGrid.cols, width, height);
167                srcGrid.lerp(t, destGrid, newGrid);
168                srcGrid.warp(srcPixels, width, height, srcGrid, newGrid, outPixels);
169                int[] destPixels2 = new int[width * height];
170                destGrid.warp(destPixels, width, height, destGrid, newGrid, destPixels2);
171                crossDissolve(outPixels, destPixels2, width, height, t);
172        }
173
174        public void crossDissolve(int[] pixels1, int[] pixels2, int width, int height, float t) {
175                int index = 0;
176                for (int y = 0; y < height; y++) {
177                        for (int x = 0; x < width; x++) {
178                                pixels1[index] = ImageMath.mixColors(t, pixels1[index], pixels2[index]);
179                                index++;
180                        }
181                }
182        }
183        
184        public String toString() {
185                return "Distort/Mesh Warp...";
186        }
187
188        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
189                Object o;
190                if((o=parameters.removeEL(KeyImpl.init("SourceGrid")))!=null)setSourceGrid(ImageFilterUtil.toWarpGrid(o,"SourceGrid"));
191                if((o=parameters.removeEL(KeyImpl.init("DestGrid")))!=null)setDestGrid(ImageFilterUtil.toWarpGrid(o,"DestGrid"));
192                if((o=parameters.removeEL(KeyImpl.init("Frames")))!=null)setFrames(ImageFilterUtil.toIntValue(o,"Frames"));
193                if((o=parameters.removeEL(KeyImpl.init("MorphImage")))!=null)setMorphImage(ImageFilterUtil.toBufferedImage(o,"MorphImage"));
194                if((o=parameters.removeEL(KeyImpl.init("Time")))!=null)setTime(ImageFilterUtil.toFloatValue(o,"Time"));
195
196                // check for arguments not supported
197                if(parameters.size()>0) {
198                        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 [SourceGrid, DestGrid, Frames, MorphImage, Time]");
199                }
200
201                return filter(src, dst);
202        }
203}
204