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.image.BufferedImage; 036 037import lucee.runtime.engine.ThreadLocalPageContext; 038import lucee.runtime.exp.FunctionException; 039import lucee.runtime.exp.PageException; 040import lucee.runtime.img.ImageUtil; 041import lucee.runtime.type.KeyImpl; 042import lucee.runtime.type.Struct; 043import lucee.runtime.type.util.CollectionUtil; 044 045/** 046 * A filter which renders "glints" on bright parts of the image. 047 */ 048public class GlintFilter extends AbstractBufferedImageOp implements DynFiltering { 049 050 private float threshold = 1.0f; 051 private int length = 5; 052 private float blur = 0.0f; 053 private float amount = 0.1f; 054 private boolean glintOnly = false; 055 private Colormap colormap = new LinearColormap( 0xffffffff, 0xff000000 ); 056 057 public GlintFilter() { 058 } 059 060 /** 061 * Set the threshold value. 062 * @param threshold the threshold value 063 * @see #getThreshold 064 */ 065 public void setThreshold( float threshold ) { 066 this.threshold = threshold; 067 } 068 069 /** 070 * Get the threshold value. 071 * @return the threshold value 072 * @see #setThreshold 073 */ 074 public float getThreshold() { 075 return threshold; 076 } 077 078 /** 079 * Set the amount of glint. 080 * @param amount the amount 081 * @min-value 0 082 * @max-value 1 083 * @see #getAmount 084 */ 085 public void setAmount( float amount ) { 086 this.amount = amount; 087 } 088 089 /** 090 * Get the amount of glint. 091 * @return the amount 092 * @see #setAmount 093 */ 094 public float getAmount() { 095 return amount; 096 } 097 098 /** 099 * Set the length of the stars. 100 * @param length the length 101 * @see #getLength 102 */ 103 public void setLength( int length ) { 104 this.length = length; 105 } 106 107 /** 108 * Get the length of the stars. 109 * @return the length 110 * @see #setLength 111 */ 112 public int getLength() { 113 return length; 114 } 115 116 /** 117 * Set the blur that is applied before thresholding. 118 * @param blur the blur radius 119 * @see #getBlur 120 */ 121 public void setBlur(float blur) { 122 this.blur = blur; 123 } 124 125 /** 126 * Set the blur that is applied before thresholding. 127 * @return the blur radius 128 * @see #setBlur 129 */ 130 public float getBlur() { 131 return blur; 132 } 133 134 /** 135 * Set whether to render the stars and the image or only the stars. 136 * @param glintOnly true to render only stars 137 * @see #getGlintOnly 138 */ 139 public void setGlintOnly(boolean glintOnly) { 140 this.glintOnly = glintOnly; 141 } 142 143 /** 144 * Get whether to render the stars and the image or only the stars. 145 * @return true to render only stars 146 * @see #setGlintOnly 147 */ 148 public boolean getGlintOnly() { 149 return glintOnly; 150 } 151 152 /** 153 * Set the colormap to be used for the filter. 154 * @param colormap the colormap 155 * @see #getColormap 156 */ 157 public void setColormap(Colormap colormap) { 158 this.colormap = colormap; 159 } 160 161 /** 162 * Get the colormap to be used for the filter. 163 * @return the colormap 164 * @see #setColormap 165 */ 166 public Colormap getColormap() { 167 return colormap; 168 } 169 170 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 171 int width = src.getWidth(); 172 int height = src.getHeight(); 173 int[] pixels = new int[width]; 174 int length2 = (int)(length / 1.414f); 175 int[] colors = new int[length+1]; 176 int[] colors2 = new int[length2+1]; 177 178 if ( colormap != null ) { 179 for (int i = 0; i <= length; i++) { 180 int argb = colormap.getColor( (float)i/length ); 181 int r = (argb >> 16) & 0xff; 182 int g = (argb >> 8) & 0xff; 183 int b = argb & 0xff; 184 argb = (argb & 0xff000000) | ((int)(amount*r) << 16) | ((int)(amount*g) << 8) | (int)(amount*b); 185 colors[i] = argb; 186 } 187 for (int i = 0; i <= length2; i++) { 188 int argb = colormap.getColor( (float)i/length2 ); 189 int r = (argb >> 16) & 0xff; 190 int g = (argb >> 8) & 0xff; 191 int b = argb & 0xff; 192 argb = (argb & 0xff000000) | ((int)(amount*r) << 16) | ((int)(amount*g) << 8) | (int)(amount*b); 193 colors2[i] = argb; 194 } 195 } 196 197 BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 198 199 int threshold3 = (int)(threshold*3*255); 200 for ( int y = 0; y < height; y++ ) { 201 getRGB( src, 0, y, width, 1, pixels ); 202 for ( int x = 0; x < width; x++ ) { 203 int rgb = pixels[x]; 204 int a = rgb & 0xff000000; 205 int r = (rgb >> 16) & 0xff; 206 int g = (rgb >> 8) & 0xff; 207 int b = rgb & 0xff; 208 int l = r + g + b; 209 if (l < threshold3) 210 pixels[x] = 0xff000000; 211 else { 212 l /= 3; 213 pixels[x] = a | (l << 16) | (l << 8) | l; 214 } 215 } 216 setRGB( mask, 0, y, width, 1, pixels ); 217 } 218 219 if ( blur != 0 ) 220 mask = new GaussianFilter(blur).filter( mask, (BufferedImage)null ); 221 222 if ( dst == null ) 223 dst = createCompatibleDestImage( src, null ); 224 int[] dstPixels; 225 if ( glintOnly ) 226 dstPixels = new int[width*height]; 227 else 228 dstPixels = getRGB( src, 0, 0, width, height, null );//FIXME - only need 2*length 229 230 for ( int y = 0; y < height; y++ ) { 231 int index = y*width; 232 getRGB( mask, 0, y, width, 1, pixels ); 233 int ymin = Math.max( y-length, 0 )-y; 234 int ymax = Math.min( y+length, height-1 )-y; 235 int ymin2 = Math.max( y-length2, 0 )-y; 236 int ymax2 = Math.min( y+length2, height-1 )-y; 237 for ( int x = 0; x < width; x++ ) { 238 if ( (pixels[x] & 0xff) > threshold*255 ) { 239 int xmin = Math.max( x-length, 0 )-x; 240 int xmax = Math.min( x+length, width-1 )-x; 241 int xmin2 = Math.max( x-length2, 0 )-x; 242 int xmax2 = Math.min( x+length2, width-1 )-x; 243 244 // Horizontal 245 for ( int i = 0, k = 0; i <= xmax; i++, k++ ) 246 dstPixels[index+i] = PixelUtils.combinePixels( dstPixels[index+i], colors[k], PixelUtils.ADD ); 247 for ( int i = -1, k = 1; i >= xmin; i--, k++ ) 248 dstPixels[index+i] = PixelUtils.combinePixels( dstPixels[index+i], colors[k], PixelUtils.ADD ); 249 // Vertical 250 for ( int i = 1, j = index+width, k = 0; i <= ymax; i++, j += width, k++ ) 251 dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors[k], PixelUtils.ADD ); 252 for ( int i = -1, j = index-width, k = 0; i >= ymin; i--, j -= width, k++ ) 253 dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors[k], PixelUtils.ADD ); 254 255 // Diagonals 256 //int xymin = Math.max( xmin2, ymin2 ); 257 //int xymax = Math.min( xmax2, ymax2 ); 258 // SE 259 int count = Math.min( xmax2, ymax2 ); 260 for ( int i = 1, j = index+width+1, k = 0; i <= count; i++, j += width+1, k++ ) 261 dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD ); 262 // NW 263 count = Math.min( -xmin2, -ymin2 ); 264 for ( int i = 1, j = index-width-1, k = 0; i <= count; i++, j -= width+1, k++ ) 265 dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD ); 266 // NE 267 count = Math.min( xmax2, -ymin2 ); 268 for ( int i = 1, j = index-width+1, k = 0; i <= count; i++, j += -width+1, k++ ) 269 dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD ); 270 // SW 271 count = Math.min( -xmin2, ymax2 ); 272 for ( int i = 1, j = index+width-1, k = 0; i <= count; i++, j += width-1, k++ ) 273 dstPixels[j] = PixelUtils.combinePixels( dstPixels[j], colors2[k], PixelUtils.ADD ); 274 } 275 index++; 276 } 277 } 278 setRGB( dst, 0, 0, width, height, dstPixels ); 279 280 return dst; 281 } 282 283 public String toString() { 284 return "Effects/Glint..."; 285 } 286 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 287 Object o; 288 if((o=parameters.removeEL(KeyImpl.init("Colormap")))!=null)setColormap(ImageFilterUtil.toColormap(o,"Colormap")); 289 if((o=parameters.removeEL(KeyImpl.init("Amount")))!=null)setAmount(ImageFilterUtil.toFloatValue(o,"Amount")); 290 if((o=parameters.removeEL(KeyImpl.init("Blur")))!=null)setBlur(ImageFilterUtil.toFloatValue(o,"Blur")); 291 if((o=parameters.removeEL(KeyImpl.init("GlintOnly")))!=null)setGlintOnly(ImageFilterUtil.toBooleanValue(o,"GlintOnly")); 292 if((o=parameters.removeEL(KeyImpl.init("Length")))!=null)setLength(ImageFilterUtil.toIntValue(o,"Length")); 293 if((o=parameters.removeEL(KeyImpl.init("Threshold")))!=null)setThreshold(ImageFilterUtil.toFloatValue(o,"Threshold")); 294 295 // check for arguments not supported 296 if(parameters.size()>0) { 297 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, Amount, Blur, GlintOnly, Length, Threshold]"); 298 } 299 300 return filter(src, dst); 301 } 302}