view scripts/image/imformats.m @ 28789:28de41192f3c

Eliminate unneeded verification of nargin, nargout in m-files. * FIRfilter.m, FIRfilter_aggregation.m, get.m, polynomial.m, polynomial_superiorto.m, polynomial2.m, makeUniqueStrings.m, base64decode.m, base64encode.m, cd.m, lin2mu.m, record.m, sound.m, soundsc.m, accumarray.m, accumdim.m, bitcmp.m, bitset.m, cart2pol.m, celldisp.m, circshift.m, cplxpair.m, cumtrapz.m, flip.m, idivide.m, interpft.m, logspace.m, pol2cart.m, polyarea.m, postpad.m, prepad.m, rat.m, rot90.m, rotdim.m, shift.m, shiftdim.m, sortrows.m, trapz.m, dsearch.m, dsearchn.m, getappdata.m, getpixelposition.m, guidata.m, guihandles.m, isappdata.m, listfonts.m, uigetdir.m, waitforbuttonpress.m, __makeinfo__.m, doc.m, get_first_help_sentence.m, autumn.m, bone.m, brighten.m, cmpermute.m, cmunique.m, colorcube.m, contrast.m, cool.m, copper.m, cubehelix.m, flag.m, gray.m, gray2ind.m, hot.m, hsv.m, im2double.m, im2frame.m, imformats.m, jet.m, lines.m, ocean.m, pink.m, prism.m, rainbow.m, rgbplot.m, spinmap.m, spring.m, summer.m, viridis.m, white.m, winter.m, beep.m, importdata.m, is_valid_file_id.m, javachk.m, javaclasspath.m, findstr.m, genvarname.m, strmatch.m, bandwidth.m, commutation_matrix.m, cond.m, cross.m, isdefinite.m, ishermitian.m, issymmetric.m, krylov.m, linsolve.m, logm.m, lscov.m, null.m, ordeig.m, orth.m, rank.m, rref.m, vecnorm.m, bunzip2.m, citation.m, computer.m, copyfile.m, dir.m, dos.m, fileattrib.m, gunzip.m, inputParser.m, inputname.m, ismac.m, ispc.m, isunix.m, license.m, list_primes.m, methods.m, mkdir.m, movefile.m, nargchk.m, news.m, orderfields.m, recycle.m, tar.m, unix.m, unpack.m, untar.m, unzip.m, ver.m, version.m, what.m, zip.m, decic.m, fminbnd.m, fminunc.m, fsolve.m, fzero.m, glpk.m, humps.m, lsqnonneg.m, optimget.m, pqpnonneg.m, sqp.m, pathdef.m, camlookat.m, hidden.m, specular.m, plotmatrix.m, smooth3.m, sombrero.m, stemleaf.m, __gnuplot_drawnow__.m, __opengl_info__.m, ancestor.m, cla.m, close.m, closereq.m, copyobj.m, gca.m, gcf.m, ginput.m, graphics_toolkit.m, groot.m, hgload.m, hgsave.m, isgraphics.m, ishold.m, linkaxes.m, meshgrid.m, newplot.m, refresh.m, refreshdata.m, rotate.m, saveas.m, struct2hdl.m, conv.m, mkpp.m, mpoles.m, padecoef.m, pchip.m, polyder.m, polyfit.m, polygcd.m, polyint.m, polyout.m, polyval.m, ppder.m, ppint.m, getpref.m, ispref.m, rmpref.m, profexport.m, profshow.m, powerset.m, arch_fit.m, arma_rnd.m, blackman.m, detrend.m, diffpara.m, fftconv.m, fftfilt.m, filter2.m, freqz.m, freqz_plot.m, hamming.m, hanning.m, sinetone.m, sinewave.m, spectral_adf.m, spectral_xdf.m, stft.m, unwrap.m, gplot.m, ichol.m, ilu.m, spdiags.m, sprand.m, sprandn.m, spstats.m, svds.m, treelayout.m, treeplot.m, betainc.m, betaincinv.m, ellipke.m, gammainc.m, gammaincinv.m, legendre.m, pow2.m, hankel.m, pascal.m, rosser.m, toeplitz.m, bounds.m, corr.m, cov.m, histc.m, kendall.m, kurtosis.m, mad.m, mode.m, moment.m, prctile.m, quantile.m, range.m, ranks.m, run_count.m, skewness.m, spearman.m, std.m, var.m, zscore.m, dec2base.m, dec2bin.m, dec2hex.m, index.m, mat2str.m, native2unicode.m, ostrsplit.m, strjoin.m, strjust.m, strtok.m, substr.m, unicode2native.m, untabify.m, __debug_octave__.m, demo.m, example.m, fail.m, oruntests.m, dump_demos.m, speed.m, test.m, date.m, datenum.m, datestr.m, datevec.m, is_leap_year.m, now.m, weekday.m: Eliminate unneeded verification of nargin, nargout in m-files now that the interpreter checks these values.
author Rik <rik@octave.org>
date Thu, 24 Sep 2020 14:44:58 -0700
parents 5a07c798eb08
children 89a425f2c202
line wrap: on
line source

