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 uses Floyd-Steinberg error diffusion dithering to halftone an image.
048 */
049public class DiffusionFilter extends WholeImageFilter  implements DynFiltering {
050
051        private final static int[] diffusionMatrix = {
052                 0, 0, 0,
053                 0, 0, 7,
054                 3, 5, 1,
055        };
056
057        private int[] matrix;
058        private int sum = 3+5+7+1;
059        private boolean serpentine = true;
060        private boolean colorDither = true;
061        private int levels = 6;
062
063        /**
064         * Construct a DiffusionFilter.
065         */
066        public DiffusionFilter() {
067                setMatrix(diffusionMatrix);
068        }
069        
070        /**
071         * Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output.
072         * @param serpentine true to use serpentine pattern
073     * @see #getSerpentine
074         */
075        public void setSerpentine(boolean serpentine) {
076                this.serpentine = serpentine;
077        }
078        
079        /**
080         * Return the serpentine setting.
081         * @return the current setting
082     * @see #setSerpentine
083         */
084        public boolean getSerpentine() {
085                return serpentine;
086        }
087        
088        /**
089         * Set whether to use a color dither.
090         * @param colorDither true to use a color dither
091     * @see #getColorDither
092         */
093        public void setColorDither(boolean colorDither) {
094                this.colorDither = colorDither;
095        }
096
097        /**
098         * Get whether to use a color dither.
099         * @return true to use a color dither
100     * @see #setColorDither
101         */
102        public boolean getColorDither() {
103                return colorDither;
104        }
105
106        /**
107         * Set the dither matrix.
108         * @param matrix the dither matrix
109     * @see #getMatrix
110         */
111        public void setMatrix(int[] matrix) {
112                this.matrix = matrix;
113                sum = 0;
114                for (int i = 0; i < matrix.length; i++)
115                        sum += matrix[i];
116        }
117
118        /**
119         * Get the dither matrix.
120         * @return the dither matrix
121     * @see #setMatrix
122         */
123        public int[] getMatrix() {
124                return matrix;
125        }
126
127        /**
128         * Set the number of dither levels.
129         * @param levels the number of levels
130     * @see #getLevels
131         */
132        public void setLevels(int levels) {
133                this.levels = levels;
134        }
135
136        /**
137         * Get the number of dither levels.
138         * @return the number of levels
139     * @see #setLevels
140         */
141        public int getLevels() {
142                return levels;
143        }
144
145        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
146                int[] outPixels = new int[width * height];
147
148                int index = 0;
149                int[] map = new int[levels];
150                for (int i = 0; i < levels; i++) {
151                        int v = 255 * i / (levels-1);
152                        map[i] = v;
153                }
154                int[] div = new int[256];
155                for (int i = 0; i < 256; i++)
156                        div[i] = levels*i / 256;
157
158                for (int y = 0; y < height; y++) {
159                        boolean reverse = serpentine && (y & 1) == 1;
160                        int direction;
161                        if (reverse) {
162                                index = y*width+width-1;
163                                direction = -1;
164                        } else {
165                                index = y*width;
166                                direction = 1;
167                        }
168                        for (int x = 0; x < width; x++) {
169                                int rgb1 = inPixels[index];
170
171                                int r1 = (rgb1 >> 16) & 0xff;
172                                int g1 = (rgb1 >> 8) & 0xff;
173                                int b1 = rgb1 & 0xff;
174
175                                if (!colorDither)
176                                        r1 = g1 = b1 = (r1+g1+b1) / 3;
177
178                                int r2 = map[div[r1]];
179                                int g2 = map[div[g1]];
180                                int b2 = map[div[b1]];
181
182                                outPixels[index] = 0xff000000 | (r2 << 16) | (g2 << 8) | b2;
183
184                                int er = r1-r2;
185                                int eg = g1-g2;
186                                int eb = b1-b2;
187
188                                for (int i = -1; i <= 1; i++) {
189                                        int iy = i+y;
190                                        if (0 <= iy && iy < height) {
191                                                for (int j = -1; j <= 1; j++) {
192                                                        int jx = j+x;
193                                                        if (0 <= jx && jx < width) {
194                                                                int w;
195                                                                if (reverse)
196                                                                        w = matrix[(i+1)*3-j+1];
197                                                                else
198                                                                        w = matrix[(i+1)*3+j+1];
199                                                                if (w != 0) {
200                                                                        int k = reverse ? index - j : index + j;
201                                                                        rgb1 = inPixels[k];
202                                                                        r1 = (rgb1 >> 16) & 0xff;
203                                                                        g1 = (rgb1 >> 8) & 0xff;
204                                                                        b1 = rgb1 & 0xff;
205                                                                        r1 += er * w/sum;
206                                                                        g1 += eg * w/sum;
207                                                                        b1 += eb * w/sum;
208                                                                        inPixels[k] = (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1);
209                                                                }
210                                                        }
211                                                }
212                                        }
213                                }
214                                index += direction;
215                        }
216                }
217
218                return outPixels;
219        }
220
221        public String toString() {
222                return "Colors/Diffusion Dither...";
223        }
224
225        public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
226                Object o;
227                if((o=parameters.removeEL(KeyImpl.init("Levels")))!=null)setLevels(ImageFilterUtil.toIntValue(o,"Levels"));
228                if((o=parameters.removeEL(KeyImpl.init("Matrix")))!=null)setMatrix(ImageFilterUtil.toAInt(o,"Matrix"));
229                if((o=parameters.removeEL(KeyImpl.init("Serpentine")))!=null)setSerpentine(ImageFilterUtil.toBooleanValue(o,"Serpentine"));
230                if((o=parameters.removeEL(KeyImpl.init("ColorDither")))!=null)setColorDither(ImageFilterUtil.toBooleanValue(o,"ColorDither"));
231
232                // check for arguments not supported
233                if(parameters.size()>0) {
234                        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, Matrix, Serpentine, ColorDither]");
235                }
236
237                return filter(src, dst);
238        }
239}
240