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; 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 046public class ShapeFilter extends WholeImageFilter implements DynFiltering { 047 048 public final static int LINEAR = 0; 049 public final static int CIRCLE_UP = 1; 050 public final static int CIRCLE_DOWN = 2; 051 public final static int SMOOTH = 3; 052 053 private float factor = 1.0f; 054 protected Colormap colormap; 055 private boolean useAlpha = true; 056 private boolean invert = false; 057 private boolean merge = false; 058 private int type; 059 060 private final static int one = 41; 061 private final static int sqrt2 = (int)(41*Math.sqrt(2)); 062 private final static int sqrt5 = (int)(41*Math.sqrt(5)); 063 064 public ShapeFilter() { 065 colormap = new LinearColormap(); 066 } 067 068 public void setFactor(float factor) { 069 this.factor = factor; 070 } 071 072 public float getFactor() { 073 return factor; 074 } 075 076 /** 077 * Set the colormap to be used for the filter. 078 * @param colormap the colormap 079 * @see #getColormap 080 */ 081 public void setColormap(Colormap colormap) { 082 this.colormap = colormap; 083 } 084 085 /** 086 * Get the colormap to be used for the filter. 087 * @return the colormap 088 * @see #setColormap 089 */ 090 public Colormap getColormap() { 091 return colormap; 092 } 093 094 public void setUseAlpha(boolean useAlpha) { 095 this.useAlpha = useAlpha; 096 } 097 098 public boolean getUseAlpha() { 099 return useAlpha; 100 } 101 102 public void setType(int type) { 103 this.type = type; 104 } 105 106 public int getType() { 107 return type; 108 } 109 110 public void setInvert(boolean invert) { 111 this.invert = invert; 112 } 113 114 public boolean getInvert() { 115 return invert; 116 } 117 118 public void setMerge(boolean merge) { 119 this.merge = merge; 120 } 121 122 public boolean getMerge() { 123 return merge; 124 } 125 126 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 127 int[] map = new int[width * height]; 128 makeMap(inPixels, map, width, height); 129 int max = distanceMap(map, width, height); 130 applyMap(map, inPixels, width, height, max); 131 132 return inPixels; 133 } 134 135 public int distanceMap(int[] map, int width, int height) { 136 int xmax = width - 3; 137 int ymax = height - 3; 138 int max = 0; 139 int v; 140 141 for (int y = 0; y < height; y++) { 142 for (int x = 0; x < width; x++) { 143 int offset = x + y * width; 144 if (map[offset] > 0) { 145 if (x < 2 || x > xmax || y < 2 || y > ymax) 146 v = setEdgeValue(x, y, map, width, offset, xmax, ymax); 147 else 148 v = setValue(map, width, offset); 149 if (v > max) 150 max = v; 151 } 152 } 153 } 154 for (int y = height-1; y >= 0; y--) { 155 for (int x = width-1; x >= 0; x--) { 156 int offset = x + y * width; 157 if (map[offset] > 0) { 158 if (x < 2 || x > xmax || y < 2 || y > ymax) 159 v = setEdgeValue(x, y, map, width, offset, xmax, ymax); 160 else 161 v = setValue(map, width, offset); 162 if (v > max) 163 max = v; 164 } 165 } 166 } 167 return max; 168 } 169 170 private void makeMap(int[] pixels, int[] map, int width, int height) { 171 for (int y = 0; y < height; y++) { 172 for (int x = 0; x < width; x++) { 173 int offset = x + y * width; 174 int b = useAlpha ? (pixels[offset] >> 24) & 0xff : PixelUtils.brightness(pixels[offset]); 175// map[offset] = b * one; 176 map[offset] = b * one / 10; 177 } 178 } 179 } 180 181 private void applyMap(int[] map, int[] pixels, int width, int height, int max) { 182 if (max == 0) 183 max = 1; 184 for (int y = 0; y < height; y++) { 185 for (int x = 0; x < width; x++) { 186 int offset = x + y * width; 187 int m = map[offset]; 188 float v = 0; 189 int sa = 0, sr = 0, sg = 0, sb = 0; 190 191 if (m == 0) { 192 // default color 193 sa = sr = sg = sb = 0; 194 sa = (pixels[offset] >> 24) & 0xff; 195 } else { 196 // get V from map 197 v = ImageMath.clamp(factor * m / max, 0, 1); 198 switch (type) { 199 case CIRCLE_UP : 200 v = (ImageMath.circleUp(v)); 201 break; 202 case CIRCLE_DOWN : 203 v = (ImageMath.circleDown(v)); 204 break; 205 case SMOOTH : 206 v = (ImageMath.smoothStep(0, 1, v)); 207 break; 208 } 209 210 if (colormap == null) { 211 sr = sg = sb = (int)(v*255); 212 } else { 213 int c = (colormap.getColor(v)); 214 215 sr = (c >> 16) & 0xFF; 216 sg = (c >> 8) & 0xFF; 217 sb = (c) & 0xFF; 218 } 219 220 sa = useAlpha ? (pixels[offset] >> 24) & 0xff : PixelUtils.brightness(pixels[offset]); 221 222 // invert v if necessary 223 if (invert) { 224 sr = 255-sr; 225 sg = 255-sg; 226 sb = 255-sb; 227 } 228 } 229 230 // write results 231 if (merge) { 232 // merge with source 233 int transp = 255; 234 int col = pixels[offset]; 235 236 int a = (col & 0xFF000000) >> 24; 237 int r = (col & 0xFF0000) >> 16; 238 int g = (col & 0xFF00) >> 8; 239 int b = (col & 0xFF); 240 241 r = ((sr*r/transp)); 242 g = ((sg*g/transp)); 243 b = ((sb*b/transp)); 244 245 // clip colors 246 if (r < 0) 247 r = 0; 248 if (r > 255) 249 r = 255; 250 if (g < 0) 251 g = 0; 252 if (g > 255) 253 g = 255; 254 if (b < 0) 255 b = 0; 256 if (b > 255) 257 b = 255; 258 259 pixels[offset] = (a << 24) | (r << 16) | (g << 8) | b; 260 } else { 261 // write gray shades 262 pixels[offset] = (sa << 24) | (sr << 16) | (sg << 8) | sb; 263 } 264 } 265 } 266 } 267 268 private int setEdgeValue(int x, int y, int[] map, int width, int offset, int xmax, int ymax) { 269 int min, v; 270 int r1, r2, r3, r4, r5; 271 272 r1 = offset - width - width - 2; 273 r2 = r1 + width; 274 r3 = r2 + width; 275 r4 = r3 + width; 276 r5 = r4 + width; 277 278 if (y == 0 || x == 0 || y == ymax+2 || x == xmax+2) 279 return map[offset] = one; 280 281 v = map[r2 + 2] + one; 282 min = v; 283 284 v = map[r3 + 1] + one; 285 if (v < min) 286 min = v; 287 288 v = map[r3 + 3] + one; 289 if (v < min) 290 min = v; 291 292 v = map[r4 + 2] + one; 293 if (v < min) 294 min = v; 295 296 v = map[r2 + 1] + sqrt2; 297 if (v < min) 298 min = v; 299 300 v = map[r2 + 3] + sqrt2; 301 if (v < min) 302 min = v; 303 304 v = map[r4 + 1] + sqrt2; 305 if (v < min) 306 min = v; 307 308 v = map[r4 + 3] + sqrt2; 309 if (v < min) 310 min = v; 311 312 if (y == 1 || x == 1 || y == ymax+1 || x == xmax+1) 313 return map[offset] = min; 314 315 v = map[r1 + 1] + sqrt5; 316 if (v < min) 317 min = v; 318 319 v = map[r1 + 3] + sqrt5; 320 if (v < min) 321 min = v; 322 323 v = map[r2 + 4] + sqrt5; 324 if (v < min) 325 min = v; 326 327 v = map[r4 + 4] + sqrt5; 328 if (v < min) 329 min = v; 330 331 v = map[r5 + 3] + sqrt5; 332 if (v < min) 333 min = v; 334 335 v = map[r5 + 1] + sqrt5; 336 if (v < min) 337 min = v; 338 339 v = map[r4] + sqrt5; 340 if (v < min) 341 min = v; 342 343 v = map[r2] + sqrt5; 344 if (v < min) 345 min = v; 346 347 return map[offset] = min; 348 } 349 350 private int setValue(int[] map, int width, int offset) { 351 int min, v; 352 int r1, r2, r3, r4, r5; 353 354 r1 = offset - width - width - 2; 355 r2 = r1 + width; 356 r3 = r2 + width; 357 r4 = r3 + width; 358 r5 = r4 + width; 359 360 v = map[r2 + 2] + one; 361 min = v; 362 v = map[r3 + 1] + one; 363 if (v < min) 364 min = v; 365 v = map[r3 + 3] + one; 366 if (v < min) 367 min = v; 368 v = map[r4 + 2] + one; 369 if (v < min) 370 min = v; 371 372 v = map[r2 + 1] + sqrt2; 373 if (v < min) 374 min = v; 375 v = map[r2 + 3] + sqrt2; 376 if (v < min) 377 min = v; 378 v = map[r4 + 1] + sqrt2; 379 if (v < min) 380 min = v; 381 v = map[r4 + 3] + sqrt2; 382 if (v < min) 383 min = v; 384 385 v = map[r1 + 1] + sqrt5; 386 if (v < min) 387 min = v; 388 v = map[r1 + 3] + sqrt5; 389 if (v < min) 390 min = v; 391 v = map[r2 + 4] + sqrt5; 392 if (v < min) 393 min = v; 394 v = map[r4 + 4] + sqrt5; 395 if (v < min) 396 min = v; 397 v = map[r5 + 3] + sqrt5; 398 if (v < min) 399 min = v; 400 v = map[r5 + 1] + sqrt5; 401 if (v < min) 402 min = v; 403 v = map[r4] + sqrt5; 404 if (v < min) 405 min = v; 406 v = map[r2] + sqrt5; 407 if (v < min) 408 min = v; 409 410 return map[offset] = min; 411 } 412 413 public String toString() { 414 return "Stylize/Shapeburst..."; 415 } 416 417 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 418 Object o; 419 if((o=parameters.removeEL(KeyImpl.init("UseAlpha")))!=null)setUseAlpha(ImageFilterUtil.toBooleanValue(o,"UseAlpha")); 420 if((o=parameters.removeEL(KeyImpl.init("Colormap")))!=null)setColormap(ImageFilterUtil.toColormap(o,"Colormap")); 421 if((o=parameters.removeEL(KeyImpl.init("Invert")))!=null)setInvert(ImageFilterUtil.toBooleanValue(o,"Invert")); 422 if((o=parameters.removeEL(KeyImpl.init("Factor")))!=null)setFactor(ImageFilterUtil.toFloatValue(o,"Factor")); 423 if((o=parameters.removeEL(KeyImpl.init("Merge")))!=null)setMerge(ImageFilterUtil.toBooleanValue(o,"Merge")); 424 if((o=parameters.removeEL(KeyImpl.init("Type")))!=null)setType(ImageFilterUtil.toIntValue(o,"Type")); 425 426 // check for arguments not supported 427 if(parameters.size()>0) { 428 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 [UseAlpha, Colormap, Invert, Factor, Merge, Type]"); 429 } 430 431 return filter(src, dst); 432 } 433}