Package net.sourceforge.jiu.geometry

Source Code of net.sourceforge.jiu.geometry.Resample$Contributor

/*
* Resample
*
* Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Marco Schmidt.
* All rights reserved.
*/

package net.sourceforge.jiu.geometry;

import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.data.IntegerImage;
import net.sourceforge.jiu.ops.ImageToImageOperation;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.WrongParameterException;

// 2007-04-19 optimization results
//* 10393 original speed in ms
//* 6053 innerloop fix
//* 5688 if .. continue loop break
//* 5485 replaced contrib[] array access by variable
//* 5173 doing the same on first pass (except innerloop fix which was ok there)
// global gain is 2x faster

/* This is the beginning of resample.pas, the Unit on which this class is based:
// -----------------------------------------------------------------------------
// Project:  bitmap resampler
// Module:  resample
// Description: Interpolated Bitmap Resampling using filters.
// Version:  01.03
// Release:  4
// Date:  29-JUN-1999
// Target:  Win32, Delphi 2, 3 & 4
// Author(s):  anme: Anders Melander, anders@melander.dk
// Copyright  (c) 1997-99 by Anders Melander
// Formatting:  2 space indent, 8 space tabs, 80 columns.
// -----------------------------------------------------------------------------
// This software is copyrighted as noted above.  It may be freely copied,
// modified, and redistributed, provided that the copyright notice(s) is
// preserved on all copies.
//
// There is no warranty or other guarantee of fitness for this software,
// it is provided solely "as is".  Bug reports or fixes may be sent
// to the author, who may or may not act on them as he desires.
//
// You may not include this software in a program or other software product
// without supplying the source, or without informing the end-user that the
// source is available for no extra charge.
//
// If you modify this software, you should include a notice in the "Revision
// history" section giving the name of the person performing the modification,
// the date of modification, and the reason for such modification.
// -----------------------------------------------------------------------------
// Here's some additional copyrights for you:
//
// From filter.c:
// The authors and the publisher hold no copyright restrictions
// on any of these files; this source code is public domain, and
// is freely available to the entire computer graphics community
// for study, use, and modification.  We do request that the
// comment at the top of each file, identifying the original
// author and its original publication in the book Graphics
// Gems, be retained in all programs that use these files.
//
// -----------------------------------------------------------------------------
// Revision history:
//
// 0100  110997  anme  - Adapted from Dale Schumacher's fzoom v0.20.
//
// 0101  110198  anme  - Added Lanczos3 and Mitchell filters.
//      - Fixed range bug.
//        Min value was not checked on conversion from Single to
//        byte.
//      - Numerous optimizations.
//      - Added TImage stretch on form resize.
//      - Added support for Delphi 2 via TCanvas.Pixels.
//      - Renamed module from stretch to resample.
//      - Moved demo code to separate module.
//
// 0102 150398  anme  - Fixed a problem that caused all pixels to be shifted
//        1/2 pixel down and to the right (in source
//        coordinates). Thanks to David Ullrich for the
//        solution.
//
// 0103  170898  anme  - Fixed typo: Renamed Strecth function to Stretch.
//        Thanks to Graham Stratford for spotting it.
//        Sorry about that.
//  081298  anme  - Added check for too small destination bitmap.
//        Thanks to Jeppe Oland for bringing this problem to my
//        attention.
//   260399  anme  - Fixed a problem with resampling of very narrow
//        bitmaps. Thanks to Holger Dors for bringing the
//        problem to my attention.
//      - Removed dependency of math unit.
//  290699  jobe  - Subsampling improvements by Josha Beukema.
//
// -----------------------------------------------------------------------------
// Credits:
// The algorithms and methods used in this library are based on the article
// "General Filtered Image Rescaling" by Dale Schumacher which appeared in the
// book Graphics Gems III, published by Academic Press, Inc.
//
// The edge offset problem was fixed by:
//   * David Ullrich <ullrich@hardy.math.okstate.edu>
//
// The subsampling problem was fixed by:
//   * Josha Beukema <jbeukema@inn.nl>
// -----------------------------------------------------------------------------
// To do (in rough order of priority):
// * Implement Dale Schumacher's "Optimized Bitmap Scaling Routines".
// * Optimize to use integer math instead of floating point where possible.
// -----------------------------------------------------------------------------
*/

