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