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.util.*;
020import java.awt.*;
021import java.awt.image.*;
022
023/**
024 * A filter which quantizes an image to a set number of colors - useful for producing
025 * images which are to be encoded using an index color model. The filter can perform
026 * Floyd-Steinberg error-diffusion dithering if required. At present, the quantization
027 * is done using an octtree algorithm but I eventually hope to add more quantization
028 * methods such as median cut. Note: at present, the filter produces an image which
029 * uses the RGB color model (because the application it was written for required it).
030 * I hope to extend it to produce an IndexColorModel by request.
031 */
032public class QuantizeFilter extends WholeImageFilter {
033
034        /**
035         * Floyd-Steinberg dithering matrix.
036         */
037        protected final static int[] matrix = {
038                 0, 0, 0,
039                 0, 0, 7,
040                 3, 5, 1,
041        };
042        private int sum = 3+5+7+1;
043
044        private boolean dither;
045        private int numColors = 256;
046        private boolean serpentine = true;
047
048        /**
049         * Set the number of colors to quantize to.
050         * @param numColors the number of colors. The default is 256.
051         */
052        public void setNumColors(int numColors) {
053                this.numColors = Math.min(Math.max(numColors, 8), 256);
054        }
055
056        /**
057         * Get the number of colors to quantize to.
058         * @return the number of colors.
059         */
060        public int getNumColors() {
061                return numColors;
062        }
063
064        /**
065         * Set whether to use dithering or not. If not, the image is posterized.
066         * @param dither true to use dithering
067         */
068        public void setDither(boolean dither) {
069                this.dither = dither;
070        }
071
072        /**
073         * Return the dithering setting
074         * @return the current setting
075         */
076        public boolean getDither() {
077                return dither;
078        }
079
080        /**
081         * Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output.
082         * @param serpentine true to use serpentine pattern
083         */
084        public void setSerpentine(boolean serpentine) {
085                this.serpentine = serpentine;
086        }
087        
088        /**
089         * Return the serpentine setting
090         * @return the current setting
091         */
092        public boolean getSerpentine() {
093                return serpentine;
094        }
095        
096        public void quantize(int[] inPixels, int[] outPixels, int width, int height, int numColors, boolean dither, boolean serpentine) {
097                int count = width*height;
098                Quantizer quantizer = new OctTreeQuantizer();
099                quantizer.setup(numColors);
100                quantizer.addPixels(inPixels, 0, count);
101                int[] table =  quantizer.buildColorTable();
102
103                if (!dither) {
104                        for (int i = 0; i < count; i++)
105                                outPixels[i] = table[quantizer.getIndexForColor(inPixels[i])];
106                } else {
107                        int index = 0;
108                        for (int y = 0; y < height; y++) {
109                                boolean reverse = serpentine && (y & 1) == 1;
110                                int direction;
111                                if (reverse) {
112                                        index = y*width+width-1;
113                                        direction = -1;
114                                } else {
115                                        index = y*width;
116                                        direction = 1;
117                                }
118                                for (int x = 0; x < width; x++) {
119                                        int rgb1 = inPixels[index];
120                                        int rgb2 = table[quantizer.getIndexForColor(rgb1)];
121
122                                        outPixels[index] = rgb2;
123
124                                        int r1 = (rgb1 >> 16) & 0xff;
125                                        int g1 = (rgb1 >> 8) & 0xff;
126                                        int b1 = rgb1 & 0xff;
127
128                                        int r2 = (rgb2 >> 16) & 0xff;
129                                        int g2 = (rgb2 >> 8) & 0xff;
130                                        int b2 = rgb2 & 0xff;
131
132                                        int er = r1-r2;
133                                        int eg = g1-g2;
134                                        int eb = b1-b2;
135
136                                        for (int i = -1; i <= 1; i++) {
137                                                int iy = i+y;
138                                                if (0 <= iy && iy < height) {
139                                                        for (int j = -1; j <= 1; j++) {
140                                                                int jx = j+x;
141                                                                if (0 <= jx && jx < width) {
142                                                                        int w;
143                                                                        if (reverse)
144                                                                                w = matrix[(i+1)*3-j+1];
145                                                                        else
146                                                                                w = matrix[(i+1)*3+j+1];
147                                                                        if (w != 0) {
148                                                                                int k = reverse ? index - j : index + j;
149                                                                                rgb1 = inPixels[k];
150                                                                                r1 = (rgb1 >> 16) & 0xff;
151                                                                                g1 = (rgb1 >> 8) & 0xff;
152                                                                                b1 = rgb1 & 0xff;
153                                                                                r1 += er * w/sum;
154                                                                                g1 += eg * w/sum;
155                                                                                b1 += eb * w/sum;
156                                                                                inPixels[k] = (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1);
157                                                                        }
158                                                                }
159                                                        }
160                                                }
161                                        }
162                                        index += direction;
163                                }
164                        }
165                }
166        }
167
168        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
169                int[] outPixels = new int[width*height];
170                
171                quantize(inPixels, outPixels, width, height, numColors, dither, serpentine);
172
173                return outPixels;
174        }
175
176        public String toString() {
177                return "Colors/Quantize...";
178        }
179        
180}