001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.geom.*;
021import java.awt.image.*;
022
023/**
024 * A page curl effect.
025 */
026public class CurlFilter extends TransformFilter {
027        
028        private float angle = 0;
029        private float transition = 0.0f;
030
031        private float width;
032        private float height;
033        private float radius;
034
035        /**
036         * Construct a CurlFilter with no distortion.
037         */
038        public CurlFilter() {
039                setEdgeAction( ZERO );
040        }
041
042        public void setTransition( float transition ) {
043                this.transition = transition;
044        }
045        
046        public float getTransition() {
047                return transition;
048        }
049        
050        public void setAngle(float angle) {
051                this.angle = angle;
052        }
053        
054        public float getAngle() {
055                return angle;
056        }
057        
058        public void setRadius( float radius ) {
059                this.radius = radius;
060        }
061
062        public float getRadius() {
063                return radius;
064        }
065        
066/*
067    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
068                this.width = src.getWidth();
069                this.height = src.getHeight();
070                return super.filter( src, dst );
071        }
072*/
073
074        static class Sampler {
075                private int edgeAction;
076                private int width, height;
077                private int[] inPixels;
078                
079                public Sampler( BufferedImage image ) {
080                        int width = image.getWidth();
081                        int height = image.getHeight();
082                        int type = image.getType();
083                        inPixels = ImageUtils.getRGB( image, 0, 0, width, height, null );
084                }
085                
086                public int sample( float x, float y ) {
087                        int srcX = (int)Math.floor( x );
088                        int srcY = (int)Math.floor( y );
089                        float xWeight = x-srcX;
090                        float yWeight = y-srcY;
091                        int nw, ne, sw, se;
092
093                        if ( srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1) {
094                                // Easy case, all corners are in the image
095                                int i = width*srcY + srcX;
096                                nw = inPixels[i];
097                                ne = inPixels[i+1];
098                                sw = inPixels[i+width];
099                                se = inPixels[i+width+1];
100                        } else {
101                                // Some of the corners are off the image
102                                nw = getPixel( inPixels, srcX, srcY, width, height );
103                                ne = getPixel( inPixels, srcX+1, srcY, width, height );
104                                sw = getPixel( inPixels, srcX, srcY+1, width, height );
105                                se = getPixel( inPixels, srcX+1, srcY+1, width, height );
106                        }
107                        return ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se);
108                }
109
110                final private int getPixel( int[] pixels, int x, int y, int width, int height ) {
111                        if (x < 0 || x >= width || y < 0 || y >= height) {
112                                switch (edgeAction) {
113                                case ZERO:
114                                default:
115                                        return 0;
116                                case WRAP:
117                                        return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)];
118                                case CLAMP:
119                                        return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)];
120                                }
121                        }
122                        return pixels[ y*width+x ];
123                }
124        }
125        
126    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
127        int width = src.getWidth();
128        int height = src.getHeight();
129                this.width = src.getWidth();
130                this.height = src.getHeight();
131                int type = src.getType();
132
133                originalSpace = new Rectangle(0, 0, width, height);
134                transformedSpace = new Rectangle(0, 0, width, height);
135                transformSpace(transformedSpace);
136
137        if ( dst == null ) {
138            ColorModel dstCM = src.getColorModel();
139                        dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height), dstCM.isAlphaPremultiplied(), null);
140                }
141                WritableRaster dstRaster = dst.getRaster();
142
143                int[] inPixels = getRGB( src, 0, 0, width, height, null );
144
145                if ( interpolation == NEAREST_NEIGHBOUR )
146                        return filterPixelsNN( dst, width, height, inPixels, transformedSpace );
147
148                int srcWidth = width;
149                int srcHeight = height;
150                int srcWidth1 = width-1;
151                int srcHeight1 = height-1;
152                int outWidth = transformedSpace.width;
153                int outHeight = transformedSpace.height;
154                int outX, outY;
155                int index = 0;
156                int[] outPixels = new int[outWidth];
157
158                outX = transformedSpace.x;
159                outY = transformedSpace.y;
160                float[] out = new float[4];
161
162                for (int y = 0; y < outHeight; y++) {
163                        for (int x = 0; x < outWidth; x++) {
164                                transformInverse(outX+x, outY+y, out);
165                                int srcX = (int)Math.floor( out[0] );
166                                int srcY = (int)Math.floor( out[1] );
167                                float xWeight = out[0]-srcX;
168                                float yWeight = out[1]-srcY;
169                                int nw, ne, sw, se;
170
171                                if ( srcX >= 0 && srcX < srcWidth1 && srcY >= 0 && srcY < srcHeight1) {
172                                        // Easy case, all corners are in the image
173                                        int i = srcWidth*srcY + srcX;
174                                        nw = inPixels[i];
175                                        ne = inPixels[i+1];
176                                        sw = inPixels[i+srcWidth];
177                                        se = inPixels[i+srcWidth+1];
178                                } else {
179                                        // Some of the corners are off the image
180                                        nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight );
181                                        ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight );
182                                        sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight );
183                                        se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight );
184                                }
185                                int rgb = ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se);
186                                int r = (rgb >> 16) & 0xff;
187                                int g = (rgb >> 8) & 0xff;
188                                int b = rgb & 0xff;
189                                float shade = out[2];
190                                r = (int)(r * shade);
191                                g = (int)(g * shade);
192                                b = (int)(b * shade);
193                                rgb = (rgb & 0xff000000) | (r << 16) | (g << 8) | b;
194                                if ( out[3] != 0 )
195                                        outPixels[x] = PixelUtils.combinePixels( rgb, inPixels[srcWidth*y + x], PixelUtils.NORMAL );
196                                else
197                                        outPixels[x] = rgb;
198                        }
199                        setRGB( dst, 0, y, transformedSpace.width, 1, outPixels );
200                }
201                return dst;
202        }
203
204        final private int getPixel( int[] pixels, int x, int y, int width, int height ) {
205                if (x < 0 || x >= width || y < 0 || y >= height) {
206                        switch (edgeAction) {
207                        case ZERO:
208                        default:
209                                return 0;
210                        case WRAP:
211                                return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)];
212                        case CLAMP:
213                                return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)];
214                        }
215                }
216                return pixels[ y*width+x ];
217        }
218
219        protected void transformInverse(int x, int y, float[] out) {
220/*Fisheye
221                float mirrorDistance = width*centreX;
222                float mirrorRadius = width*centreY;
223                float cx = width*.5f;
224                float cy = height*.5f;
225                float dx = x-cx;
226                float dy = y-cy;
227                float r2 = dx*dx+dy*dy;
228                float r = (float)Math.sqrt( r2 );
229                float phi = (float)(Math.PI*.5-Math.asin( Math.sqrt( mirrorRadius*mirrorRadius-r2 )/mirrorRadius ));
230                r = r > mirrorRadius ? width : mirrorDistance * (float)Math.tan( phi );
231                phi = (float)Math.atan2( dy, dx );
232                out[0] = cx + r*(float)Math.cos( phi );
233                out[1] = cy + r*(float)Math.sin( phi );
234*/
235                float t = transition;
236                float px = x, py = y;
237                float s = (float)Math.sin( angle );
238                float c = (float)Math.cos( angle );
239                float tx = t*width;
240                tx = t * (float)Math.sqrt( width*width + height*height);
241
242                // Start from the correct corner according to the angle
243                float xoffset = c < 0 ? width : 0;
244                float yoffset = s < 0 ? height : 0;
245
246                // Transform into unrotated coordinates
247                px -= xoffset;
248                py -= yoffset;
249
250                float qx = px * c + py * s;
251                float qy = -px * s + py * c;
252
253                boolean outside = qx < tx;
254                boolean unfolded = qx > tx*2;
255                boolean oncurl = !(outside || unfolded);
256
257                qx = qx > tx*2 ? qx : 2*tx-qx;
258        
259                // Transform back into rotated coordinates
260                px = qx * c - qy * s;
261                py = qx * s + qy * c;
262                px += xoffset;
263                py += yoffset;
264
265                // See if we're off the edge of the page
266                boolean offpage = px < 0 || py < 0 || px >= width || py >= height;
267
268                // If we're off the edge, but in the curl...
269                if ( offpage && oncurl ) {
270                        px = x;
271                        py = y;
272                }
273        
274                // Shade the curl
275                float shade = !offpage && oncurl ? 1.9f * (1.0f-(float)Math.cos( Math.exp((qx-tx)/radius) )) : 0;
276                out[2] = 1-shade;
277
278                if ( outside ) {
279                        out[0] = out[1] = -1;
280                } else {
281                        out[0] = px;
282                        out[1] = py;
283                }
284                
285                out[3] = !offpage && oncurl ? 1 : 0;
286        }
287
288        public String toString() {
289                return "Distort/Curl...";
290        }
291
292}