package no.hiof.imagepr.filters;

import no.hiof.imagepr.*;

/**
<p>
    This class contain two methods which filter an image using local histogram equalization.
    The difference between these two methods is that one method receives and returns an image, 
    the other method receives and returns a matrix. If you call the matrix-method, the matrix should be
    the intensityvalues of an intensityimage or the V-values of a HSV-image. This is the most common way to
    filter images using local histogram equalization. Although you can also use the matrix-method to filter
    the R-, G- and B-components in a RGB-image or the H- and/or S-component of a HSV-image if that is what
    best serve your application.
</p>

<p>
    The user has three option of how the edges should be handled (By &#34;edges&#34; it's ment the pixels
    in the input-image that are so close to the edges that the local-histogram-mask can't be placed so
    the pixel is in center of the mask):
</p>

<ol>
    <li>
        All edge-pixels get the same specific grayscale-intensity. This mean that the image will be croped,
        and there will be a frame around the filtered image.
    </li>
    <li>
        The egdes are croped. This mean that the filtered image is smaller than the original image.
    </li>
    <li>
        The egdes are filtered too. This mean that the a larger image is created and the original image is placed in
        the middel of the new one. All pixels that are outside the original image get the intensity-vaule of the pixel
        in the original image that are closest to the outside-pixel. Then the method described in
        the the previous point is being used on the new image. The filtered image has the same size as the
        original image.
    </li>
</ol>

<p>
    This filter is implemented in a quite efficient way. This is done by just creating a new histogram for
    every row, not for every pixel. For all pixels, except for the first pixel on a row, the histogram is
    updated instead of recreated.
</p>

<pre>
This gives the following estimate of how long time the filter will use to process the image:

- If the edges are filtered (point 3 above):  a(X * x * y) + b(X * Y * x) + c(64 * X * Y)

- Otherwise (point 1 and 2 above):  a((X - x) * x * y) + b((X - x) * (Y - y) * x) + c(64 * (X - x) * (Y - y))


X is the heigth of the image.

Y is the width of the image.

x is the heigth of the mask.

y is the width of the mask.

a, b and c are constants.
</pre>

<p>
    It's important to notice that this is only a rough estimate, and must of course be multiplied by a faktor that depends on
    your hardware and OS.
</p>

<h3>Pseudocode</h3>
<p>
    If the user wants to filter an intensityimage, the intensityvalues of the image are filtered using local histogram equalization.
    If the user wants to filter a colorimage, the image is convered into a HSV-image (if it isn't a HSV-image already), then the
    V-component of the image are filtered using local histogram equalization.
</p>
<ol>
    <li>
        Make an array of 256 short-variabels (one for every possible intensity-value).
        Name this array sum.
    </li>
    <li>
        If the user want to filter the edges too, then enlarge the image. How this is done is explaned above.
    </li>
    <li>
        For every pixel in the image (going from top to buttom in the outer loop, and from left to right
        in the inner loop):
        <ol>
            <li>
                If masks left edge is against the left egde of the image, then all set elements of sum to 0.
                Go througth every pixel in the mask, and increase the element of the array sum that has the same
                index as the intensity-value of the pixel by 1. This makes a histogram for the intensity-values
                of the image.
                <br/><br/>
                Else update the histogram: 1) Go througth the the pixels in the image that are no longer in the
                mask, and decrease the element of the array sum that has the same index as the intensity-value of
                the pixel by 1. 2) Go througth the the pixels in the image that just has becomed a part of the
                mask, and increase the element of the array sum that has the same index as the intensity-value of
                the pixel by 1.
                <br/><br/>
                Set the corresponding pixel of the output to count * 255 / maskSize, where count is the sum of all
                elements of the array sum that has an index equal to or lower than the intensity-value of
                center-pixel of the mask, and maskSize is the number of pixels in the mask.
            </li>
        </ol>
    </li>
    <li>
        If the user want to has the same intensity on all pixels in the egdes, then set all the edge-pixels to
        this intensity-value. Else decrease the size of the image by croping the edges.
    </li>
</ol>

<h3>Usage</h3>
<ol>
    <li>
        Create a LocalHistogramFilter-objekt.
    </li>
    <li>
        Set the filterparameters (maskHeight, maskWidth and edgeType). This can be done by giving the values
        as parameters to the constuctor, or by using the methods setMaskHeight, setMaskWidth, setEdgeType and
        setFilterParam (the first three set one parameter each, the last one sets all three parameters at once).
        If you don't set the parameters the default parameters are used
        (maskHeight = maskWidth = 3 and edgeType = FILTER_EDGES).
    </li>
    <li>
        Filter an image by calling the method filter. The method takes an image as parameter. The image can either be
        an image-object or a matrix. The method returns the filtered image. The returned image is an image-object
        or a matrix depending on how the method was called.
    </li>
</ol>
<p>
    Please notice that an LocalHistogramFilter-object only stores the filterparameters. It is not able to store the
    image itself.
</p>
<p>
    Currently this filter can handle images of type IntensityImage, RGBImage, HSIImage and HSVImage.
    In all others cases the filter will return a null-pointer. The filter also return a null-pointer if the
    filtering-process for some reason failed.
</p>
<p>
    Authors: Algoritm and testing by &Oslash;ystein Igeland and Magnus Komper&oslash;d. Implemented by Magnus Komper&oslash;d.
</p>
*/

