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.*;
022import com.jhlabs.math.*;
023
024/**
025 * A filter which produces an image with a cellular texture.
026 */
027public class CellularFilter extends WholeImageFilter implements Function2D, Cloneable {
028
029        protected float scale = 32;
030        protected float stretch = 1.0f;
031        protected float angle = 0.0f;
032        public float amount = 1.0f;
033        public float turbulence = 1.0f;
034        public float gain = 0.5f;
035        public float bias = 0.5f;
036        public float distancePower = 2;
037        public boolean useColor = false;
038        protected Colormap colormap = new Gradient();
039        protected float[] coefficients = { 1, 0, 0, 0 };
040        protected float angleCoefficient;
041        protected Random random = new Random();
042        protected float m00 = 1.0f;
043        protected float m01 = 0.0f;
044        protected float m10 = 0.0f;
045        protected float m11 = 1.0f;
046        protected Point[] results = null;
047        protected float randomness = 0;
048        protected int gridType = HEXAGONAL;
049        private float min;
050        private float max;
051        private static byte[] probabilities;
052        private float gradientCoefficient;
053        
054        public final static int RANDOM = 0;
055        public final static int SQUARE = 1;
056        public final static int HEXAGONAL = 2;
057        public final static int OCTAGONAL = 3;
058        public final static int TRIANGULAR = 4;
059
060        public CellularFilter() {
061                results = new Point[3];
062                for (int j = 0; j < results.length; j++)
063                        results[j] = new Point();
064                if (probabilities == null) {
065                        probabilities = new byte[8192];
066                        float factorial = 1;
067                        float total = 0;
068                        float mean = 2.5f;
069                        for (int i = 0; i < 10; i++) {
070                                if (i > 1)
071                                        factorial *= i;
072                                float probability = (float)Math.pow(mean, i) * (float)Math.exp(-mean) / factorial;
073                                int start = (int)(total * 8192);
074                                total += probability;
075                                int end = (int)(total * 8192);
076                                for (int j = start; j < end; j++)
077                                        probabilities[j] = (byte)i;
078                        }       
079                }
080        }
081        
082        /**
083     * Specifies the scale of the texture.
084     * @param scale the scale of the texture.
085     * @min-value 1
086     * @max-value 300+
087     * @see #getScale
088     */
089        public void setScale(float scale) {
090                this.scale = scale;
091        }
092
093        /**
094     * Returns the scale of the texture.
095     * @return the scale of the texture.
096     * @see #setScale
097     */
098        public float getScale() {
099                return scale;
100        }
101
102        /**
103     * Specifies the stretch factor of the texture.
104     * @param stretch the stretch factor of the texture.
105     * @min-value 1
106     * @max-value 50+
107     * @see #getStretch
108     */
109        public void setStretch(float stretch) {
110                this.stretch = stretch;
111        }
112
113        /**
114     * Returns the stretch factor of the texture.
115     * @return the stretch factor of the texture.
116     * @see #setStretch
117     */
118        public float getStretch() {
119                return stretch;
120        }
121
122        /**
123     * Specifies the angle of the texture.
124     * @param angle the angle of the texture.
125     * @angle
126     * @see #getAngle
127     */
128        public void setAngle(float angle) {
129                this.angle = angle;
130                float cos = (float)Math.cos(angle);
131                float sin = (float)Math.sin(angle);
132                m00 = cos;
133                m01 = sin;
134                m10 = -sin;
135                m11 = cos;
136        }
137
138        /**
139     * Returns the angle of the texture.
140     * @return the angle of the texture.
141     * @see #setAngle
142     */
143        public float getAngle() {
144                return angle;
145        }
146
147        public void setCoefficient(int i, float v) {
148                coefficients[i] = v;
149        }
150
151        public float getCoefficient(int i) {
152                return coefficients[i];
153        }
154
155        public void setAngleCoefficient(float angleCoefficient) {
156                this.angleCoefficient = angleCoefficient;
157        }
158
159        public float getAngleCoefficient() {
160                return angleCoefficient;
161        }
162
163        public void setGradientCoefficient(float gradientCoefficient) {
164                this.gradientCoefficient = gradientCoefficient;
165        }
166
167        public float getGradientCoefficient() {
168                return gradientCoefficient;
169        }
170
171        public void setF1( float v ) {
172                coefficients[0] = v;
173        }
174
175        public float getF1() {
176                return coefficients[0];
177        }
178
179        public void setF2( float v ) {
180                coefficients[1] = v;
181        }
182
183        public float getF2() {
184                return coefficients[1];
185        }
186
187        public void setF3( float v ) {
188                coefficients[2] = v;
189        }
190
191        public float getF3() {
192                return coefficients[2];
193        }
194
195        public void setF4( float v ) {
196                coefficients[3] = v;
197        }
198
199        public float getF4() {
200                return coefficients[3];
201        }
202
203    /**
204     * Set the colormap to be used for the filter.
205     * @param colormap the colormap
206     * @see #getColormap
207     */
208        public void setColormap(Colormap colormap) {
209                this.colormap = colormap;
210        }
211        
212    /**
213     * Get the colormap to be used for the filter.
214     * @return the colormap
215     * @see #setColormap
216     */
217        public Colormap getColormap() {
218                return colormap;
219        }
220        
221        public void setRandomness(float randomness) {
222                this.randomness = randomness;
223        }
224
225        public float getRandomness() {
226                return randomness;
227        }
228
229        public void setGridType(int gridType) {
230                this.gridType = gridType;
231        }
232
233        public int getGridType() {
234                return gridType;
235        }
236
237        public void setDistancePower(float distancePower) {
238                this.distancePower = distancePower;
239        }
240
241        public float getDistancePower() {
242                return distancePower;
243        }
244
245        /**
246     * Specifies the turbulence of the texture.
247     * @param turbulence the turbulence of the texture.
248     * @min-value 0
249     * @max-value 1
250     * @see #getTurbulence
251     */
252        public void setTurbulence(float turbulence) {
253                this.turbulence = turbulence;
254        }
255
256        /**
257     * Returns the turbulence of the effect.
258     * @return the turbulence of the effect.
259     * @see #setTurbulence
260     */
261        public float getTurbulence() {
262                return turbulence;
263        }
264
265        /**
266         * Set the amount of effect.
267         * @param amount the amount
268     * @min-value 0
269     * @max-value 1
270     * @see #getAmount
271         */
272        public void setAmount(float amount) {
273                this.amount = amount;
274        }
275
276        /**
277         * Get the amount of texture.
278         * @return the amount
279     * @see #setAmount
280         */
281        public float getAmount() {
282                return amount;
283        }
284
285        public class Point {
286                public int index;
287                public float x, y;
288                public float dx, dy;
289                public float cubeX, cubeY;
290                public float distance;
291        }
292        
293        private float checkCube(float x, float y, int cubeX, int cubeY, Point[] results) {
294                int numPoints;
295                random.setSeed(571*cubeX + 23*cubeY);
296                switch (gridType) {
297                case RANDOM:
298                default:
299                        numPoints = probabilities[random.nextInt() & 0x1fff];
300                        break;
301                case SQUARE:
302                        numPoints = 1;
303                        break;
304                case HEXAGONAL:
305                        numPoints = 1;
306                        break;
307                case OCTAGONAL:
308                        numPoints = 2;
309                        break;
310                case TRIANGULAR:
311                        numPoints = 2;
312                        break;
313                }
314                for (int i = 0; i < numPoints; i++) {
315                        float px = 0, py = 0;
316                        float weight = 1.0f;
317                        switch (gridType) {
318                        case RANDOM:
319                                px = random.nextFloat();
320                                py = random.nextFloat();
321                                break;
322                        case SQUARE:
323                                px = py = 0.5f;
324                                if (randomness != 0) {
325                                        px += randomness * (random.nextFloat()-0.5);
326                                        py += randomness * (random.nextFloat()-0.5);
327                                }
328                                break;
329                        case HEXAGONAL:
330                                if ((cubeX & 1) == 0) {
331                                        px = 0.75f; py = 0;
332                                } else {
333                                        px = 0.75f; py = 0.5f;
334                                }
335                                if (randomness != 0) {
336                                        px += randomness * Noise.noise2(271*(cubeX+px), 271*(cubeY+py));
337                                        py += randomness * Noise.noise2(271*(cubeX+px)+89, 271*(cubeY+py)+137);
338                                }
339                                break;
340                        case OCTAGONAL:
341                                switch (i) {
342                                case 0: px = 0.207f; py = 0.207f; break;
343                                case 1: px = 0.707f; py = 0.707f; weight = 1.6f; break;
344                                }
345                                if (randomness != 0) {
346                                        px += randomness * Noise.noise2(271*(cubeX+px), 271*(cubeY+py));
347                                        py += randomness * Noise.noise2(271*(cubeX+px)+89, 271*(cubeY+py)+137);
348                                }
349                                break;
350                        case TRIANGULAR:
351                                if ((cubeY & 1) == 0) {
352                                        if (i == 0) {
353                                                px = 0.25f; py = 0.35f;
354                                        } else {
355                                                px = 0.75f; py = 0.65f;
356                                        }
357                                } else {
358                                        if (i == 0) {
359                                                px = 0.75f; py = 0.35f;
360                                        } else {
361                                                px = 0.25f; py = 0.65f;
362                                        }
363                                }
364                                if (randomness != 0) {
365                                        px += randomness * Noise.noise2(271*(cubeX+px), 271*(cubeY+py));
366                                        py += randomness * Noise.noise2(271*(cubeX+px)+89, 271*(cubeY+py)+137);
367                                }
368                                break;
369                        }
370                        float dx = (float)Math.abs(x-px);
371                        float dy = (float)Math.abs(y-py);
372                        float d;
373                        dx *= weight;
374                        dy *= weight;
375                        if (distancePower == 1.0f)
376                                d = dx + dy;
377                        else if (distancePower == 2.0f)
378                                d = (float)Math.sqrt(dx*dx + dy*dy);
379                        else
380                                d = (float)Math.pow((float)Math.pow(dx, distancePower) + (float)Math.pow(dy, distancePower), 1/distancePower);
381
382                        // Insertion sort the long way round to speed it up a bit
383                        if (d < results[0].distance) {
384                                Point p = results[2];
385                                results[2] = results[1];
386                                results[1] = results[0];
387                                results[0] = p;
388                                p.distance = d;
389                                p.dx = dx;
390                                p.dy = dy;
391                                p.x = cubeX+px;
392                                p.y = cubeY+py;
393                        } else if (d < results[1].distance) {
394                                Point p = results[2];
395                                results[2] = results[1];
396                                results[1] = p;
397                                p.distance = d;
398                                p.dx = dx;
399                                p.dy = dy;
400                                p.x = cubeX+px;
401                                p.y = cubeY+py;
402                        } else if (d < results[2].distance) {
403                                Point p = results[2];
404                                p.distance = d;
405                                p.dx = dx;
406                                p.dy = dy;
407                                p.x = cubeX+px;
408                                p.y = cubeY+py;
409                        }
410                }
411                return results[2].distance;
412        }
413        
414        public float evaluate(float x, float y) {
415                for (int j = 0; j < results.length; j++)
416                        results[j].distance = Float.POSITIVE_INFINITY;
417
418                int ix = (int)x;
419                int iy = (int)y;
420                float fx = x-ix;
421                float fy = y-iy;
422
423                float d = checkCube(fx, fy, ix, iy, results);
424                if (d > fy)
425                        d = checkCube(fx, fy+1, ix, iy-1, results);
426                if (d > 1-fy)
427                        d = checkCube(fx, fy-1, ix, iy+1, results);
428                if (d > fx) {
429                        checkCube(fx+1, fy, ix-1, iy, results);
430                        if (d > fy)
431                                d = checkCube(fx+1, fy+1, ix-1, iy-1, results);
432                        if (d > 1-fy)
433                                d = checkCube(fx+1, fy-1, ix-1, iy+1, results);
434                }
435                if (d > 1-fx) {
436                        d = checkCube(fx-1, fy, ix+1, iy, results);
437                        if (d > fy)
438                                d = checkCube(fx-1, fy+1, ix+1, iy-1, results);
439                        if (d > 1-fy)
440                                d = checkCube(fx-1, fy-1, ix+1, iy+1, results);
441                }
442
443                float t = 0;
444                for (int i = 0; i < 3; i++)
445                        t += coefficients[i] * results[i].distance;
446                if (angleCoefficient != 0) {
447                        float angle = (float)Math.atan2(y-results[0].y, x-results[0].x);
448                        if (angle < 0)
449                                angle += 2*(float)Math.PI;
450                        angle /= 4*(float)Math.PI;
451                        t += angleCoefficient * angle;
452                }
453                if (gradientCoefficient != 0) {
454                        float a = 1/(results[0].dy+results[0].dx);
455                        t += gradientCoefficient * a;
456                }
457                return t;
458        }
459        
460        public float turbulence2(float x, float y, float freq) {
461                float t = 0.0f;
462
463                for (float f = 1.0f; f <= freq; f *= 2)
464                        t += evaluate(f*x, f*y) / f;
465                return t;
466        }
467
468        public int getPixel(int x, int y, int[] inPixels, int width, int height) {
469                float nx = m00*x + m01*y;
470                float ny = m10*x + m11*y;
471                nx /= scale;
472                ny /= scale * stretch;
473                nx += 1000;
474                ny += 1000;     // Reduce artifacts around 0,0
475                float f = turbulence == 1.0f ? evaluate(nx, ny) : turbulence2(nx, ny, turbulence);
476                // Normalize to 0..1
477//              f = (f-min)/(max-min);
478                f *= 2;
479                f *= amount;
480                int a = 0xff000000;
481                int v;
482                if (colormap != null) {
483                        v = colormap.getColor(f);
484                        if (useColor) {
485                                int srcx = ImageMath.clamp((int)((results[0].x-1000)*scale), 0, width-1);
486                                int srcy = ImageMath.clamp((int)((results[0].y-1000)*scale), 0, height-1);
487                                v = inPixels[srcy * width + srcx];
488                                f = (results[1].distance - results[0].distance) / (results[1].distance + results[0].distance);
489                                f = ImageMath.smoothStep(coefficients[1], coefficients[0], f);
490                                v = ImageMath.mixColors(f, 0xff000000, v);
491                        }
492                        return v;
493                } else {
494                        v = PixelUtils.clamp((int)(f*255));
495                        int r = v << 16;
496                        int g = v << 8;
497                        int b = v;
498                        return a|r|g|b;
499                }
500        }
501
502        protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
503//              float[] minmax = Noise.findRange(this, null);
504//              min = minmax[0];
505//              max = minmax[1];
506
507                int index = 0;
508                int[] outPixels = new int[width * height];
509
510                for (int y = 0; y < height; y++) {
511                        for (int x = 0; x < width; x++) {
512                                outPixels[index++] = getPixel(x, y, inPixels, width, height);
513                        }
514                }
515                return outPixels;
516        }
517
518        public Object clone() {
519                CellularFilter f = (CellularFilter)super.clone();
520                f.coefficients = (float[])coefficients.clone();
521                f.results = (Point[])results.clone();
522                f.random = new Random();
523//              if (colormap != null)
524//                      f.colormap = (Colormap)colormap.clone();
525                return f;
526        }
527        
528        public String toString() {
529                return "Texture/Cellular...";
530        }
531        
532}