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}