public class LocalHistogramFilter implements ImageFilter
{
   /**
      The edges will be croped when the image is filtered.
   */
   public static final int CROP_EDGES = 256;

   /**
      The edges will be filtered when the image is filtered.
   */
   public static final int FILTER_EDGES = 257;
   
   /**
      The edges will be croped when the image is filtered. A black frame will be added to the
      filtered image so that the filtered image is as big as the input-image.
   */
   public static final int BLACK_EDGES = 0;

   /**
      The edges will be croped when the image is filtered. A dark gray frame will be added to the
      filtered image so that the filtered image is as big as the input-image.
   */
   public static final int DARK_GRAY_EDGES = 63;

   /**
      The edges will be croped when the image is filtered. A gray frame will be added to the
      filtered image so that the filtered image is as big as the input-image.
   */
   public static final int GRAY_EDGES = 127;

   /**
      The edges will be croped when the image is filtered. A light gray frame will be added to the
      filtered image so that the filtered image is as big as the input-image.
   */
   public static final int LIGHT_GRAY_EDGES = 191;

   /**
      The edges will be croped when the image is filtered. A white frame will be added to the
      filtered image so that the filtered image is as big as the input-image.
   */
   public static final int WHITE_EDGES = 255;

   private int maskHeight;
   private int maskWidth;
   private int edgeType;

   /**
      Constuctor that sets the filterparameters to the default values (maskHeight = maskWidth = 3 and edgeType = FILTER_EDGES)
   */
   public LocalHistogramFilter ()
   {
      this.maskHeight = 3;
      this.maskWidth = 3;
      this.edgeType = FILTER_EDGES;
   }

   /**
      Constructor that sets the filterparameters to the values specified by the user.
      @param maskHeight Must be an odd number, and equal to or greater than 3.
      @param maskWidth Must be an odd number, and equal to or greater than 3.
      @param edgeType Must be BLACK_EDGES, DARK_GARY_EDGES, GRAY_EDGES, LIGHT_GRAY_EDGES, WHITE_EGDES, FILTER_EDGES, CROP_EDGES or an integer in the range 0 - 255.
   */
   public LocalHistogramFilter (int maskHeight, int maskWidth, int edgeType)
   {
      setMaskHeight (maskHeight);
      setMaskWidth (maskWidth);
      setEdgeType (edgeType);
   }

   /**
      Sets the maskheight.
      @param height Must be an odd number, and equal to or greater than 3.
      @return 0 if update is successful, else 1.
   */
   public int setMaskHeight (int height)
   {
      // If the new value is OK, then update.
      if (height % 2 == 1 && height >= 3)
         {
            maskHeight = height;
            return 0;
         }

      // The new value is wrong, so we keep the old value.
      else if (maskHeight % 2 == 1 && maskHeight >= 3)
         {
            System.err.println ("maskHeight must be an odd number, and equal to or larger than 3.");
            System.err.println ("maskHeight is left unchanged.");
            return 1;
         }

      // The new value is wrong, and there is no old value or the old value is also wrong. We set
      // the maskHeight to 3;
      else
         {
            System.err.println ("maskHeight must be an odd number, and equal to or larger than 3.");
            System.err.println ("maskHeight is set to 3.");
            maskHeight = 3;
            return 1;
         }

   }

