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.awt.*; 020import java.awt.image.*; 021 022/** 023 * A filter which uses Floyd-Steinberg error diffusion dithering to halftone an image. 024 */ 025public class DiffusionFilter extends WholeImageFilter { 026 027 private final static int[] diffusionMatrix = { 028 0, 0, 0, 029 0, 0, 7, 030 3, 5, 1, 031 }; 032 033 private int[] matrix; 034 private int sum = 3+5+7+1; 035 private boolean serpentine = true; 036 private boolean colorDither = true; 037 private int levels = 6; 038 039 /** 040 * Construct a DiffusionFilter. 041 */ 042 public DiffusionFilter() { 043 setMatrix(diffusionMatrix); 044 } 045 046 /** 047 * Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output. 048 * @param serpentine true to use serpentine pattern 049 * @see #getSerpentine 050 */ 051 public void setSerpentine(boolean serpentine) { 052 this.serpentine = serpentine; 053 } 054 055 /** 056 * Return the serpentine setting. 057 * @return the current setting 058 * @see #setSerpentine 059 */ 060 public boolean getSerpentine() { 061 return serpentine; 062 } 063 064 /** 065 * Set whether to use a color dither. 066 * @param colorDither true to use a color dither 067 * @see #getColorDither 068 */ 069 public void setColorDither(boolean colorDither) { 070 this.colorDither = colorDither; 071 } 072 073 /** 074 * Get whether to use a color dither. 075 * @return true to use a color dither 076 * @see #setColorDither 077 */ 078 public boolean getColorDither() { 079 return colorDither; 080 } 081 082 /** 083 * Set the dither matrix. 084 * @param matrix the dither matrix 085 * @see #getMatrix 086 */ 087 public void setMatrix(int[] matrix) { 088 this.matrix = matrix; 089 sum = 0; 090 for (int i = 0; i < matrix.length; i++) 091 sum += matrix[i]; 092 } 093 094 /** 095 * Get the dither matrix. 096 * @return the dither matrix 097 * @see #setMatrix 098 */ 099 public int[] getMatrix() { 100 return matrix; 101 } 102 103 /** 104 * Set the number of dither levels. 105 * @param levels the number of levels 106 * @see #getLevels 107 */ 108 public void setLevels(int levels) { 109 this.levels = levels; 110 } 111 112 /** 113 * Get the number of dither levels. 114 * @return the number of levels 115 * @see #setLevels 116 */ 117 public int getLevels() { 118 return levels; 119 } 120 121 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 122 int[] outPixels = new int[width * height]; 123 124 int index = 0; 125 int[] map = new int[levels]; 126 for (int i = 0; i < levels; i++) { 127 int v = 255 * i / (levels-1); 128 map[i] = v; 129 } 130 int[] div = new int[256]; 131 for (int i = 0; i < 256; i++) 132 div[i] = levels*i / 256; 133 134 for (int y = 0; y < height; y++) { 135 boolean reverse = serpentine && (y & 1) == 1; 136 int direction; 137 if (reverse) { 138 index = y*width+width-1; 139 direction = -1; 140 } else { 141 index = y*width; 142 direction = 1; 143 } 144 for (int x = 0; x < width; x++) { 145 int rgb1 = inPixels[index]; 146 147 int r1 = (rgb1 >> 16) & 0xff; 148 int g1 = (rgb1 >> 8) & 0xff; 149 int b1 = rgb1 & 0xff; 150 151 if (!colorDither) 152 r1 = g1 = b1 = (r1+g1+b1) / 3; 153 154 int r2 = map[div[r1]]; 155 int g2 = map[div[g1]]; 156 int b2 = map[div[b1]]; 157 158 outPixels[index] = (rgb1 & 0xff000000) | (r2 << 16) | (g2 << 8) | b2; 159 160 int er = r1-r2; 161 int eg = g1-g2; 162 int eb = b1-b2; 163 164 for (int i = -1; i <= 1; i++) { 165 int iy = i+y; 166 if (0 <= iy && iy < height) { 167 for (int j = -1; j <= 1; j++) { 168 int jx = j+x; 169 if (0 <= jx && jx < width) { 170 int w; 171 if (reverse) 172 w = matrix[(i+1)*3-j+1]; 173 else 174 w = matrix[(i+1)*3+j+1]; 175 if (w != 0) { 176 int k = reverse ? index - j : index + j; 177 rgb1 = inPixels[k]; 178 r1 = (rgb1 >> 16) & 0xff; 179 g1 = (rgb1 >> 8) & 0xff; 180 b1 = rgb1 & 0xff; 181 r1 += er * w/sum; 182 g1 += eg * w/sum; 183 b1 += eb * w/sum; 184 inPixels[k] = (inPixels[k] & 0xff000000) | (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1); 185 } 186 } 187 } 188 } 189 } 190 index += direction; 191 } 192 } 193 194 return outPixels; 195 } 196 197 public String toString() { 198 return "Colors/Diffusion Dither..."; 199 } 200 201} 202