changeset 31198:93eb0d6e7f62

__tiff_imwrite__: converted to private octave function * libinterp/corefcn/__tiff__.cc: removed internal function __tiff_imwrite__. * scripts/image/private/__tiff_imwrite__.m: added private function __tiff_imwrite__. * scripts/image/module.mk: added entry for __tiff_imwrite__.m.
author magedrifaat <magedrifaat@gmail.com>
date Fri, 02 Sep 2022 03:06:29 +0200
parents 1604c8812b67
children 30b28458bb06
files libinterp/corefcn/__tiff__.cc scripts/image/module.mk scripts/image/private/__tiff_imwrite__.m
diffstat 3 files changed, 248 insertions(+), 262 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/__tiff__.cc	Thu Sep 01 01:56:20 2022 +0200
+++ b/libinterp/corefcn/__tiff__.cc	Fri Sep 02 03:06:29 2022 +0200
@@ -128,13 +128,6 @@
       }
   }
 
-  bool
-  is_colormap (octave_value ov)
-  {
-    return ov.isnumeric () && ov.isreal () && ov.isfloat ()
-           && ov.ndims () == 2 && ov.columns () == 3;
-  }
-
   // A map of tag names supported by matlab, there are some differences
   // than LibTIFF's names (e.g. Photometric vs PhotometricInterpretation)
   static const std::map<std::string, ttag_t> tag_name_map = {
@@ -3584,261 +3577,6 @@
 #endif
   }
 
