/*
 * Gray8CannyHoriz.java
 *
 * Created on August 27, 2006, 4:32, PM
 *
 * To change this templatef, choose Tools | Template Manager
 * and open the template in the editor.
 *
 * Copyright 2007 by Jon A. Webb
 *     This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the Lesser GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.github.ojil.algorithm;

import com.github.ojil.core.Gray8Image;
import com.github.ojil.core.Image;
import com.github.ojil.core.ImageError;
import com.github.ojil.core.PipelineStage;

/**
 * Computes the horizontal Canny operator for an input gray image. The sigma
 * value for the operator is set in the constructor or setSigma. All
 * calculations are done in integer (per CLDC 1.0) and the sigma value is
 * specified as multiplied by 10.0. The minimum value for the unmultiplied sigma
 * is 0.1; the maximum value is about 10.0. Larger sigma values give an operator
 * which is less sensitive to high frequencies and more sensitive to low
 * frequencies.
 * <p>
 * 
 * @author webb
 */
public class Gray8CannyHoriz extends PipelineStage {
    /**
     * cSigma is the sigma value we'll be using. It has been multiplied by 10.0
     * and converted to integer because CLDC 1.0 doesn't allow floating point.
     * The minimum legal value for cSigma is 1; the maximum is given by the
     * length of nCoeff below.
     */
    private int cSigma;
    /**
     * nCoeff row i is the precomputed Canny coefficients for sigma = i/10.0.
     * They have been scaled by 256 and converted to integer because CLDC 1.0
     * doesn't allow floating point. The coefficients have been scaled and
     * normalized so the sum is 0 and the sum of the absolute values is 256
     * (counting both sides of the Canny operator -- the operator is symmetric
     * so we just give one side below.) The number of coefficients was
     * determined by generating them out to the point at which the unscaled
     * coefficient was less than a threshold, here 0.05.
     */
    private final Integer[][] nCoeff = {// @formatter:off
            {0},
            {-127, 21, 21, 21},
            {-127, 21, 21, 21},
            {-127, 25, 19, 19},
            {-128, 42, 10, 10},
            {-127, 56, 3, 3},
            {-128, 57, 5},
            {-128, 48, 15},
            {-128, 32, 29, 1},
            {-128, 16, 42, 5},
            {-128, 0, 51, 11},
            {-104, -11, 45, 16, 1},
            {-89, -19, 39, 20, 3},
            {-79, -24, 33, 24, 5},
            {-72, -27, 27, 26, 8, 1},
            {-67, -30, 21, 27, 11, 2},
            {-63, -32, 16, 27, 14, 4},
            {-61, -33, 12, 27, 17, 6},
            {-58, -34, 8, 26, 20, 8},
            {-56, -35, 4, 25, 22, 11},
            {-55, -36, 0, 22, 22, 12, 4, 1},
            {-51, -35, -2, 19, 22, 14, 6, 2},
            {-48, -34, -5, 16, 21, 15, 7, 2},
            {-45, -33, -7, 13, 20, 16, 9, 3},
            {-43, -32, -9, 11, 19, 17, 10, 5},
            {-41, -32, -10, 9, 18, 17, 11, 6},
            {-40, -31, -11, 7, 17, 18, 13, 7},
            {-39, -31, -12, 5, 16, 18, 14, 8},
            {-38, -31, -13, 4, 15, 18, 15, 10},
            {-37, -30, -14, 2, 14, 18, 16, 11},
            {-36, -30, -15, 1, 13, 18, 17, 12},
            {-34, -29, -16, -1, 10, 15, 15, 11, 7, 4},
            {-33, -28, -16, -2, 9, 14, 15, 12, 8, 4},
            {-32, -27, -16, -3, 7, 13, 14, 12, 8, 5},
            {-31, -27, -16, -4, 6, 13, 14, 13, 9, 6},
            {-30, -26, -17, -4, 5, 12, 14, 13, 10, 7},
            {-29, -26, -17, -5, 4, 11, 14, 13, 11, 7},
            {-29, -25, -17, -6, 3, 11, 14, 14, 11, 8},
            {-28, -25, -17, -6, 3, 10, 14, 14, 12, 9},
            {-27, -25, -17, -7, 2, 9, 13, 14, 13, 10},
            {-27, -24, -17, -7, 1, 9, 13, 14, 13, 11},
            {-26, -23, -17, -9, 0, 6, 11, 12, 11, 9, 7, 5},
            {-25, -23, -17, -9, 0, 5, 10, 12, 11, 10, 7, 5},
            {-25, -22, -17, -9, -1, 5, 9, 11, 11, 10, 8, 6},
            {-24, -22, -17, -9, -2, 4, 9, 11, 11, 10, 8, 6},
            {-23, -22, -17, -10, -2, 4, 8, 11, 12, 11, 9, 7},
            {-23, -21, -17, -10, -3, 3, 8, 11, 12, 11, 9, 7},
            {-23, -21, -16, -10, -3, 3, 7, 10, 12, 11, 10, 8},
            {-22, -21, -16, -10, -3, 2, 7, 10, 12, 11, 10, 8},
            {-22, -20, -16, -10, -4, 2, 7, 10, 12, 12, 10, 9},
            {-22, -20, -16, -11, -4, 1, 6, 10, 11, 12, 11, 9},
            {-21, -19, -16, -11, -5, 0, 4, 8, 9, 10, 9, 8, 6, 5},
            {-20, -19, -16, -11, -5, 0, 4, 7, 9, 10, 9, 8, 7, 5},
            {-20, -19, -16, -11, -6, 0, 3, 7, 9, 10, 10, 8, 7, 6},
            {-20, -19, -16, -11, -6, -1, 3, 7, 9, 10, 10, 9, 7, 6},
            {-19, -18, -15, -11, -6, -1, 3, 6, 9, 10, 10, 9, 8, 6},
            {-19, -18, -15, -11, -6, -1, 2, 6, 8, 10, 10, 9, 8, 7},
            {-19, -18, -15, -11, -6, -1, 2, 6, 8, 10, 10, 9, 8, 7},
            {-18, -18, -15, -11, -7, -2, 2, 5, 8, 9, 10, 10, 9, 7},
            {-18, -17, -15, -11, -7, -2, 1, 5, 8, 9, 10, 10, 9, 8},
            {-18, -17, -15, -11, -7, -2, 1, 5, 8, 9, 10, 10, 9, 8},
            {-17, -17, -14, -11, -7, -3, 0, 3, 6, 8, 8, 8, 8, 7, 6, 5},
            {-17, -16, -14, -11, -7, -3, 0, 3, 6, 7, 8, 8, 8, 7, 6, 5},
            {-17, -16, -14, -11, -8, -4, 0, 3, 5, 7, 8, 9, 8, 7, 6, 5},
            {-17, -16, -14, -11, -8, -4, 0, 2, 5, 7, 8, 9, 8, 8, 7, 6},
            {-16, -16, -14, -11, -8, -4, 0, 2, 5, 7, 8, 9, 8, 8, 7, 6},
            {-16, -16, -14, -11, -8, -4, 0, 2, 5, 7, 8, 9, 8, 8, 7, 6},
            {-16, -15, -14, -11, -8, -4, -1, 2, 4, 6, 8, 8, 9, 8, 7, 6},
            {-16, -15, -14, -11, -8, -4, -1, 1, 4, 6, 8, 8, 9, 8, 8, 7},
            {-16, -15, -13, -11, -8, -4, -1, 1, 4, 6, 8, 8, 9, 8, 8, 7},
            {-15, -15, -13, -11, -8, -5, -1, 1, 4, 6, 8, 8, 9, 9, 8, 7},
            {-15, -14, -13, -11, -8, -5, -2, 0, 3, 5, 6, 7, 7, 7, 7, 6, 5, 5},
            {-15, -14, -13, -11, -8, -5, -2, 0, 2, 4, 6, 7, 7, 7, 7, 6, 6, 5},
            {-15, -14, -13, -11, -8, -5, -2, 0, 2, 4, 6, 7, 7, 7, 7, 7, 6, 5},
            {-14, -14, -13, -11, -8, -5, -2, 0, 2, 4, 6, 7, 7, 8, 7, 7, 6, 5},
            {-14, -14, -13, -11, -8, -5, -3, 0, 2, 4, 6, 7, 7, 8, 7, 7, 6, 6},
            {-14, -14, -12, -11, -8, -6, -3, 0, 2, 4, 5, 7, 7, 8, 7, 7, 6, 6},
            {-14, -14, -12, -11, -8, -6, -3, 0, 1, 4, 5, 7, 7, 8, 8, 7, 7, 6},
            {-14, -13, -12, -11, -8, -6, -3, 0, 1, 3, 5, 6, 7, 8, 8, 7, 7, 6},
            {-14, -13, -12, -10, -8, -6, -3, 0, 1, 3, 5, 6, 7, 8, 8, 7, 7, 6},
            {-13, -13, -12, -10, -8, -6, -3, -1, 1, 3, 5, 6, 7, 8, 8, 8, 7, 7},
            {-13, -13, -12, -10, -8, -6, -4, -1, 0, 2, 4, 5, 6, 6, 7, 7, 6, 6, 5, 4},
            {-13, -13, -12, -10, -8, -6, -4, -1, 0, 2, 4, 5, 6, 6, 7, 7, 6, 6, 5, 5},
            {-13, -12, -12, -10, -8, -6, -4, -1, 0, 2, 3, 5, 6, 6, 7, 7, 6, 6, 5, 5},
            {-13, -12, -11, -10, -8, -6, -4, -2, 0, 2, 3, 5, 6, 6, 7, 7, 6, 6, 6, 5},
            {-13, -12, -11, -10, -8, -6, -4, -2, 0, 1, 3, 5, 6, 6, 7, 7, 7, 6, 6, 5},
            {-12, -12, -11, -10, -8, -6, -4, -2, 0, 1, 3, 4, 6, 6, 7, 7, 7, 6, 6, 5},
            {-12, -12, -11, -10, -8, -6, -4, -2, 0, 1, 3, 4, 5, 6, 7, 7, 7, 6, 6, 5},
            {-12, -12, -11, -10, -8, -6, -4, -2, 0, 1, 3, 4, 5, 6, 7, 7, 7, 7, 6, 6},
            {-12, -12, -11, -10, -8, -6, -4, -2, 0, 1, 3, 4, 5, 6, 7, 7, 7, 7, 6, 6},
            {-12, -12, -11, -10, -8, -6, -4, -2, 0, 1, 3, 4, 5, 6, 7, 7, 7, 7, 6, 6},
            {-12, -11, -11, -10, -8, -6, -5, -3, -1, 0, 2, 3, 4, 5, 6, 6, 6, 6, 6, 5, 5, 4},
            {-11, -11, -11, -10, -8, -6, -5, -3, -1, 0, 2, 3, 4, 5, 6, 6, 6, 6, 6, 5, 5, 4},
            {-11, -11, -11, -9, -8, -6, -5, -3, -1, 0, 1, 3, 4, 5, 6, 6, 6, 6, 6, 5, 5, 4},
            {-11, -11, -10, -9, -8, -6, -5, -3, -1, 0, 1, 3, 4, 5, 6, 6, 6, 6, 6, 6, 5, 5},
            {-11, -11, -10, -9, -8, -7, -5, -3, -1, 0, 1, 3, 4, 5, 5, 6, 6, 6, 6, 6, 5, 5},
            {-11, -11, -10, -9, -8, -7, -5, -3, -1, 0, 1, 3, 4, 5, 5, 6, 6, 6, 6, 6, 5, 5},
            {-11, -11, -10, -9, -8, -7, -5, -3, -1, 0, 1, 2, 4, 5, 5, 6, 6, 6, 6, 6, 5, 5},
            {-11, -11, -10, -9, -8, -7, -5, -3, -1, 0, 1, 2, 4, 5, 5, 6, 6, 6, 6, 6, 6, 5},
            {-11, -11, -10, -9, -8, -7, -5, -3, -1, 0, 1, 2, 4, 5, 5, 6, 6, 6, 6, 6, 6, 5},
    };// @formatter:on
    
