changeset 16901:861516dcad19

New function imformats. * imformats.m: the new function. * __magick_read__.cc: created new internal function __magick__formats__ to be used by imformats(). * NEWS: add imformats to list of new functions. * image.txi: add reference to new function to the Octave manual. * module.mk: include new function in the build system.
author Carnë Draug <carandraug@octave.org>
date Thu, 04 Jul 2013 18:35:11 +0100
parents 434a0b29ab12
children 51c1076a9c13
files NEWS doc/interpreter/image.txi libinterp/dldfcn/__magick_read__.cc scripts/image/imformats.m scripts/image/module.mk
diffstat 5 files changed, 333 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Fri Jul 05 07:12:18 2013 -0400
+++ b/NEWS	Thu Jul 04 18:35:11 2013 +0100
@@ -200,6 +200,7 @@
       debug_jit                   importdata      struct2hdl          
       doc_cache_create            iscolormap      tetramesh           
       ellipj                      jit_enable      waterfall  
+      imformats
 
  ** Deprecated functions.
 
--- a/doc/interpreter/image.txi	Fri Jul 05 07:12:18 2013 -0400
+++ b/doc/interpreter/image.txi	Thu Jul 04 18:35:11 2013 +0100
@@ -77,6 +77,8 @@
 
 @DOCSTRING(imfinfo)
 
+@DOCSTRING(imformats)
+
 @node Displaying Images
 @section Displaying Images
 
--- a/libinterp/dldfcn/__magick_read__.cc	Fri Jul 05 07:12:18 2013 -0400
+++ b/libinterp/dldfcn/__magick_read__.cc	Thu Jul 04 18:35:11 2013 +0100
@@ -1152,6 +1152,59 @@
 
 #undef GET_PARAM
 
