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}