    /**
     * Creates a new instance of Gray8CannyHoriz
     * 
     * @param cSigma
     *            the sigma value for the operator, which is the sigma in the
     *            Gaussian distribution multipied by 10.0 and converted to
     *            integer.
     * @throws ImageError
     *             if cSigma is out of range.
     */
    public Gray8CannyHoriz(final int cSigma) throws ImageError {
        setSigma(cSigma);
    }
    
    /**
     * Apply the Canny operator horizontally to the input input image. The sigma
     * value for the operator is set in the class constructor. We handle the
     * borders of the image a little carefully to avoid creating spurious edges
     * at them. The image value at the border is reflected so that image(0,-1),
     * for example, is made equal to image(0,1).
     * 
     * @param image
     *            the input Gray8Image
     * @throws ImageError
     *             if the input is not a Gray8Image.
     */
    @Override
    public void push(final Image<?, ?> image) throws ImageError {
        if (!(image instanceof Gray8Image)) {
            throw new ImageError(ImageError.PACKAGE.ALGORITHM, AlgorithmErrorCodes.IMAGE_NOT_GRAY8IMAGE, image.toString(), null, null);
        }
        final Gray8Image<?> input = (Gray8Image<?>) image;
        final Gray8Image<?> result = new Gray8Image<>(image.getWidth(), image.getHeight());
        final Byte[] bIn = input.getData();
        final Byte[] bResult = result.getData();
        final Integer[] wCoeff = nCoeff[cSigma];
        final int cWidth = input.getWidth();
        for (int i = 0; i < input.getHeight(); i++) {
            for (int j = 0; j < cWidth; j++) {
                /* left side of Canny operator */
                int wSum = 0;
                /*
                 * Use Math.abs to mirror the index at the border
                 */
                for (int k = 1; k < wCoeff.length; k++) {
                    wSum += wCoeff[k] * bIn[(i * cWidth) + Math.abs(j - k)];
                }
                /* right side of Canny operator */
                for (int k = 0; k < wCoeff.length; k++) {
                    if ((j + k) < cWidth) {
                        wSum += wCoeff[k] * bIn[(i * cWidth) + j + k];
                    } else {
                        // reflect at border. j + k >= cWidth so
                        // 2*cWidth - (j + k + 1) < cWidth
                        final int cPos = (2 * cWidth) - (j + k + 1);
                        wSum += wCoeff[k] * bIn[(i * cWidth) + cPos];
                    }
                }
                /*
                 * Canny coefficients are scaled so sum of absolute values is
                 * 256.
                 */
                wSum = wSum >> 8;
                    bResult[(i * cWidth) + j] = (byte) wSum;
            }
        }
        super.setOutput(result);
    }
    
    /**
     * Returns the current value of sigma.
     *
     * @return the sigma value
     */
    public int getSigma() {
        return cSigma;
    }
    
    /**
     * sets a new value for sigma. Sigma controls the frequency of edges that
     * the operator responds to. A small sigma value gives higher frequency
     * edges, while a larger sigma gives lower frequency edges.
     * 
     * @param cSigma
     *            the new sigma value
     * @throws ImageError
     *             if cSigma is out of range.
     */
    public void setSigma(final int cSigma) throws ImageError {
        if ((cSigma <= 1) || (cSigma >= nCoeff.length)) {
            throw new ImageError(ImageError.PACKAGE.ALGORITHM, AlgorithmErrorCodes.PARAMETER_OUT_OF_RANGE, new Integer(cSigma).toString(), new Integer(1).toString(),
                    new Integer(nCoeff.length).toString());
        }
        this.cSigma = cSigma;
    }
    
    /**
     * returns a string describing this Canny operator.
     *
     * @return a string describing the Canny operator.
     */
    @Override
    public String toString() {
        return super.toString() + " (" + cSigma + ")"; //$NON-NLS-1$ //$NON-NLS-2$
    }
}
