changeset 16944:1b3b3ee88284

Add optional extension argument to image IO functions. * imfinfo.m, private/core_imfinfo.m, imread.m, private/core_imread.m, imwrite.m: added support for optional EXT argument. * imwrite.m: in addition to support the extra EXT argument, removed list of supported image formats from help text, and suggested to use imformats() instead. * private/core_imwrite.m: remove usage of __magick_format_list__() which is being replaced by imformats(). Check validity of colormap earlier on. Removed tests that are repeated in imwrite.m (leftovers from copying the file). * private/imageIO.m: deal with separate filename and extension and ignore file existence checking if we are writing. * __magic_read__.cc: remove private function __magick_format_list__() that was only being used by core_imwrite.m and is being replaced by imformats().
author Carnë Draug <carandraug@octave.org>
date Wed, 10 Jul 2013 18:18:21 +0100
parents 0dab17e69a55
children bb7b99324267
files libinterp/dldfcn/__magick_read__.cc scripts/image/imfinfo.m scripts/image/imread.m scripts/image/imwrite.m scripts/image/private/core_imfinfo.m scripts/image/private/core_imread.m scripts/image/private/core_imwrite.m scripts/image/private/imageIO.m
diffstat 8 files changed, 140 insertions(+), 189 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/dldfcn/__magick_read__.cc	Tue Jul 09 18:53:06 2013 -0700
+++ b/libinterp/dldfcn/__magick_read__.cc	Wed Jul 10 18:18:21 2013 +0100
@@ -1206,66 +1206,3 @@
 ## No test needed for internal helper function.
 %!assert (1)
 */
-
-// Determine the file formats supported by GraphicsMagick.  This is
-// called once at the beginning of imread or imwrite to determine
-// exactly which file formats are supported, so error messages can be
-// displayed properly.
-
-DEFUN_DLD (__magick_format_list__, args, ,
-  "-*- texinfo -*-\n\
-@deftypefn {Loadable Function} {} __magick_format_list__ (@var{formats})\n\
-Undocumented internal function.\n\
-@end deftypefn")
-{
-  octave_value retval;
-
-#ifdef HAVE_MAGICK
-  maybe_initialize_magick ();
-
-  std::list<std::string> accepted_formats;
-
-  if (args.length () == 1)
-    {
-      Cell c = args (0).cell_value ();
-
-      if (! error_state)
-        {
-          for (octave_idx_type i = 0; i < c.nelem (); i++)
-            {
-              try
-                {
-                  std::string fmt = c.elem (i).string_value ();
-
-                  Magick::CoderInfo info(fmt);
-
-                  if (info.isReadable () && info.isWritable ())
-                    accepted_formats.push_back (fmt);
-                }
-              catch (Magick::Exception& e)
-                {
-                  // Do nothing: exception here are simply missing formats.
-                }
-            }
-        }
-      else
-        error ("__magick_format_list__: expecting a cell array of image format names");
-    }
-  else
-    print_usage ();
-
-  retval = Cell (accepted_formats);
-
-#else
-
-  error ("__magick_format_list__: not available in this version of Octave");
-
-#endif
-
-  return retval;
-}
-
-/*
-## No test needed for internal helper function.
-%!assert (1)
-*/
--- a/scripts/image/imfinfo.m	Tue Jul 09 18:53:06 2013 -0700
+++ b/scripts/image/imfinfo.m	Wed Jul 10 18:18:21 2013 +0100
@@ -18,12 +18,16 @@
 
 ## -*- texinfo -*-
 ## @deftypefn  {Function File} {@var{info} =} imfinfo (@var{filename})
+## @deftypefnx {Function File} {@var{info} =} imfinfo (@var{filename}, @var{ext})
 ## @deftypefnx {Function File} {@var{info} =} imfinfo (@var{url})
 ## Read image information from a file.
 ##
 ## @code{imfinfo} returns a structure containing information about the image