-  DEFUN (__tiff_imwrite__, args, ,
-         "Handler for imwrite that uses Tiff interface")
-  {
-#if defined (HAVE_TIFF)
-    int nargin = args.length ();
-
-    if (nargin < 2)
-      error ("imwrite: Wrong number of arguments");
-
-    if (! (args(0).isnumeric () || args(0).is_bool_matrix ()
-           || args(0).is_bool_scalar ()))
-      error ("imwrite: Expected image matrix as first argument");
-
-    // The argument checks here are very similar to the ones found in
-    // __imwrite__.m and imwrite_filename.m, the code is duplicated here since
-    // the original code is tailored to the generic library graphicsmagick
-    // so it is not suitable to be used directly for this case
-    uint16_t offset = 1;
-    std::string filename;
-    octave_value cmap = octave_value (NDArray ());
-    if (args(1).is_string ())
-      {
-        filename = args(1).string_value ();
-        offset++;
-      }
-    else if (nargin > 2 && is_colormap (args(1)) && args(2).is_string ())
-      {
-        filename = args(2).string_value ();
-        cmap = args(1);
-        offset += 2;
-      }
-    else
-      error ("imwrite: no FILENAME specified");
-    
-    filename = sys::file_ops::tilde_expand (filename);
-    
-    if (nargin > offset && (nargin - offset) % 2 != 0
-        && args(offset).is_string ())
-      offset++;
-
-    if ((nargin - offset) % 2 != 0)
-      error ("imwrite: no pair for all arguments (odd number left)");
-
-    // Setting the default value for the rest of the parameters
-    bool append = false;
-    uint16_t compression = COMPRESSION_NONE;
-    uint32_t rows_per_strip = UINT32_MAX;
-    float x_res = 72, y_res = 72;
-    std::string description = "";
-
-    // Extract the values for the paramters provided
-    for (uint16_t arg_idx = offset; arg_idx < nargin; arg_idx+=2)
-      {
-        if (! args(arg_idx).is_string ())
-          error ("imwrite: PARAM in PARAM/VALUE pair must be string");
-        
-        const char *param_cstr = args(arg_idx).string_value ().c_str ();
-        if (strcasecmp (param_cstr, "colorspace") == 0)
-          {
-            // Matlab specifies three colorspaces: RGB, CIELAB and ICCLAB.
-            // Of the three, only RGB is currently supported so this tag
-            // can be ignored.
-          }
-        else if (strcasecmp (param_cstr, "compression") == 0)
-          {
-            if (! args(arg_idx + 1).is_string ())
-              error ("imwrite: value for %s option must be a string",
-                     param_cstr);
-            std::string comp = args(arg_idx + 1).string_value ();
-            if (comp == "packbits")
-              compression = COMPRESSION_PACKBITS;
-            else if (comp == "lzw")
-              compression = COMPRESSION_LZW;
-            else if (comp == "deflate")
-              compression = COMPRESSION_DEFLATE;
-            else if (comp == "jpeg")
-              compression = COMPRESSION_JPEG;
-            else if (comp == "ccitt")
-              compression = COMPRESSION_CCITTRLE;
-            else if (comp == "fax3")
-              compression = COMPRESSION_CCITTFAX3;
-            else if (comp == "fax4")
-              compression = COMPRESSION_CCITTFAX4;
-            else
-              error ("imwrite: invalid compression '%s'", comp.c_str ());
-          }
-        else if (strcasecmp (param_cstr, "Description") == 0)
-          {
-            if (! args(arg_idx + 1).is_string ())
-              error ("imwrite: value for %s option must be a string",
-                     param_cstr);
-            description = args(arg_idx + 1).string_value ();
-          }
-        else if (strcasecmp (param_cstr, "resolution") == 0)
-          {
-            if (! args(arg_idx + 1).isnumeric ())
-              error ("imwrite: value for %s option must be numeric",
-                     param_cstr);
-            NDArray res = args(arg_idx + 1).array_value ();
-            if (res.numel () == 1)
-              {
-                x_res = res(0);
-                y_res = res(0);
-              }
-            else if (res.numel () == 2)
-              {
-                x_res = res(0);
-                y_res = res(1);
-              }
-            else
-              error ("imwrite: value for %s option must be either a scalar value or a two-element vector",
-                     param_cstr);
-          }
-        else if (strcasecmp (param_cstr, "RowsPerStrip") == 0)
-          {
-            if (! args(arg_idx + 1).isnumeric ()
-                || ! args(arg_idx + 1).is_scalar_type ())
-              error ("imwrite: value for %s option must be a scalar value",
-                     param_cstr);
-            rows_per_strip = args(arg_idx + 1).uint32_scalar_value ();
-          }
-        else if (strcasecmp (param_cstr, "WriteMode") == 0)
-          {
-            if (! args(arg_idx + 1).is_string ())
-              error ("imwrite: value for %s option must be a string",
-                     param_cstr);
-            std::string wmode = args(arg_idx + 1).string_value ();
-            if (wmode == "overwrite")
-              append = false;
-            else if (wmode == "append")
-              append = true;
-            else
-              error ("imwrite: value for %s option must be either 'overwrite' or 'append'",
-                     param_cstr);
-          }
-        else
-          error ("imread: invalid PARAMETER '%s'", param_cstr);
-      }
-    
-    // Matlab specifies that JPEG compression must also specify RowsPerStrip
-    // and must be a value divisible by 8
-    if (compression == COMPRESSION_JPEG
-        && (rows_per_strip == UINT32_MAX || rows_per_strip % 8 != 0))
-      error ("imwrite: RowsPerStrip option must be specified for jpeg compression and must be divisible by 8");
-    
-    // These compression schemes are only available for binary images
-    if (! (args(0).is_bool_matrix () || args(0).is_bool_scalar ())
-        && (compression == COMPRESSION_CCITTRLE
-            || compression == COMPRESSION_CCITTFAX3
-            || compression == COMPRESSION_CCITTFAX4))
-      error ("imwrite: the specified compression scheme is for binary images only");
-
-    // Matlab rejects 4D data for TIFF images
-    dim_vector img_dims = args(0).dims ();
-    if (args(0).ndims () != 2 && args(0).ndims () != 3)
-      error ("imwrite: expected 2-dimensional or 3-dimensional image matrix");
-
-    if (args(0).ndims () > 2 && img_dims(2) > 1 && cmap.numel () > 0)
-      error ("imwrite: palette images must be 1 channel only");
-
-    TIFF *tif;
-    if (append)
-      tif = TIFFOpen (filename.c_str (), "a");
-    else
-      tif = TIFFOpen (filename.c_str (), "w");
-    
-    if (! tif)
-      error ("Failed to open file %s", filename.c_str ());
-    
-    // A simple way to make sure the file will be closed when the function
-    // returns or when an error occurs as the destructor will always be called
-    octave_tiff_handle tiff_handle (tif);
-    
-    // Set all necessary tags
-    if (! TIFFSetField (tif, TIFFTAG_IMAGELENGTH,
-                        static_cast<uint32_t>(img_dims(0)))
-        || ! TIFFSetField (tif, TIFFTAG_IMAGEWIDTH,
-                           static_cast<uint32_t>(img_dims(1))))
-      error ("Failed to set image dimensions");
-  
-    if (img_dims.ndims () > 2)
-      if (! TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, img_dims(2)))
-        error ("Failed to set the number of samples");
-    
-    if (! TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
-      error ("Failed to set the planar configuration");
-    
-    if (! TIFFSetField (tif, TIFFTAG_COMPRESSION, compression))
-      error ("Failed to set the compressoin tag");
-    
-    if (! TIFFSetField (tif, TIFFTAG_XRESOLUTION, x_res)
-        || ! TIFFSetField (tif, TIFFTAG_YRESOLUTION, y_res))
-      error ("Failed to set resolution tag");
-    
-    if (! TIFFSetField (tif, TIFFTAG_IMAGEDESCRIPTION, description.c_str ()))
-      error ("Failed to set description tag");
-
-    if (rows_per_strip == UINT32_MAX)
-      rows_per_strip = TIFFDefaultStripSize (tif, 0);
-    
-    if (! TIFFSetField (tif, TIFFTAG_ROWSPERSTRIP, rows_per_strip))
-      error ("Failed to set the RowsPerStrip tag");
-    
-    uint16_t bits_per_sample;
-    if (args(0).is_bool_matrix () || args(0).is_bool_scalar ())
-      bits_per_sample = 1;
-    else if (args(0).is_uint8_type ())
-      bits_per_sample = 8;
-    else if (args(0).is_uint16_type ())
-      bits_per_sample = 16;
-    else if (args(0).is_uint32_type () || args(0).is_single_type ())
-      bits_per_sample = 32;
-    else if (args(0).is_double_type ())
-      bits_per_sample = 64;
-    else
-      error ("imwrite: unsupported image data type");
-    
-    if (! TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample))
-      error ("Failed to set BitsPerSample tag");
-    
-    // Infer the photometric interpretation of the image from the color map
-    // and the number of channels of the input
-    uint16_t photometric = PHOTOMETRIC_MINISBLACK;
-    if (cmap.numel () > 0)
-      {
-        photometric = PHOTOMETRIC_PALETTE;
-        set_field_data (tif, TIFFFieldWithTag (tif, TIFFTAG_COLORMAP),
-                        cmap);
-      }
-    else if (img_dims(2) == 3)
-      photometric = PHOTOMETRIC_RGB;
-    else if (img_dims(2) == 4)
-      photometric = PHOTOMETRIC_SEPARATED;
-
-    if (! TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, photometric))
-      error ("Failed to set the photometric tag");
-    
-    tiff_image_data image_data (tif);
-    if (args(0).is_bool_scalar () || args(0).is_bool_matrix ()
-        || args(0).is_uint8_type () || args(0).is_uint16_type ()
-        || args(0).is_uint32_type ())
-      write_unsigned_image (tif, args(0), &image_data);
-    else
-      {
-        if (!TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP))
-          error ("Failed to set SampleFormat tag");
-        write_float_image (tif, args(0).as_double (), &image_data);
-      }
-    
-    return octave_value_list ();
-#else
-    err_disabled_feature ("imwrite", "Tiff");
-#endif
-  }
-
   DEFUN (__tiff_imfinfo__, args, ,
          "Handler for imfinfo that uses Tiff interface")
   {
--- a/scripts/image/module.mk	Thu Sep 01 01:56:20 2022 +0200
+++ b/scripts/image/module.mk	Fri Sep 02 03:06:29 2022 +0200
@@ -7,6 +7,7 @@
   %reldir%/private/__imread__.m \
   %reldir%/private/__imwrite__.m \
   %reldir%/private/__tiff_imread__.m \
+  %reldir%/private/__tiff_imwrite__.m \
   %reldir%/private/colorspace_conversion_input_check.m \
   %reldir%/private/colorspace_conversion_revert.m \
   %reldir%/private/imageIO.m \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/image/private/__tiff_imwrite__.m	Fri Sep 02 03:06:29 2022 +0200
@@ -0,0 +1,247 @@
+function __tiff_imwrite__ (img, varargin)
+  ## A lot of the input sanitising logic here is copied from
+  ## scripts/image/__imwrite__.m and adapted to the needs of the Tiff
+  ## interface
+
+  if (nargin < 2 || ! (isnumeric (img) || islogical (img)))
+    print_usage ("imwrite");
+  endif
+
+  [filename, ext, map, param_list] = imwrite_filename (varargin{:});
+  
+  if (isempty (img))
+    error ("imwrite: invalid empty image");
+  elseif (issparse (img) || issparse (map))
+    error ("imwrite: sparse images are not supported");
+  endif
+
+  if (rem (numel (param_list), 2) != 0)
+    error ("imwrite: no pair for all arguments (odd number left)");
+  endif
+
+  writemode = "w";
+  compression = Tiff.Compression.None;
+  rows_per_strip = -1;
+  res = [72, 72];
+  description = "";
+  alpha = [];
+
+  for idx = 1:2:numel (param_list)
+    switch (tolower (param_list{idx}))
+      case "alpha"
+        alpha = param_list{idx + 1};
+        if (! isnumeric (alpha))
+          error ("imwrite: value for %s option must be a numeric matrix",
+                 param_list{idx});
+        elseif (size (alpha, 3) != 1)
+          error ("imwrite: 3rd dimension of matrix for %s must be singleton",
+                 param_list{idx});
+        elseif (ndims (alpha) > 4
+                || any (size (alpha)([1 2]) != size (img)([1 2]))
+                || size (alpha, 4) != size (img, 4))
+          error ("imwrite: matrix for %s must have same dimension as image",
+                 param_list{idx});
+        endif
+      
+      case "colorspace"
+        ## Matlab specifies three colorspaces: RGB, CIELAB and ICCLAB.
+        ## Of the three, only RGB is currently supported so this tag
+        ## can be ignored.
+      
+      case "compression"
+        switch (tolower (param_list{idx + 1}))
+          case "packbits"
+            compression = Tiff.Compression.PackBits;
+          case "lzw"
+            compression = Tiff.Compression.LZW;
+          case "deflate"
+            compression = Tiff.Compression.Deflate;
+          case "jpeg"
+            compression = Tiff.Compression.JPEG;
+          case "ccitt"
+            compression = Tiff.Compression.CCITTRLE;
+          case "fax3"
+            compression = Tiff.Compression.CCITTFax3;
+          case "fax4"
+            compression = Tiff.Compression.CCITTFax4;
+          otherwise
+            error ("imwrite: invalid compression '%s'", compression);
+        endswitch
+      
+      case "description"
+        if (! ischar (param_list{idx + 1}))
+            error ("imwrite: value for %s option must be a string",
+                   param_list{idx});
+        endif
+        description = param_list{idx + 1};
+      
+      case "resolution"
+        if (! isnumeric (param_list{idx + 1})
+            || all (numel (param_list) != [1, 2]))
+          error ("imwrite: value for %s option must be either a scalar value or a two-element vector",
+                 param_list{idx});
+        endif
+        resolution = param_list{idx + 1};
+        if (numel (resolution) == 1)
+          resolution(2) = resolution(1);
+        endif
+      
+      case "rowsperstrip"
+        if (! isnumeric (param_list{idx + 1})
+            || ! isscalar (param_list{idx + 1}))
+          error ("imwrite: value for %s option must be a numeric scalar",
+                 param_list{idx});
+        endif
+        rows_per_strip = param_list{idx + 1};
+      
+      case "writemode"
+        if (! ischar (param_list{idx + 1})
+            || ! any (strcmpi (param_list{idx + 1}, {"append", "overwrite"})))
+          error ('imwrite: value for %s option must be "append" or "overwrite"',
+                 param_list{idx});
+        endif
+        if (strcmpi (param_list{idx + 1}, {"append"}))
+          writemode = "a";
+        endif
+      
+      otherwise
+        error ("imwrite: invalid PARAMETER '%s'", param_list{idx});
+    endswitch
+  endfor
+
+  if (! isempty (map))
+    if (! iscolormap (map))
+      error ("imwrite: invalid MAP for indexed image");
+    elseif (ndims (img) != 2)
+      error ("imwrite: indexed image must have 2 (found %i)", ndims (img));
+    endif
+
+    ## If the image is floating point, then we convert it to integer because
+    ## it represents indices of the color map which must be integers.  Also,
+    ## if it's floating point, it has an offset of 1
+    if (isfloat (img))
+      if (rows (map) <= 256)
+        img = uint8 (img - 1);
+      else
+        img = uint16 (img - 1);
+      endif
+    endif
+
+    ## Fill in the colormap as required with rgb (0, 0, 0)
+    nColors = rows (map);
+    if (islogical (img))
+      required_colors = 2;
+    else
+      required_colors = double (intmax (class (img))) + 1;
+    endif
+
+    if (nColors < required_colors)
+      warning ("imwrite: MAP has not enough colors.  Filling with black");
+      map(nColors+1:required_colors,:) = 0;
+    endif
+  endif
+
+  if (ndims (img) > 4)
+    error ("imwrite: invalid %d-dimensional image data", ndims (img));
+  elseif (all (size (img, 3) != [1 3 4]))
+    ## 1, 3, or 4 for grayscle, RGB, and CMYK respectively
+    error ("imwrite: IMG 3rd dimension must be 1, 3, or 4");
+  endif
+  
+  ## Matlab specifies that JPEG compression must also specify RowsPerStrip
+  ## and must be a value divisible by 8
+  if (compression == Tiff.Compression.JPEG
+      && (rows_per_strip == -1 || rem (rows_per_strip, 8) != 0))
+    error ("imwrite: RowsPerStrip option must be specified for jpeg compression and must be divisible by 8");
+  endif
+  
+  ## These compression schemes are only available for binary images
+  if (! (islogical (img))
+      && any (compression == [Tiff.Compression.CCITTRLE,
+                              Tiff.Compression.CCITTFax3,
+                              Tiff.Compression.CCITTFax4]))
+    error ("imwrite: the specified compression scheme is for binary images only");
+  endif
+
+  ## Set the image tag data
+  tags.ImageLength = size (img, 1);
+  tags.ImageWidth = size (img, 2);
+
+  ## See if SamplesPerPixel should account for the alpha channel
+  if (! isempty (alpha))
+    tags.SamplesPerPixel = size (img, 3) + 1;
+    tags.ExtraSamples = Tiff.ExtraSamples.AssociatedAlpha;
+  else
+    tags.SamplesPerPixel = size (img, 3);
+  endif
+
+  tags.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky;
+  tags.Compression = compression;
+  tags.XResolution = res(1);
+  tags.YResolution = res(2);
+  tags.ImageDescription = description;
+  tags.BitsPerSample = infer_bitdepth (class (img));
+  
+  ## Infer the photometric interpretation of the image from the color map
+  ## and the number of channels of the input
+  tags.Photometric = Tiff.Photometric.MinIsBlack;
+  if (! isempty (map))
+    tags.Photometric = Tiff.Photometric.Palette;
+    tags.ColorMap = map;
+  elseif (size(img, 3) == 3)
+    tags.Photometric = Tiff.Photometric.RGB;
+  elseif (size(img, 3) == 4)
+    tags.Photometric = Tiff.Photometric.CMYK;
+  endif
+  
+  ## Set the correct sample format for floating-point and signed types
+  if (isfloat (img))
+    tags.SampleFormat = Tiff.SampleFormat.IEEEFP;
+  elseif (any (strcmp (class (img), {"int8", "int16", "int32"})))
+    tags.SampleFormat = Tiff.SampleFormat.Int;
+  endif
+
+  ## Add the alpha channel to the image data
+  if (! isempty (alpha))
+    img = cat (3, img, alpha);
+  endif
+
+  tif = Tiff (filename, writemode);
+
+  for dir_idx = 1:size(img, 4)
+    tif.setTag (tags);
+    ## Try to get a reasonable size for RowsPerStrip if not set
+    ## This must be done after the other tags are set so LibTIFF
+    ## can make an informed calculation.
+    % if (rows_per_strip == -1)
+    %   rows_per_strip = tif.getDefaultStripLength ();
+    % endif
+    if (rows_per_strip != -1)
+      tif.setTag (Tiff.TagID.RowsPerStrip, rows_per_strip);
+    endif
+
+    tif.write (img(:,:,:, dir_idx));
+
+    tif.writeDirectory ();
+  endfor
+
+  tif.close ();
+
+endfunction
+
+function bitdepth = infer_bitdepth (img_class)
+  switch (img_class)
+    case "logical"
+      bitdepth = 1;
+    case {"int8", "uint8"}
+      bitdepth = 8;
+    case {"int16", "uint16"}
+      bitdepth = 16;
+    case {"int32", "uint32", "single"}
+      bitdepth = 32;
+    case "double"
+      bitdepth = 64;
+    otherwise
+      error ("imwrite: Unsupported data type for image data");
+  endswitch
+endfunction