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 046/** 047 * A filter which draws contours on an image at given brightness levels. 048 */ 049public class ContourFilter extends WholeImageFilter implements DynFiltering { 050 051 private float levels = 5; 052 private float scale = 1; 053 private float offset = 0; 054 private int contourColor = 0xff000000; 055 056 public ContourFilter() { 057 } 058 059 public void setLevels( float levels ) { 060 this.levels = levels; 061 } 062 063 public float getLevels() { 064 return levels; 065 } 066 067 /** 068 * Specifies the scale of the contours. 069 * @param scale the scale of the contours. 070 * @min-value 0 071 * @max-value 1 072 * @see #getScale 073 */ 074 public void setScale( float scale ) { 075 this.scale = scale; 076 } 077 078 /** 079 * Returns the scale of the contours. 080 * @return the scale of the contours. 081 * @see #setScale 082 */ 083 public float getScale() { 084 return scale; 085 } 086 087 public void setOffset( float offset ) { 088 this.offset = offset; 089 } 090 091 public float getOffset() { 092 return offset; 093 } 094 095 public void setContourColor( int contourColor ) { 096 this.contourColor = contourColor; 097 } 098 099 public int getContourColor() { 100 return contourColor; 101 } 102 103 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 104 int index = 0; 105 short[][] r = new short[3][width]; 106 int[] outPixels = new int[width * height]; 107 108 short[] table = new short[256]; 109 int offsetl = (int)(offset * 256 / levels); 110 for ( int i = 0; i < 256; i++ ) 111 table[i] = (short)PixelUtils.clamp( (int)(255 * Math.floor(levels*(i+offsetl) / 256) / (levels-1) - offsetl) ); 112 113 for (int x = 0; x < width; x++) { 114 int rgb = inPixels[x]; 115 r[1][x] = (short)PixelUtils.brightness( rgb ); 116 } 117 for (int y = 0; y < height; y++) { 118 boolean yIn = y > 0 && y < height-1; 119 int nextRowIndex = index+width; 120 if ( y < height-1) { 121 for (int x = 0; x < width; x++) { 122 int rgb = inPixels[nextRowIndex++]; 123 r[2][x] = (short)PixelUtils.brightness( rgb ); 124 } 125 } 126 for (int x = 0; x < width; x++) { 127 boolean xIn = x > 0 && x < width-1; 128 int w = x-1; 129 //int e = x+1; 130 int v = 0; 131 132 if ( yIn && xIn ) { 133 short nwb = r[0][w]; 134 short neb = r[0][x]; 135 short swb = r[1][w]; 136 short seb = r[1][x]; 137 short nw = table[nwb]; 138 short ne = table[neb]; 139 short sw = table[swb]; 140 short se = table[seb]; 141 142 if (nw != ne || nw != sw || ne != se || sw != se) { 143 v = (int)(scale * (Math.abs(nwb - neb) + Math.abs(nwb - swb) + Math.abs(neb - seb) + Math.abs(swb - seb))); 144// v /= 255; 145 if (v > 255) 146 v = 255; 147 } 148 } 149 150 if ( v != 0 ) 151 outPixels[index] = PixelUtils.combinePixels( inPixels[index], contourColor, PixelUtils.NORMAL, v ); 152// outPixels[index] = PixelUtils.combinePixels( (contourColor & 0xff)|(v << 24), inPixels[index], PixelUtils.NORMAL ); 153 else 154 outPixels[index] = inPixels[index]; 155 index++; 156 } 157 short[] t; 158 t = r[0]; 159 r[0] = r[1]; 160 r[1] = r[2]; 161 r[2] = t; 162 } 163 164 return outPixels; 165 } 166 167 public String toString() { 168 return "Stylize/Contour..."; 169 } 170 171 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 172 Object o; 173 if((o=parameters.removeEL(KeyImpl.init("Levels")))!=null)setLevels(ImageFilterUtil.toFloatValue(o,"Levels")); 174 if((o=parameters.removeEL(KeyImpl.init("ContourColor")))!=null)setContourColor(ImageFilterUtil.toColorRGB(o,"ContourColor")); 175 if((o=parameters.removeEL(KeyImpl.init("Offset")))!=null)setOffset(ImageFilterUtil.toFloatValue(o,"Offset")); 176 if((o=parameters.removeEL(KeyImpl.init("Scale")))!=null)setScale(ImageFilterUtil.toFloatValue(o,"Scale")); 177 178 // check for arguments not supported 179 if(parameters.size()>0) { 180 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 [Levels, ContourColor, Offset, Scale]"); 181 } 182 183 return filter(src, dst); 184 } 185} 186