001 /* 002 * 003 004 Licensed under the Apache License, Version 2.0 (the "License"); 005 you may not use this file except in compliance with the License. 006 You may obtain a copy of the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010 Unless required by applicable law or agreed to in writing, software 011 distributed under the License is distributed on an "AS IS" BASIS, 012 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 See the License for the specific language governing permissions and 014 limitations under the License. 015 */ 016 017 package railo.runtime.img.filter;import java.awt.Rectangle; 018 import java.awt.image.BufferedImage; 019 020 import railo.runtime.engine.ThreadLocalPageContext; 021 import railo.runtime.exp.FunctionException; 022 import railo.runtime.exp.PageException; 023 import railo.runtime.img.ImageUtil; 024 import railo.runtime.type.KeyImpl; 025 import railo.runtime.type.List; 026 import railo.runtime.type.Struct; 027 028 /** 029 * A filter which uses Floyd-Steinberg error diffusion dithering to halftone an image. 030 */ 031 public class DiffusionFilter extends WholeImageFilter implements DynFiltering { 032 033 private final static int[] diffusionMatrix = { 034 0, 0, 0, 035 0, 0, 7, 036 3, 5, 1, 037 }; 038 039 private int[] matrix; 040 private int sum = 3+5+7+1; 041 private boolean serpentine = true; 042 private boolean colorDither = true; 043 private int levels = 6; 044 045 /** 046 * Construct a DiffusionFilter. 047 */ 048 public DiffusionFilter() { 049 setMatrix(diffusionMatrix); 050 } 051 052 /** 053 * Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output. 054 * @param serpentine true to use serpentine pattern 055 * @see #getSerpentine 056 */ 057 public void setSerpentine(boolean serpentine) { 058 this.serpentine = serpentine; 059 } 060 061 /** 062 * Return the serpentine setting. 063 * @return the current setting 064 * @see #setSerpentine 065 */ 066 public boolean getSerpentine() { 067 return serpentine; 068 } 069 070 /** 071 * Set whether to use a color dither. 072 * @param colorDither true to use a color dither 073 * @see #getColorDither 074 */ 075 public void setColorDither(boolean colorDither) { 076 this.colorDither = colorDither; 077 } 078 079 /** 080 * Get whether to use a color dither. 081 * @return true to use a color dither 082 * @see #setColorDither 083 */ 084 public boolean getColorDither() { 085 return colorDither; 086 } 087 088 /** 089 * Set the dither matrix. 090 * @param matrix the dither matrix 091 * @see #getMatrix 092 */ 093 public void setMatrix(int[] matrix) { 094 this.matrix = matrix; 095 sum = 0; 096 for (int i = 0; i < matrix.length; i++) 097 sum += matrix[i]; 098 } 099 100 /** 101 * Get the dither matrix. 102 * @return the dither matrix 103 * @see #setMatrix 104 */ 105 public int[] getMatrix() { 106 return matrix; 107 } 108 109 /** 110 * Set the number of dither levels. 111 * @param levels the number of levels 112 * @see #getLevels 113 */ 114 public void setLevels(int levels) { 115 this.levels = levels; 116 } 117 118 /** 119 * Get the number of dither levels. 120 * @return the number of levels 121 * @see #setLevels 122 */ 123 public int getLevels() { 124 return levels; 125 } 126 127 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 128 int[] outPixels = new int[width * height]; 129 130 int index = 0; 131 int[] map = new int[levels]; 132 for (int i = 0; i < levels; i++) { 133 int v = 255 * i / (levels-1); 134 map[i] = v; 135 } 136 int[] div = new int[256]; 137 for (int i = 0; i < 256; i++) 138 div[i] = levels*i / 256; 139 140 for (int y = 0; y < height; y++) { 141 boolean reverse = serpentine && (y & 1) == 1; 142 int direction; 143 if (reverse) { 144 index = y*width+width-1; 145 direction = -1; 146 } else { 147 index = y*width; 148 direction = 1; 149 } 150 for (int x = 0; x < width; x++) { 151 int rgb1 = inPixels[index]; 152 153 int r1 = (rgb1 >> 16) & 0xff; 154 int g1 = (rgb1 >> 8) & 0xff; 155 int b1 = rgb1 & 0xff; 156 157 if (!colorDither) 158 r1 = g1 = b1 = (r1+g1+b1) / 3; 159 160 int r2 = map[div[r1]]; 161 int g2 = map[div[g1]]; 162 int b2 = map[div[b1]]; 163 164 outPixels[index] = 0xff000000 | (r2 << 16) | (g2 << 8) | b2; 165 166 int er = r1-r2; 167 int eg = g1-g2; 168 int eb = b1-b2; 169 170 for (int i = -1; i <= 1; i++) { 171 int iy = i+y; 172 if (0 <= iy && iy < height) { 173 for (int j = -1; j <= 1; j++) { 174 int jx = j+x; 175 if (0 <= jx && jx < width) { 176 int w; 177 if (reverse) 178 w = matrix[(i+1)*3-j+1]; 179 else 180 w = matrix[(i+1)*3+j+1]; 181 if (w != 0) { 182 int k = reverse ? index - j : index + j; 183 rgb1 = inPixels[k]; 184 r1 = (rgb1 >> 16) & 0xff; 185 g1 = (rgb1 >> 8) & 0xff; 186 b1 = rgb1 & 0xff; 187 r1 += er * w/sum; 188 g1 += eg * w/sum; 189 b1 += eb * w/sum; 190 inPixels[k] = (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1); 191 } 192 } 193 } 194 } 195 } 196 index += direction; 197 } 198 } 199 200 return outPixels; 201 } 202 203 public String toString() { 204 return "Colors/Diffusion Dither..."; 205 } 206 207 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 208 Object o; 209 if((o=parameters.removeEL(KeyImpl.init("Levels")))!=null)setLevels(ImageFilterUtil.toIntValue(o,"Levels")); 210 if((o=parameters.removeEL(KeyImpl.init("Matrix")))!=null)setMatrix(ImageFilterUtil.toAInt(o,"Matrix")); 211 if((o=parameters.removeEL(KeyImpl.init("Serpentine")))!=null)setSerpentine(ImageFilterUtil.toBooleanValue(o,"Serpentine")); 212 if((o=parameters.removeEL(KeyImpl.init("ColorDither")))!=null)setColorDither(ImageFilterUtil.toBooleanValue(o,"ColorDither")); 213 214 // check for arguments not supported 215 if(parameters.size()>0) { 216 throw new FunctionException(ThreadLocalPageContext.get(), "ImageFilter", 3, "parameters", "the parameter"+(parameters.size()>1?"s":"")+" ["+List.arrayToList(parameters.keysAsString(),", ")+"] "+(parameters.size()>1?"are":"is")+" not allowed, only the following parameters are supported [Levels, Matrix, Serpentine, ColorDither]"); 217 } 218 219 return filter(src, dst); 220 } 221 } 222