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