   /**
      Sets the maskwidth.
      @param width Must be an odd number, and equal to or greater than 3.
      @return 0 if update is successful, else 1.
   */
   public int setMaskWidth (int width)
   {
      // If the new value is OK, then update.
      if (width % 2 == 1 && width >= 3)
         {
            maskWidth = width;
            return 0;
         }

      // The new value is wrong, so we keep the old value.
      else if (maskWidth % 2 == 1 && maskWidth >= 3)
         {
            System.err.println ("maskWidth must be an odd number, and equal to or larger than 3.");
            System.err.println ("maskWidth is left unchanged.");
            return 1;
         }

      // The new value is wrong, and there is no old value or the old value is also wrong. We set
      // the maskWidth to 3;
      else
         {
            System.err.println ("maskWidth must be an odd number, and equal to or larger than 3.");
            System.err.println ("maskWidth is set to 3.");
            maskWidth = 3;
            return 1;
         }
   }

   /**
      Sets the type of edge that the filtered image will get.
      @param type Must be BLACK_EDGES, DARK_GARY_EDGES, GRAY_EDGES, LIGHT_GRAY_EDGES, WHITE_EGDES, FILTER_EDGES, CROP_EDGES or an integer in the range 0 - 255.
      @return 0 if update is successful, else 1.
   */
   public int setEdgeType (int type)
   {
      // If the new value is OK, then update.
      if ((type >= BLACK_EDGES && type <= WHITE_EDGES) || type == FILTER_EDGES || type == CROP_EDGES)
         {
            edgeType = type;
            return 0;
         }

      // The new value is wrong, so we keep the old value.
      else if ((edgeType >= BLACK_EDGES && edgeType <= WHITE_EDGES) || edgeType == FILTER_EDGES || edgeType == CROP_EDGES)
         {
            System.err.println ("edgeType must be: BLACK_EDGES, DARK_GARY_EDGES, GRAY_EDGES, LIGHT_GRAY_EDGES, WHITE_EGDES, ");
            System.err.println ("FILTER_EDGES, CROP_EDGES or an integer in the range 0 - 255.");
            System.err.println ("edgeType is left unchaged.");
            return 1;
         }

      // The new value is wrong, and there is no old value or the old value is also wrong. We set
      // the egdeType to FILTER_EDGES;
      else
         {
            System.err.println ("edgeType must be: BLACK_EDGES, DARK_GARY_EDGES, GRAY_EDGES, LIGHT_GRAY_EDGES, WHITE_EGDES, ");
            System.err.println ("FILTER_EDGES, CROP_EDGES or an integer in the range 0 - 255.");
            System.err.println ("edgeType is set to FILTER_EDGES.");
            edgeType = FILTER_EDGES;
            return 1;
         }
   }

   /**
      Update the filterparameters (maskHeight, maskWidth and edgeType)
      @param maskHeight Must be an odd number, and equal to or greater than 3.
      @param maskWidth Must be an odd number, and equal to or greater than 3.
      @param edgeType Must be BLACK_EDGES, DARK_GARY_EDGES, GRAY_EDGES, LIGHT_GRAY_EDGES, WHITE_EGDES, FILTER_EDGES, CROP_EDGES or an integer in the range 0 - 255.
      @return If setMaskHeight failed to be set 1 is returned, if setMaskWidth failed to be set 2 is returned, if edgeType  failed to be set 4 is returned. If several parameters failed to be set the corresponding sum is returned. 0 is returned if everything succeed.
   */
   public int setFilterParam (int maskHeight, int maskWidth, int edgeType)
   {
      int wrong = 0;

      wrong += setMaskHeight (maskHeight);
      wrong += 2 * setMaskWidth (maskWidth);
      wrong += 4 * setEdgeType (edgeType);

      return wrong;
   }

   /**
      @return The maskheight.
   */
   public int getMaskHeight ()
   {
      return maskHeight;
   }

   /**
      @return The maskwidth.
   */
   public int getMaskWidth ()
   {
      return maskWidth;
   }

   /**
      @return The edgetype.
   */
   public int getEdgeType ()
   {
      return edgeType;
   }
   
