# HG changeset patch
# User Adam H Aitkenhead <adamhaitkenhead@gmail.com>
# Date 1367661223 -3600
# Node ID 5d6243c2acbff82a792f069126f735e53796a1f7
# Parent  a1f613e5066d4cd442732e1c24ccb38c6154480f
rgb2ind.m: Enable handling of ND images, and enable n,tol,map,dither as inputs.

* rgb2ind.m: Enable handling of ND images, and enable n,tol,map,dither as inputs.

diff -r a1f613e5066d -r 5d6243c2acbf scripts/image/rgb2ind.m
--- a/scripts/image/rgb2ind.m	Sat May 04 09:37:28 2013 +0200
+++ b/scripts/image/rgb2ind.m	Sat May 04 10:53:43 2013 +0100
@@ -1,5 +1,6 @@
 ## Copyright (C) 1994-2012 John W. Eaton
 ## Copyright (C) 2012 Carnë Draug
+## Copyright (C) 2013 Adam H Aitkenhead
 ##
 ## This file is part of Octave.
 ##
@@ -19,50 +20,148 @@
 
 ## -*- texinfo -*-
 ## @deftypefn  {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{rgb})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{rgb}, @var{map})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{rgb}, @var{n})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{rgb}, @var{tol})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{rgb}, @var{map}, @var{dither_option})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{rgb}, @var{n}, @var{dither_option})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{rgb}, @var{tol}, @var{dither_option})
 ## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{R}, @var{G}, @var{B})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{R}, @var{G}, @var{B}, @var{map})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{R}, @var{G}, @var{B}, @var{n})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{R}, @var{G}, @var{B}, @var{tol})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{R}, @var{G}, @var{B}, @var{map}, @var{dither_option})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{R}, @var{G}, @var{B}, @var{n}, @var{dither_option})
+## @deftypefnx {Function File} {[@var{x}, @var{map}] =} rgb2ind (@var{R}, @var{G}, @var{B}, @var{tol}, @var{dither_option})
 ## Convert an image in red-green-blue (RGB) color space to an indexed image.
+##
+## The input image @var{rgb} must be an N-dimensional RGB image (MxNxO...x3 array) where M,N,O... are the
+## image dimensions, and the final dimension contains the values in the red, green and blue
+## channels.  Alternatively, the red, green and blue color channels can be input as separate arrays @var{R}, @var{G} and  @var{B}.
+##
+## @var{map} defines the colormap to be used.  Alternatively, @var{n} or @var{tol} may be used to define
+## the maximum number of colors to use in an automatically generated colormap.  @var{n} is related to @var{tol}
+## by:  @var{n} = (floor (1/@var{tol}) + 1)^3;
+## @var{tol} must be >0 and <=1.
+##
+## @var{dither_option} is a string which enables or disables dithering: 'dither' (default) or 'nodither'.
+##
 ## @seealso{ind2rgb, rgb2hsv, rgb2ntsc}
 ## @end deftypefn
 
-## FIXME: This function has a very different syntax than the Matlab
-##        one of the same name.
-##        Octave function does not support N, MAP, DITHER, or TOL arguments.
-
 ## Author: Tony Richardson <arichard@stark.cc.oh.us>
 ## Created: July 1994
 ## Adapted-By: jwe
 
-function [x, map] = rgb2ind (R, G, B)
+function [x, map] = rgb2ind (varargin)
 
-  if (nargin != 1 && nargin != 3)
+  ## Gather the inputs
+  if (nargin < 1 || nargin > 6)
     print_usage ();