/**
* Resizes grayscale and truecolor images using filters.
* For other image types (including paletted or bilevel images), you might
* want to use the {@link ScaleReplication} class or convert the images to
* grayscale (or RGB truecolor) first and then use this class.
* Several algorithms for resampling are implemented, they differ in resulting image quality
* and computational complexity.
*
* <h3>Usage example</h3>
* This will scale <code>image</code> to 150 percent of its original size
* in both directions, using the Lanczos3 filter type:
* <pre>
* Resample resample = new Resample();
* resample.setInputImage(image);
* resample.setSize(image.getWidth() * 3 / 2, image.getHeight() * 3 / 2);
* resample.setFilter(Resample.FILTER_TYPE_LANCZOS3);
* resample.process();
* PixelImage scaledImage = resample.getOutputImage();
* </pre>
*
* <h3>Known problems</h3>
* <ul>
* <li>Scaling down certain images (with stripe or checkers patterns in them) will
*  lead to moire effects in the resulting image.
*  These effects can be somewhat reduced by scaling down in several step (e.g.
*  first from 1600 x 1200 to 800 x 600, then from 800 x 600 to 400 x 300, and so on).</li>
* <li>Scaling down with filter type {@link #FILTER_TYPE_BOX} can lead to errors in the scaled image.
*  No fix known yet. Workaround: Use the class {@link ScaleReplication}.</li>
* </ul>
*
* <h3>Origin</h3>
* This code is a port of Anders Melander's
* Object Pascal (Delphi) unit <tt>resample.pas</tt> to Java.
* The Delphi code is an adaptation (with some improvements) of
* Dale Schumacher's <tt>fzoom</tt> C code.
* <del>Check out the homepage for the Delphi resample code, a demo application
* to compare the different filtering algorithms is also provided:
* <a target="_top" href="http://www.melander.dk/delphi/resampler/index.html">http://www.melander.dk/delphi/resampler/index.html</a>.
* You will also find the original C code there.</del>
* <ins>The site seems to have gone for good.</ins>
*
* <h3>Theory</h3>
* The theoretical background for all implementations is Dale Schumacher's article
* <em>General Filtered Image Rescaling</em>
* in <em>Graphics Gems III</em>, editor David Kirk, Academic Press, pages 8-16, 1994.
* <p>
* The <em>Graphics Gems Repository</em> can be found at
* <a target="_top" href="http://www.acm.org/tog/GraphicsGems/">http://www.acm.org/tog/GraphicsGems/</a>.
* It also includes information on the books and how to order them.
*
* @author Marco Schmidt
*/
public class Resample extends ImageToImageOperation
{
  /**
   * Constant for the Box filter (also known as Nearest Neighbor filter).
   */
  public static final int FILTER_TYPE_BOX = 0;

  /**
   * Constant for the Triangle filter (also known as Linear filter or Bilinear filter).
   */
  public static final int FILTER_TYPE_TRIANGLE = 1;

  /**
   * Constant for the Hermite filter.
   */
  public static final int FILTER_TYPE_HERMITE = 2;

  /**
   * Constant for the Bell filter.
   */
  public static final int FILTER_TYPE_BELL = 3;

  /**
   * Constant for the B-Spline filter.
   */
  public static final int FILTER_TYPE_B_SPLINE = 4;

  /**
   * Constant for the Lanczos3 filter.
   */
  public static final int FILTER_TYPE_LANCZOS3 = 5;

  /**
   * Constant for the Mitchell filter.
   */
  public static final int FILTER_TYPE_MITCHELL = 6;

  class Contributor
  {
    int pixel; // Source pixel
    float weight; // Pixel weight
  }

  class CList
  {
    int n;
    Contributor[] p;
  }

  private Integer outWidth;
  private Integer outHeight;
  private ResampleFilter filter;

  private static ResampleFilter createFilter(int filterType)
  {
    switch(filterType)
    {
      case(FILTER_TYPE_BOX): return new BoxFilter();
      case(FILTER_TYPE_TRIANGLE): return new TriangleFilter();
      case(FILTER_TYPE_HERMITE): return new HermiteFilter();
      case(FILTER_TYPE_BELL): return new BellFilter();
      case(FILTER_TYPE_B_SPLINE): return new BSplineFilter();
      case(FILTER_TYPE_LANCZOS3): return new Lanczos3Filter();
      case(FILTER_TYPE_MITCHELL): return new MitchellFilter();
      default:
      {
        throw new IllegalArgumentException("Unknown filter type in Resample: " + filterType);
      }
    }
  }

