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.Point;
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 class which warps an image using a field Warp algorithm.
048 */
049public class FieldWarpFilter extends TransformFilter  implements DynFiltering {
050
051        public static class Line {
052                public int x1, y1, x2, y2;
053                public int dx, dy;
054                public float length, lengthSquared;
055                
056                public Line(int x1, int y1, int x2, int y2) {
057                        this.x1 = x1;
058                        this.y1 = y1;
059                        this.x2 = x2;
060                        this.y2 = y2;
061                }
062                
063                public void setup() {
064                        dx = x2-x1;
065                        dy = y2-y1;
066                        lengthSquared = dx*dx + dy*dy;
067                        length = (float)Math.sqrt(lengthSquared);
068                }
069        }
070
071        private float amount = 1.0f;
072        private float power = 1.0f;
073        private float strength = 2.0f;
074        private Line[] inLines;
075        private Line[] outLines;
076        private Line[] intermediateLines;
077        private float width, height;
078
079        public FieldWarpFilter() {
080        }
081
082        /**
083         * Set the amount of warp.
084         * @param amount the amount
085     * @min-value 0
086     * @max-value 1
087     * @see #getAmount
088         */
089        public void setAmount(float amount) {
090                this.amount = amount;
091        }
092        
093        /**
094         * Get the amount of warp.
095         * @return the amount
096     * @see #setAmount
097         */
098        public float getAmount() {
099                return amount;
100        }
101        
102        public void setPower(float power) {
103                this.power = power;
104        }
105        
106        public float getPower() {
107                return power;
108        }
109        
110        public void setStrength(float strength) {
111                this.strength = strength;
112        }
113        
114        public float getStrength() {
115                return strength;
116        }
117        
118        public void setInLines( Line[] inLines ) {
119                this.inLines = inLines;
120        }
121        
122        public Line[] getInLines() {
123                return inLines;
124        }
125        
126        public void setOutLines( Line[] outLines ) {
127                this.outLines = outLines;
128        }
129        
130        public Line[] getOutLines() {
131                return outLines;
132        }
133        
134        protected void transform(int x, int y, Point out) {
135        }
136
137        protected void transformInverse(int x, int y, float[] out) {
138                float u = 0, v = 0;
139                float fraction = 0;
140                float distance;
141                float fdist;
142                float weight;
143                float a = 0.001f;
144                float b = 1.5f*strength + 0.5f;
145                float p = power;
146
147                float totalWeight = 0.0f;
148                float sumX = 0.0f;
149                float sumY = 0.0f;
150
151                for (int line = 0; line < inLines.length; line++) {
152                        Line l1 = inLines[line];
153                        Line l = intermediateLines[line];
154                        float dx = x - l.x1;
155                        float dy = y - l.y1;
156
157                        fraction = (dx * l.dx + dy * l.dy) / l.lengthSquared;
158                        fdist = (dy * l.dx - dx * l.dy) / l.length;
159                        if (fraction <= 0)
160                                distance = (float)Math.sqrt(dx*dx + dy*dy);
161                        else if (fraction >= 1) {
162                                dx = x - l.x2;
163                                dy = y - l.y2;
164                                distance = (float)Math.sqrt(dx*dx + dy*dy);
165                        } else if (fdist >= 0)
166                                distance = fdist;
167                        else
168                                distance = -fdist;
169                        u = l1.x1 + fraction * l1.dx - fdist * l1.dy / l1.length;
170                        v = l1.y1 + fraction * l1.dy + fdist * l1.dx / l1.length;
171
172                        weight = (float)Math.pow(Math.pow(l.length, p) / (a + distance), b);
173
174                        sumX += (u - x) * weight;
175                        sumY += (v - y) * weight;
176//if (x % 10 == 0&&y == 20)System.out.println("distance="+distance+" weight="+weight+" sumX="+sumX+" sumY="+sumY+" u="+u+" v="+v);
177                        totalWeight += weight;
178                }
179
180//              out[0] = ImageMath.clamp(x + sumX / totalWeight + 0.5f, 0, width-1);
181//              out[1] = ImageMath.clamp(y + sumY / totalWeight + 0.5f, 0, height-1);
182                out[0] = x + sumX / totalWeight + 0.5f;
183                out[1] = y + sumY / totalWeight + 0.5f;
184        }
185
186    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
187                this.width = width;
188                this.height = height;
189                if ( inLines != null && outLines != null ) {
190                        intermediateLines = new Line[inLines.length];
191                        for (int line = 0; line < inLines.length; line++) {
192                                Line l = intermediateLines[line] = new Line(
193                                        ImageMath.lerp(amount, inLines[line].x1, outLines[line].x1),
194                                        ImageMath.lerp(amount, inLines[line].y1, outLines[line].y1),
195                                        ImageMath.lerp(amount, inLines[line].x2, outLines[line].x2),
196                                        ImageMath.lerp(amount, inLines[line].y2, outLines[line].y2)
197                                );
198                                l.setup();
199                                inLines[line].setup();
200                        }
201                        dst = super.filter( src, dst );
202                        intermediateLines = null;
203                        return dst;
204                }
205                return src;
206        }
207
208        public String toString() {
209                return "Distort/Field Warp...";
210        }
211
212        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
213                Object o;
214                if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount"));
215                if((o=parameters.removeEL(KeyImpl.init("Power")))!=null)setPower(ImageFilterUtil.toFloatValue(o,"Power"));
216                if((o=parameters.removeEL(KeyImpl.init("Strength")))!=null)setStrength(ImageFilterUtil.toFloatValue(o,"Strength"));
217                if((o=parameters.removeEL(KeyImpl.init("InLines")))!=null)setInLines(ImageFilterUtil.toAFieldWarpFilter$Line(o,"InLines"));
218                if((o=parameters.removeEL(KeyImpl.init("OutLines")))!=null)setOutLines(ImageFilterUtil.toAFieldWarpFilter$Line(o,"OutLines"));
219                if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction"));
220                if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation"));
221
222                // check for arguments not supported
223                if(parameters.size()>0) {
224                        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 [Amount, Power, Strength, InLines, OutLines, EdgeAction, Interpolation]");
225                }
226
227                return filter(src, dst);
228        }
229}