########################################################################
##
## Copyright (C) 2013-2020 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## 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
## <https://www.gnu.org/licenses/>.
##
########################################################################

## -*- texinfo -*-
## @deftypefn  {} {} imformats ()
## @deftypefnx {} {@var{formats} =} imformats (@var{ext})
## @deftypefnx {} {@var{formats} =} imformats (@var{format})
## @deftypefnx {} {@var{formats} =} imformats ("add", @var{format})
## @deftypefnx {} {@var{formats} =} imformats ("remove", @var{ext})
## @deftypefnx {} {@var{formats} =} imformats ("update", @var{ext}, @var{format})
## @deftypefnx {} {@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 @var{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 @nospell{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 @qcode{"add"}, @qcode{"remove"}, and @qcode{"update"}, and supplying
## a structure @var{format} with the required fields.  The option
## @qcode{"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

function varargout = imformats (arg1, arg2, arg3)

  mlock (); # prevent formats to be removed by "clear all"
  persistent formats = default_formats ();

  if (nargin == 0 && nargout == 0)
    pretty_print_formats (formats);
  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 + 1: end + numel (arg2)) = arg2;
          varargout{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
          varargout{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))
            varargout{1} = formats(match);
          else
            varargout{1} = struct ();
          endif
      endswitch
    else
      error ("imformats: first argument must be either a structure or string.");
    endif
  else
    varargout{1} = formats;
  endif

endfunction

