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}