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.Graphics2D;
037import java.awt.Rectangle;
038import java.awt.Shape;
039import java.awt.geom.AffineTransform;
040import java.awt.geom.Point2D;
041import java.awt.image.BufferedImage;
042import java.util.Random;
043
044import lucee.runtime.engine.ThreadLocalPageContext;
045import lucee.runtime.exp.FunctionException;
046import lucee.runtime.exp.PageException;
047import lucee.runtime.img.ImageUtil;
048import lucee.runtime.type.KeyImpl;
049import lucee.runtime.type.Struct;
050import lucee.runtime.type.util.CollectionUtil;
051
052public class ShatterFilter extends AbstractBufferedImageOp  implements DynFiltering {
053    private float centreX = 0.5f, centreY = 0.5f;
054    private float distance;
055    private float transition;
056    private float rotation;
057    private float zoom;
058    private float startAlpha = 1;
059    private float endAlpha = 1;
060    private int iterations = 5;
061    private int tile;
062
063    public ShatterFilter() {
064        }
065        
066        public void setTransition( float transition ) {
067                this.transition = transition;
068        }
069
070        public float getTransition() {
071                return transition;
072        }
073        
074        public void setDistance( float distance ) {
075                this.distance = distance;
076        }
077
078        public float getDistance() {
079                return distance;
080        }
081        
082        public void setRotation( float rotation ) {
083                this.rotation = rotation;
084        }
085
086        public float getRotation() {
087                return rotation;
088        }
089        
090        public void setZoom( float zoom ) {
091                this.zoom = zoom;
092        }
093
094        public float getZoom() {
095                return zoom;
096        }
097        
098        public void setStartAlpha( float startAlpha ) {
099                this.startAlpha = startAlpha;
100        }
101
102        public float getStartAlpha() {
103                return startAlpha;
104        }
105        
106        public void setEndAlpha( float endAlpha ) {
107                this.endAlpha = endAlpha;
108        }
109
110        public float getEndAlpha() {
111                return endAlpha;
112        }
113        
114        public void setCentreX( float centreX ) {
115                this.centreX = centreX;
116        }
117
118        public float getCentreX() {
119                return centreX;
120        }
121        
122        public void setCentreY( float centreY ) {
123                this.centreY = centreY;
124        }
125
126        public float getCentreY() {
127                return centreY;
128        }
129
130        public Point2D getCentre() {
131                return new Point2D.Float( centreX, centreY );
132        }
133        
134        public void setIterations( int iterations ) {
135                this.iterations = iterations;
136        }
137
138        public int getIterations() {
139                return iterations;
140        }
141        
142        public void setTile( int tile ) {
143                this.tile = tile;
144        }
145
146        public int getTile() {
147                return tile;
148        }
149        
150        static class Tile {
151                float x, y, vx, vy, w, h;
152                float rotation;
153                Shape shape;
154        }
155        
156    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
157        if ( dst == null )
158            dst = createCompatibleDestImage( src, null );
159        float width = src.getWidth();
160        float height = src.getHeight();
161        float cx = src.getWidth() * centreX;
162        float cy = src.getHeight() * centreY;
163        //float imageRadius = (float)Math.sqrt( cx*cx + cy*cy );
164
165//        BufferedImage[] tiles = new BufferedImage[iterations];
166                int numTiles = iterations*iterations;
167        Tile[] shapes = new Tile[numTiles];
168        float[] rx = new float[numTiles];
169        float[] ry = new float[numTiles];
170        float[] rz = new float[numTiles];
171
172                Graphics2D g = dst.createGraphics();
173//              g.drawImage( src, null, null );
174
175        Random random = new Random( 0 );
176                //float lastx = 0, lasty = 0;
177/*
178        for ( int i = 0; i <= numTiles; i++ ) {
179            double angle = (double)i * 2*Math.PI / numTiles;
180            float x = cx + width*(float)Math.cos(angle);
181            float y = cy + height*(float)Math.sin(angle);
182            g.setColor( Color.black );
183            g.setColor( Color.getHSBColor( (float)angle, 1, 1 ) );
184                        if ( i != 0 ) {
185                                rz[i-1] = tile*(2*random.nextFloat()-1);
186                                ry[i-1] = tile*random.nextFloat();
187                                rz[i-1] = tile*random.nextFloat();
188                GeneralPath p = new GeneralPath();
189                p.moveTo( cx, cy );
190                p.lineTo( lastx, lasty );
191                p.lineTo( x, y );
192                p.closePath();
193                                shapes[i-1] = p;
194//                Rectangle r = p.getBounds();
195//                r.intersect( r, new Rectangle( (int)width, (int)height ), r );
196            }
197            lastx = x;
198            lasty = y;
199        }
200*/
201        for ( int y = 0; y < iterations; y++ ) {
202                        int y1 = (int)height*y/iterations;
203                        int y2 = (int)height*(y+1)/iterations;
204                        for ( int x = 0; x < iterations; x++ ) {
205                                int i = y*iterations+x;
206                                int x1 = (int)width*x/iterations;
207                                int x2 = (int)width*(x+1)/iterations;
208                                rx[i] = tile*random.nextFloat();
209                                ry[i] = tile*random.nextFloat();
210                        rx[i] = 0;
211                        ry[i] = 0;
212                                rz[i] = tile*(2*random.nextFloat()-1);
213                Shape p = new Rectangle( x1, y1, x2-x1, y2-y1 );
214                                shapes[i] = new Tile();
215                                shapes[i].shape = p;
216                                shapes[i].x = (x1+x2)*0.5f;
217                                shapes[i].y = (y1+y2)*0.5f;
218                                shapes[i].vx = width-(cx-x);
219                                shapes[i].vy = height-(cy-y);
220                                shapes[i].w = x2-x1;
221                                shapes[i].h = y2-y1;
222            }
223        }
224
225                for ( int i = 0; i < numTiles; i++ ) {
226                        float h = (float)i / numTiles;
227                        double angle = h * 2*Math.PI;
228                        float x = transition * width*(float)Math.cos(angle);
229                        float y = transition * height*(float)Math.sin(angle);
230
231                        Tile tile = shapes[i];
232                        //Rectangle r = 
233                        tile.shape.getBounds();
234                        AffineTransform t = g.getTransform();
235x = tile.x + transition * tile.vx;
236y = tile.y + transition * tile.vy;
237                        g.translate( x, y );
238//                      g.translate( tile.w*0.5f, tile.h*0.5f );
239                        g.rotate( transition * rz[i] );
240//                      g.scale( (float)Math.cos( transition * rx[i] ), (float)Math.cos( transition * ry[i] ) );
241//                      g.translate( -tile.w*0.5f, -tile.h*0.5f );
242                        g.setColor( Color.getHSBColor( h, 1, 1 ) );
243                        Shape clip = g.getClip();
244                        g.clip( tile.shape );
245                        g.drawImage( src, 0, 0, null );
246                        g.setClip( clip );
247                        g.setTransform( t );
248                }
249
250                g.dispose();
251        return dst;
252    }
253    
254        public String toString() {
255                return "Transition/Shatter...";
256        }
257        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
258                Object o;
259                if((o=parameters.removeEL(KeyImpl.init("Iterations")))!=null)setIterations(ImageFilterUtil.toIntValue(o,"Iterations"));
260                if((o=parameters.removeEL(KeyImpl.init("CentreX")))!=null)setCentreX(ImageFilterUtil.toFloatValue(o,"CentreX"));
261                if((o=parameters.removeEL(KeyImpl.init("CentreY")))!=null)setCentreY(ImageFilterUtil.toFloatValue(o,"CentreY"));
262                //if((o=parameters.removeEL(KeyImpl.init("Centre")))!=null)setCentre(ImageFilterUtil.toPoint2D(o,"Centre"));
263                if((o=parameters.removeEL(KeyImpl.init("Transition")))!=null)setTransition(ImageFilterUtil.toFloatValue(o,"Transition"));
264                if((o=parameters.removeEL(KeyImpl.init("Distance")))!=null)setDistance(ImageFilterUtil.toFloatValue(o,"Distance"));
265                if((o=parameters.removeEL(KeyImpl.init("Rotation")))!=null)setRotation(ImageFilterUtil.toFloatValue(o,"Rotation"));
266                if((o=parameters.removeEL(KeyImpl.init("Zoom")))!=null)setZoom(ImageFilterUtil.toFloatValue(o,"Zoom"));
267                if((o=parameters.removeEL(KeyImpl.init("StartAlpha")))!=null)setStartAlpha(ImageFilterUtil.toFloatValue(o,"StartAlpha"));
268                if((o=parameters.removeEL(KeyImpl.init("EndAlpha")))!=null)setEndAlpha(ImageFilterUtil.toFloatValue(o,"EndAlpha"));
269                if((o=parameters.removeEL(KeyImpl.init("Tile")))!=null)setTile(ImageFilterUtil.toIntValue(o,"Tile"));
270
271                // check for arguments not supported
272                if(parameters.size()>0) {
273                        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 [Iterations, CentreX, CentreY, Centre, Transition, Distance, Rotation, Zoom, StartAlpha, EndAlpha, Tile]");
274                }
275
276                return filter(src, dst);
277        }
278}