001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.image.*;
021
022/**
023 * A filter which uses Floyd-Steinberg error diffusion dithering to halftone an image.
024 */
025public class DiffusionFilter extends WholeImageFilter {
026
027        private final static int[] diffusionMatrix = {
028                 0, 0, 0,
029                 0, 0, 7,
030                 3, 5, 1,
031        };
032
033        private int[] matrix;
034        private int sum = 3+5+7+1;
035        private boolean serpentine = true;
036        private boolean colorDither = true;
037        private int levels = 6;
038
039        /**
040         * Construct a DiffusionFilter.
041         */
042        public DiffusionFilter() {
043                setMatrix(diffusionMatrix);
044        }
045        
046        /**
047         * Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output.
048         * @param serpentine true to use serpentine pattern
049     * @see #getSerpentine
050         */
051        public void setSerpentine(boolean serpentine) {
052                this.serpentine = serpentine;
053        }
054        
055        /**
056         * Return the serpentine setting.
057         * @return the current setting
058     * @see #setSerpentine
059         */
060        public boolean getSerpentine() {
061                return serpentine;
062        }
063        
064        /**
065         * Set whether to use a color dither.
066         * @param colorDither true to use a color dither
067     * @see #getColorDither
068         */
069        public void setColorDither(boolean colorDither) {
070                this.colorDither = colorDither;
071        }
072
073        /**
074         * Get whether to use a color dither.
075         * @return true to use a color dither
076     * @see #setColorDither
077         */
078        public boolean getColorDither() {
079                return colorDither;
080        }
081
082        /**
083         * Set the dither matrix.
084         * @param matrix the dither matrix
085     * @see #getMatrix
086         */
087        public void setMatrix(int[] matrix) {
088                this.matrix = matrix;
089                sum = 0;
090                for (int i = 0; i < matrix.length; i++)
091                        sum += matrix[i];
092        }
093
094        /**
095         * Get the dither matrix.
096         * @return the dither matrix
097     * @see #setMatrix
098         */
099        public int[] getMatrix() {
100                return matrix;
101        }
102
103        /**
104         * Set the number of dither levels.
105         * @param levels the number of levels
106     * @see #getLevels
107         */
108        public void setLevels(int levels) {
109                this.levels = levels;
110        }
111
112        /**
113         * Get the number of dither levels.
114         * @return the number of levels
115     * @see #setLevels
116         */
117        public int getLevels() {
118                return levels;
119        }
120
121        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
122                int[] outPixels = new int[width * height];
123
124                int index = 0;
125                int[] map = new int[levels];
126                for (int i = 0; i < levels; i++) {
127                        int v = 255 * i / (levels-1);
128                        map[i] = v;
129                }
130                int[] div = new int[256];
131                for (int i = 0; i < 256; i++)
132                        div[i] = levels*i / 256;
133
134                for (int y = 0; y < height; y++) {
135                        boolean reverse = serpentine && (y & 1) == 1;
136                        int direction;
137                        if (reverse) {
138                                index = y*width+width-1;
139                                direction = -1;
140                        } else {
141                                index = y*width;
142                                direction = 1;
143                        }
144                        for (int x = 0; x < width; x++) {
145                                int rgb1 = inPixels[index];
146
147                                int r1 = (rgb1 >> 16) & 0xff;
148                                int g1 = (rgb1 >> 8) & 0xff;
149                                int b1 = rgb1 & 0xff;
150
151                                if (!colorDither)
152                                        r1 = g1 = b1 = (r1+g1+b1) / 3;
153
154                                int r2 = map[div[r1]];
155                                int g2 = map[div[g1]];
156                                int b2 = map[div[b1]];
157
158                                outPixels[index] = (rgb1 & 0xff000000) | (r2 << 16) | (g2 << 8) | b2;
159
160                                int er = r1-r2;
161                                int eg = g1-g2;
162                                int eb = b1-b2;
163
164                                for (int i = -1; i <= 1; i++) {
165                                        int iy = i+y;
166                                        if (0 <= iy && iy < height) {
167                                                for (int j = -1; j <= 1; j++) {
168                                                        int jx = j+x;
169                                                        if (0 <= jx && jx < width) {
170                                                                int w;
171                                                                if (reverse)
172                                                                        w = matrix[(i+1)*3-j+1];
173                                                                else
174                                                                        w = matrix[(i+1)*3+j+1];
175                                                                if (w != 0) {
176                                                                        int k = reverse ? index - j : index + j;
177                                                                        rgb1 = inPixels[k];
178                                                                        r1 = (rgb1 >> 16) & 0xff;
179                                                                        g1 = (rgb1 >> 8) & 0xff;
180                                                                        b1 = rgb1 & 0xff;
181                                                                        r1 += er * w/sum;
182                                                                        g1 += eg * w/sum;
183                                                                        b1 += eb * w/sum;
184                                                                        inPixels[k] = (inPixels[k] & 0xff000000) | (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1);
185                                                                }
186                                                        }
187                                                }
188                                        }
189                                }
190                                index += direction;
191                        }
192                }
193
194                return outPixels;
195        }
196
197        public String toString() {
198                return "Colors/Diffusion Dither...";
199        }
200
201}
202