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.jingleold.mediaimpl.sshare.api;
018
019import java.awt.Rectangle;
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 static final 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    @Override
167    protected int[] filterPixels(int width, int height, int[] inPixels, Rectangle transformedSpace) {
168        int[] outPixels = new int[width * height];
169
170        quantize(inPixels, outPixels, width, height, numColors, dither, serpentine);
171
172        return outPixels;
173    }
174
175    @Override
176    public String toString() {
177        return "Colors/Quantize...";
178    }
179
180}