+  else
+
+    ## Test for dither_option, by checking if the final input is a string
+    if ischar (varargin{end})
+      dither_option = varargin{end};
+      dither_check  = true;
+    else
+      dither_option = 'dither';
+      dither_check  = false;
+    endif
+
+    ## Read the rgb input
+    if (nargin-dither_check==1 || nargin-dither_check==2)
+
+      rgb = varargin{1};
+      if (size (rgb)(end) != 3)
+        error ("rgb2ind: The input image must be an RGB image (MxNxO...x3 array).");
+      elseif (min (rgb(:)) < 0 || max (rgb(:)) > 1)
+        error ("rgb2ind: The input image must contain values between 0 and 1.");
+      endif
+      if (nargin-dither_check==2)
+        option = varargin{2};
+      else
+        dither_option = 'nodither';
+      endif
+
+    ## Read the R,G,B inputs
+    elseif (nargin-dither_check==3 || nargin-dither_check==4)
+
+      R = varargin{1};
+      G = varargin{2};
+      B = varargin{3};
+      if (! size_equal (R, G, B))
+        error ("rgb2ind: R, G, and B must have the same size");
+      endif
+      if (nargin-dither_check==4)
+        option = varargin{4};
+      else
+        dither_option = 'nodither';
+      endif
+      
+      rgb = reshape ([R(:), G(:), B(:)], [size(R), 3]);
+
+    endif
+  endif
+      
+  ## Apply a limited colormap if required
+  if (exist ('option','var'))
+
+    if (size (option,1)==1)
+
+      if option>0 && option<=1
+        ## option: tol
+        tol = option;
+        n   = (floor (1/option) + 1)^3;
+      else
+        ## option: n
+        n   = option;
+      endif
+      optionstr = sprintf ('-colors %d',n);
+      
+    else
+
+      ## option: map
+      map = option;
+      if (isequal (map(:,1),map(:,2)) || isequal (map(:,1),map(:,3)) || isequal (map(:,2),map(:,3)))
+        error ('rgb2ind: The colormap cannot contain matching R,G, or B channels.')
+      endif
+      fnmap = tmpnam;
+      map = reshape (map, size (map, 1), 1, 3);
+      imwrite (map, fnmap, 'tiff');
+      optionstr = sprintf ('-map %s', fnmap);
+      
+    endif
+  
+    ## Prepare the Graphicsmagick dithering option
+    if strcmp (dither_option, 'nodither')
+      ditherstr = '+dither';
+    elseif strcmp (dither_option, 'dither')
+      ditherstr = '-dither';
+    endif
+      
+    ## Perform the image processing using Graphicsmagick
+    fna = tmpnam;
+    fnb = tmpnam;
+    imwrite (rgb, fna, 'tiff');
+    gmstr = sprintf ('gm convert %s %s %s %s', fna, ditherstr, optionstr, fnb);
+    system (gmstr);
+    rgb = imread (fnb);
+    
   endif
 
-  if (nargin == 1)
-    rgb = R;
-    if (ndims (rgb) != 3 || size (rgb, 3) != 3)
-      error ("rgb2ind: argument is not an RGB image");
-    else
-      R = rgb(:,:,1);
-      G = rgb(:,:,2);
-      B = rgb(:,:,3);
-    endif
-  elseif (! size_equal (R, G, B))
-    error ("rgb2ind: R, G, and B must have the same size");
-  endif
-
-  x = reshape (1:numel (R), size (R));
-
-  map    = unique ([R(:) G(:) B(:)], "rows");
-  [~, x] = ismember ([R(:) G(:) B(:)], map, "rows");
-  x      = reshape (x, size (R));
+  ## Conversion of rgb image to x,map
+  sz = size (rgb);
+  pr = prod (sz(1:end-1));
+  x = zeros (sz(1:end-1));
+  [map,~,x(:)] = unique (reshape(rgb, [pr, 3]), "rows");
 
   ## a colormap is of class double and values between 0 and 1
-  switch (class (R))
+  switch (class (rgb))
     case {"single", "double", "logical"}
       ## do nothing, return the same
     case {"uint8", "uint16"}
-      map = double (map) / double (intmax (class (R)));
+      map = double (map) / double (intmax (class (rgb)));
     case "int16"
       map = (double (im) + 32768) / 65535;
     otherwise