-## stored in the file @var{filename}.  The output structure contains the
-## following fields.
+## stored in the file @var{filename}.  If there is no file @var{filename},
+## and @var{ext} was specified, it will look for a file named @var{filename}
+## and extension @var{ext}, i.e., a file named @var{filename}.@var{ext}.
+##
+## The output structure @var{info} contains the following fields:
 ##
 ## @table @samp
 ## @item Filename
@@ -103,16 +107,18 @@
 ## FlashPix viewing parameters.
 ## @end table
 ##
-## @seealso{imread, imwrite, imshow}
+## @seealso{imread, imwrite, imshow, imformats}
 ## @end deftypefn
 
 ## Author: Soren Hauberg <hauberg@gmail.com>
 
-function info = imfinfo (filename)
-  if (nargin < 1)
+function info = imfinfo (varargin)
+  if (nargin < 1 || nargin > 2)
     print_usage ();
-  elseif (! ischar (filename))
+  elseif (! ischar (varargin{1}))
     error ("imfinfo: FILENAME must be a string");
+  elseif (nargin > 1 && ! ischar (varargin{2}))
+    error ("imfinfo: EXT must be a string");
   endif
-  info = imageIO (@core_imfinfo, "info", filename, filename);
+  info = imageIO (@core_imfinfo, "info", varargin, varargin);
 endfunction
--- a/scripts/image/imread.m	Tue Jul 09 18:53:06 2013 -0700
+++ b/scripts/image/imread.m	Wed Jul 10 18:18:21 2013 +0100
@@ -20,16 +20,17 @@
 ## along with Octave; see the file COPYING.  If not, see
 ## <http://www.gnu.org/licenses/>.
 
-## Author: Thomas L. Scofield <scofield@calvin.edu>
-## Author: Kristian Rumberg <kristianrumberg@gmail.com>
-## Author: Thomas Weber <thomas.weber.mail@gmail.com>
-## Author: Stefan van der Walt <stefan@sun.ac.za>
-## Author: Andy Adler
-
 ## -*- texinfo -*-
-## @deftypefn {Function File} {[@var{img}, @var{map}, @var{alpha}] =} imread (@var{filename})
+## @deftypefn  {Function File} {[@var{img}, @var{map}, @var{alpha}] =} imread (@var{filename})
+## @deftypefnx {Function File} {[@dots{}] =} imread (@var{filename}, @var{ext})
+## @deftypefnx {Function File} {[@dots{}] =} imread (@var{url})
 ## Read images from various file formats.
 ##
+## Reads an image as a matrix from the file @var{filename}.  If there is
+## no file @var{filename}, and @var{ext} was specified, it will look for
+## a file named @var{filename} and extension @var{ext}, i.e., a file named
+## @var{filename}.@var{ext}.
+##
 ## The size and numeric class of the output depends on the
 ## format of the image.  A color image is returned as an
 ## @nospell{MxNx3} matrix.  Gray-level and black-and-white images are
@@ -38,17 +39,32 @@
 ## class of the output: "uint8" or "uint16" for gray
 ## and color, and "logical" for black and white.
 ##
-## @seealso{imwrite, imfinfo}
+## @seealso{imwrite, imfinfo, imformats}
 ## @end deftypefn
 
-function varargout = imread (filename, varargin)
+## Author: Thomas L. Scofield <scofield@calvin.edu>
+## Author: Kristian Rumberg <kristianrumberg@gmail.com>
+## Author: Thomas Weber <thomas.weber.mail@gmail.com>
+## Author: Stefan van der Walt <stefan@sun.ac.za>
+## Author: Andy Adler
+
+function varargout = imread (varargin)
   if (nargin < 1)
     print_usage ();
-  elseif (! ischar (filename))
+  elseif (! ischar (varargin{1}))
     error ("imread: FILENAME must be a string");
   endif