+DEFUN_DLD (__magick_formats__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __magick_imformats__ (@var{formats})\n\
+Fill formats info with GraphicsMagick CoderInfo.\n\
+@end deftypefn")
+{
+  octave_value retval;
+#ifndef HAVE_MAGICK
+  gripe_disabled_feature ("imformats", "Image IO");
+#else
+  if (args.length () != 1 || ! args (0).is_map ())
+    {
+      print_usage ();
+      return retval;
+    }
+  octave_map formats = args(0).map_value ();
+
+  maybe_initialize_magick ();
+  for (octave_idx_type idx = 0; idx < formats.numel (); idx++)
+    {
+      try
+        {
+          octave_scalar_map fmt = formats.checkelem (idx);
+          Magick::CoderInfo coder (fmt.getfield ("coder").string_value ());
+
+          fmt.setfield ("description", octave_value (coder.description ()));
+          fmt.setfield ("multipage", coder.isMultiFrame () ? true : false);
+          // default for read and write is a function handle. If we can't
+          // read or write them, them set it to an empty value
+          if (! coder.isReadable ())
+            fmt.setfield ("read",  Matrix ());
+          if (! coder.isWritable ())
+            fmt.setfield ("write", Matrix ());
+          formats.fast_elem_insert (idx, fmt);
+        }
+      catch (Magick::Exception& e)
+        {
+          // Exception here are missing formats. So we remove the format
+          // from the structure and reduce idx.
+          formats.delete_elements (idx);
+          idx--;
+        }
+    }
+  retval = formats;
+#endif
+  return retval;
+}
+
+/*
+## 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/image/imformats.m	Thu Jul 04 18:35:11 2013 +0100
@@ -0,0 +1,276 @@
+## Copyright (C) 2013 Carnë Draug
+##
+## This file is part of Octave.
+##
+## Octave 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.
+##
+## Octave 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 Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {} imformats ()
+## @deftypefnx {Function File} {@var{formats} =} imformats (@var{ext})
+## @deftypefnx {Function File} {@var{formats} =} imformats (@var{format})
+## @deftypefnx {Function File} {@var{formats} =} imformats ("add", @var{format})
+## @deftypefnx {Function File} {@var{formats} =} imformats ("remove", @var{ext})
+## @deftypefnx {Function File} {@var{formats} =} imformats ("update", @var{ext}, @var{format})
+## @deftypefnx {Function File} {@var{formats} =} imformats ("factory")
+## Manage supported image formats.
+##
+## @var{formats} is a structure with information about each supported file
+## format, or from a specific format @var{ext}, the value displayed on the
+## field @code{ext}.  It contains the following fields:
+##
+## @table @asis
+## @item ext
+## The name of the file format.  This may match the file extension but Octave
+## will automatically detect the file format.
+## @item description
+## A long description of the file format.
+## @item isa
+## A function handle to confirm if a file is of the specified format.
+## @item write
+## A function handle to write if a file is of the specified format.
+## @item read
+## A function handle to open files the specified format.
+## @item info
+## A function handle to obtain image information of the specified format.
+## @item alpha
+## Logical value if format supports alpha channel (transparency or matte).
+## @item multipage
+## Logical value if format supports multipage (multiple images per file).
+## @end table
+##
+## It is possible to change the way Octave manages file formats with the options
+## @code{"add"}, @code{"remove"}, and @code{"update"}, and supplying a
+## structure @var{format} with the required fields.  The option
+## @code{"factory"} resets the configuration to the default.
+##
+## This can be used by Octave packages to extend the image reading capabilities
+## Octave, through use of the PKG_ADD and PKG_DEL commands.
+##
+## @seealso{imfinfo, imread, imwrite}
+## @end deftypefn
+
+## Author: Carnë Draug <carandraug@octave.org>
+
+function varargout = imformats (arg1, arg2, arg3)
+  if (nargin > 3)
+    print_usage ();
+  endif
+
+  persistent formats = default_formats ();
+
+  if (nargin == 0 && nargout == 0)
+    error ("imformats: pretty print not yet implemented.");
+  elseif (nargin >= 1)
+    if (isstruct (arg1))
+      arrayfun (@is_valid_format, arg1);
+      ## FIXME: what is the return value in this situation?
+      formats = arg1;
+
+    elseif (ischar (arg1))
+      switch (tolower (arg1))
+        case "add",
+          if (! isstruct (arg2))
+            error ("imformats: FORMAT to %s must be a structure.", arg1);
+          endif
+          arrayfun (@is_valid_format, arg2);
+          formats(end + numel (b)) = arg2;
+          varargin{1} = formats;
+
+        case {"remove", "update"},
+          if (! ischar (arg2))
+            error ("imformats: EXT to %s must be a string.", arg1);
+          endif
+          ## FIXME: suppose a format with multiple extensions. If one of
+          ##        them is requested to be removed, should we remove the
+          ##        whole format, or just that extension from the format?
+          match = find_ext_idx (formats, arg2)
+          if (! any (match))
+            error ("imformats: no EXT `%s' found.", arg2);
+          endif
+          if (strcmpi (arg1, "remove"))
+            formats(match) = [];
+          else
+            ## then it's update
+            if (! isstruct (arg3))
+              error ("imformats: FORMAT to update must be a structure.");
+            endif
+            is_valid_format (arg3);
+            formats(match) = arg3;
+          endif
+          varargin{1} = formats;
+
+        case "factory",
+          formats = default_formats ();
+        otherwise
+          ## then we look for a format with that extension.
+          match = find_ext_idx (formats, arg1)
+          ## For matlab compatibility, if we don't find any format we must
+          ## return an empty struct with NO fields. We can't use match as mask
+          if (any (match))
+            varargin{1} = formats(match);
+          else
+            varargin{1} = struct ();
+          endif
+      endswitch
+    else
+      error ("imformats: first argument must be either a structure or string.");
+    endif
+  else
+    varargout{1} = formats;
+  endif
+endfunction
+
+function formats = default_formats ()
+
+  ## The available formats are dependent on what the user has installed at
+  ## a given time, and how GraphicsMagick was built. Checking for
+  ## GraphicsMagick features when building Octave is not enough since it
+  ## delegates some of them to external programs which can be removed or
+  ## installed at any time.
+  ## The recommended method would be to use CoderInfoList() to get a list of
+  ## all available coders and try to write and read back a small test image.
+  ## But this will not work since some coders are readable or writable only.
+  ## It will still fail if we test only the ones marked as readable and
+  ## writable because some RW coders are not of image formats (NULL, 8BIM,
+  ## or EXIF for example).
+  ## So we'd need a blacklist (unacceptable because a `bad' coder may be
+  ## added later) or a whitelist. A whitelist means that even with a
+  ## super-fancy recent build of GraphicsMagick, some formats won't be listed
+  ## by imformats but in truth, we will still be able to read and write them
+  ## since imread() and imwrite() will give it a try anyway.
+  ##
+  ## For more info and comments from the GraphicsMagick main developer, see
+  ## http://sourceforge.net/mailarchive/forum.php?thread_name=alpine.GSO.2.01.1304301916050.2267%40freddy.simplesystems.org&forum_name=graphicsmagick-help
+
+  persistent formats = struct ( "coder", {},
+                                "ext", {},
+                                "isa", {},
+                                "info", {},
+                                "read", {},
+                                "write", {},
+                                "alpha", {},
+                                "description", {},
+                                "multipage", {});
+
+  ## Image IO abilities won't change during the same Octave session,
+  ## there's no need to go and calculate it all over again if we are
+  ## requested to reset back to factory.
+  if (! isempty (formats))
+    return
+  endif
+
+  ##      Building the formats info
+  ##
+  ## As mentioned above we start with a whitelist of coders. Since the
+  ## GraphicsMagick build may be missing some coders, we will remove those
+  ## from the list. Some info can be obtained directly from GraphicsMagick
+  ## through the CoderInfo object. However, some will need to be hardcoded.
+  ##
+  ## The association between file extensions and coders needs to be done
+  ## with a manually coded list (file extensions do not define the image
+  ## format and GraphicsMagick will not be fooled by changing the extension).
+  ##
+  ## We can get the read, write, description and multipage fields from
+  ## CoderInfo in C++. We should do the same for alpha (GraphicsMagick
+  ## calls it matte) but it's not available from CoderInfo. The only way to
+  ## check it is to create a sample image with each coder, then try to read
+  ## it back with GraphicsMagick and use the matte method on the Image class.
+  ## But making such test for each Octave session... meh! While technically
+  ## it may be possible that the same coder has different support for alpha
+  ## channel in different versions and builds, this doesn't seem to happen.
+  ## So we also hardcode those. In the future, maybe the CoderInfo class will
+  ## have a matte method like it does for multipage.
+  ##
+  ## Other notes: some formats have more than one coder that do the same. For
+  ## example, for jpeg images there is both the JPG and JPEG coders. However,
+  ## it seems that when reading images, GraphicsMagick only uses one of them
+  ## and that's the one we list (it's the one reported by imfinfo and that we
+  ## can use for isa). However, in some cases GraphicsMagick seems to rely
+  ## uniquely on the file extension ((JBIG and JBG at least. Create an image
+  ## with each of those coders, swap their extension and it will report the
+  ## other coder). We don't have such cases on the whitelist but if we did, we
+  ## would need two entries for such cases.
+
+  ## each row: 1st => Coder, 2nd=> file extensions, 3rd=> alpha
+  coders = {"BMP",  {"bmp"},          true;
+            "CUR",  {"cur"},          false;
+            "GIF",  {"gif"},          true;
+            "ICO",  {"ico"},          true;
+            "JBG",  {"jbg"},          false;
+            "JBIG", {"jbig"},         false;
+            "JP2",  {"jp2", "jpx"},   true;
+            "JPEG", {"jpg", "jpeg"},  false; # there is also a JPG coder
+            "PBM",  {"pbm"},          false;
+            "PCX",  {"pcx"},          true;
+            "PGM",  {"pgm"},          false;
+            "PGM",  {"pgm"},          false;
+            "PNG",  {"png"},          true;
+            ## PNM is a family of formats supporting portable bitmaps (PBM),
+            ## graymaps (PGM), and pixmaps (PPM). There is no file format
+            ## associated with pnm itself. If PNM is used as the output format
+            ## specifier, then GraphicsMagick automatically selects the most
+            ## appropriate format to represent the image.
+            "PNM",  {"pnm"},          true;
+            "PPM",  {"ppm"},          false;
+            "SUN",  {"ras"},          true; # SUN Rasterfile
+            "TGA",  {"tga", "tpic"},  true;
+            "TIFF", {"tif", "tiff"},  true;
+            "XBM",  {"xbm"},          false;
+            "XPM",  {"xpm"},          true;
+            "XWD",  {"xwd"},          false;
+            };
+
+  for fidx = 1: rows(coders)
+    formats(fidx).coder = coders{fidx, 1};
+    formats(fidx).ext   = coders{fidx, 2};
+    formats(fidx).alpha = coders{fidx, 3};
+    ## default isa is to check if the format returned by imfinfo is the coder
+    formats(fidx).isa   = @(x) isa_magick (coders{fidx,1}, x);
+  endfor
+
+  ## the default info, read, and write functions
+  [formats.info ] = deal (@imfinfo);
+  [formats.read ] = deal (@imread);
+  [formats.write] = deal (@imread);
+
+  ## fills rest of format information by checking with GraphicsMagick
+  formats = __magick_formats__ (formats);
+endfunction
+
+function is_valid_format (format)
+  ## the minimal list of fields required in the structure. We don't
+  ## require multipage because it doesn't exist in matlab
+  min_fields  = {"ext", "read", "isa", "write", "info", "alpha", "description"};
+  fields_mask = cellfun (@(x) isfield (format, x), min_fields);
+  if (! all (fields_mask))
+    error ("imformats: structure has missing field `%s'.", min_fields(! fields_mask){1});
+  endif
+endfunction
+
+function match = find_ext_idx (formats, ext)
+  ## FIXME: is matlab sensitive to file extensions?
+  ## XXX: what should we do if there's more than one hit?
+  ##      Should this function prevent the addition of
+  ##      duplicated extensions?
+  match = cellfun (@(x) any (strcmp (x, ext)), {formats.ext});
+endfunction
+
+function bool = isa_magick (coder, filename)
+  bool = false;
+  try
+    info = imfinfo (filename);
+    bool = strcmp (coder, info.Format);
+  end_try_catch
+endfunction
--- a/scripts/image/module.mk	Fri Jul 05 07:12:18 2013 -0400
+++ b/scripts/image/module.mk	Thu Jul 04 18:35:11 2013 +0100
@@ -25,6 +25,7 @@
   image/image.m \
   image/imagesc.m \
   image/imfinfo.m \
+  image/imformats.m \
   image/imread.m \
   image/imshow.m \
   image/imwrite.m \