Package com.alkacon.simapi.CmykJpegReader

Source Code of com.alkacon.simapi.CmykJpegReader.ResampleOp$HanningFilter

/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name "TwelveMonkeys" nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
*******************************************************************************
*
*  Based on example code found in Graphics Gems III, Filtered Image Rescaling
*  (filter_rcg.c), available from http://www.acm.org/tog/GraphicsGems/.
*
*  Public Domain 1991 by Dale Schumacher. Mods by Ray Gardener
*
*  Original by Dale Schumacher (fzoom)
*
*  Additional changes by Ray Gardener, Daylon Graphics Ltd.
*  December 4, 1999
*
*******************************************************************************
*
*  Aditional changes inspired by ImageMagick's resize.c.
*
*******************************************************************************
*
*  Java port and additional changes/bugfixes by Harald Kuhr, Twelvemonkeys.
*  February 20, 2006
*
*******************************************************************************
*/

package com.alkacon.simapi.CmykJpegReader;

import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

/**
* Resamples (scales) a {@code BufferedImage} to a new width and height, using
* high performance and high quality algorithms.
* Several different interpolation algorithms may be specifed in the
* constructor, either using the
* <a href="#field_summary">filter type constants</a>, or one of the
* {@code RendereingHints}.
* <p/>
* For fastest results, use {@link #FILTER_POINT} or {@link #FILTER_BOX}.
* In most cases, {@link #FILTER_TRIANGLE} will produce acceptable results, while
* being relatively fast.
* For higher quality output, use more sophisticated interpolation algorithms,
* like {@link #FILTER_MITCHELL} or {@link #FILTER_LANCZOS}.
* <p/>
* Example:
* <blockquote><pre>
* BufferedImage image;
* <p/>
* //...
* <p/>
* ResampleOp resampler = new ResampleOp(100, 100, ResampleOp.FILTER_TRIANGLE);
* BufferedImage thumbnail = resampler.filter(image, null);
* </pre></blockquote>
* <p/>
* If your imput image is very large, it's possible to first resample using the
* very fast {@code FILTER_POINT} algorithm, then resample to the wanted size,
* using a higher quality algorithm:
* <blockquote><pre>
* BufferedImage verylLarge;
* <p/>
* //...
* <p/>
* int w = 300;
* int h = 200;
* <p/>
* BufferedImage temp = new ResampleOp(w * 2, h * 2, FILTER_POINT).filter(verylLarge, null);
* <p/>
* BufferedImage scaled = new ResampleOp(w, h).filter(temp, null);
* </pre></blockquote>
* <p/>
* For maximum performance, this class will use native code, through
* <a href="http://www.yeo.id.au/jmagick/">JMagick</a>, when available.
* Otherwise, the class will silently fall back to pure Java mode.
* Native code may be disabled globally, by setting the system property
* {@code com.twelvemonkeys.image.accel} to {@code false}.
* To allow debug of the native code, set the system property
* {@code com.twelvemonkeys.image.magick.debug} to {@code true}.
* <p/>
* This {@code BufferedImageOp} is based on C example code found in
* <a href="http://www.acm.org/tog/GraphicsGems/">Graphics Gems III</a>,
* Filtered Image Rescaling, by Dale Schumacher (with additional improvments by
* Ray Gardener).
* Additional changes are inspired by
* <a href="http://www.imagemagick.org/">ImageMagick</a> and
* Marco Schmidt's <a href="http://schmidt.devlib.org/jiu/">Java Imaging Utilities</a>
* (which are also adaptions of the same original code from Graphics Gems III).
* <p/>
* For a description of the various interpolation algorithms, see
* <em>General Filtered Image Rescaling</em> in <em>Graphics Gems III</em>,
* Academic Press, 1994.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ResampleOp.java#1 $
* @see #ResampleOp(int,int,int)
* @see #ResampleOp(int,int,java.awt.RenderingHints)
* @see BufferedImage
* @see RenderingHints
* @see AffineTransformOp
*/
// TODO: Consider using AffineTransformOp for more operations!?
public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */{

    // NOTE: These MUST correspond to ImageMagick filter types, for the
    // MagickAccelerator to work consistently (see magick.FilterType).

    static class BlackmanBesselFilter implements InterpolationFilter {

        public final double filter(final double t) {

            return blackman(t / support()) * bessel(t);
        }

        public final double support() {

            return 3.2383;
        }
    }

    static class BlackmanSincFilter implements InterpolationFilter {

        public final double filter(final double t) {

            return blackman(t / support()) * sinc(t);
        }

        public final double support() {

            return 4.0;
        }
    }

    static class BlacmanFilter implements InterpolationFilter {

        public final double filter(final double t) {

            return blackman(t);
        }

        public final double support() {

            return 1.0;
        }
    }

    static class BoxFilter implements InterpolationFilter {

        private final double mSupport;

        public BoxFilter() {

            mSupport = 0.5;
        }

        protected BoxFilter(double pSupport) {

            mSupport = pSupport;
        }

        public final double filter(final double t) {

            //if ((t > -0.5) && (t <= 0.5)) {
            if ((t >= -0.5) && (t < 0.5)) {// ImageMagick resample.c
                return 1.0;
            }
            return 0.0;
        }

        public final double support() {

            return mSupport;
        }
    }

    static class CatromFilter implements InterpolationFilter {

        public final double filter(double t) {

            if (t < 0) {
                t = -t;
            }
            if (t < 1.0) {
                return 0.5 * (2.0 + (t * t * (-5.0 + (t * 3.0))));
            }
            if (t < 2.0) {
                return 0.5 * (4.0 + (t * (-8.0 + (t * (5.0 - t)))));
            }
            return 0.0;
        }

        public final double support() {

            return 2.0;
        }
    }

    /*
    *  image rescaling routine
    */
    class Contributor {

        int pixel;
        double weight;
    }

    class ContributorList {

        int n;/* number of contributors (may be < p.length) */
        Contributor[] p;/* pointer to list of contributions */
    }

    static class CubicFilter implements InterpolationFilter {

        // AKA B-Spline
        public final double filter(double t)/* box (*) box (*) box (*) box */{

            final double tt;

            if (t < 0) {
                t = -t;
            }
            if (t < 1) {
                tt = t * t;
                return ((.5 * tt * t) - tt) + (2.0 / 3.0);
            } else if (t < 2) {
                t = 2 - t;
                return (1.0 / 6.0) * (t * t * t);
            }
            return 0.0;
        }

        public final double support() {

            return 2.0;
        }
    }

    static class GaussianFilter implements InterpolationFilter {

        public final double filter(final double t) {

            return Math.exp(-2.0 * t * t) * Math.sqrt(2.0 / Math.PI);
        }

        public final double support() {

            return 1.25;
        }
    }

    static class HammingFilter implements InterpolationFilter {

        public final double filter(final double t) {

            return 0.54 + (0.46 * Math.cos(Math.PI * t));
        }

        public final double support() {

            return 1.0;
        }
    }

    static class HanningFilter implements InterpolationFilter {

        public final double filter(final double t) {

            return 0.5 + (0.5 * Math.cos(Math.PI * t));
        }

        public final double support() {

            return 1.0;
        }
    }

    static class HermiteFilter implements InterpolationFilter {

        public final double filter(double t) {

            /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */
            if (t < 0.0) {
                t = -t;
            }
            if (t < 1.0) {
                return (((2.0 * t) - 3.0) * t * t) + 1.0;
            }
            return 0.0;
        }

        public final double support() {

            return 1.0;
        }
    }

    static interface InterpolationFilter {

        double filter(double t);

        double support();
    }

    /**
     * RendereingHints.Key implementation, works only with Value values.
     */
    // TODO: Move to abstract class AbstractBufferedImageOp?
    static class Key extends RenderingHints.Key {

        static int sIndex = 10000;

        private final String name;

        public Key(final String pName) {

            super(sIndex++);
            name = pName;
        }

        public boolean isCompatibleValue(Object pValue) {

            return (pValue instanceof Value) && ((Value)pValue).isCompatibleKey(this);
        }

        public String toString() {

            return name;
        }
    }

    static class LanczosFilter implements InterpolationFilter {

        // AKA Lanczos3
        public final double filter(double t) {

            if (t < 0) {
                t = -t;
            }
            if (t < 3.0) {
                return sinc(t) * sinc(t / 3.0);
            }
            return 0.0;
        }

        public final double support() {

            return 3.0;
        }
    }

    static class MitchellFilter implements InterpolationFilter {

        public final double filter(double t) {

            if (t < -2.0) {
                return 0.0;
            }
            if (t < -1.0) {
                return Q0 - (t * (Q1 - (t * (Q2 - (t * Q3)))));
            }
            if (t < 0.0) {
                return P0 + (t * t * (P2 - (t * P3)));
            }
            if (t < 1.0) {
                return P0 + (t * t * (P2 + (t * P3)));
            }
            if (t < 2.0) {
                return Q0 + (t * (Q1 + (t * (Q2 + (t * Q3)))));
            }
            return 0.0;
        }

        public final double support() {

            return 2.0;
        }
    }

    static class PointFilter extends BoxFilter {

        public PointFilter() {

            super(0.0);
        }
    }

    static class QuadraticFilter implements InterpolationFilter {

        // AKA Bell
        public final double filter(double t)/* box (*) box (*) box */{

            if (t < 0) {
                t = -t;
            }
            if (t < .5) {
                return .75 - (t * t);
            }
            if (t < 1.5) {
                t = (t - 1.5);
                return .5 * (t * t);
            }
            return 0.0;
        }

        public final double support() {

            return 1.5;
        }
    }

    static class TriangleFilter implements InterpolationFilter {

        public final double filter(double t) {

            if (t < 0.0) {
                t = -t;
            }
            if (t < 1.0) {
                return 1.0 - t;
            }
            return 0.0;
        }

        public final double support() {

            return 1.0;
        }
    }

    /**
     * RenderingHints value implementation, works with Key keys.
     */
    // TODO: Extract abstract Value class, and move to AbstractBufferedImageOp
    static final class Value {

        final private RenderingHints.Key key;
        final private String name;
        final private int type;

        public Value(final RenderingHints.Key pKey, final String pName, final int pType) {

            key = pKey;
            name = pName;
            type = validateFilterType(pType);
        }

        public int getFilterType() {

            return type;
        }

        public boolean isCompatibleKey(Key pKey) {

            return pKey == key;
        }

        public String toString() {

            return name;
        }
    }

    /**
     * Undefined interpolation, filter method will use default filter.
     */
    public final static int FILTER_UNDEFINED = 0;
    /**
     * Point interpolation (also known as "nearest neighbour").
     * Very fast, but low quality
     * (similar to {@link RenderingHints#VALUE_INTERPOLATION_NEAREST_NEIGHBOR}
     * and {@link Image#SCALE_REPLICATE}).
     */
    public final static int FILTER_POINT = 1;
    /**
     * Box interpolation. Fast, but low quality.
     */
    public final static int FILTER_BOX = 2;
    /**
     * Triangle interpolation (also known as "linear" or "bilinear").
     * Quite fast, with acceptable quality
     * (similar to {@link RenderingHints#VALUE_INTERPOLATION_BILINEAR} and
     * {@link Image#SCALE_AREA_AVERAGING}).
     */
    public final static int FILTER_TRIANGLE = 3;
    /**
     * Hermite interpolation.
     */
    public final static int FILTER_HERMITE = 4;
    /**
     * Hanning interpolation.
     */
    public final static int FILTER_HANNING = 5;
    /**
     * Hamming interpolation.
     */
    public final static int FILTER_HAMMING = 6;
    /**
     * Blackman interpolation..
     */
    public final static int FILTER_BLACKMAN = 7;
    /**
     * Gaussian interpolation.
     */
    public final static int FILTER_GAUSSIAN = 8;
    /**
     * Quadratic interpolation.
     */
    public final static int FILTER_QUADRATIC = 9;
    /**
     * Cubic interpolation.
     */
    public final static int FILTER_CUBIC = 10;
    /**
     * Catrom interpolation.
     */
    public final static int FILTER_CATROM = 11;

    /**
     * Mitchell interpolation. High quality.
     */
    public final static int FILTER_MITCHELL = 12; // IM default scale with palette or alpha, or scale up
    /**
     * Lanczos interpolation. High quality.
     */
    public final static int FILTER_LANCZOS = 13; // IM default

    /**
     * Blackman-Bessel interpolation. High quality.
     */
    public final static int FILTER_BLACKMAN_BESSEL = 14;

    /**
     * Blackman-Sinc interpolation. High quality.
     */
    public final static int FILTER_BLACKMAN_SINC = 15;

    /**
     * RenderingHints.Key specifying resampling interpolation algorithm.
     */
    public final static RenderingHints.Key KEY_RESAMPLE_INTERPOLATION = new Key("ResampleInterpolation");

    /**
     * @see #FILTER_POINT
     */
    public final static Object VALUE_INTERPOLATION_POINT = new Value(KEY_RESAMPLE_INTERPOLATION, "Point", FILTER_POINT);

    /**
     * @see #FILTER_BOX
     */
    public final static Object VALUE_INTERPOLATION_BOX = new Value(KEY_RESAMPLE_INTERPOLATION, "Box", FILTER_BOX);

    /**
     * @see #FILTER_TRIANGLE
     */
    public final static Object VALUE_INTERPOLATION_TRIANGLE = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Triangle",
        FILTER_TRIANGLE);

    /**
     * @see #FILTER_HERMITE
     */
    public final static Object VALUE_INTERPOLATION_HERMITE = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Hermite",
        FILTER_HERMITE);

    /**
     * @see #FILTER_HANNING
     */
    public final static Object VALUE_INTERPOLATION_HANNING = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Hanning",
        FILTER_HANNING);

    /**
     * @see #FILTER_HAMMING
     */
    public final static Object VALUE_INTERPOLATION_HAMMING = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Hamming",
        FILTER_HAMMING);

    /*
    private static BufferedImage pointResample(final BufferedImage pInput, final BufferedImage pOutput, final int pWidth, final int pHeight) {
        double xScale = pWidth / (double) pInput.getWidth();
        double yScale = pHeight / (double) pInput.getHeight();

        // NOTE: This is extremely fast, native, possibly H/W accelerated code
        AffineTransform transform = AffineTransform.getScaleInstance(xScale, yScale);
        AffineTransformOp scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        return scale.filter(pInput, pOutput);
    }
    */

    /*
    // TODO: This idea from Chet and Romain is actually not too bad..
    // It reuses the image/raster/graphics...
    // However, they forget to end with a halve operation..
    private static BufferedImage getFasterScaledInstance(BufferedImage img,
            int targetWidth, int targetHeight, Object hint,
            boolean progressiveBilinear) {
        int type = (img.getTransparency() == Transparency.OPAQUE) ?
            BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage ret = img;
        BufferedImage scratchImage = null;
        Graphics2D g2 = null;
        int w, h;
        int prevW = ret.getWidth();
        int prevH = ret.getHeight();
        boolean isTranslucent = img.getTransparency() !=  Transparency.OPAQUE;

        if (progressiveBilinear) {
            // Use multi-step technique: start with original size, then
            // scale down in multiple passes with drawImage()
            // until the target size is reached
            w = img.getWidth();
            h = img.getHeight();
        } else {
            // Use one-step technique: scale directly from original
            // size to target size with a single drawImage() call
            w = targetWidth;
            h = targetHeight;
        }

        do {
            if (progressiveBilinear && w > targetWidth) {
                w /= 2;
                if (w < targetWidth) {
                    w = targetWidth;
                }
            }

            if (progressiveBilinear && h > targetHeight) {
                h /= 2;
                if (h < targetHeight) {
                    h = targetHeight;
                }
            }

            if (scratchImage == null || isTranslucent) {
                // Use a single scratch buffer for all iterations
                // and then copy to the final, correctly-sized image
                // before returning
                scratchImage = new BufferedImage(w, h, type);
                g2 = scratchImage.createGraphics();
            }
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null);
            prevW = w;
            prevH = h;

            ret = scratchImage;
        } while (w != targetWidth || h != targetHeight);

        if (g2 != null) {
            g2.dispose();
        }

        // If we used a scratch buffer that is larger than our target size,
        // create an image of the right size and copy the results into it
        if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) {
            scratchImage = new BufferedImage(targetWidth, targetHeight, type);
            g2 = scratchImage.createGraphics();
            g2.drawImage(ret, 0, 0, null);
            g2.dispose();
            ret = scratchImage;
        }

        return ret;
    }
    */

    /**
     * @see #FILTER_BLACKMAN
     */
    public final static Object VALUE_INTERPOLATION_BLACKMAN = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Blackman",
        FILTER_BLACKMAN);

    /**
     * @see #FILTER_GAUSSIAN
     */
    public final static Object VALUE_INTERPOLATION_GAUSSIAN = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Gaussian",
        FILTER_GAUSSIAN);

    /**
     * @see #FILTER_QUADRATIC
     */
    public final static Object VALUE_INTERPOLATION_QUADRATIC = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Quadratic",
        FILTER_QUADRATIC);

    /**
     * @see #FILTER_CUBIC
     */
    public final static Object VALUE_INTERPOLATION_CUBIC = new Value(KEY_RESAMPLE_INTERPOLATION, "Cubic", FILTER_CUBIC);

    /**
     * @see #FILTER_CATROM
     */
    public final static Object VALUE_INTERPOLATION_CATROM = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Catrom",
        FILTER_CATROM);

    /**
     * @see #FILTER_MITCHELL
     */
    public final static Object VALUE_INTERPOLATION_MITCHELL = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Mitchell",
        FILTER_MITCHELL);

    /**
     * @see #FILTER_LANCZOS
     */
    public final static Object VALUE_INTERPOLATION_LANCZOS = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Lanczos",
        FILTER_LANCZOS);

    /* -- Java port of filter_rcg.c below... -- */

    /*
    *  filter function definitions
    */

    /**
     * @see #FILTER_BLACKMAN_BESSEL
     */
    public final static Object VALUE_INTERPOLATION_BLACKMAN_BESSEL = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Blackman-Bessel",
        FILTER_BLACKMAN_BESSEL);

    /**
     * @see #FILTER_BLACKMAN_SINC
     */
    public final static Object VALUE_INTERPOLATION_BLACKMAN_SINC = new Value(
        KEY_RESAMPLE_INTERPOLATION,
        "Blackman-Sinc",
        FILTER_BLACKMAN_SINC);

    // Member variables
    // Package access, to allow access from MagickAccelerator
    int width;

    int height;

    int filterType;

    private final static double B = 1.0 / 3.0;

    private final static double C = 1.0 / 3.0;

    private final static double P0 = (6.0 - (2.0 * B)) / 6.0;

    private final static double P2 = (-18.0 + (12.0 * B) + (6.0 * C)) / 6.0;

    private final static double P3 = (12.0 - (9.0 * B) - (6.0 * C)) / 6.0;
    private final static double Q0 = ((8.0 * B) + (24.0 * C)) / 6.0;
    private final static double Q1 = ((-12.0 * B) - (48.0 * C)) / 6.0;
    private final static double Q2 = ((6.0 * B) + (30.0 * C)) / 6.0;
    private final static double Q3 = ((-1.0 * B) - (6.0 * C)) / 6.0;

    /**
     * Creates a {@code ResampleOp} that will resample input images to the
     * given width and height, using the default interpolation filter.
     *
     * @param width  width of the re-sampled image
     * @param height height of the re-sampled image
     */
    public ResampleOp(int width, int height) {

        this(width, height, FILTER_UNDEFINED);
    }

    /**
     * Creates a {@code ResampleOp} that will resample input images to the
     * given width and height, using the given interpolation filter.
     *
     * @param width      width of the re-sampled image
     * @param height     height of the re-sampled image
     * @param filterType interpolation filter algorithm
     * @see <a href="#field_summary">filter type constants</a>
     */
    public ResampleOp(int width, int height, int filterType) {

        if ((width <= 0) || (height <= 0)) {
            // NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P
            throw new IllegalArgumentException("width and height must be positive");
        }

        this.width = width;
        this.height = height;

        this.filterType = validateFilterType(filterType);
    }

    /**
     * Creates a {@code ResampleOp} that will resample input images to the
     * given width and height, using the interpolation filter specified by
     * the given hints.
     * If using {@code RenderingHints}, the hints are mapped as follows:
     * <ul>
     * <li>{@code KEY_RESAMPLE_INTERPOLATION} takes precedence over any
     * standard {@code java.awt} hints, and dictates interpolation
     * directly, see
     * <a href="#field_summary">{@code RenderingHints} constants</a>.</li>
     * <p/>
     * <li>{@code KEY_INTERPOLATION} takes precedence over other hints.
     * <ul>
     * <li>{@link RenderingHints#VALUE_INTERPOLATION_NEAREST_NEIGHBOR} specifies
     * {@code FILTER_POINT}</li>
     * <li>{@link RenderingHints#VALUE_INTERPOLATION_BILINEAR} specifies
     * {@code FILTER_TRIANGLE}</li>
     * <li>{@link RenderingHints#VALUE_INTERPOLATION_BICUBIC} specifies
     * {@code FILTER_QUADRATIC}</li>
     * </ul>
     * </li>
     * <p/>
     * <li>{@code KEY_RENDERING} or {@code KEY_COLOR_RENDERING}
     * <ul>
     * <li>{@link RenderingHints#VALUE_RENDER_SPEED} specifies
     * {@code FILTER_POINT}</li>
     * <li>{@link RenderingHints#VALUE_RENDER_QUALITY} specifies
     * {@code FILTER_MITCHELL}</li>
     * </ul>
     * </li>
     * </ul>
     * Other hints have no effect on this filter.
     *
     * @param width  width of the re-sampled image
     * @param height height of the re-sampled image
     * @param hints  rendering hints, affecting interpolation algorithm
     * @see #KEY_RESAMPLE_INTERPOLATION
     * @see RenderingHints#KEY_INTERPOLATION
     * @see RenderingHints#KEY_RENDERING
     * @see RenderingHints#KEY_COLOR_RENDERING
     */
    public ResampleOp(int width, int height, RenderingHints hints) {

        this(width, height, getFilterType(hints));
    }

    static double besselOrderOne(double t) {

        double p, q;

        if (t == 0.0) {
            return 0.0;
        }
        p = t;
        if (t < 0.0) {
            t = -t;
        }
        if (t < 8.0) {
            return p * j1(t);
        }
        q = Math.sqrt(2.0 / (Math.PI * t))
            * ((p1(t) * ((1.0 / Math.sqrt(2.0)) * (Math.sin(t) - Math.cos(t)))) - ((8.0 / t) * q1(t) * ((-1.0 / Math.sqrt(2.0)) * (Math.sin(t) + Math.cos(t)))));
        if (p < 0.0) {
            q = -q;
        }
        return q;
    }

    static int round(double d) {

        // NOTE: This code seems to be faster than Math.round(double)...
        // Version that uses no function calls at all.
        int n = (int)d;
        double diff = d - n;
        if (diff < 0) {
            diff = -diff;
        }
        if (diff >= 0.5) {
            if (d < 0) {
                n--;
            } else {
                n++;
            }
        }
        return n;
    }/* round */

    private static double bessel(final double t) {

        if (t == 0.0) {
            return Math.PI / 4.0;
        }
        return besselOrderOne(Math.PI * t) / (2.0 * t);
    }

    private static double blackman(final double t) {

        return 0.42 + (0.50 * Math.cos(Math.PI * t)) + (0.08 * Math.cos(2.0 * Math.PI * t));
    }

    private static InterpolationFilter createFilter(int pFilterType) {

        // TODO: Select correct filter based on scale up or down, if undefined!
        if (pFilterType == FILTER_UNDEFINED) {
            pFilterType = FILTER_LANCZOS;
        }

        switch (pFilterType) {
            case FILTER_POINT:
                return new PointFilter();
            case FILTER_BOX:
                return new BoxFilter();
            case FILTER_TRIANGLE:
                return new TriangleFilter();
            case FILTER_HERMITE:
                return new HermiteFilter();
            case FILTER_HANNING:
                return new HanningFilter();
            case FILTER_HAMMING:
                return new HammingFilter();
            case FILTER_BLACKMAN:
                return new BlacmanFilter();
            case FILTER_GAUSSIAN:
                return new GaussianFilter();
            case FILTER_QUADRATIC:
                return new QuadraticFilter();
            case FILTER_CUBIC:
                return new CubicFilter();
            case FILTER_CATROM:
                return new CatromFilter();
            case FILTER_MITCHELL:
                return new MitchellFilter();
            case FILTER_LANCZOS:
                return new LanczosFilter();
            case FILTER_BLACKMAN_BESSEL:
                return new BlackmanBesselFilter();
            case FILTER_BLACKMAN_SINC:
                return new BlackmanSincFilter();
            default:
                throw new IllegalStateException("Unknown filter type: " + pFilterType);
        }
    }

    private static BufferedImage fastResample(
        final BufferedImage input,
        final BufferedImage output,
        final int width,
        final int height,
        final int type) {

        BufferedImage temp = input;

        double xScale;
        double yScale;

        AffineTransform transform;
        AffineTransformOp scale;

        if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
            // Initially scale so all remaining operations will halve the image
            if ((width < input.getWidth()) || (height < input.getHeight())) {
                int w = width;
                int h = height;
                while (w < (input.getWidth() / 2)) {
                    w *= 2;
                }
                while (h < (input.getHeight() / 2)) {
                    h *= 2;
                }

                xScale = w / (double)input.getWidth();
                yScale = h / (double)input.getHeight();

                //System.out.println("First scale by x=" + xScale + ", y=" + yScale);

                transform = AffineTransform.getScaleInstance(xScale, yScale);
                scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
                temp = scale.filter(temp, null);
            }
        }

        scale = null; // NOTE: This resets!

        xScale = width / (double)temp.getWidth();
        yScale = height / (double)temp.getHeight();

        if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
            // TODO: Test skipping first scale (above), and instead scale once
            // more here, and a little less than .5 each time...
            // That would probably make the scaling smoother...
            while ((xScale < 0.5) || (yScale < 0.5)) {
                if (xScale >= 0.5) {
                    //System.out.println("Halving by y=" + (yScale * 2.0));
                    transform = AffineTransform.getScaleInstance(1.0, 0.5);
                    scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);

                    yScale *= 2.0;
                } else if (yScale >= 0.5) {
                    //System.out.println("Halving by x=" + (xScale * 2.0));
                    transform = AffineTransform.getScaleInstance(0.5, 1.0);
                    scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);

                    xScale *= 2.0;
                } else {
                    //System.out.println("Halving by x=" + (xScale * 2.0)  + ", y=" + (yScale * 2.0));
                    xScale *= 2.0;
                    yScale *= 2.0;
                }

                if (scale == null) {
                    transform = AffineTransform.getScaleInstance(0.5, 0.5);
                    scale = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
                }

                temp = scale.filter(temp, null);
            }
        }

        //System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale);

        transform = AffineTransform.getScaleInstance(xScale, yScale);
        scale = new AffineTransformOp(transform, type);

        return scale.filter(temp, output);
    }

    /**
     * Gets the filter type specified by the given hints.
     *
     * @param pHints rendering hints
     * @return a filter type constant
     */
    private static int getFilterType(RenderingHints pHints) {

        if (pHints == null) {
            return FILTER_UNDEFINED;
        }

        if (pHints.containsKey(KEY_RESAMPLE_INTERPOLATION)) {
            Object value = pHints.get(KEY_RESAMPLE_INTERPOLATION);
            // NOTE: Workaround for a bug in RenderingHints constructor (Bug id# 5084832)
            if (!KEY_RESAMPLE_INTERPOLATION.isCompatibleValue(value)) {
                throw new IllegalArgumentException(value + " incompatible with key " + KEY_RESAMPLE_INTERPOLATION);
            }
            return value != null ? ((Value)value).getFilterType() : FILTER_UNDEFINED;
        } else if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
            || (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION) && (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING)) || RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))))) {
            // Nearest neighbour, or prioritize speed
            return FILTER_POINT;
        } else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
            // Triangle equals bi-linear interpolation
            return FILTER_TRIANGLE;
        } else if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
            return FILTER_QUADRATIC;// No idea if this is correct..?
        } else if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
            || RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) {
            // Prioritize quality
            return FILTER_MITCHELL;
        }

        // NOTE: Other hints are ignored
        return FILTER_UNDEFINED;
    }

    private static double j1(final double t) {

        final double[] pOne = {
            0.581199354001606143928050809e+21,
            -0.6672106568924916298020941484e+20,
            0.2316433580634002297931815435e+19,
            -0.3588817569910106050743641413e+17,
            0.2908795263834775409737601689e+15,
            -0.1322983480332126453125473247e+13,
            0.3413234182301700539091292655e+10,
            -0.4695753530642995859767162166e+7,
            0.270112271089232341485679099e+4};
        final double[] qOne = {
            0.11623987080032122878585294e+22,
            0.1185770712190320999837113348e+20,
            0.6092061398917521746105196863e+17,
            0.2081661221307607351240184229e+15,
            0.5243710262167649715406728642e+12,
            0.1013863514358673989967045588e+10,
            0.1501793594998585505921097578e+7,
            0.1606931573481487801970916749e+4,
            0.1e+1};

        double p = pOne[8];
        double q = qOne[8];
        for (int i = 7; i >= 0; i--) {
            p = (p * t * t) + pOne[i];
            q = (q * t * t) + qOne[i];
        }
        return p / q;
    }

    private static double p1(final double t) {

        final double[] pOne = {
            0.352246649133679798341724373e+5,
            0.62758845247161281269005675e+5,
            0.313539631109159574238669888e+5,
            0.49854832060594338434500455e+4,
            0.2111529182853962382105718e+3,
            0.12571716929145341558495e+1};
        final double[] qOne = {
            0.352246649133679798068390431e+5,
            0.626943469593560511888833731e+5,
            0.312404063819041039923015703e+5,
            0.4930396490181088979386097e+4,
            0.2030775189134759322293574e+3,
            0.1e+1};

        double p = pOne[5];
        double q = qOne[5];
        for (int i = 4; i >= 0; i--) {
            p = (p * (8.0 / t) * (8.0 / t)) + pOne[i];
            q = (q * (8.0 / t) * (8.0 / t)) + qOne[i];
        }
        return p / q;
    }

    private static double q1(final double t) {

        final double[] pOne = {
            0.3511751914303552822533318e+3,
            0.7210391804904475039280863e+3,
            0.4259873011654442389886993e+3,
            0.831898957673850827325226e+2,
            0.45681716295512267064405e+1,
            0.3532840052740123642735e-1};
        final double[] qOne = {
            0.74917374171809127714519505e+4,
            0.154141773392650970499848051e+5,
            0.91522317015169922705904727e+4,
            0.18111867005523513506724158e+4,
            0.1038187585462133728776636e+3,
            0.1e+1};

        double p = pOne[5];
        double q = qOne[5];
        for (int i = 4; i >= 0; i--) {
            p = (p * (8.0 / t) * (8.0 / t)) + pOne[i];
            q = (q * (8.0 / t) * (8.0 / t)) + qOne[i];
        }
        return p / q;
    }

    private static double sinc(double x) {

        x *= Math.PI;
        if (x != 0.0) {
            return Math.sin(x) / x;
        }
        return 1.0;
    }

    private static int validateFilterType(int pFilterType) {

        switch (pFilterType) {
            case FILTER_UNDEFINED:
            case FILTER_POINT:
            case FILTER_BOX:
            case FILTER_TRIANGLE:
            case FILTER_HERMITE:
            case FILTER_HANNING:
            case FILTER_HAMMING:
            case FILTER_BLACKMAN:
            case FILTER_GAUSSIAN:
            case FILTER_QUADRATIC:
            case FILTER_CUBIC:
            case FILTER_CATROM:
            case FILTER_MITCHELL:
            case FILTER_LANCZOS:
            case FILTER_BLACKMAN_BESSEL:
            case FILTER_BLACKMAN_SINC:
                return pFilterType;
            default:
                throw new IllegalArgumentException("Unknown filter type: " + pFilterType);
        }
    }

    public final BufferedImage createCompatibleDestImage(final BufferedImage pInput, final ColorModel pModel) {

        if (pInput == null) {
            throw new NullPointerException("pInput == null");
        }

        ColorModel cm = pModel != null ? pModel : pInput.getColorModel();

        // TODO: Might not work with all colormodels..
        // If indexcolormodel, we probably don't want to use that...
        // NOTE: Either BOTH or NONE of the images must have ALPHA

        return new BufferedImage(
            cm,
            ImageUtil.createCompatibleWritableRaster(pInput, cm, width, height),
            cm.isAlphaPremultiplied(),
            null);
    }

    /**
     * Re-samples (scales) the image to the size, and using the algorithm
     * specified in the constructor.
     *
     * @param input  The {@code BufferedImage} to be filtered
     * @param output The {@code BufferedImage} in which to store the resampled
     *                image
     * @return The re-sampled {@code BufferedImage}.
     * @throws NullPointerException     if {@code input} is {@code null}
     * @throws IllegalArgumentException if {@code input == output}.
     * @see #ResampleOp(int,int,int)
     */
    public final BufferedImage filter(final BufferedImage input, final BufferedImage output) {

        if (input == null) {
            throw new NullPointerException("Input == null");
        }
        if (input == output) {
            throw new IllegalArgumentException("Output image cannot be the same as the input image");
        }

        InterpolationFilter filter;

        // Special case for POINT, TRIANGLE and QUADRATIC filter, as standard
        // Java implementation is very fast (possibly H/W accelerated)
        switch (filterType) {
            case FILTER_POINT:
                if (input.getType() != BufferedImage.TYPE_CUSTOM) {
                    return fastResample(input, output, width, height, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
                }
                // Else fall through
            case FILTER_TRIANGLE:
                if (input.getType() != BufferedImage.TYPE_CUSTOM) {
                    return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
                }
                // Else fall through
            case FILTER_QUADRATIC:
                if (input.getType() != BufferedImage.TYPE_CUSTOM) {
                    return fastResample(input, output, width, height, AffineTransformOp.TYPE_BICUBIC);
                }
                // Else fall through
            default:
                filter = createFilter(filterType);
                // NOTE: Workaround for filter throwing exceptions when input or output is less than support...
                if ((Math.min(input.getWidth(), input.getHeight()) <= filter.support())
                    || (Math.min(width, height) <= filter.support())) {
                    return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
                }
                // Fall through
        }
        BufferedImage result;

        //        // Try to use native ImageMagick code
        //        BufferedImage result = MagickAccelerator.filter(this, input, output);
        //        if (result != null) {
        //            return result;
        //        }
        //
        //        // Otherwise, continue in pure Java mode

        // TODO: What if output != null and wrong size? Create new? Render on only a part? Document?

        // If filter type != POINT or BOX an input has IndexColorModel, convert
        // to true color, with alpha reflecting that of the original color model.
        BufferedImage temp;
        ColorModel cm;
        if ((filterType != FILTER_POINT)
            && (filterType != FILTER_BOX)
            && ((cm = input.getColorModel()) instanceof IndexColorModel)) {
            // TODO: OPTIMIZE: If color model has only b/w or gray, we could skip color info
            temp = ImageUtil.toBuffered(input, cm.hasAlpha()
            ? BufferedImage.TYPE_4BYTE_ABGR
            : BufferedImage.TYPE_3BYTE_BGR);
        } else {
            temp = input;
        }

        // Create or convert output to a suitable image
        // TODO: OPTIMIZE: Don't really need to convert all types to same as input
        result = (output != null) && (temp.getType() != BufferedImage.TYPE_CUSTOM) ? /*output*/ImageUtil.toBuffered(
            output,
            temp.getType()) : createCompatibleDestImage(temp, null);

        resample(temp, result, filter);

        // If output != null and needed to be converted, draw it back
        if ((output != null) && (output != result)) {
            //output.setData(output.getRaster());
            ImageUtil.drawOnto(output, result);
            result = output;
        }

        return result;
    }

    public Rectangle2D getBounds2D(BufferedImage src) {

        return new Rectangle(width, height);
    }

    /**
     * Returns the current filter type constant.
     *
     * @return the current filter type constant.
     * @see <a href="#field_summary">filter type constants</a>
     */
    public int getFilterType() {

        return filterType;
    }

    public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {

        // TODO: This is wrong...
        // How can I possible know how much one point is scaled, without first knowing the ration?!
        // TODO: Maybe set all points outside of bounds, inside?
        // TODO: Assume input image of Integer.MAX_VAL x Integer.MAX_VAL?! ;-)
        if (dstPt == null) {
            if (srcPt instanceof Point2D.Double) {
                dstPt = new Point2D.Double();
            } else {
                dstPt = new Point2D.Float();
            }
            dstPt.setLocation(srcPt);
        }
        return dstPt;
    }

    /*
        round()

        Round an FP value to its closest int representation.
        General routine; ideally belongs in general math lib file.
    */

    public RenderingHints getRenderingHints() {

        Object value;
        switch (filterType) {
            case FILTER_UNDEFINED:
                return null;
            case FILTER_POINT:
                value = VALUE_INTERPOLATION_POINT;
                break;
            case FILTER_BOX:
                value = VALUE_INTERPOLATION_BOX;
                break;
            case FILTER_TRIANGLE:
                value = VALUE_INTERPOLATION_TRIANGLE;
                break;
            case FILTER_HERMITE:
                value = VALUE_INTERPOLATION_HERMITE;
                break;
            case FILTER_HANNING:
                value = VALUE_INTERPOLATION_HANNING;
                break;
            case FILTER_HAMMING:
                value = VALUE_INTERPOLATION_HAMMING;
                break;
            case FILTER_BLACKMAN:
                value = VALUE_INTERPOLATION_BLACKMAN;
                break;
            case FILTER_GAUSSIAN:
                value = VALUE_INTERPOLATION_GAUSSIAN;
                break;
            case FILTER_QUADRATIC:
                value = VALUE_INTERPOLATION_QUADRATIC;
                break;
            case FILTER_CUBIC:
                value = VALUE_INTERPOLATION_CUBIC;
                break;
            case FILTER_CATROM:
                value = VALUE_INTERPOLATION_CATROM;
                break;
            case FILTER_MITCHELL:
                value = VALUE_INTERPOLATION_MITCHELL;
                break;
            case FILTER_LANCZOS:
                value = VALUE_INTERPOLATION_LANCZOS;
                break;
            case FILTER_BLACKMAN_BESSEL:
                value = VALUE_INTERPOLATION_BLACKMAN_BESSEL;
                break;
            case FILTER_BLACKMAN_SINC:
                value = VALUE_INTERPOLATION_BLACKMAN_SINC;
                break;
            default:
                throw new IllegalStateException("Unknown filter type: " + filterType);
        }

        return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value);
    }

    /*
        calcXContrib()

        Calculates the filter weights for a single target column.
        contribX->p must be freed afterwards.

        Returns -1 if error, 0 otherwise.
    */
    private ContributorList calcXContrib(double xscale, double fwidth, int srcwidth, InterpolationFilter pFilter, int i) {

        // TODO: What to do when fwidth > srcwidyj or dstwidth

        double width;
        double fscale;
        double center;
        double weight;

        ContributorList contribX = new ContributorList();

        if (xscale < 1.0) {
            /* Shrinking image */
            width = fwidth / xscale;
            fscale = 1.0 / xscale;

            if (width <= .5) {
                // Reduce to point sampling.
                width = .5 + 1.0e-6;
                fscale = 1.0;
            }

            //contribX.n = 0;
            contribX.p = new Contributor[(int)((width * 2.0) + 1.0)];

            center = i / xscale;
            int left = (int)Math.ceil(center - width);// Note: Assumes width <= .5
            int right = (int)Math.floor(center + width);

            double density = 0.0;

            for (int j = left; j <= right; j++) {
                weight = center - j;
                weight = pFilter.filter(weight / fscale) / fscale;
                int n;
                if (j < 0) {
                    n = -j;
                } else if (j >= srcwidth) {
                    n = ((srcwidth - j) + srcwidth) - 1;
                } else {
                    n = j;
                }

                /**/
                if (n >= srcwidth) {
                    n = n % srcwidth;
                } else if (n < 0) {
                    n = srcwidth - 1;
                }
                /**/

                int k = contribX.n++;
                contribX.p[k] = new Contributor();
                contribX.p[k].pixel = n;
                contribX.p[k].weight = weight;

                density += weight;

            }

            if ((density != 0.0) && (density != 1.0)) {
                //Normalize.
                density = 1.0 / density;
                for (int k = 0; k < contribX.n; k++) {
                    contribX.p[k].weight *= density;
                }
            }
        } else {
            /* Expanding image */
            //contribX.n = 0;
            contribX.p = new Contributor[(int)((fwidth * 2.0) + 1.0)];

            center = i / xscale;
            int left = (int)Math.ceil(center - fwidth);
            int right = (int)Math.floor(center + fwidth);

            for (int j = left; j <= right; j++) {
                weight = center - j;
                weight = pFilter.filter(weight);

                int n;
                if (j < 0) {
                    n = -j;
                } else if (j >= srcwidth) {
                    n = ((srcwidth - j) + srcwidth) - 1;
                } else {
                    n = j;
                }

                /**/
                if (n >= srcwidth) {
                    n = n % srcwidth;
                } else if (n < 0) {
                    n = srcwidth - 1;
                }
                /**/

                int k = contribX.n++;
                contribX.p[k] = new Contributor();
                contribX.p[k].pixel = n;
                contribX.p[k].weight = weight;
            }
        }
        return contribX;
    }/* calcXContrib */

    /*
        resample()

        Resizes bitmaps while resampling them.
    */
    private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) {

        final int dstWidth = pDest.getWidth();
        final int dstHeight = pDest.getHeight();

        final int srcWidth = pSource.getWidth();
        final int srcHeight = pSource.getHeight();

        /* create intermediate column to hold horizontal dst column zoom */
        final ColorModel cm = pSource.getColorModel();
        //        final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight);
        final WritableRaster work = ImageUtil.createCompatibleWritableRaster(pSource, cm, 1, srcHeight);

        double xscale = (double)dstWidth / (double)srcWidth;
        double yscale = (double)dstHeight / (double)srcHeight;

        ContributorList[] contribY = new ContributorList[dstHeight];
        for (int i = 0; i < contribY.length; i++) {
            contribY[i] = new ContributorList();
        }

        // TODO: What to do when fwidth > srcHeight or dstHeight
        double fwidth = pFilter.support();
        if (yscale < 1.0) {
            double width = fwidth / yscale;
            double fscale = 1.0 / yscale;

            if (width <= .5) {
                // Reduce to point sampling.
                width = .5 + 1.0e-6;
                fscale = 1.0;
            }

            for (int i = 0; i < dstHeight; i++) {
                //contribY[i].n = 0;
                contribY[i].p = new Contributor[(int)((width * 2.0) + 1)];

                double center = i / yscale;
                int left = (int)Math.ceil(center - width);
                int right = (int)Math.floor(center + width);

                double density = 0.0;

                for (int j = left; j <= right; j++) {
                    double weight = center - j;
                    weight = pFilter.filter(weight / fscale) / fscale;
                    int n;
                    if (j < 0) {
                        n = -j;
                    } else if (j >= srcHeight) {
                        n = ((srcHeight - j) + srcHeight) - 1;
                    } else {
                        n = j;
                    }

                    /**/
                    if (n >= srcHeight) {
                        n = n % srcHeight;
                    } else if (n < 0) {
                        n = srcHeight - 1;
                    }
                    /**/

                    int k = contribY[i].n++;
                    contribY[i].p[k] = new Contributor();
                    contribY[i].p[k].pixel = n;
                    contribY[i].p[k].weight = weight;

                    density += weight;
                }

                if ((density != 0.0) && (density != 1.0)) {
                    //Normalize.
                    density = 1.0 / density;
                    for (int k = 0; k < contribY[i].n; k++) {
                        contribY[i].p[k].weight *= density;
                    }
                }
            }
        } else {
            for (int i = 0; i < dstHeight; ++i) {
                //contribY[i].n = 0;
                contribY[i].p = new Contributor[(int)((fwidth * 2) + 1)];

                double center = i / yscale;
                double left = Math.ceil(center - fwidth);
                double right = Math.floor(center + fwidth);
                for (int j = (int)left; j <= right; ++j) {
                    double weight = center - j;
                    weight = pFilter.filter(weight);
                    int n;
                    if (j < 0) {
                        n = -j;
                    } else if (j >= srcHeight) {
                        n = ((srcHeight - j) + srcHeight) - 1;
                    } else {
                        n = j;
                    }

                    /**/
                    if (n >= srcHeight) {
                        n = n % srcHeight;
                    } else if (n < 0) {
                        n = srcHeight - 1;
                    }
                    /**/

                    int k = contribY[i].n++;
                    contribY[i].p[k] = new Contributor();
                    contribY[i].p[k].pixel = n;
                    contribY[i].p[k].weight = weight;
                }
            }
        }

        final Raster raster = pSource.getRaster();
        final WritableRaster out = pDest.getRaster();

        // TODO: This is not optimal for non-byte-packed rasters...
        // (What? Maybe I implemented the fix, but forgot to remove the TODO?)
        final int numChannels = raster.getNumBands();
        final int[] channelMax = new int[numChannels];
        for (int k = 0; k < numChannels; k++) {
            channelMax[k] = (1 << pSource.getColorModel().getComponentSize(k)) - 1;
        }

        for (int xx = 0; xx < dstWidth; xx++) {
            ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, xx);
            /* Apply horiz filter to make dst column in tmp. */
            for (int k = 0; k < srcHeight; k++) {
                for (int channel = 0; channel < numChannels; channel++) {

                    double weight = 0.0;
                    boolean bPelDelta = false;
                    // TODO: This line throws index out of bounds, if the image
                    // is smaller than filter.support()
                    double pel = raster.getSample(contribX.p[0].pixel, k, channel);
                    for (int j = 0; j < contribX.n; j++) {
                        double pel2 = j == 0 ? pel : raster.getSample(contribX.p[j].pixel, k, channel);
                        if (pel2 != pel) {
                            bPelDelta = true;
                        }
                        weight += pel2 * contribX.p[j].weight;
                    }
                    weight = bPelDelta ? round(weight) : pel;

                    if (weight < 0) {
                        weight = 0;
                    } else if (weight > channelMax[channel]) {
                        weight = channelMax[channel];
                    }

                    work.setSample(0, k, channel, weight);

                }
            }/* next row in temp column */

            /* The temp column has been built. Now stretch it vertically into dst column. */
            for (int i = 0; i < dstHeight; i++) {
                for (int channel = 0; channel < numChannels; channel++) {

                    double weight = 0.0;
                    boolean bPelDelta = false;
                    double pel = work.getSample(0, contribY[i].p[0].pixel, channel);

                    for (int j = 0; j < contribY[i].n; j++) {
                        // TODO: This line throws index out of bounds, if the image
                        // is smaller than filter.support()
                        double pel2 = j == 0 ? pel : work.getSample(0, contribY[i].p[j].pixel, channel);
                        if (pel2 != pel) {
                            bPelDelta = true;
                        }
                        weight += pel2 * contribY[i].p[j].weight;
                    }
                    weight = bPelDelta ? round(weight) : pel;
                    if (weight < 0) {
                        weight = 0;
                    } else if (weight > channelMax[channel]) {
                        weight = channelMax[channel];
                    }

                    out.setSample(xx, i, channel, weight);
                }
            }/* next dst row */
        }/* next dst column */
        return pDest;
    }/* resample */
}
TOP

Related Classes of com.alkacon.simapi.CmykJpegReader.ResampleOp$HanningFilter

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.