-  varargout{1:nargout} = imageIO (@core_imread, "read", filename,
-                                  filename, varargin{:});
+  ## In case the file format was specified as a separate argument we
+  ## do this. imageIO() will ignore the second part if filename on its
+  ## own is enough. And if the second argument was a parameter name instead
+  ## of an extension, it is still going to be passed to the next function
+  ## since we are passing the whole function input as well.
+  filename = {varargin{1}};
+  if (nargin > 1 && ischar (varargin {2}))
+    filename{2} = varargin{2};
+  endif
+
+  varargout{1:nargout} = imageIO (@core_imread, "read", filename, varargin{:});
 endfunction
 
 %!testif HAVE_MAGICK
--- a/scripts/image/imwrite.m	Tue Jul 09 18:53:06 2013 -0700
+++ b/scripts/image/imwrite.m	Wed Jul 10 18:18:21 2013 +0100
@@ -18,13 +18,15 @@
 
 ## -*- texinfo -*-
 ## @deftypefn  {Function File} {} imwrite (@var{img}, @var{filename})
-## @deftypefnx {Function File} {} imwrite (@var{img}, @var{filename}, @var{fmt})
-## @deftypefnx {Function File} {} imwrite (@var{img}, @var{filename}, @var{fmt}, @var{p1}, @var{v1}, @dots{})
+## @deftypefnx {Function File} {} imwrite (@var{img}, @var{filename}, @var{ext})
+## @deftypefnx {Function File} {} imwrite (@var{img}, @var{filename}, @var{ext}, @var{p1}, @var{v1}, @dots{})
 ## @deftypefnx {Function File} {} imwrite (@var{img}, @var{map}, @var{filename}, @dots{})
 ## Write images in various file formats.
 ##
-## If @var{fmt} is not supplied, the file extension of @var{filename} is used
-## to determine the format.
+## If @var{ext} is not supplied, the file extension of @var{filename} is used
+## to determine the format.  The actual supported formats are dependent on
+## options made during the build of Octave.  Use @code{imformats} to check
+## the support of the different image formats.
 ##
 ## The parameter-value pairs (@var{p1}, @var{v1}, @dots{}) are optional.
 ## Currently the following options are supported for @t{JPEG} images:
@@ -36,31 +38,7 @@
 ## quality and lower compression.
 ## @end table
 ##
-## @strong{Supported Formats}
-## @multitable @columnfractions .33 .66
-## @headitem Extension @tab Format
-## @item bmp @tab Windows Bitmap
-## @item gif @tab Graphics Interchange Format
-## @item jpg and jpeg @tab Joint Photographic Experts Group
-## @item pbm @tab Portable Bitmap
-## @item pcx @tab
-## @item pgm @tab Portable Graymap
-## @item png @tab Portable Network Graphics
-## @item pnm @tab Portable Anymap
-## @item ppm @tab Portable Pixmap
-## @item ras @tab Sun Raster
-## @item tif and tiff @tab Tagged Image File Format
-## @item xwd @tab X11 Dump
-## @end multitable
-##
-## @strong{Unsupported Formats}
-## @multitable @columnfractions .33 .66
-## @headitem Extension @tab Format
-## @item hdf @tab Hierarchical Data Format V4
-## @item @nospell{jp2} and jpx @tab Joint Photographic Experts Group 2000
-## @end multitable
-##
-## @seealso{imread, imfinfo}
+## @seealso{imread, imfinfo, imformats}
 ## @end deftypefn
 
 function imwrite (varargin)
@@ -68,15 +46,24 @@
     print_usage ();
   endif
 
+  ## This input checking is a bit convoluted to support the multiple
+  ## ways the function can be called. Basically, after the image we
+  ## can have the filename or a colormap. If we have a colormap, then
+  ## the filename becomes the third argument. After that, we may have
+  ## the optional file extension.
   if (ischar (varargin{2}))
-    filename = varargin{2};
+    filename_idx = 2;
   elseif (nargin >= 3 && iscolormap (varargin{2}) && ! ischar (varargin{3}))
