/*
    Scan Tailor - Interactive post-processing tool for scanned pages.
    Copyright (C) 2015  Joseph Artsimovich <joseph.artsimovich@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef IMAGEPROC_SOBEL_H_
#define IMAGEPROC_SOBEL_H_

#include "imageproc_config.h"

/**
 * \file
 *
 * The Sobel operator treats a 2D grid of data points as a function f(x, y)
 * and approximates its gradient.  It computes not the gradient itself,
 * but the gradient multiplied by 8.
 */

namespace imageproc
{

/**
 * Computes approximation of the horizontal gradient component, that is
 * the partial derivative with respect to x (multiplied by 8).
 *
 * \tparam T The type used for intermediate calculations.  Must be signed.
 * \param width Horizontal size of a grid.  Zero or negative value
 *        will cause this function to return without doing anything.
 * \param height Vertical size of a grid.  Zero or negative value
 *        will cause this function to return without doing anything.
 * \param src Pointer or a random access iterator to the top-left corner
 *        of the source grid.
 * \param src_stride The distance from a point on the source grid to the
 *        point directly below it, in terms of iterator difference.
 * \param src_reader A functor that gets passed a dereferenced iterator
 *        to the source grid and returns some type convertable to T.
 *        It's called like this:
 *        \code
 *        SrcIt src_it = ...;
 *        T const var(src_reader(*src_it));
 *        \endcode
 *        Consider using boost::lambda for constructing such a functor,
 *        possibly combined with one of the functors from ValueConf.h
 * \param tmp Pointer or a random access iterator to the top-left corner
 *        of the temporary grid.  The temporary grid will have the same
 *        width and height as the source and destination grids.
 *        Having the destination grid also serve as a temporary grid
 *        is supported, provided it's able to store signed values.
 *        Having all 3 to be the same is supported as well, subject
 *        to the same condition.
 * \param tmp_stride The distance from a point on the temporary grid to the
 *        point directly below it, in terms of iterator difference.
 * \param tmp_writer A functor that writes a value to the temporary grid.
 *        It's called like this:
 *        \code
 *        TmpIt tmp_it = ...;
 *        T val = ...;
 *        tmp_writer(*tmp_it, val);
 *        \endcode
 * \param tmp_reader A functor that gets passed a dereferenced iterator
 *        to the temporary grid and returns some type convertable to T.
 *        See \p src_reader for more info.
 * \param dst Pointer or a random access iterator to the top-left corner
 *        of the destination grid.
 * \param dst_stride The distance from a point on the destination grid to the
 *        point directly below it, in terms of iterator difference.
 * \param dst_writer A functor that writes a value to the destination grid.
 *        See \p tmp_writer for more info.
 */
template<
	typename T, typename SrcIt, typename TmpIt, typename DstIt,
	typename SrcReader, typename TmpWriter, typename TmpReader, typename DstWriter
>
void horizontalSobel(
	int width, int height, SrcIt src, int src_stride, SrcReader src_reader,
	TmpIt tmp, int tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader,
	DstIt dst, int dst_stride, DstWriter dst_writer);


/**
 * \see horizontalSobel()
 */
template<
	typename T, typename SrcIt, typename TmpIt, typename DstIt,
	typename SrcReader, typename TmpWriter,	typename TmpReader, typename DstWriter
>
void verticalSobel(
	int width, int height, SrcIt src, int src_stride, SrcReader src_reader,
	TmpIt tmp, int tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader,
	DstIt dst, int dst_stride, DstWriter dst_writer);


template<
	typename T, typename SrcIt, typename TmpIt, typename DstIt,
	typename SrcReader, typename TmpWriter, typename TmpReader, typename DstWriter
>
void horizontalSobel(
	int const width, int const height, SrcIt src, int src_stride, SrcReader src_reader,
	TmpIt tmp, int const tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader,
	DstIt dst, int const dst_stride, DstWriter dst_writer)
{
	if (width <= 0 || height <= 0) {
		return;
	}

	// Vertical pre-accumulation pass: mid = top + mid*2 + bottom
	for (int x = 0; x < width; ++x) {
		SrcIt p_src(src + x);
		TmpIt p_tmp(tmp + x);
		
		T top(src_reader(*p_src));
		if (height == 1) {
			tmp_writer(*p_tmp, top + top + top + top);
			continue;
		}

		T mid(src_reader(p_src[src_stride]));
		tmp_writer(*p_tmp, top + top + top + mid);
		
		for (int y = 1; y < height - 1; ++y) {
			p_src += src_stride;
			p_tmp += tmp_stride;
			T const bottom(src_reader(p_src[src_stride]));
			tmp_writer(*p_tmp, top + mid + mid + bottom);
			top = mid;
			mid = bottom;
		}

		p_src += src_stride;
		p_tmp += tmp_stride;
		tmp_writer(*p_tmp, top + mid + mid + mid);
	}

	// Horizontal pass: mid = right - left
	for (int y = 0; y < height; ++y) {
		T left(tmp_reader(*tmp));
		
		if (width == 1) {
			dst_writer(*dst, left - left);
		} else {
			T mid(tmp_reader(tmp[1]));
			dst_writer(dst[0], mid - left);
			
			int x = 1;
			for (; x < width - 1; ++x) {
				T const right(tmp_reader(tmp[x + 1]));
				dst_writer(dst[x], right - left);
				left = mid;
				mid = right;
			}

			dst_writer(dst[x], mid - left);
		}

		tmp += tmp_stride;
		dst += dst_stride;
	}
}

template<
	typename T, typename SrcIt, typename TmpIt, typename DstIt,
	typename SrcReader, typename TmpWriter, typename TmpReader, typename DstWriter
>
void verticalSobel(
	int const width, int const height, SrcIt src, int src_stride, SrcReader src_reader,
	TmpIt tmp, int const tmp_stride, TmpWriter tmp_writer, TmpReader tmp_reader,
	DstIt dst, int const dst_stride, DstWriter dst_writer)
{
	if (width <= 0 || height <= 0) {
		return;
	}

	TmpIt const tmp_orig(tmp);

	// Horizontal pre-accumulation pass: mid = left + mid*2 + right
	for (int y = 0; y < height; ++y) {
		T left(src_reader(*src));

		if (width == 1) {
			tmp_writer(*tmp, left + left + left + left);
		} else {
			T mid(src_reader(src[1]));
			tmp_writer(tmp[0], left + left + left + mid);
			
			int x = 1;
			for (; x < width - 1; ++x) {
				T const right(src_reader(src[x + 1]));
				tmp_writer(tmp[x], left + mid + mid + right);
				left = mid;
				mid = right;
			}

			tmp_writer(tmp[x], left + mid + mid + mid);
		}
		src += src_stride;
		tmp += tmp_stride;
	}

	// Vertical pass: mid = bottom - top
	for (int x = 0; x < width; ++x) {
		TmpIt p_tmp(tmp_orig + x);
		TmpIt p_dst(dst + x);

		T top(tmp_reader(*p_tmp));
		if (height == 1) {
			dst_writer(*p_dst, top - top);
			continue;
		}

		T mid(tmp_reader(p_tmp[tmp_stride]));
		dst_writer(*p_dst, mid - top);
		
		for (int y = 1; y < height - 1; ++y) {
			p_tmp += tmp_stride;
			p_dst += dst_stride;
			T const bottom(tmp_reader(p_tmp[tmp_stride]));
			dst_writer(*p_dst, bottom - top);
			top = mid;
			mid = bottom;
		}

		p_tmp += tmp_stride;
		p_dst += dst_stride;
		dst_writer(*p_dst, mid - top);
	}
}

} // namespace imageproc

#endif
