## Copyright (C) 2004 Josep Mones i Teixidor
##
## 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 2 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, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

## -*- texinfo -*-
## @deftypefn {Function File} {@var{B} = } padarray (@var{A},@var{padsize})
## @deftypefnx {Function File} {@var{B} = } padarray (@var{A},@var{padsize},@var{padval})
## @deftypefnx {Function File} {@var{B} = } padarray (@var{A},@var{padsize},@var{padval},@var{direction})
## Pads an array in a configurable way.
##
## B = padarray(A,padsize) pads an array @var{A} with zeros, where
## @var{padsize} defines the amount of padding to add in each dimension
## (it must be a vector of positive integers). 
##
## Each component of @var{padsize} defines the number of elements of
## padding that will be added in the corresponding dimension. For
## instance, [4,5] adds 4 elements of padding in first dimension (vertical)
## and 5 in second dimension (horizontal).
##
## B = padarray(A,padsize,padval) pads @var{A} using the value specified
## by @var{padval}. @var{padval} can be a scalar or a string. Possible
## values are:
## @table @code
## @item 0
## Pads with 0 as described above. This is the default behaviour.
## @item scalar
## Pads using @var{padval} as a padding value.
## @item 'circular'
## Pads with a circular repetition of elements in @var{A} (similar to
## tiling @var{A}).
## @item 'replicate'
## Pads 'replicating' values of @var{A} which are at the border of the
## array.
## @item 'symmetric'
## Pads with a mirror reflection of @var{A}.
## @end table
##
## B = padarray(A,padsize,padval,direction) pads @var{A} defining the
## direction of the pad. Possible values are:
## @table @code
## @item 'both'
## For each dimension it pads before the first element the number
## of elements defined by @var{padsize} and the same number again after
## the last element. This is the default value.
## @item 'pre'
## For each dimension it pads before the first element the number of
## elements defined by @var{padsize}.
## @item 'post'
## For each dimension it pads after the last element the number of
## elements defined by @var{padsize}.
## @end table
## @end deftypefn

## Author:  Josep Mones i Teixidor <jmones@puntbarra.com>

function B = padarray(A, padsize, padval, direction)
  # Check parameters
  if (nargin<2 || nargin>4)
    usage ("B = padarray(A, padsize [, padval] [, direction])");
  endif
  if (nargin<3)
    padval=0;
  endif
  if (nargin<4)
    direction='both';
  endif

  if (!isvector(padsize) || any(padsize<0))
    error("padarray: padsize must be a vector of positive integers.");
  endif

  ## Assure padsize is a row vector
  padsize=padsize(:).';

  # Check direction
  pre=false;
  post=false;
  switch(direction)
    case('pre')
      pre=true;
    case('post')
      post=true;
    case('both')
      post=true;
      pre=true;
    otherwise
      error ("padarray: direction possible values are: 'pre', 'post' and 'both'");
  endswitch
  
  
  B=A;
  dim=1;
  for s=padsize
    if (s>0)
      # padding in this dimension was requested
      ds=size(B);
      ds=[ds, ones(1,dim-length(ds))]; # data size
      ps=ds;
      ps(dim)=s;		       # padding size

      if (ischar(padval))
	# Init a "index all" cell array. All cases need it.
	idx=cell();
	for i=1:length(ds)
	  idx{i}=1:ds(i);
	endfor

	switch(padval)
	  case('circular')
	    complete=0;
	    D=B;
	    if (ps(dim)>ds(dim))
	      complete=floor(ps(dim)/ds(dim));
	      ps(dim)=rem(ps(dim),ds(dim));
	    endif
	    if (pre)
	      for i=1:complete
		B=cat(dim,D,B);
	      endfor
	      idxt=idx;
	      idxt{dim}=ds(dim)-ps(dim)+1:ds(dim);
	      B=cat(dim,D(idxt{:}),B);
	    endif
	    if (post)
	      for i=1:complete
		B=cat(dim,B,D);
	      endfor
	      idxt=idx;
	      idxt{dim}=1:ps(dim);
	      B=cat(dim,B,D(idxt{:}));
	    endif
	    # end circular case

	  case('replicate')
	    if (pre)
	      idxt=idx;
	      idxt{dim}=1;
	      pad=B(idxt{:});
	      # can we do this without the loop?	
	      for i=1:s
		B=cat(dim,pad,B);
	      endfor
	    endif
	    if (post)
	      idxt=idx;
	      idxt{dim}=size(B,dim);
	      pad=B(idxt{:});
	      for i=1:s
		B=cat(dim,B,pad);
	      endfor
	    endif
	    # end replicate case
	
	  case('symmetric')
	    if (ps(dim)>ds(dim))
	      error("padarray: padding is longer than data using symmetric padding");
	    endif
	    if (pre)
	      idxt=idx;
	      idxt{dim}=ps(dim):-1:1;
	      B=cat(dim,B(idxt{:}),B);
	    endif
	    if (post)
	      idxt=idx;
	      sbd=size(B,dim);
	      idxt{dim}=sbd:-1:sbd-ps(dim)+1;
	      B=cat(dim,B,B(idxt{:}));
	    endif
	    # end symmetric case

	  otherwise
	    error("padarray: invalid string in padval parameter.");

	endswitch
	# end cases where padval is a string

      elseif (isscalar(padval))
	# Handle fixed value padding
	if (padval==0)
	  pad=zeros(ps,class(A));       ## class(pad) = class(A)
	else
	  pad=padval*ones(ps,class(A)); ## class(pad) = class(A)
	endif
	if(pre&&post)
	  # check if this is not quicker than just 2 calls (one for each)
	  B=cat(dim,pad,B,pad);
	elseif(pre)
	  B=cat(dim,pad,B);
	elseif(post)
	  B=cat(dim,B,pad);
	endif
      else
	error ("padarray: padval can only be a scalar or a string.");
      endif
    endif
    dim+=1;
  endfor
