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.*; 021import java.util.*; 022 023/** 024 * A filter which draws a coloured gradient. This is largely superceded by GradientPaint in Java1.2, but does provide a few 025 * more gradient options. 026 */ 027public class GradientFilter extends AbstractBufferedImageOp { 028 029 public final static int LINEAR = 0; 030 public final static int BILINEAR = 1; 031 public final static int RADIAL = 2; 032 public final static int CONICAL = 3; 033 public final static int BICONICAL = 4; 034 public final static int SQUARE = 5; 035 036 public final static int INT_LINEAR = 0; 037 public final static int INT_CIRCLE_UP = 1; 038 public final static int INT_CIRCLE_DOWN = 2; 039 public final static int INT_SMOOTH = 3; 040 041 private float angle = 0; 042 private int color1 = 0xff000000; 043 private int color2 = 0xffffffff; 044 private Point p1 = new Point(0, 0), p2 = new Point(64, 64); 045 private boolean repeat = false; 046 private float x1; 047 private float y1; 048 private float dx; 049 private float dy; 050 private Colormap colormap = null; 051 private int type; 052 private int interpolation = INT_LINEAR; 053 private int paintMode = PixelUtils.NORMAL; 054 055 public GradientFilter() { 056 } 057 058 public GradientFilter(Point p1, Point p2, int color1, int color2, boolean repeat, int type, int interpolation) { 059 this.p1 = p1; 060 this.p2 = p2; 061 this.color1 = color1; 062 this.color2 = color2; 063 this.repeat = repeat; 064 this.type = type; 065 this.interpolation = interpolation; 066 colormap = new LinearColormap(color1, color2); 067 } 068 069 public void setPoint1(Point point1) { 070 this.p1 = point1; 071 } 072 073 public Point getPoint1() { 074 return p1; 075 } 076 077 public void setPoint2(Point point2) { 078 this.p2 = point2; 079 } 080 081 public Point getPoint2() { 082 return p2; 083 } 084 085 public void setType(int type) { 086 this.type = type; 087 } 088 089 public int getType() { 090 return type; 091 } 092 093 public void setInterpolation(int interpolation) { 094 this.interpolation = interpolation; 095 } 096 097 public int getInterpolation() { 098 return interpolation; 099 } 100 101 /** 102 * Specifies the angle of the texture. 103 * @param angle the angle of the texture. 104 * @angle 105 * @see #getAngle 106 */ 107 public void setAngle(float angle) { 108 this.angle = angle; 109 p2 = new Point((int)(64*Math.cos(angle)), (int)(64*Math.sin(angle))); 110 } 111 112 /** 113 * Returns the angle of the texture. 114 * @return the angle of the texture. 115 * @see #setAngle 116 */ 117 public float getAngle() { 118 return angle; 119 } 120 121 /** 122 * Set the colormap to be used for the filter. 123 * @param colormap the colormap 124 * @see #getColormap 125 */ 126 public void setColormap(Colormap colormap) { 127 this.colormap = colormap; 128 } 129 130 /** 131 * Get the colormap to be used for the filter. 132 * @return the colormap 133 * @see #setColormap 134 */ 135 public Colormap getColormap() { 136 return colormap; 137 } 138 139 public void setPaintMode(int paintMode) { 140 this.paintMode = paintMode; 141 } 142 143 public int getPaintMode() { 144 return paintMode; 145 } 146 147 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 148 int width = src.getWidth(); 149 int height = src.getHeight(); 150 151 if ( dst == null ) 152 dst = createCompatibleDestImage( src, null ); 153 154 int rgb1, rgb2; 155 float x1, y1, x2, y2; 156 x1 = p1.x; 157 x2 = p2.x; 158 159 if (x1 > x2 && type != RADIAL) { 160 y1 = x1; 161 x1 = x2; 162 x2 = y1; 163 y1 = p2.y; 164 y2 = p1.y; 165 rgb1 = color2; 166 rgb2 = color1; 167 } else { 168 y1 = p1.y; 169 y2 = p2.y; 170 rgb1 = color1; 171 rgb2 = color2; 172 } 173 float dx = x2 - x1; 174 float dy = y2 - y1; 175 float lenSq = dx * dx + dy * dy; 176 this.x1 = x1; 177 this.y1 = y1; 178 if (lenSq >= Float.MIN_VALUE) { 179 dx = dx / lenSq; 180 dy = dy / lenSq; 181 if (repeat) { 182 dx = dx % 1.0f; 183 dy = dy % 1.0f; 184 } 185 } 186 this.dx = dx; 187 this.dy = dy; 188 189 int[] pixels = new int[width]; 190 for (int y = 0; y < height; y++ ) { 191 getRGB( src, 0, y, width, 1, pixels ); 192 switch (type) { 193 case LINEAR: 194 case BILINEAR: 195 linearGradient(pixels, y, width, 1); 196 break; 197 case RADIAL: 198 radialGradient(pixels, y, width, 1); 199 break; 200 case CONICAL: 201 case BICONICAL: 202 conicalGradient(pixels, y, width, 1); 203 break; 204 case SQUARE: 205 squareGradient(pixels, y, width, 1); 206 break; 207 } 208 setRGB( dst, 0, y, width, 1, pixels ); 209 } 210 return dst; 211 } 212 213 private void repeatGradient(int[] pixels, int w, int h, float rowrel, float dx, float dy) { 214 int off = 0; 215 for (int y = 0; y < h; y++) { 216 float colrel = rowrel; 217 int j = w; 218 int rgb; 219 while (--j >= 0) { 220 if (type == BILINEAR) 221 rgb = colormap.getColor(map(ImageMath.triangle(colrel))); 222 else 223 rgb = colormap.getColor(map(ImageMath.mod(colrel, 1.0f))); 224 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 225 off++; 226 colrel += dx; 227 } 228 rowrel += dy; 229 } 230 } 231 232 private void singleGradient(int[] pixels, int w, int h, float rowrel, float dx, float dy) { 233 int off = 0; 234 for (int y = 0; y < h; y++) { 235 float colrel = rowrel; 236 int j = w; 237 int rgb; 238 if (colrel <= 0.0) { 239 rgb = colormap.getColor(0); 240 do { 241 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 242 off++; 243 colrel += dx; 244 } while (--j > 0 && colrel <= 0.0); 245 } 246 while (colrel < 1.0 && --j >= 0) { 247 if (type == BILINEAR) 248 rgb = colormap.getColor(map(ImageMath.triangle(colrel))); 249 else 250 rgb = colormap.getColor(map(colrel)); 251 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 252 off++; 253 colrel += dx; 254 } 255 if (j > 0) { 256 if (type == BILINEAR) 257 rgb = colormap.getColor(0.0f); 258 else 259 rgb = colormap.getColor(1.0f); 260 do { 261 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 262 off++; 263 } while (--j > 0); 264 } 265 rowrel += dy; 266 } 267 } 268 269 private void linearGradient(int[] pixels, int y, int w, int h) { 270 int x = 0; 271 float rowrel = (x - x1) * dx + (y - y1) * dy; 272 if (repeat) 273 repeatGradient(pixels, w, h, rowrel, dx, dy); 274 else 275 singleGradient(pixels, w, h, rowrel, dx, dy); 276 } 277 278 private void radialGradient(int[] pixels, int y, int w, int h) { 279 int off = 0; 280 float radius = distance(p2.x-p1.x, p2.y-p1.y); 281 for (int x = 0; x < w; x++) { 282 float distance = distance(x-p1.x, y-p1.y); 283 float ratio = distance / radius; 284 if (repeat) 285 ratio = ratio % 2; 286 else if (ratio > 1.0) 287 ratio = 1.0f; 288 int rgb = colormap.getColor(map(ratio)); 289 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 290 off++; 291 } 292 } 293 294 private void squareGradient(int[] pixels, int y, int w, int h) { 295 int off = 0; 296 float radius = Math.max(Math.abs(p2.x-p1.x), Math.abs(p2.y-p1.y)); 297 for (int x = 0; x < w; x++) { 298 float distance = Math.max(Math.abs(x-p1.x), Math.abs(y-p1.y)); 299 float ratio = distance / radius; 300 if (repeat) 301 ratio = ratio % 2; 302 else if (ratio > 1.0) 303 ratio = 1.0f; 304 int rgb = colormap.getColor(map(ratio)); 305 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 306 off++; 307 } 308 } 309 310 private void conicalGradient(int[] pixels, int y, int w, int h) { 311 int off = 0; 312 float angle0 = (float)Math.atan2(p2.x-p1.x, p2.y-p1.y); 313 for (int x = 0; x < w; x++) { 314 float angle = (float)(Math.atan2(x-p1.x, y-p1.y) - angle0) / (ImageMath.TWO_PI); 315 angle += 1.0f; 316 angle %= 1.0f; 317 if (type == BICONICAL) 318 angle = ImageMath.triangle(angle); 319 int rgb = colormap.getColor(map(angle)); 320 pixels[off] = PixelUtils.combinePixels(rgb, pixels[off], paintMode); 321 off++; 322 } 323 } 324 325 private float map(float v) { 326 if (repeat) 327 v = v > 1.0 ? 2.0f-v : v; 328 switch (interpolation) { 329 case INT_CIRCLE_UP: 330 v = ImageMath.circleUp(ImageMath.clamp(v, 0.0f, 1.0f)); 331 break; 332 case INT_CIRCLE_DOWN: 333 v = ImageMath.circleDown(ImageMath.clamp(v, 0.0f, 1.0f)); 334 break; 335 case INT_SMOOTH: 336 v = ImageMath.smoothStep(0, 1, v); 337 break; 338 } 339 return v; 340 } 341 342 private float distance(float a, float b) { 343 return (float)Math.sqrt(a*a+b*b); 344 } 345 346 public String toString() { 347 return "Other/Gradient Fill..."; 348 } 349}