-    filename = varargin{3}'
+    filename_idx = 3;
   else
     error ("imwrite: no FILENAME specified");
   endif
-  varargout{1:nargout} = imageIO (@core_imwrite, "write", filename, varargin{:});
+  filename = {varargin{filename_idx}};
+  if (nargin > filename_idx + 1 && ischar (varargin {filename_idx + 1}))
+    filename{2} = varargin{filename_idx + 1};
+  endif
 
+  imageIO (@core_imwrite, "write", filename, varargin{:});
 endfunction
 
 %% Test input validation
--- a/scripts/image/private/core_imfinfo.m	Tue Jul 09 18:53:06 2013 -0700
+++ b/scripts/image/private/core_imfinfo.m	Wed Jul 10 18:18:21 2013 +0100
@@ -1,4 +1,5 @@
 ## Copyright (C) 2008-2012 Soren Hauberg
+## Copyright (C) 2013 Carnë Draug
 ##
 ## This file is part of Octave.
 ##
@@ -23,14 +24,16 @@
 
 ## Author: Soren Hauberg <hauberg@gmail.com>
 
-function info = core_imfinfo (filename)
+function info = core_imfinfo (filename, ext)
 
-  if (nargin < 1)
+  if (nargin < 1 || nargin > 2)
     print_usage ("imfinfo");
   endif
 
   if (! ischar (filename))
     error ("imfinfo: FILENAME must be a string");
+  elseif (nargin >= 2 && ! ischar (ext))
+    error ("imfinfo: EXT must be a string");
   endif
   filename = tilde_expand (filename);
 
@@ -38,18 +41,24 @@
   unwind_protect
 
     fn = file_in_path (IMAGE_PATH, filename);
+    if (isempty (fn))
+      ## We couldn't find the file so...
+      if (nargin >= 2)
+        ## try adding a possible file extesion
+        filename  = [filename "." ext];
+        fn        = file_in_path (IMAGE_PATH, filename);
+        if (isempty (fn))
+          error ("imfinfo: cannot find file %s", filename);
+        endif
+      else
+        ## try filename as an URL
+        [fn, status, msg] = urlwrite (filename, tmpnam ());
+        if (! status)
+          error ("imfinfo: cannot find or download %s: %s", filename, msg);
+        endif
+        delete_file = true;
+    endif
 
-    if (isempty (fn))
-      ## Couldn't find file. See if it's an URL.
-      [fn, status, msg] = urlwrite (filename, tmpnam ());
-      if (! status)
-        error ("imfinfo: cannot find %s", filename);
-      endif
-
-      if (! isempty (fn))
-        delete_file = true;
-      endif
-    endif
     info = __magick_finfo__ (fn);
 
   unwind_protect_cleanup
--- a/scripts/image/private/core_imread.m	Tue Jul 09 18:53:06 2013 -0700
+++ b/scripts/image/private/core_imread.m	Wed Jul 10 18:18:21 2013 +0100
@@ -1,3 +1,4 @@
+## Copyright (C) 2013 Carnë Draug
 ## Copyright (C) 2008-2012 Thomas L. Scofield
 ## Copyright (C) 2008 Kristian Rumberg
 ## Copyright (C) 2006 Thomas Weber
@@ -20,41 +21,33 @@
 ## along with Octave; see the file COPYING.  If not, see
 ## <http://www.gnu.org/licenses/>.
 
+## This function does all the work of imread. It exists here as private
+## function so that imread can use other functions if imformats is
+## configured to. It is also needed so that imformats can create a
+## function handle for it.
+
+## Author: Carnë Draug <carandraug@octave.org>
 ## Author: Thomas L. Scofield <scofield@calvin.edu>
 ## Author: Kristian Rumberg <kristianrumberg@gmail.com>
 ## Author: Thomas Weber <thomas.weber.mail@gmail.com>
 ## Author: Stefan van der Walt <stefan@sun.ac.za>
 ## Author: Andy Adler
 