  /**
   * Returns the filter to be used in this operation.
   * @return ResampleFilter object or <code>null</code> if none was defined yet
   */
  public ResampleFilter getFilter()
  {
    return filter;
  }

  /**
   * Returns the names of all predefined filters.
   * Each FILTER_TYPE_xyz constant can be used as an index into the array that is returned.
   * Names are retrieved by creating an object of each predefined filter class and calling its
   * getName method.
   * @return String array with filter names
   */
  public static String[] getFilterNames()
  {
    String[] result = new String[getNumFilters()];
    for (int i = 0; i < getNumFilters(); i++)
    {
      ResampleFilter filter = createFilter(i);
      result[i] = filter.getName();
    }
    return result;
  }

  /**
   * Returns the number of predefined filters.
   * @return number of filters
   */
  public static int getNumFilters()
  {
    return 7;
  }

  /**
   * This method does the actual work of rescaling an image.
   */
  private void process(IntegerImage in, IntegerImage out)
  {
    if (out == null)
    {
      out = (IntegerImage)in.createCompatibleImage(outWidth.intValue(), outHeight.intValue());
      setOutputImage(out);
    }
    if (filter == null)
    {
      filter = new TriangleFilter();
    }
    float fwidth = filter.getSamplingRadius();
    final int dstWidth = outWidth.intValue();
    final int dstHeight = outHeight.intValue();
    final int srcWidth = in.getWidth();
    final int srcHeight = in.getHeight();
     /* if (SrcWidth < 1) or (SrcHeight < 1) then
    raise Exception.Create('Source bitmap too small');*/
    // Create intermediate image to hold horizontal zoom
    IntegerImage work = (IntegerImage)in.createCompatibleImage(dstWidth, srcHeight);
    float xscale;
    float yscale;
    if (srcWidth == 1)
    {
      xscale = (float)dstWidth / (float)srcWidth;
    }
    else
    {
      xscale = (float)(dstWidth - 1) / (float)(srcWidth - 1);
    }
    if (srcHeight == 1)
    {
      yscale = (float)dstHeight / (float)srcHeight;
    }
    else
    {
      yscale = (float)(dstHeight - 1) / (float)(srcHeight - 1);
    }

    /* Marco: the following two variables are used for progress notification */
    int processedItems = 0;
    int totalItems = /*dstWidth +*/ srcHeight + /*dstHeight +*/ dstWidth;

    // --------------------------------------------
    // Pre-calculate filter contributions for a row
    // -----------------------------------------------
    CList[] contrib = new CList[dstWidth];
    for (int i = 0; i < contrib.length; i++)
    {
      contrib[i] = new CList();
    }
   
    // Horizontal sub-sampling
    // Scales from bigger to smaller width
    if (xscale < 1.0f)
    {
      float width = fwidth / xscale;
      float fscale = 1.0f / xscale;
      int numPixels = (int)(width * 2.0f + 1);
      for (int i = 0; i < dstWidth; i++)
      {
        contrib[i].n = 0;
        contrib[i].p = new Contributor[numPixels];
        for (int j = 0; j < contrib[i].p.length; j++)
        {
          contrib[i].p[j] = new Contributor();
        }
        float center = i / xscale;
        int left = (int)Math.floor(center - width);
        int right = (int)Math.ceil(center + width);
        for (int j = left; j <= right; j++)
        {
          float weight = filter.apply((center - j) / fscale) / fscale;
          if (weight == 0.0f)
          {
            continue;
          }
          int n;
          if (j < 0)
          {
            n = -j;
          }
          else
          if (j >= srcWidth)
          {
            n = srcWidth - j + srcWidth - 1;
          }
          else
          {
            n = j;
          }
          int k = contrib[i].n;
          contrib[i].n = contrib[i].n + 1;
          contrib[i].p[k].pixel = n;
          contrib[i].p[k].weight = weight;
        }
        //setProgress(processedItems++, totalItems);
      }
    }
    else
      // Horizontal super-sampling
       // Scales from smaller to bigger width
      {
      int numPixels = (int)(fwidth * 2.0f + 1);
      for (int i = 0; i < dstWidth; i++)
      {
          contrib[i].n = 0;
        contrib[i].p = new Contributor[numPixels];
        for (int j = 0; j < contrib[i].p.length; j++)
        {
          contrib[i].p[j] = new Contributor();
        }
        float center = i / xscale;
        int left = (int)Math.floor(center - fwidth);
        int right = (int)Math.ceil(center + fwidth);
        for (int j = left; j <= right; j++)
        {
          float weight = filter.apply(center - j);
          if (weight == 0.0f)
          {
            continue;
          }
          int n;
          if (j < 0)
          {
            n = -j;
          }
          else
          if (j >= srcWidth)
          {
            n = srcWidth - j + srcWidth - 1;
          }
          else
          {
            n = j;
          }
          int k = contrib[i].n;
          if (n < 0 || n >= srcWidth)
          {
            weight = 0.0f;
          }
          contrib[i].n = contrib[i].n + 1;
          contrib[i].p[k].pixel = n;
          contrib[i].p[k].weight = weight;
        }
        //setProgress(processedItems++, totalItems);
      }
    }
   
    // ----------------------------------------------------
    // Apply filter to sample horizontally from Src to Work
    // ----------------------------------------------------

    // start of Java-specific code
    // Marco: adjusted code to work with multi-channel images
    //        where each channel can have a different maximum sample value (not only 255)
    final int NUM_CHANNELS = work.getNumChannels();
    final int[] MAX = new int[NUM_CHANNELS];
    for (int k = 0; k < NUM_CHANNELS; k++)
    {
      MAX[k] = work.getMaxSample(k);
    }
    // end of Java-specific code

    for (int k = 0; k < srcHeight; k++)
    {
      for (int i = 0; i < dstWidth; i++)
      {
        for (int channel = 0; channel < NUM_CHANNELS; channel++)
        {
                    CList c=contrib[i];
          float sample = 0.0f;
                    int max=c.n;
          for (int j = 0; j < max; j++)
          {
            sample+=in.getSample(channel, c.p[j].pixel, k) * c.p[j].weight;
          }
          // Marco: procedure BoundRound included directly
          int result = (int)sample;
          if (result < 0)
          {
            result = 0;
          }
          else
          if (result > MAX[channel])
          {
            result = MAX[channel];
          }
          work.putSample(channel, i, k, result);
        }
      }
      setProgress(processedItems++, totalItems);
    }

    /* Marco: no need for "free memory" code as Java has garbage collection:
        // Free the memory allocated for horizontal filter weights
        for i := 0 to DstWidth-1 do
            FreeMem(contrib^[i].p);

        FreeMem(contrib);
    */

    // -----------------------------------------------
    // Pre-calculate filter contributions for a column
    // -----------------------------------------------

      /*GetMem(contrib, DstHeight* sizeof(TCList));*/
    contrib = new CList[dstHeight];
    for (int i = 0; i < contrib.length; i++)
    {
      contrib[i] = new CList();
    }
    // Vertical sub-sampling
    // Scales from bigger to smaller height
    if (yscale < 1.0f)
    {
      float width = fwidth / yscale;
      float fscale = 1.0f / yscale;
      int numContributors = (int)(width * 2.0f + 1);
      for (int i = 0; i < dstHeight; i++)
      {
        contrib[i].n = 0;
        contrib[i].p = new Contributor[numContributors];
        for (int j = 0; j < contrib[i].p.length; j++)
        {
          contrib[i].p[j] = new Contributor();
        }
        float center = i / yscale;
        int left = (int)Math.floor(center - width);
        int right = (int)Math.ceil(center + width);
        for (int j = left; j <= right; j++)
        {
          float weight = filter.apply((center - j) / fscale) / fscale;
          // change suggested by Mike Dillon; not thoroughly tested;
          // old version:
          // float weight = filter.apply(center - j);
          if (weight == 0.0f)
          {
            continue;
          }
          int n;
          if (j < 0)
          {
            n = -j;
          }
          else
          if (j >= srcHeight)
          {
            n = srcHeight - j + srcHeight - 1;
          }
          else
          {
            n = j;
          }
          int k = contrib[i].n;
          contrib[i].n = contrib[i].n + 1;
          if (n < 0 || n >= srcHeight)
          {
            weight = 0.0f;// Flag that cell should not be used
          }
          contrib[i].p[k].pixel = n;
          contrib[i].p[k].weight = weight;
        }
        //setProgress(processedItems++, totalItems);
      }
    }
    else
    // Vertical super-sampling
    // Scales from smaller to bigger height
    {
      int numContributors = (int)(fwidth * 2.0f + 1);
      for (int i = 0; i < dstHeight; i++)
      {
        contrib[i].n = 0;
        contrib[i].p = new Contributor[numContributors];
        for (int j = 0; j < contrib[i].p.length; j++)
        {
          contrib[i].p[j] = new Contributor();
        }
        float center = i / yscale;
        int left = (int)Math.floor(center - fwidth);
        int right = (int)Math.ceil(center + fwidth);
        for (int j = left; j <= right; j++)
        {
          float weight = filter.apply(center - j);
          if (weight == 0.0f)
          {
            continue;
          }
          int n;
          if (j < 0)
          {
            n = -j;
          }
          else
          if (j >= srcHeight)
          {
            n = srcHeight - j + srcHeight - 1;
          }
          else
          {
            n = j;
          }
          int k = contrib[i].n;
          contrib[i].n = contrib[i].n + 1;
          if (n < 0 || n >= srcHeight)
          {
            weight = 0.0f;// Flag that cell should not be used
          }
          contrib[i].p[k].pixel = n;
          contrib[i].p[k].weight = weight;
        }
        //setProgress(processedItems++, totalItems);
      }
    }

    // --------------------------------------------------
    // Apply filter to sample vertically from Work to Dst
    // --------------------------------------------------
    for (int k = 0; k < dstWidth; k++)
    {
      for (int i = 0; i < dstHeight; i++)
      {
        for (int channel = 0; channel < NUM_CHANNELS; channel++)
        {
          float sample = 0.0f;
                    CList c=contrib[i];
                    int max=c.n;
                    for (int j = 0; j < max; j++)
          {
                      sample += work.getSample(channel, k, c.p[j].pixel) * c.p[j].weight;
          }
                    int result = (int)sample;
                    if (result < 0)
                    {
                        result = 0;
                    }
                    else
                    if (result > MAX[channel])
                    {
                        result = MAX[channel];
                    }
                    out.putSample(channel, k, i, result);
        }
      }
      setProgress(processedItems++, totalItems);
    }
  }

