Smack/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/mediaimpl/sshare/api/QuantizeFilter.java

179 lines
4.8 KiB
Java

/**
*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingleold.mediaimpl.sshare.api;
import java.awt.Rectangle;
/**
* A filter which quantizes an image to a set number of colors - useful for producing
* images which are to be encoded using an index color model. The filter can perform
* Floyd-Steinberg error-diffusion dithering if required. At present, the quantization
* is done using an octtree algorithm but I eventually hope to add more quantization
* methods such as median cut. Note: at present, the filter produces an image which
* uses the RGB color model (because the application it was written for required it).
* I hope to extend it to produce an IndexColorModel by request.
*/
public class QuantizeFilter extends WholeImageFilter {
/**
* Floyd-Steinberg dithering matrix.
*/
protected final static int[] matrix = {
0, 0, 0,
0, 0, 7,
3, 5, 1,
};
private int sum = 3+5+7+1;
private boolean dither;
private int numColors = 256;
private boolean serpentine = true;
/**
* Set the number of colors to quantize to.
* @param numColors the number of colors. The default is 256.
*/
public void setNumColors(int numColors) {
this.numColors = Math.min(Math.max(numColors, 8), 256);
}
/**
* Get the number of colors to quantize to.
* @return the number of colors.
*/
public int getNumColors() {
return numColors;
}
/**
* Set whether to use dithering or not. If not, the image is posterized.
* @param dither true to use dithering
*/
public void setDither(boolean dither) {
this.dither = dither;
}
/**
* Return the dithering setting
* @return the current setting
*/
public boolean getDither() {
return dither;
}
/**
* Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output.
* @param serpentine true to use serpentine pattern
*/
public void setSerpentine(boolean serpentine) {
this.serpentine = serpentine;
}
/**
* Return the serpentine setting
* @return the current setting
*/
public boolean getSerpentine() {
return serpentine;
}
public void quantize(int[] inPixels, int[] outPixels, int width, int height, int numColors, boolean dither, boolean serpentine) {
int count = width*height;
Quantizer quantizer = new OctTreeQuantizer();
quantizer.setup(numColors);
quantizer.addPixels(inPixels, 0, count);
int[] table = quantizer.buildColorTable();
if (!dither) {
for (int i = 0; i < count; i++)
outPixels[i] = table[quantizer.getIndexForColor(inPixels[i])];
} else {
int index = 0;
for (int y = 0; y < height; y++) {
boolean reverse = serpentine && (y & 1) == 1;
int direction;
if (reverse) {
index = y*width+width-1;
direction = -1;
} else {
index = y*width;
direction = 1;
}
for (int x = 0; x < width; x++) {
int rgb1 = inPixels[index];
int rgb2 = table[quantizer.getIndexForColor(rgb1)];
outPixels[index] = rgb2;
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
int er = r1-r2;
int eg = g1-g2;
int eb = b1-b2;
for (int i = -1; i <= 1; i++) {
int iy = i+y;
if (0 <= iy && iy < height) {
for (int j = -1; j <= 1; j++) {
int jx = j+x;
if (0 <= jx && jx < width) {
int w;
if (reverse)
w = matrix[(i+1)*3-j+1];
else
w = matrix[(i+1)*3+j+1];
if (w != 0) {
int k = reverse ? index - j : index + j;
rgb1 = inPixels[k];
r1 = (rgb1 >> 16) & 0xff;
g1 = (rgb1 >> 8) & 0xff;
b1 = rgb1 & 0xff;
r1 += er * w/sum;
g1 += eg * w/sum;
b1 += eb * w/sum;
inPixels[k] = (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1);
}
}
}
}
}
index += direction;
}
}
}
}
protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
int[] outPixels = new int[width*height];
quantize(inPixels, outPixels, width, height, numColors, dither, serpentine);
return outPixels;
}
public String toString() {
return "Colors/Quantize...";
}
}