endfunction

%!demo
%! padarray([1,2,3;4,5,6],[2,1])
%! % pads [1,2,3;4,5,6] with a whole border of 2 rows and 1 columns of 0

%!demo
%! padarray([1,2,3;4,5,6],[2,1],5)
%! % pads [1,2,3;4,5,6] with a whole border of 2 rows and 1 columns of 5

%!demo
%! padarray([1,2,3;4,5,6],[2,1],0,'pre')
%! % pads [1,2,3;4,5,6] with a left and top border of 2 rows and 1 columns of 0

%!demo
%! padarray([1,2,3;4,5,6],[2,1],'circular')
%! % pads [1,2,3;4,5,6] with a whole 'circular' border of 2 rows and 1 columns
%! % border 'repeats' data as if we tiled blocks of data

%!demo
%! padarray([1,2,3;4,5,6],[2,1],'replicate')
%! % pads [1,2,3;4,5,6] with a whole border of 2 rows and 1 columns which
%! % 'replicates' edge data

%!demo
%! padarray([1,2,3;4,5,6],[2,1],'symmetric')
%! % pads [1,2,3;4,5,6] with a whole border of 2 rows and 1 columns which
%! % is symmetric to the data on the edge 

% Test default padval and direction
%!assert(padarray([1;2],[1]), [0;1;2;0]);
%!assert(padarray([3,4],[0,2]), [0,0,3,4,0,0]);
%!assert(padarray([1,2,3;4,5,6],[1,2]), \
%!      [zeros(1,7);0,0,1,2,3,0,0;0,0,4,5,6,0,0;zeros(1,7)]);

% Test padding on 3D array
%!test
%! int8(0); % fail for octave <= 2.1.57 without crashing
%! assert(padarray([1,2,3;4,5,6],[3,2,1]), cat(3, 			\
%! 	zeros(8,7),							\
%! 	[zeros(3,7); [zeros(2,2), [1,2,3;4,5,6], zeros(2,2)]; zeros(3,7)], \
%! 	zeros(8,7))); 

% Test if default param are ok
%!assert(padarray([1,2],[4,5])==padarray([1,2],[4,5],0));
%!assert(padarray([1,2],[4,5])==padarray([1,2],[4,5],0,'both'));

% Test literal padval
%!assert(padarray([1;2],[1],i), [i;1;2;i]);

% Test directions (horizontal)
%!assert(padarray([1;2],[1],i,'pre'), [i;1;2]);
%!assert(padarray([1;2],[1],i,'post'), [1;2;i]);
%!assert(padarray([1;2],[1],i,'both'), [i;1;2;i]);

% Test directions (vertical)
%!assert(padarray([1,2],[0,1],i,'pre'), [i,1,2]);
%!assert(padarray([1,2],[0,1],i,'post'), [1,2,i]);
%!assert(padarray([1,2],[0,1],i,'both'), [i,1,2,i]);

% Test vertical padsize
%!assert(padarray([1,2],[0;1],i,'both'), [i,1,2,i]);

