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}