001 /* 002 * 003 004 Licensed under the Apache License, Version 2.0 (the "License"); 005 you may not use this file except in compliance with the License. 006 You may obtain a copy of the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010 Unless required by applicable law or agreed to in writing, software 011 distributed under the License is distributed on an "AS IS" BASIS, 012 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 See the License for the specific language governing permissions and 014 limitations under the License. 015 */ 016 017 package railo.runtime.img.filter;import java.awt.Point; 018 import java.awt.image.BufferedImage; 019 020 import railo.runtime.engine.ThreadLocalPageContext; 021 import railo.runtime.exp.FunctionException; 022 import railo.runtime.exp.PageException; 023 import railo.runtime.img.ImageUtil; 024 import railo.runtime.type.KeyImpl; 025 import railo.runtime.type.Struct; 026 import railo.runtime.type.util.CollectionUtil; 027 028 /** 029 * A filter which draws a coloured gradient. This is largely superceded by GradientPaint in Java1.2, but does provide a few 030 * more gradient options. 031 */ 032 public class GradientFilter extends AbstractBufferedImageOp implements DynFiltering { 033 034 public final static int LINEAR = 0; 035 public final static int BILINEAR = 1; 036 public final static int RADIAL = 2; 037 public final static int CONICAL = 3; 038 public final static int BICONICAL = 4; 039 public final static int SQUARE = 5; 040 041 public final static int INT_LINEAR = 0; 042 public final static int INT_CIRCLE_UP = 1; 043 public final static int INT_CIRCLE_DOWN = 2; 044 public final static int INT_SMOOTH = 3; 045 046 private float angle = 0; 047 private int color1 = 0xff000000; 048 private int color2 = 0xffffffff; 049 private Point p1 = new Point(0, 0), p2 = new Point(64, 64); 050 private boolean repeat = false; 051 private float x1; 052 private float y1; 053 private float dx; 054 private float dy; 055 private Colormap colormap = null; 056 private int type; 057 private int interpolation = INT_LINEAR; 058 private int paintMode = PixelUtils.NORMAL; 059 060 public GradientFilter() { 061 colormap = new LinearColormap(color1, color2); 062 } 063 064 public GradientFilter(Point p1, Point p2, int color1, int color2, boolean repeat, int type, int interpolation) { 065 this.p1 = p1; 066 this.p2 = p2; 067 this.color1 = color1; 068 this.color2 = color2; 069 this.repeat = repeat; 070 this.type = type; 071 this.interpolation = interpolation; 072 colormap = new LinearColormap(color1, color2); 073 } 074 075 public void setPoint1(Point point1) { 076 this.p1 = point1; 077 } 078 079 public Point getPoint1() { 080 return p1; 081 } 082 083 public void setPoint2(Point point2) { 084 this.p2 = point2; 085 } 086 087 public Point getPoint2() { 088 return p2; 089 } 090 091 public void setType(int type) { 092 this.type = type; 093 } 094 095 public int getType() { 096 return type; 097 } 098 099 public void setInterpolation(int interpolation) { 100 this.interpolation = interpolation; 101 } 102 103 public int getInterpolation() { 104 return interpolation; 105 } 106 107 /** 108 * Specifies the angle of the texture. 109 * @param angle the angle of the texture. 110 * @angle 111 * @see #getAngle 112 */ 113 public void setAngle(float angle) { 114 this.angle = angle; 115 p2 = new Point((int)(64*Math.cos(angle)), (int)(64*Math.sin(angle))); 116 } 117 118 /** 119 * Returns the angle of the texture. 120 * @return the angle of the texture. 121 * @see #setAngle 122 */ 123 public float getAngle() { 124 return angle; 125 } 126 127 /** 128 * Set the colormap to be used for the filter. 129 * @param colormap the colormap 130 * @see #getColormap 131 */ 132 public void setColormap(Colormap colormap) { 133 this.colormap = colormap; 134 } 135 136 /** 137 * Get the colormap to be used for the filter. 138 * @return the colormap 139 * @see #setColormap 140 */ 141 public Colormap getColormap() { 142 return colormap; 143 } 144 145 public void setPaintMode(int paintMode) { 146 this.paintMode = paintMode; 147 } 148 149 public int getPaintMode() { 150 return paintMode; 151 } 152 153 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 154 int width = src.getWidth(); 155 int height = src.getHeight(); 156 157 if ( dst == null ) 158 dst = createCompatibleDestImage( src, null ); 159 160 //int rgb1, rgb2; 161 float x1, y1, x2, y2; 162 x1 = p1.x; 163 x2 = p2.x; 164 165 if (x1 > x2 && type != RADIAL) { 166 y1 = x1; 167 x1 = x2; 168 x2 = y1; 169 y1 = p2.y; 170 y2 = p1.y; 171 //rgb1 = color2; 172 //rgb2 = color1; 173 } else { 174 y1 = p1.y; 175 y2 = p2.y; 176 //rgb1 = color1; 177 //rgb2 = color2; 178 } 179 float dx = x2 - x1; 180 float dy = y2 - y1; 181 float lenSq = dx * dx + dy * dy; 182 this.x1 = x1; 183 this.y1 = y1; 184 if (lenSq >= Float.MIN_VALUE) { 185 dx = dx / lenSq; 186 dy = dy / lenSq; 187 if (repeat) { 188 dx = dx % 1.0f; 189 dy = dy % 1.0f; 190 } 191 } 192 this.dx = dx; 193 this.dy = dy; 194 195 int[] pixels = new int[width]; 196 for (int y = 0; y < height; y++ ) { 197 getRGB( src, 0, y, width, 1, pixels ); 198 switch (type) { 199 case LINEAR: 200 case BILINEAR: 201 linearGradient(pixels, y, width, 1); 202 break; 203 case RADIAL: 204 radialGradient(pixels, y, width, 1); 205 break; 206 case CONICAL: 207 case BICONICAL: 208 conicalGradient(pixels, y, width, 1); 209 break; 210 case SQUARE: 211 squareGradient(pixels, y, width, 1); 212 break; 213 } 214 setRGB( dst, 0, y, width, 1, pixels ); 215 } 216 return dst; 217 } 218 219 private void repeatGradient(int[] pixels, int w, int h, float rowrel, float dx, float dy) { 220 int off = 0; 221 for (int y = 0; y < h; y++) { 222 float colrel = rowrel; 223 int j = w; 224 int rgb; 225 while (--j >= 0) { 226 if (type == BILINEAR) 227 rgb = colormap.getColor(map(ImageMath.triangle(colrel))); 228 else 229 rgb = colormap.getColor(map(ImageMath.mod(colrel, 1.0f))); 230 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 231 off++; 232 colrel += dx; 233 } 234 rowrel += dy; 235 } 236 } 237 238 private void singleGradient(int[] pixels, int w, int h, float rowrel, float dx, float dy) { 239 int off = 0; 240 for (int y = 0; y < h; y++) { 241 float colrel = rowrel; 242 int j = w; 243 int rgb; 244 if (colrel <= 0.0) { 245 rgb = colormap.getColor(0); 246 do { 247 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 248 off++; 249 colrel += dx; 250 } while (--j > 0 && colrel <= 0.0); 251 } 252 while (colrel < 1.0 && --j >= 0) { 253 if (type == BILINEAR) 254 rgb = colormap.getColor(map(ImageMath.triangle(colrel))); 255 else 256 rgb = colormap.getColor(map(colrel)); 257 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 258 off++; 259 colrel += dx; 260 } 261 if (j > 0) { 262 if (type == BILINEAR) 263 rgb = colormap.getColor(0.0f); 264 else 265 rgb = colormap.getColor(1.0f); 266 do { 267 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 268 off++; 269 } while (--j > 0); 270 } 271 rowrel += dy; 272 } 273 } 274 275 private void linearGradient(int[] pixels, int y, int w, int h) { 276 int x = 0; 277 float rowrel = (x - x1) * dx + (y - y1) * dy; 278 if (repeat) 279 repeatGradient(pixels, w, h, rowrel, dx, dy); 280 else 281 singleGradient(pixels, w, h, rowrel, dx, dy); 282 } 283 284 private void radialGradient(int[] pixels, int y, int w, int h) { 285 int off = 0; 286 float radius = distance(p2.x-p1.x, p2.y-p1.y); 287 for (int x = 0; x < w; x++) { 288 float distance = distance(x-p1.x, y-p1.y); 289 float ratio = distance / radius; 290 if (repeat) 291 ratio = ratio % 2; 292 else if (ratio > 1.0) 293 ratio = 1.0f; 294 int rgb = colormap.getColor(map(ratio)); 295 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 296 off++; 297 } 298 } 299 300 private void squareGradient(int[] pixels, int y, int w, int h) { 301 int off = 0; 302 float radius = Math.max(Math.abs(p2.x-p1.x), Math.abs(p2.y-p1.y)); 303 for (int x = 0; x < w; x++) { 304 float distance = Math.max(Math.abs(x-p1.x), Math.abs(y-p1.y)); 305 float ratio = distance / radius; 306 if (repeat) 307 ratio = ratio % 2; 308 else if (ratio > 1.0) 309 ratio = 1.0f; 310 int rgb = colormap.getColor(map(ratio)); 311 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 312 off++; 313 } 314 } 315 316 private void conicalGradient(int[] pixels, int y, int w, int h) { 317 int off = 0; 318 float angle0 = (float)Math.atan2(p2.x-p1.x, p2.y-p1.y); 319 for (int x = 0; x < w; x++) { 320 float angle = (float)(Math.atan2(x-p1.x, y-p1.y) - angle0) / (ImageMath.TWO_PI); 321 angle += 1.0f; 322 angle %= 1.0f; 323 if (type == BICONICAL) 324 angle = ImageMath.triangle(angle); 325 int rgb = colormap.getColor(map(angle)); 326 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 327 off++; 328 } 329 } 330 331 private float map(float v) { 332 if (repeat) 333 v = v > 1.0 ? 2.0f-v : v; 334 switch (interpolation) { 335 case INT_CIRCLE_UP: 336 v = ImageMath.circleUp(ImageMath.clamp(v, 0.0f, 1.0f)); 337 break; 338 case INT_CIRCLE_DOWN: 339 v = ImageMath.circleDown(ImageMath.clamp(v, 0.0f, 1.0f)); 340 break; 341 case INT_SMOOTH: 342 v = ImageMath.smoothStep(0, 1, v); 343 break; 344 } 345 return v; 346 } 347 348 private float distance(float a, float b) { 349 return (float)Math.sqrt(a*a+b*b); 350 } 351 352 public String toString() { 353 return "Other/Gradient Fill..."; 354 } 355 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 356 Object o; 357 if((o=parameters.removeEL(KeyImpl.init("Colormap")))!=null)setColormap(ImageFilterUtil.toColormap(o,"Colormap")); 358 if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toIntValue(o,"Interpolation")); 359 if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle")); 360 if((o=parameters.removeEL(KeyImpl.init("Point1")))!=null)setPoint1(ImageFilterUtil.toPoint(o,"Point1")); 361 if((o=parameters.removeEL(KeyImpl.init("Point2")))!=null)setPoint2(ImageFilterUtil.toPoint(o,"Point2")); 362 if((o=parameters.removeEL(KeyImpl.init("PaintMode")))!=null)setPaintMode(ImageFilterUtil.toIntValue(o,"PaintMode")); 363 if((o=parameters.removeEL(KeyImpl.init("Type")))!=null)setType(ImageFilterUtil.toIntValue(o,"Type")); 364 365 // check for arguments not supported 366 if(parameters.size()>0) { 367 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 [Colormap, Interpolation, Angle, Point1, Point2, PaintMode, Type]"); 368 } 369 370 return filter(src, dst); 371 } 372 }