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}