   /**
      Filter an image using local-histogram-equalization. This method can handle IntensityImage, RGBImage, HSIImage and HSVImage.
      If a different class of Image are received a null-pointer is returned.
      @param image The image to be filtered (input-image).
      @return The filtered image (output-image). If the method fails to filter the image, a null-pointer is returned.
   */
   public Image filter (Image image)
   {
      // If we get an IntensityImage we filter it directly.
      if (image instanceof IntensityImage)
         {
            // Convert from image to matrix and filter the matrix.
            short[][] returnvalue = filter (((IntensityImage)image).getData());

            // If the filtering did't succeed we return a null-pointer.
            if (returnvalue == null)
               return null;

            // The filtering succeed. We convert the matrix to an IntensityImage and return the image.
            return new IntensityImage (returnvalue); 
         }

      // If we get a HSVImage we filter the value-component.
      if (image instanceof HSVImage)
         {
            short[][] sat=((HSVImage) image).getSaturation ();
            short[][] hue=null;

            int m=0, n=0;
            int xmin = (maskHeight - 1) / 2;
            int ymin = (maskWidth - 1) / 2;
            int xmax = sat.length - xmin - 1;
            int ymax = sat[0].length - ymin - 1;
                  
            // If the user wants edges with black/white-intensityvalue, we must set the saturation-component
            // to zero for every edgepixels.
            if (edgeType >= BLACK_EDGES && edgeType <= WHITE_EDGES)
               {
                  // Left and right egde
                  for (; m < sat.length; m++)
                     {
                        for (n=0; n < ymin; n++)
                           {
                              sat[m][n] = 0;
                           }

                        for (n = sat[0].length - 1; n >= sat[0].length - ymin; n--)
                           {
                              sat[m][n] = 0;
                           }
                     }

                  // Top and bottom edges.
                  for (n=ymin; n <= ymax; n++)
                     {
                        for (m=0; m < xmin; m++)
                           {
                              sat[m][n] = 0;
                           }

                        for (m = xmax + 1; m < sat.length; m++)
                           {
                              sat[m][n] = 0;
                           }
                     }
               }

            short[][] value = filter ((((HSVImage)image)).getValue ());

            // If the filtering did't succeed we return a null-pointer.
            if (value == null)
               return null;

            // If the user wants to crop the edges, we must crop the matrices of the hue-and saturation-component too.
            else if (edgeType == CROP_EDGES)
               {
                  hue = ((HSVImage) image).getHue ();

                  short[][] hue2 = new short [hue.length - maskHeight + 1][hue[0].length - maskWidth + 1];
                  short[][] sat2 = new short [hue.length - maskHeight + 1][hue[0].length - maskWidth + 1];

                  // Crop the hue-and saturation matrices.
                  for (m = 0; m <= hue.length - maskWidth; m++)
                     {
                        for (n = 0; n <= hue[0].length - maskWidth; n++)
                           {
                              hue2[m][n] = hue[m + xmin][n + ymin];
                              sat2[m][n] = sat[m + xmin][n + ymin];
                           }
                     }

                  // Create a new image and returns it.
                  return new HSVImage (hue2, sat2, value);
               }


            ((HSVImage)image).setValue (value);

            return image;
         }

      // If we get an RGBImage we convert it into an HSVImage and call this method recursive.
      if (image instanceof RGBImage)
         {
            image = new HSVImage ((RGBImage) image);
            image = filter ((HSVImage) image);

            // If the filtering did't succeed we return a null-pointer.
            if (image == null)
               return null;
            
            return ((HSVImage)image).makeRGBImage ();
         }

      // If we get an HSIImage we convert it into an RGBImage and call this method recursive.
      if (image instanceof HSIImage)
         {
            image = ((HSIImage) image).makeRGBImage ();
            image = filter (image);

            // If the filtering did't succeed we return a null-pointer.
            if (image == null)
               return null;

            return new HSIImage ((RGBImage) image);
         }  

      // If the image isn't an IntensityImage, nor HSVImage, nor RGBImage, nor HSIImage, there is nothing we can do.
      System.err.println ("The class LocalHistogramFilter can only handle images of class IntensityImage, HSVImage, RGBImage and HSIImage.");
      System.err.println ("A null-pointer is returned.");
      return null;
   }

