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