-## -*- texinfo -*-
-## @deftypefn {Function File} {[@var{img}, @var{map}, @var{alpha}] =} imread (@var{filename})
-## Read images from various file formats.
-##
-## The size and numeric class of the output depends on the
-## format of the image.  A color image is returned as an
-## @nospell{MxNx3} matrix.  Gray-level and black-and-white images are
-## of size @nospell{MxN}.
-## The color depth of the image determines the numeric
-## class of the output: "uint8" or "uint16" for gray
-## and color, and "logical" for black and white.
-##
-## @seealso{imwrite, imfinfo}
-## @end deftypefn
-
 function varargout = core_imread (filename, varargin)
 
   if (nargin < 1)
     print_usage ("imread");
-  endif
-
-  if (! ischar (filename))
+  elseif (! ischar (filename))
     error ("imread: FILENAME must be a string");
   endif
 
-  filename = tilde_expand (filename);
-
-  fn = file_in_path (IMAGE_PATH, filename);
-
+  filename  = tilde_expand (filename);
+  fn        = file_in_path (IMAGE_PATH, filename);
+  if (isempty (fn) && nargin >= 2 && ischar (varargin{1}))
+    ## if we can't find the file, check if the next input is the file extension
+    filename  = [filename "." varargin{1}];
+    fn        = file_in_path (IMAGE_PATH, filename);
+  endif
   if (isempty (fn))
     error ("imread: cannot find %s", filename);
   endif
--- a/scripts/image/private/core_imwrite.m	Tue Jul 09 18:53:06 2013 -0700
+++ b/scripts/image/private/core_imwrite.m	Wed Jul 10 18:18:21 2013 +0100
@@ -23,12 +23,6 @@
 
 function core_imwrite (img, varargin)
 
-  persistent imwrite_possible_formats = {
-    "bmp"; "gif"; "jp2"; "jpg"; "jpx"; "jpeg"; "hdf"; "pbm"; "pcx";
-    "pgm"; "png"; "pnm"; "ppm"; "ras"; "tif"; "tiff"; "xwd" };
-
-  persistent accepted_formats = __magick_format_list__ (imwrite_possible_formats);
-
   if (nargin < 2 || ! (isnumeric (img) || islogical (img)))
     print_usage ("imwrite");
   endif
@@ -39,8 +33,8 @@
   offset = 1;
   if (isnumeric (varargin{1}))
     map = varargin{1};
-    if (isempty (map))
-      error ("imwrite: colormap must not be empty");
+    if (! iscolormap (map))
+      error ("imwrite: invalid COLORMAP");
     endif
     offset = 2;
   endif
@@ -55,18 +49,18 @@
     print_usage ("imwrite");
   endif
   if (offset < length (varargin))
-    has_param_list = 1;
+    has_param_list = true;
     for ii = offset:2:(length (varargin) - 1)
       options.(varargin{ii}) = varargin{ii + 1};
     endfor
   else
-    has_param_list = 0;
+    has_param_list = false;
   endif
 
   filename = tilde_expand (filename);
 
   if (isempty (fmt))
-    [d, n, fmt] = fileparts (filename);
+    [~, ~, fmt] = fileparts (filename);
     if (! isempty (fmt))
       fmt = fmt(2:end);
     endif
@@ -80,10 +74,6 @@
     error ("imwrite: sparse images not supported");
   endif
 
-  if (! strcmp (fmt, accepted_formats))
-    error ("imwrite: %s: unsupported or invalid image format", fmt);
-  endif
-
   img_class = class (img);
   map_class = class (map);
   nd = ndims (img);
@@ -97,6 +87,7 @@
       if (nd == 3 && size (img, 3) < 3)
         error ("imwrite: invalid dimensions for truecolor image");
       endif