   /**
      Filter a matrix using local-histogram-equalization.
      @param matrix The matrix to be filtered. The matrix should be the intensityvalues of an intensityimage or the V-value of an HSV-image.
      @return The filtered matrix. If the method fails to filter the matrix, a null-pointer is retured.
   */
   public short[][] filter (short[][] matrix)
   {
      int maskSize = maskHeight * maskWidth;

      // Checks if the mask's size is OK.
      if ((maskHeight % 2 != 1) || (maskHeight < 3) || (maskWidth % 2 != 1) || (maskWidth < 3))
         {
            System.err.println ("maskHeigth and maskWidth must be odd numbers and equal to or greater than 3.");
            System.err.println ("A null-pointer is returned.");

            return null;
         }

      if (maskHeight > matrix.length)
         {
            System.err.println ("maskHeight is larger than the image's heigth.");
            System.err.println ("A null-pointer is returned.");

            return null;
         }

      if (maskWidth > matrix[0].length)
         {
            System.err.println ("maskWidth is larger than the image's width.");
            System.err.println ("A null-pointer is returned.");

            return null;
         }

      // Filter the image. For now, we don't care about the edges.
      int xmin = (maskHeight - 1) / 2;
      int xmax = matrix.length - xmin - 1;

      int ymin = (maskWidth - 1) / 2;
      int ymax = matrix[0].length - ymin - 1;

      int x=0, y=0, xn=0, xp=0, yn=0, yp=0, xi=0, yi=0, count=0, i=0;

      int sum[] = new int[256];


      // If the user wants to crop the egdes and add a frame to it.
      if (edgeType >= BLACK_EDGES && edgeType <= WHITE_EDGES)
         {

            // Create a matrix for output-image.
            short[][] output = new short[matrix.length][matrix[0].length];

            // Goes through every pixel, except for the edges.
            for (x = xmin; x <=  xmax; x++)
               {

                  // Finds the limit for the lokal histogram.
                  xn = x - xmin;
                  xp = x + xmin;

                  for (i=0; i < 256; i++)
                     {
                        sum[i] = 0;
                     }

                  for (y = ymin; y <= ymax; y++)
                     {
                        // Calculates a histogram for every pixel.
                  
                        // Finds the limit for the lokal-histogram-mask.
                        yn = y - ymin;
                        yp = y + ymin;
                  
                        // If the pixel is the first (left-most) pixel in a row we calculate the
                        // the whole histogram from scratch.
                        if (y == ymin)
                           {
                              for (xi = xn; xi <= xp; xi++)
                                 {
                                    for (yi = yn; yi <= yp; yi++)
                                       {
                                          sum[matrix[xi][yi]]++;
                                       }
                                 }
                           }

                        // ...else we update the histogram. That i much faster than making a new one.
                        else
                           {
                              for (xi = xn; xi <= xp; xi++)
                                 {
                                    sum[matrix[xi][yn-1]]--;
                              
                                    sum[matrix[xi][yp]]++;
                                 }
                           }
                  
                        count = 0;

                        // Sum up the elements in the array up to the center-pixels intensityvalue.
                        // If the intensityvalue is larger than arraySize / 2 it is faster to
                        // "sum down" from 255.

                        if (matrix[x][y] <= 128)
                           {
                              for (i=0; i <= matrix[x][y]; i++)
                                 {
                                    count += sum[i];
                                 }
                           }
                        else
                           {
                              for (i=255; i > matrix[x][y]; i--)
                                 {
                                    count += sum[i];
                                 }

                              count = maskSize - count;
                           }

                        // Calculates the intensity-value and stores it in the output-matrix.
                        output[x][y] = (short)((count * 255) / maskSize);
                     }
               }

            // So far we have just taken care of the center of the image, now we shall handle the edges.
            // Now the edges are black, if we want brigther edges we sets the egde-pixels to a gray or white value.
            if (edgeType > BLACK_EDGES && edgeType <= WHITE_EDGES)
               {
                  int m=0, n=0;

                  // Left and right egde
                  for (; m < output.length; m++)
                     {
                        for (n=0; n < ymin; n++)
                           {
                              output[m][n] = (short)edgeType;
                           }

                        for (n = output[0].length - 1; n >= output[0].length - ymin; n--)
                           {
                              output[m][n] = (short)edgeType;
                           }
                     }

                  // Top and bottom edges.
                  for (n=ymin; n <= ymax; n++)
                     {
                        for (m=0; m < xmin; m++)
                           {
                              output[m][n] = (short)edgeType;
                           }

                        for (m = xmax + 1; m < output.length; m++)
                           {
                              output[m][n] = (short)edgeType;
                           }
                     }
               }

            return output;
         }


      // If the edges are to be croped, or the user want to filter the edges too.
      else if (edgeType == CROP_EDGES || edgeType == FILTER_EDGES)
         {
            
            // If the user want to filter the egdes, we must create a image that are
            // lagrer than the original image. Then we must copy the original image to the
            // center of the new image.

            if (edgeType == FILTER_EDGES)
               {
                  short[][] tempimage = new short[matrix.length + 2 * xmin][matrix[0].length + 2 * ymin];
                  
                  // Create a extended image based on the original image.
                  matrix = extendImage (matrix, tempimage); 

                  // Calulates new values that match the new image
                  xmin = (maskHeight - 1) / 2;
                  xmax = matrix.length - xmin - 1;
                  
                  ymin = (maskWidth - 1) / 2;
                  ymax = matrix[0].length - ymin - 1;
               }

            // The rest of this method is equal for cropping the image and for filter the egdes.

            // Create a matrix for output-image.
            short[][] output = new short[matrix.length - maskHeight + 1][matrix[0].length - maskWidth + 1];

            // Goes through every pixel, except for the edges.
            for (x = xmin; x <=  xmax; x++)
               {

                  // Finds the limit for the lokal histogram.
                  xn = x - xmin;
                  xp = x + xmin;

                  for (i=0; i < 256; i++)
                     {
                        sum[i] = 0;
                     }

                  for (y = ymin; y <= ymax; y++)
                     {
                        // Calculates a histogram for every pixel.
                  
                        // Finds the limit for the lokal histogram.
                        yn = y - ymin;
                        yp = y + ymin;
                  
                  
                        // If the pixel is the first (left-most) pixel in a row we calculate the
                        // the whole histogram from scratch.                        
                        if (y == ymin)
                           {
                              for (xi = xn; xi <= xp; xi++)
                                 {
                                    for (yi = yn; yi <= yp; yi++)
                                       {
                                          sum[matrix[xi][yi]]++;
                                       }
                                 }
                           }

                        // ...else it is much faster to update the histogram than making it
                        // from scratch.
                        else
                           {
                              for (xi = xn; xi <= xp; xi++)
                                 {
                                    sum[matrix[xi][yn-1]]--;
                              
                                    sum[matrix[xi][yp]]++;
                                 }
                           }
                  
                        count = 0;

                        // Sum up the elements in the array up to the center-pixels intensityvalue.
                        // If the intensityvalue is larger than arraySize / 2 it is faster to
                        // "sum down" from 255.

                        if (matrix[x][y] <= 128)
                           {
                              for (i=0; i <= matrix[x][y]; i++)
                                 {
                                    count += sum[i];
                                 }
                           }
                        else
                           {
                              for (i=255; i > matrix[x][y]; i--)
                                 {
                                    count += sum[i];
                                 }

                              count = maskSize - count;
                           }

                        // Calculates the intensity-value and stores it in the output-matrix.
                        output[x-xmin][y-ymin] = (short)((count * 255) / maskSize);
                     }
               }

            return output;
         }

      // If the method hasn't returned yet, the parameter egdeType is not valid.
      System.err.println ("The parameter \"edgeType\" is out of range (0 - 257).");
      System.err.println ("A null-pointer is returned.");
      return null;
   }


