/*********************************************************************
 * This file is part of the PRAPI library.
 *
 * Copyright (C) 2002 Topi Mäenpää
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.9 $
 *********************************************************************/

#ifndef _BINARYMORPHOLOGY_H
#define _BINARYMORPHOLOGY_H

#include <Matrix.h>
#include "../ImageTransform.h"
#include "../ConvolutionMask.h"

namespace prapi { namespace binary {

	/**
	 * Implemented binary morphology operations
	 * <ul>
	 * <li>BIN_ERODE - erosion</li>
	 * <li>BIN_DILATE - dilation</li>
	 * <li>BIN_OPEN - erosion followed by dilation</li>
	 * <li>BIN_CLOSE - dilation followed by erosion</li>
	 * <li>BIN_TOPHAT - the result of opening subtracted from the original image</li>
	 * <li>BIN_BOTTOMHAT - the original image subtracted from the result of binary closure</li>
	 * </ul>
	 **/
	enum MorphologyOperation { BIN_ERODE, BIN_DILATE, BIN_OPEN, BIN_CLOSE, BIN_TOPHAT, BIN_BOTTOMHAT };

	/**
	 * Binary morphology operations. This class performs the basic
	 * dilation and erosion operations with different structuring
	 * elements. It also has implementations for some higher-level
	 * operations.<p>
	 *
	 * An example of usage:
	 *
	 * <pre>
	 * // Make a new ConvolutionMask
	 * Matrix&lt;int&gt; mask(2,3,
	 *                  1,1,1,
	 *                  1,1,1);
	 * ConvolutionMask&lt;int&gt; se(mask);
	 *
	 * // If the "operation" point of your structuring element differs
	 * // from the center point of the element, you have to set it.
	 * Point&lt;int&gt; point(0,0);
	 * set.setOrigin(point);
	 *
	 * BinaryMorphology&lt;int&gt; morph(se, BIN_OPEN);
	 *
	 * Matrix&lt;int&gt; result(morph.getTransformedImage(some_binary_matrix));
	 * </pre>
	 **/
  template <class T=bool> class BinaryMorphology: public ImageTransform<T,T>
	{
	public:
		/**
		 * Create a new binary morphology operator.
		 *
		 * @param structuringElement the structuring element (binary mask)
		 * used for morphology operations.
		 * @param op the type of operation
		 **/
		BinaryMorphology(const ConvolutionMask<T>& structuringElement,
										 MorphologyOperation op) :
			_mask(structuringElement), _operation(op) {}

		/**
		 * Set the type of operation.
		 **/
		void setOperation(MorphologyOperation op) { _operation = op; }

		/**
		 * Get the type of operation.
		 **/
		MorphologyOperation getOperation() const { return _operation; }
		
		/**
		 * Get the ConvolutionMask.
		 **/
		ConvolutionMask<T> getConvolutionMask() const { return _mask; }
		/**
		 * Set the ConvolutionMask.
		 **/
		void setConvolutionMask(const ConvolutionMask<T>& mask) { _mask = mask; }

		util::Matrix<T> getTransformedImage(const util::Matrix<T>& image) throw (ImageTransformException&);

		/**
		 * Perform binary erosion using the current convolution mask as a
		 * structuring element.
		 **/
		util::Matrix<T> erode(const util::Matrix<T>& image) throw (ImageTransformException&);
		/**
		 * Perform binary dilation using the current convolution mask as a
		 * structuring element.
		 **/
		util::Matrix<T> dilate(const util::Matrix<T>& image) throw (ImageTransformException&);

	private:
		ConvolutionMask<T> _mask;
		MorphologyOperation _operation;
	};

	template <class T> util::Matrix<T> BinaryMorphology<T>::getTransformedImage(const util::Matrix<T>& image)
		throw (ImageTransformException&)
	{
		switch (_operation)
			{
			case BIN_ERODE:
				return erode(image);
			case BIN_DILATE:
				return dilate(image);
			case BIN_OPEN:
				return dilate(erode(image));
			case BIN_CLOSE:
				return erode(dilate(image));
			case BIN_TOPHAT:
				return image - dilate(erode(image));
			case BIN_BOTTOMHAT:
				return erode(dilate(image)) - image;
			}
		return image;
	}
	
	template <class T> util::Matrix<T> BinaryMorphology<T>::erode(const util::Matrix<T>& image)
		throw (ImageTransformException&)
	{
		int maskRows = _mask.getRows(), maskCols = _mask.getColumns();
		int rows = image.getRows(), cols = image.getColumns();
		int rShift = _mask.origin().y, cShift = _mask.origin().x;
		if (maskRows > rows || maskCols > cols)
			throw ImageTransformException("BinaryMorphology::erode(const Matrix&): Mask cannot be larger than image.");

		int cDiff = cols - maskCols;
		const T* imageData = image.getData();
		util::Matrix<T> result(rows,cols);

		for (int r=0; r<rows-maskRows; r++, imageData+=maskCols)
			for (int c=0; c<cols-maskCols; c++, imageData++)
				{
					T* maskData = _mask.getData();
					const T* ptr = imageData;
					for (int mr=0; mr<maskRows; mr++, ptr+=cDiff)
						for (int mc=0; mc<maskCols; mc++, maskData++, ptr++)
							{
								if (*maskData && !*ptr)
									goto out;
							}
					result(r+rShift, c+cShift) = image(r+rShift, c+cShift);
				out:;
				}
		return result;
	}

	template <class T> util::Matrix<T> BinaryMorphology<T>::dilate(const util::Matrix<T>& image)
		throw (ImageTransformException&)
	{
		int maskRows = _mask.getRows(), maskCols = _mask.getColumns();
		int rows = image.getRows(), cols = image.getColumns();
		int rShift = _mask.origin().y, cShift = _mask.origin().x;
		if (maskRows > rows || maskCols > cols)
			throw ImageTransformException("BinaryMorphology::dilate(const Matrix&): Mask cannot be larger than image.");

		const T* imageData = image.getData();
		util::Matrix<T> result(rows,cols);

		for (int r=0; r<rows; r++)
			for (int c=0; c<cols; c++, imageData++)
				{
					if (!*imageData)
						continue;

					T* maskData = _mask.getData();
					for (int mr=0; mr<maskRows; mr++)
						for (int mc=0; mc<maskCols; mc++, maskData++)
							{
								int realR = r+mr-rShift, realC = c+mc-cShift;
								if (*maskData &&
										realR >= 0 && realR < rows &&
										realC >= 0 && realC < cols)
									result(realR, realC) = *maskData;
							}
				}
		return result;
	}
	
}}
#endif