function rformats = 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))
    rformats = 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;
            "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 (@__imwrite__);

  ## fills rest of format information by checking with GraphicsMagick
  formats = __magick_formats__ (formats);

  rformats = 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 = isfield (format, 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: what should we do if there's more than one hit?
  ##        Should this function prevent the addition of
  ##        duplicated extensions?
  match = cellfun (@(x) any (strcmpi (x, ext)), {formats.ext});
endfunction

function bool = isa_magick (coder, filename)

  bool = false;
  try
    info = __magick_ping__ (filename, 1);
    bool = strcmp (coder, info.Format);
  end_try_catch

endfunction

function pretty_print_formats (formats)
  ## define header names (none should be shorter than 3 characters)
  headers = {"Extension", "isa", "Info", "Read", "Write", "Alpha", "Description"};
  cols_length = cellfun (@numel, headers);

  ## Adjust the maximal length of the extensions column
  extensions = cellfun (@strjoin, {formats.ext}, {", "},
                        "UniformOutput", false);
  cols_length(1) = max (max (cellfun (@numel, extensions)), cols_length(1));
  headers{1} = postpad (headers{1}, cols_length(1), " ");

  ## Print the headers
  disp (strjoin (headers, " | "));
  under_headers = cellfun (@(x) repmat ("-", 1, numel (x)), headers,
                           "UniformOutput", false);
  disp (strjoin (under_headers, "-+-"));

  template = strjoin (arrayfun (@(x) sprintf ("%%-%is", x), cols_length,
                                "UniformOutput", false), " | ");

  ## Print the function handle for this things won't be a pretty table.  So
  ## instead we replace them with "yes" or "no", based on the support it has.
  yes_no_cols = cat (2, {formats.isa}(:), {formats.info}(:), {formats.read}(:),
                     {formats.write}(:), {formats.alpha}(:));
  empty = cellfun (@isempty, yes_no_cols);
  yes_no_cols(empty) = "no";
  yes_no_cols(! empty) = "yes";

  descriptions = {formats.description};
  table = cat (2, extensions(:), yes_no_cols, descriptions(:));
  printf ([template "\n"], table'{:});

endfunction


## This must work, even without support for image IO
%!test
%! formats = imformats ();
%! assert (isstruct (formats));
%!
%! min_fields = {"ext", "read", "isa", "write", "info", "alpha", "description"};
%! assert (all (ismember (min_fields, fieldnames (formats))));
%!
%! if (__have_feature__ ("MAGICK"))
%!   assert (numel (formats) > 0);
%! else
%!   assert (numel (formats), 0);
%! endif

## When imread or imfinfo are called, the file must exist or the
## function defined by imformats will never be called.  Because
## of this, we must create a file for the tests to work.

## changing the function that does the reading
%!testif HAVE_MAGICK
%! fname = [tempname() ".jpg"];
%! def_fmt = imformats ();
%! fid = fopen (fname, "w");
%! unwind_protect
%!   fmt = imformats ("jpg");
%!   fmt.read = @numel;
%!   imformats ("update", "jpg", fmt);
%!   assert (imread (fname), numel (fname));
%! unwind_protect_cleanup
%!   fclose (fid);
%!   unlink (fname);
%!   imformats (def_fmt);
%! end_unwind_protect

## adding a new format
%!testif HAVE_MAGICK
%! fname = [tempname() ".new_fmt"];
%! def_fmt = imformats ();
%! fid = fopen (fname, "w");
%! unwind_protect
%!   fmt = imformats ("jpg"); # take jpg as template
%!   fmt.ext = "new_fmt";
%!   fmt.read = @(~) true ();
%!   imformats ("add", fmt);
%!   assert (imread (fname), true);
%! unwind_protect_cleanup
%!   fclose (fid);
%!   unlink (fname);
%!   imformats (def_fmt);
%! end_unwind_protect

## adding multiple formats at the same time
%!testif HAVE_MAGICK
%! fname1 = [tempname() ".new_fmt1"];
%! fid1 = fopen (fname1, "w");
%! fname2 = [tempname() ".new_fmt2"];
%! fid2 = fopen (fname2, "w");
%! def_fmt = imformats ();
%! unwind_protect
%!   fmt = imformats ("jpg"); # take jpg as template
%!   fmt.ext = "new_fmt1";
%!   fmt.read = @(~) true();
%!   fmt(2) = fmt(1);
%!   fmt(2).ext = "new_fmt2";
%!   imformats ("add", fmt);
%!   assert (imread (fname1), true);
%!   assert (imread (fname2), true);
%! unwind_protect_cleanup
%!   fclose (fid1);
%!   fclose (fid2);
%!   unlink (fname1);
%!   unlink (fname2);
%!   imformats (def_fmt);
%! end_unwind_protect

## changing format and resetting back to default
%!testif HAVE_MAGICK
%! ori_fmt = mod_fmt = imformats ("jpg");
%! mod_fmt.description = "Another description";
%! imformats ("update", "jpg", mod_fmt);
%! new_fmt = imformats ("jpg");
%! assert (new_fmt.description, mod_fmt.description);
%! imformats ("factory");
%! new_fmt = imformats ("jpg");
%! assert (new_fmt.description, ori_fmt.description);

## updating to an invalid format should cause an error
%!testif HAVE_MAGICK
%! fmt = imformats ("jpg");
%! fmt = rmfield (fmt, "read");
%! error_thrown = false;
%! try
%!   imformats ("update", "jpg", fmt);
%! catch
%!   error_thrown = true;
%! end_try_catch
%! assert (error_thrown, true);