   // This method takes two matrixes as parameters. The output-matrix should not be
   // smaller than the input-matrix (neigther in x-nor y-coordinate).
   // The input-matrix will be copyed to the center of the
   // output-image. Then the pixels in the output-matrix that hasn't got a value
   // from the input-matrix, will be set to the same value as the pixel closest to
   // it that has got a value from the input-matrix.

   private short[][] extendImage (short[][] input, short[][] output)
   {

      if (input.length > output.length || input[0].length > output[0].length)
         {
            System.err.println ("The output-image is smaller than the input-image.");
            System.err.println ("The input image is returned.");

            return input;
         }

      int m=0, n=0;

      int xmin = (output.length - input.length) / 2;
      int ymin = (output[0].length - input[0].length) / 2;

      // Copy the original image
      for (; m < input.length; m++)
         {
            for (n=0; n < input[0].length; n++)
               {
                  output[m + xmin][n + ymin] = input[m][n];
               }
         }

      // Extends the original image by copying the egde-pixels to all
      // pixels that is outside the original image.

      for (n = 0; n < input[0].length; n++)
         {
            for (m = 0; m < xmin; m++)
               {
                  output[m][n + ymin] = input[0][n];
               }

            for (m = output.length - xmin; m < output.length; m++)
               {
                  output[m][n + ymin] = input[input.length - 1][n];
               }
         }

      for (m=0; m < output.length; m++)
         {
            for (n=0; n < ymin; n++)
               {
                  output[m][n] = output[m][ymin];
               }
                        
            for (n = output[0].length - ymin; n < output[0].length; n++)
               {
                  output[m][n] = output[m][output[0].length - ymin - 1];
               }
         }

      return output;
   }
}
