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; 037import java.util.Date; 038import java.util.Random; 039 040import lucee.runtime.engine.ThreadLocalPageContext; 041import lucee.runtime.exp.FunctionException; 042import lucee.runtime.exp.PageException; 043import lucee.runtime.img.ImageUtil; 044import lucee.runtime.type.KeyImpl; 045import lucee.runtime.type.Struct; 046import lucee.runtime.type.util.CollectionUtil; 047 048public class SmearFilter extends WholeImageFilter implements DynFiltering { 049 050 public final static int CROSSES = 0; 051 public final static int LINES = 1; 052 public final static int CIRCLES = 2; 053 public final static int SQUARES = 3; 054 055 private Colormap colormap = new LinearColormap(); 056 private float angle = 0; 057 private float density = 0.5f; 058 private float scatter = 0.0f; 059 private int distance = 8; 060 private Random randomGenerator; 061 private long seed = 567; 062 private int shape = LINES; 063 private float mix = 0.5f; 064 private int fadeout = 0; 065 private boolean background = false; 066 067 public SmearFilter() { 068 randomGenerator = new Random(); 069 } 070 071 public void setShape(int shape) { 072 this.shape = shape; 073 } 074 075 public int getShape() { 076 return shape; 077 } 078 079 public void setDistance(int distance) { 080 this.distance = distance; 081 } 082 083 public int getDistance() { 084 return distance; 085 } 086 087 public void setDensity(float density) { 088 this.density = density; 089 } 090 091 public float getDensity() { 092 return density; 093 } 094 095 public void setScatter(float scatter) { 096 this.scatter = scatter; 097 } 098 099 public float getScatter() { 100 return scatter; 101 } 102 103 /** 104 * Specifies the angle of the texture. 105 * @param angle the angle of the texture. 106 * @angle 107 * @see #getAngle 108 */ 109 public void setAngle(float angle) { 110 this.angle = angle; 111 } 112 113 /** 114 * Returns the angle of the texture. 115 * @return the angle of the texture. 116 * @see #setAngle 117 */ 118 public float getAngle() { 119 return angle; 120 } 121 122 public void setMix(float mix) { 123 this.mix = mix; 124 } 125 126 public float getMix() { 127 return mix; 128 } 129 130 public void setFadeout(int fadeout) { 131 this.fadeout = fadeout; 132 } 133 134 public int getFadeout() { 135 return fadeout; 136 } 137 138 public void setBackground(boolean background) { 139 this.background = background; 140 } 141 142 public boolean getBackground() { 143 return background; 144 } 145 146 public void randomize() { 147 seed = new Date().getTime(); 148 } 149 150 private float random(float low, float high) { 151 return low+(high-low) * randomGenerator.nextFloat(); 152 } 153 154 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 155 int[] outPixels = new int[width * height]; 156 157 randomGenerator.setSeed(seed); 158 float sinAngle = (float)Math.sin(angle); 159 float cosAngle = (float)Math.cos(angle); 160 161 int i = 0; 162 int numShapes; 163 164 for (int y = 0; y < height; y++) 165 for (int x = 0; x < width; x++) { 166 outPixels[i] = background ? 0xffffffff : inPixels[i]; 167 i++; 168 } 169 170 switch (shape) { 171 case CROSSES: 172 //Crosses 173 numShapes = (int)(2*density*width * height / (distance + 1)); 174 for (i = 0; i < numShapes; i++) { 175 int x = (randomGenerator.nextInt() & 0x7fffffff) % width; 176 int y = (randomGenerator.nextInt() & 0x7fffffff) % height; 177 int length = randomGenerator.nextInt() % distance + 1; 178 int rgb = inPixels[y*width+x]; 179 for (int x1 = x - length; x1 < x + length + 1; x1++) { 180 if (x1 >= 0 && x1 < width) { 181 int rgb2 = background ? 0xffffffff : outPixels[y*width+x1]; 182 outPixels[y*width+x1] = ImageMath.mixColors(mix, rgb2, rgb); 183 } 184 } 185 for (int y1 = y - length; y1 < y + length + 1; y1++) { 186 if (y1 >= 0 && y1 < height) { 187 int rgb2 = background ? 0xffffffff : outPixels[y1*width+x]; 188 outPixels[y1*width+x] = ImageMath.mixColors(mix, rgb2, rgb); 189 } 190 } 191 } 192 break; 193 case LINES: 194 numShapes = (int)(2*density*width * height / 2); 195 196 for (i = 0; i < numShapes; i++) { 197 int sx = (randomGenerator.nextInt() & 0x7fffffff) % width; 198 int sy = (randomGenerator.nextInt() & 0x7fffffff) % height; 199 int rgb = inPixels[sy*width+sx]; 200 int length = (randomGenerator.nextInt() & 0x7fffffff) % distance; 201 int dx = (int)(length*cosAngle); 202 int dy = (int)(length*sinAngle); 203 204 int x0 = sx-dx; 205 int y0 = sy-dy; 206 int x1 = sx+dx; 207 int y1 = sy+dy; 208 int x, y, d, incrE, incrNE, ddx, ddy; 209 210 if (x1 < x0) 211 ddx = -1; 212 else 213 ddx = 1; 214 if (y1 < y0) 215 ddy = -1; 216 else 217 ddy = 1; 218 dx = x1-x0; 219 dy = y1-y0; 220 dx = Math.abs(dx); 221 dy = Math.abs(dy); 222 x = x0; 223 y = y0; 224 225 if (x < width && x >= 0 && y < height && y >= 0) { 226 int rgb2 = background ? 0xffffffff : outPixels[y*width+x]; 227 outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb); 228 } 229 if (Math.abs(dx) > Math.abs(dy)) { 230 d = 2*dy-dx; 231 incrE = 2*dy; 232 incrNE = 2*(dy-dx); 233 234 while (x != x1) { 235 if (d <= 0) 236 d += incrE; 237 else { 238 d += incrNE; 239 y += ddy; 240 } 241 x += ddx; 242 if (x < width && x >= 0 && y < height && y >= 0) { 243 int rgb2 = background ? 0xffffffff : outPixels[y*width+x]; 244 outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb); 245 } 246 } 247 } else { 248 d = 2*dx-dy; 249 incrE = 2*dx; 250 incrNE = 2*(dx-dy); 251 252 while (y != y1) { 253 if (d <= 0) 254 d += incrE; 255 else { 256 d += incrNE; 257 x += ddx; 258 } 259 y += ddy; 260 if (x < width && x >= 0 && y < height && y >= 0) { 261 int rgb2 = background ? 0xffffffff : outPixels[y*width+x]; 262 outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb); 263 } 264 } 265 } 266 } 267 break; 268 case SQUARES: 269 case CIRCLES: 270 int radius = distance+1; 271 int radius2 = radius * radius; 272 numShapes = (int)(2*density*width * height / radius); 273 for (i = 0; i < numShapes; i++) { 274 int sx = (randomGenerator.nextInt() & 0x7fffffff) % width; 275 int sy = (randomGenerator.nextInt() & 0x7fffffff) % height; 276 int rgb = inPixels[sy*width+sx]; 277 for (int x = sx - radius; x < sx + radius + 1; x++) { 278 for (int y = sy - radius; y < sy + radius + 1; y++) { 279 int f; 280 if (shape == CIRCLES) 281 f = (x - sx) * (x - sx) + (y - sy) * (y - sy); 282 else 283 f = 0; 284 if (x >= 0 && x < width && y >= 0 && y < height && f <= radius2) { 285 int rgb2 = background ? 0xffffffff : outPixels[y*width+x]; 286 outPixels[y*width+x] = ImageMath.mixColors(mix, rgb2, rgb); 287 } 288 } 289 } 290 } 291 } 292 293 return outPixels; 294 } 295 296 public String toString() { 297 return "Effects/Smear..."; 298 } 299 300 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 301 Object o; 302 if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle")); 303 if((o=parameters.removeEL(KeyImpl.init("Density")))!=null)setDensity(ImageFilterUtil.toFloatValue(o,"Density")); 304 if((o=parameters.removeEL(KeyImpl.init("Distance")))!=null)setDistance(ImageFilterUtil.toIntValue(o,"Distance")); 305 if((o=parameters.removeEL(KeyImpl.init("Shape")))!=null)setShape(ImageFilterUtil.toIntValue(o,"Shape")); 306 if((o=parameters.removeEL(KeyImpl.init("Scatter")))!=null)setScatter(ImageFilterUtil.toFloatValue(o,"Scatter")); 307 if((o=parameters.removeEL(KeyImpl.init("Mix")))!=null)setMix(ImageFilterUtil.toFloatValue(o,"Mix")); 308 if((o=parameters.removeEL(KeyImpl.init("Fadeout")))!=null)setFadeout(ImageFilterUtil.toIntValue(o,"Fadeout")); 309 if((o=parameters.removeEL(KeyImpl.init("Background")))!=null)setBackground(ImageFilterUtil.toBooleanValue(o,"Background")); 310 311 // check for arguments not supported 312 if(parameters.size()>0) { 313 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 [Angle, Density, Distance, Shape, Scatter, Mix, Fadeout, Background]"); 314 } 315 316 return filter(src, dst); 317 } 318}