% Test circular padding
%!test
%! A=[1,2,3;4,5,6];
%! B=repmat(A,7,9);
%! assert(padarray(A,[1,2],'circular','pre'), B(2:4,2:6));
%! assert(padarray(A,[1,2],'circular','post'), B(3:5,4:8));
%! assert(padarray(A,[1,2],'circular','both'), B(2:5,2:8));
%! % This tests when padding is bigger than data
%! assert(padarray(A,[5,10],'circular','both'), B(2:13,3:25));

% Test replicate padding
%!test
%! A=[1,2;3,4];
%! B=kron(A,ones(10,5));
%! assert(padarray(A,[9,4],'replicate','pre'), B(1:11,1:6));
%! assert(padarray(A,[9,4],'replicate','post'), B(10:20,5:10));
%! assert(padarray(A,[9,4],'replicate','both'), B);

% Test symmetric padding
%!test
%! A=[1:3;4:6];
%! HA=[3:-1:1;6:-1:4];
%! VA=[4:6;1:3];
%! VHA=[6:-1:4;3:-1:1];
%! B=[VHA,VA,VHA; HA,A,HA; VHA,VA,VHA];
%! assert(padarray(A,[1,2],'symmetric','pre'), B(2:4,2:6));
%! assert(padarray(A,[1,2],'symmetric','post'), B(3:5,4:8));
%! assert(padarray(A,[1,2],'symmetric','both'), B(2:5,2:8));

% Repeat some tests with int* uint* class types
%!assert(padarray(int8([1;2]),[1]), int8([0;1;2;0]));
%!assert(padarray(uint8([3,4]),[0,2]), uint8([0,0,3,4,0,0]));
%!assert(padarray(int16([1;2]),[1],4), int16([4;1;2;4]));
%!assert(padarray(uint16([1;2]),[1],0), uint16([0;1;2;0]));
%!assert(padarray(int32([1;2]),[1],int32(4),'pre'), int32([4;1;2]));
%!assert(padarray(uint32([1;2]),[1],6,'post'), uint32([1;2;6]));

% Test circular padding with int* uint* class types
%!test
%! A=int8([1,2,3;4,5,6]);
%! B=repmat(A,7,9);
%! assert(padarray(A,[1,2],'circular','pre'), B(2:4,2:6));
%! assert(padarray(A,[1,2],'circular','post'), B(3:5,4:8));
%! assert(padarray(A,[1,2],'circular','both'), B(2:5,2:8));
%! % This tests when padding is bigger than data
%! assert(padarray(A,[5,10],'circular','both'), B(2:13,3:25));

% Test replicate padding with int* uint* class types
%!test
%! A=uint8([1,2;3,4]);
%! B=[ones(10,5,"uint8")*1,ones(10,5,"uint8")*2; \
%!    ones(10,5,"uint8")*3,ones(10,5,"uint8")*4];
%! assert(padarray(A,[9,4],'replicate','pre'), B(1:11,1:6));
%! assert(padarray(A,[9,4],'replicate','post'), B(10:20,5:10));
%! assert(padarray(A,[9,4],'replicate','both'), B);

% Test symmetric padding with int* uint* class types
%!test
%! A=int16([1:3;4:6]);
%! HA=int16([3:-1:1;6:-1:4]);
%! VA=int16([4:6;1:3]);
%! VHA=int16([6:-1:4;3:-1:1]);
%! B=[VHA,VA,VHA; HA,A,HA; VHA,VA,VHA];
%! assert(padarray(A,[1,2],'symmetric','pre'), B(2:4,2:6));
%! assert(padarray(A,[1,2],'symmetric','post'), B(3:5,4:8));
%! assert(padarray(A,[1,2],'symmetric','both'), B(2:5,2:8));



%
% $Log: padarray.m,v $
% Revision 1.6  2005/09/08 02:00:17  pkienzle
% [for Bill Denney] isstr -> ischar
%
% Revision 1.5  2004/09/03 18:33:11  pkienzle
% skip tests which use cat(3,X,Y) for octave <= 2.1.57
%
% Revision 1.4  2004/09/03 13:37:10  jmones
% Corrected behaviour for int* and uint* types
%
% Revision 1.3  2004/08/15 19:21:50  jmones
% support column vector padsize
%
% Revision 1.2  2004/08/11 15:04:59  pkienzle
% Convert dos line endings to unix line endings
%
% Revision 1.1  2004/08/08 21:20:25  jmones
% uintlut and padarray functions added
%
%
