changeset 31193:c142c153034c

Tiff: implemented imwrite handler that uses the Tiff interface * __tiff__.cc (F__tiff_imwrite__): implemented internal function to act as imwrite handler for tiff images but uses the new Tiff interface.
author magedrifaat <magedrifaat@gmail.com>
date Sat, 27 Aug 2022 23:06:54 +0200
parents 6a2bb6f4e41e
children 0cdb7f35641e
files libinterp/corefcn/__tiff__.cc scripts/image/PKG_ADD
diffstat 2 files changed, 261 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/__tiff__.cc	Sat Aug 27 16:23:59 2022 +0200
+++ b/libinterp/corefcn/__tiff__.cc	Sat Aug 27 23:06:54 2022 +0200
@@ -13,6 +13,7 @@
 #include "errwarn.h"
 
 #include "fcntl-wrappers.h"
+#include "file-ops.h"
 
 #if defined (HAVE_TIFF)
 #  include <tiffio.h>
@@ -126,6 +127,13 @@
     return ov.isnumeric () && ov.isreal () && ov.is_scalar_type ();
   }
 
+  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 PhotometricInerpretation)
   static const std::map<std::string, ttag_t> tag_name_map = {
@@ -2741,6 +2749,11 @@
     
     // The TIFFRGBAImageGet interface is used instead of TIFFReadRGBAImage
     // to have control on the requested orientation of the image
+    // Matlab R2022a handles the orientation for each individual strip
+    // or tile on its own, this results in a distorted image which is
+    // not useful, and is probably a bug. So this deviated from matlab
+    // by applying the orientation to the entire image to produce more
+    // useful results.
     TIFFRGBAImage img_config;
     char emsg[1024];
     if (! TIFFRGBAImageOK (tif, emsg)
@@ -3599,7 +3612,7 @@
             if (is_numeric_scalar (val))
               page = val.uint16_scalar_value ();
             else
-              error ("imread: %s must be a scalar", param_cstr);
+              error ("imread: %s must be a numeric scalar", param_cstr);
           }
       }
     
@@ -3736,4 +3749,250 @@
     err_disabled_feature ("imread", "Tiff");
 #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");
+  
+    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)");
+
+    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 = "";
+
+    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")
+              // FIXME: check matlab if this is adobe deflate
+              compression = COMPRESSION_DEFLATE;
+            else if (comp == "jpeg")
+              compression = COMPRESSION_JPEG;
+            else if (comp == "ccitt")
+              // FIXME: check matlab which one this is
+              compression = COMPRESSION_CCITT_T4;
+            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);
+      }
+    
+    // FIXME: does matlab error if appending to non existant file?
+    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");
+    
+    if (! (args(0).is_bool_matrix () || args(0).is_bool_scalar ())
+        && (compression == COMPRESSION_CCITT_T4
+            || compression == COMPRESSION_CCITTFAX3
+            || compression == COMPRESSION_CCITTFAX4))
+      error ("imwrite: the specified compression scheme is for binary images only");
+
+    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");
+    
+    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
+  }
 }
--- a/scripts/image/PKG_ADD	Sat Aug 27 16:23:59 2022 +0200
+++ b/scripts/image/PKG_ADD	Sat Aug 27 23:06:54 2022 +0200
@@ -1,7 +1,7 @@
 if __have_feature__ ("TIFF")
     format = imformats("tif");
     format.read = @__tiff_imread__;
-    format.write = @__imwrite__;
+    format.write = @__tiff_imwrite__;
     format.info = @__tiff_imfinfo__;
     imformats("update", "tif", format);
 endif
\ No newline at end of file