+      ## FIXME: why nd>5? Shouldn't it be nd>4? What's the 5th dimension for?
       if (nd > 5)
         error ("imwrite: invalid %d-dimensional image data", nd);
       endif
@@ -139,16 +130,3 @@
   endif
 
 endfunction
-
-
-%% Test input validation
-%!error imwrite ()                            # Wrong # of args
-%!error imwrite (1)                           # Wrong # of args
-%!error imwrite ({"cell"}, "filename.jpg")    # Wrong class for img
-%!error imwrite (1, [], "filename.jpg")       # Empty image map
-%!error imwrite (1, 2, 3)                     # No filename specified
-%!error imwrite (1, "filename")               # No fmt specified
-%!error imwrite (1, "filename", "junk")       # Invalid fmt specified
-%!error imwrite ([], "filename.jpg")          # Empty img matrix
-%!error imwrite (spones (2), "filename.jpg")  # Invalid sparse img
-
--- a/scripts/image/private/imageIO.m	Tue Jul 09 18:53:06 2013 -0700
+++ b/scripts/image/private/imageIO.m	Wed Jul 10 18:18:21 2013 +0100
@@ -16,29 +16,54 @@
 ## along with Octave; see the file COPYING.  If not, see
 ## <http://www.gnu.org/licenses/>.
 
-## This function simply connects the function that call it to all
+## This function simply connects the function that calls it to all
 ## other imageIO functions. It does it by checking the file extension
 ## of the file and redirecting to the appropriate function after checking
 ## with imformats.
 ##
 ## First argument is a function handle for the default imageIO function (what
-## to use if the extensino is not listed by imformats), second argument is
-## the fieldname in the struct returned by imformats with a function handle
-## for the function to use, and all the others are the input argument mean for
-## the destination function.
+## to use if the file extension for the image file is not listed by imformats).
+## Second argument is the fieldname in the struct returned by imformats with a
+## function handle for the function to use. Third argument is a cell array, its
+## first element the filename, and the second, an optional file extension to
+## add to filename, if filename alone does not exist. All the others are the
+## original input arguments passed to the original imageIO function which will
+## be passed on to the destination function.
 ##
-## No input checking whatsoever is performed. That is already done by the
+## No input checking whatsoever is performed. That should be performed by the
 ## function calling it.
 
 function varargout = imageIO (core_func, fieldname, filename, varargin)
-  [~, ~, ext] = fileparts (filename);
-  ## remove dot from extension
-  if (! isempty (ext) && strcmp (ext(1), "."));
-    ext(1) = [];
+
+  ## It should not be this function job to check if the file exists or not.
+  ## However, we need to know the file extension to use with imformats and
+  ## that means we need to know the actual filename that will be used which
+  ## is dependent on whether a file exists.
+  ##
+  ## If a file named filename{1} exists, then that's it, we will use that
+  ## wether it has an extension or not. If it does not exist and we have
+  ## something in filename{2}, then we will consider it the file extension.
+  ## Note the detail that if we find a file using filename{1} only, then we
+  ## should completely ignore filename{2}. It won't even be used by
+  ## imformats() at all, even if filename{1} has no extension to use with
+  ## imformats().
+  ##
+  ## To further complicate things, when we are going to be writing a
+  ## file, whether the file exists or not does not matter.
+  if (isscalar (filename) || (strcmp (fieldname, "write") &&
+      ! isempty (file_in_path (IMAGE_PATH, filename{1}))))
+    [~, ~, ext] = fileparts (filename{1});
+    if (! isempty (ext))
+      ## remove dot from extension
+      ext(1) = [];
+    endif
+  else
+    ext = filename{2};
   endif
-  fmt = imformats ("ext");
+
+  fmt = imformats (ext);
   ## When there is no match, fmt will be a 1x1 structure with no fields,
-  ## so we can't just use isempty ().
+  ## so we can't just use `isempty (fmt)'.
   if (isempty (fieldnames (fmt)))
     varargout{1:nargout} = core_func (varargin{:});
   else