001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030/**
031 *
032 */
033package org.openimaj.image.processing.mask;
034
035import org.openimaj.image.FImage;
036import org.openimaj.image.MBFImage;
037import org.openimaj.math.geometry.line.Line2d;
038import org.openimaj.math.geometry.point.Point2d;
039import org.openimaj.math.geometry.point.Point2dImpl;
040
041/**
042 *      Generator for grayscale mattes.
043 *
044 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
045 *  @created 31 Jan 2013
046 *      @version $Author$, $Revision$, $Date$
047 */
048public class MatteGenerator
049{
050        /**
051         *      An enumerator for various matte algorithms.
052         *
053         *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
054         *  @created 31 Jan 2013
055         *      @version $Author$, $Revision$, $Date$
056         */
057        public enum MatteType
058        {
059                /**
060                 *      A basic linear vertical gradient. The argument should be boolean which, if TRUE,
061                 *      determines that the gradient will be black at the top and white at the bottom.
062                 *      If non-existent, or FALSE, the gradient will be white at the top and black at the bottom.
063                 */
064                LINEAR_VERTICAL_GRADIENT
065                {
066            @Override
067            public void generateMatte( final FImage img, final Object... args )
068            {
069                boolean whiteAtTop = false;
070                if( args.length == 0 || ((args[0] instanceof Boolean) && !((Boolean)args[0]).booleanValue()) )
071                                whiteAtTop = true;
072
073                final double g = (whiteAtTop?1:0);
074                final double scalar = (whiteAtTop?-1d/img.getHeight():1d/img.getHeight());
075
076                for( int y = 0; y < img.getHeight(); y++ )
077                        for( int x = 0; x < img.getWidth(); x++ )
078                                img.pixels[y][x] = (float)(g + y*scalar);
079            }
080                },
081                /**
082                 *      A basic linear horizontal gradient. The argument should be boolean which, if TRUE,
083                 *      determines that the gradient will be black at the left and white at the right.
084                 *      If non-existent, or FALSE, the gradient will be white at the left and black at the right.
085                 */
086                LINEAR_HORIZONTAL_GRADIENT
087                {
088            @Override
089            public void generateMatte( final FImage img, final Object... args )
090            {
091                boolean whiteAtLeft = false;
092                if( args.length == 0 || ((args[0] instanceof Boolean) && !((Boolean)args[0]).booleanValue()) )
093                                whiteAtLeft = true;
094
095                final double g = (whiteAtLeft?1:0);
096                final double scalar = (whiteAtLeft?-1d/img.getWidth():1d/img.getWidth());
097
098                for( int y = 0; y < img.getHeight(); y++ )
099                        for( int x = 0; x < img.getWidth(); x++ )
100                                img.pixels[y][x] = (float)(g + x*scalar);
101            }
102                },
103                /**
104                 *      Basic radial gradient centred on the middle of the matte. The argument should be
105                 *      boolean which, if TRUE, determines whether the gradient will be black or white in
106                 *      the middle (TRUE for white).
107                 */
108                RADIAL_GRADIENT
109                {
110                        @Override
111            public void generateMatte( final FImage img, final Object... args )
112            {
113                                boolean whiteInCentre = false;
114                                if( args.length > 0 && args[0] instanceof Boolean && ((Boolean)args[0]).booleanValue())
115                                        whiteInCentre = true;
116
117                                // Centre coordinates
118                                final int cx = img.getWidth()  /2;
119                                final int cy = img.getHeight() /2;
120
121                                // Outside edge of radial
122                                final int maxDist = Math.max(
123                                                Math.max( img.getWidth()  - cx, cx ),
124                                                Math.max( img.getHeight() - cy, cy )
125                                );
126                                final double scale = maxDist;
127
128                                for( int y = 0; y < img.getHeight(); y++ )
129                                        for( int x = 0; x < img.getWidth(); x++ )
130                                                img.pixels[y][x] = whiteInCentre?
131                                                                1f-(float)this.distanceFromCentre( cx, cy, x, y, scale ):
132                                                                   (float)this.distanceFromCentre( cx, cy, x, y, scale );
133            }
134
135                        /**
136                         *      Calculates the distance from the centre, scaled to the maximum distance
137                         *      with clipping bounds 0 <= d <= 1.
138                         *
139                         *      @param cx Centre x-coordinate
140                         *      @param cy Centre y-coordinate
141                         *      @param x X position of point to find distance
142                         *      @param y Y position of point to find distance
143                         *      @param scale maximum distance
144                         *      @return Scaled, clipped distance measure
145                         */
146                        private double distanceFromCentre( final int cx, final int cy, final int x, final int y,
147                    final double scale )
148            {
149                                final double b = cx - x;
150                                final double c = cy - y;
151                    double v = Math.abs( Math.sqrt( b*b + c*c ) )/scale;
152                    if( v > 1 ) v = 1;
153                    if( v < 0 ) v = 0;
154                    return v;
155            }
156                },
157                /**
158                 *      Generates linear gradients that can be angled to any angle. The angle of the
159                 *      gradient is given in the first argument as a double. The position of the point
160                 *      at which the gradient is rotated, is given in the next two arguments, also as
161                 *      doubles. If any are missing, they are considered 0. To invert the gradient,
162                 *      invert the resulting FImage.
163                 */
164                ANGLED_LINEAR_GRADIENT
165                {
166
167                        @Override
168            public void generateMatte( final FImage img, final Object... args )
169            {
170                                // Angle and position of gradient axis
171                                double angle = 0;
172                                double lx = 0;
173                                double ly = 0;
174
175                                // Get any arguments
176                                if( args.length > 0 && args[0] instanceof Double )
177                                        angle = ((Double)args[0]).doubleValue();
178                                if( args.length > 1 && args[1] instanceof Double )
179                                        lx = ((Double)args[1]).doubleValue();
180                                if( args.length > 2 && args[2] instanceof Double )
181                                        ly = ((Double)args[2]).doubleValue();
182
183                                // Outside edge of radial
184                                final double scalar = Math.max(
185                                                Math.max( img.getWidth()  - lx, lx ),
186                                                Math.max( img.getHeight() - ly, ly )
187                                );
188
189                                for( int y = 0; y < img.getHeight(); y++ )
190                                        for( int x = 0; x < img.getWidth(); x++ )
191                                                img.pixels[y][x] = (float)this.distanceFromAxis( lx, ly, angle, x, y, scalar );
192            }
193
194                        /**
195                         *      calculate the distance from the gradient axis.
196                         *      @param lx A point on the gradient axis - x coordinate
197                         *      @param ly A point on the gradient axis - y coordinate
198                         *      @param angle The angle of the axis
199                         *      @param x The x position to find the distance
200                         *      @param y The y position to find the distance
201                         *      @param scalar The scalar for the final vector
202                         *      @return
203                         */
204                        private double distanceFromAxis( final double lx, final double ly, final double angle,
205                    final double x, final double y, final double scalar )
206            {
207                                // See http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
208                                final Line2d line = Line2d.lineFromRotation( (int)lx, (int)ly, angle, 1 );
209                                final Point2d A = line.begin;
210                                final Point2d B = line.end;
211                                final Point2dImpl P = new Point2dImpl( (float)x, (float)y );
212                                final double normalLength = Math.hypot(B.getX() - A.getX(), B.getY() - A.getY());
213                                double grad = Math.abs((P.x - A.getX()) * (B.getY() - A.getY()) - (P.y - A.getY()) *
214                                                (B.getX() - A.getX())) / normalLength / scalar;
215                                if( grad < 0 ) grad = 0;
216                                if( grad > 1 ) grad = 1;
217                                return grad;
218            }
219                }
220                ;
221
222                /**
223                 *      Generate the matte into the given image.
224                 *      @param img The image to generate the matte into
225                 *      @param args The arguments for the matte generator
226                 */
227                public abstract void generateMatte( FImage img, Object... args );
228        }
229
230        /**
231         *      Generate a matte into the given image on the given band.
232         *
233         *      @param image The image to write the matte into
234         *      @param band The band of the image to write the matte into
235         *      @param type The type of the matte to draw
236         *      @param args The arguments for the matte generator
237         *      @return The input image (for chaining) 
238         */
239        public static FImage generateMatte( final MBFImage image, final int band,
240                        final MatteType type, final Object... args )
241        {
242                return MatteGenerator.generateMatte( image.getBand( band ), type, args );
243        }
244
245        /**
246         *      Generate a matte into the given {@link FImage}.
247         *
248         *      @param image The image to write the matte into
249         *      @param type The type of the matte to draw
250         *      @param args The arguments for the matte generator
251         *      @return The input image (for chaining) 
252         */
253        public static FImage generateMatte( final FImage image, final MatteType type, final Object... args )
254        {
255                type.generateMatte( image, args );
256                return image;
257        }
258}