  public void process() throws
    MissingParameterException,
    WrongParameterException
  {
    ensureInputImageIsAvailable();
    if (outWidth == null && outHeight == null && getOutputImage() != null)
    {
      PixelImage out = getOutputImage();
      outWidth = new Integer(out.getWidth());
      outHeight = new Integer(out.getHeight());
    }
    if (outWidth == null)
    {
      throw new MissingParameterException("Output width has not been initialized");
    }
    if (outHeight == null)
    {
      throw new MissingParameterException("Output height has not been initialized");
    }
    PixelImage image = getInputImage();
    if (image.getWidth() == outWidth.intValue() &&
        image.getHeight() == outHeight.intValue())
    {
      throw new WrongParameterException("Input image already has the size specified by setSize.");
    }
    ensureOutputImageResolution(outWidth.intValue(), outHeight.intValue());
    if (image instanceof IntegerImage)
    {
      process((IntegerImage)image, (IntegerImage)getOutputImage());
    }
    else
    {
      throw new WrongParameterException("Input image must implement IntegerImage.");
    }
  }

  /**
   * Set the pixel resolution of the output image.
   * @param width the horizontal resolution of the output image
   * @param height the vertical resolution of the output image
   */
  public void setSize(int width, int height)
  {
    outWidth = new Integer(width);
    outHeight = new Integer(height);
  }

  /**
   * Set a new filter object to be used with this operation.
   * @param newFilter a resample filter to be used for scaling
   */
  public void setFilter(ResampleFilter newFilter)
  {
    filter = newFilter;
  }

  /**
   * Sets a new filter type, using the default sampling radius of that filter.
   * @param filterType the new filter type, one of the FILTER_TYPE_xyz constants of this class
   */
  public void setFilter(int filterType)
  {
    setFilter(createFilter(filterType));
  }

  /**
   * Sets a new filter type with a user-defined sampling radius.
   * @param filterType the new filter type, one of the FILTER_TYPE_xyz constants of this class
   * @param samplingRadius the sampling radius to be used with that filter, must be larger than 0.0f
   */
  public void setFilter(int filterType, float samplingRadius)
  {
    ResampleFilter newFilter = createFilter(filterType);
    newFilter.setSamplingRadius(samplingRadius);
    setFilter(newFilter);
  }
}
TOP

Related Classes of net.sourceforge.jiu.geometry.Resample$Contributor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.