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