changeset 31153:c66d6c7f025e

Tiff: implemented write method for stripped images * __tiff__.cc(F_tiff_write__): implemented internal function for processing write method arguments. * __tiff__.cc(write_stripped_image): implemented support for writing to a stripped image for all cases exept logical images. * Tiff.m: added write method to the Tiff class.
author magedrifaat <magedrifaat@gmail.com>
date Wed, 03 Aug 2022 22:06:43 +0200
parents 2244617f4da5
children 828b7cc9aa36
files libinterp/dldfcn/__tiff__.cc scripts/io/Tiff.m
diffstat 2 files changed, 230 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/dldfcn/__tiff__.cc	Wed Aug 03 03:39:30 2022 +0200
+++ b/libinterp/dldfcn/__tiff__.cc	Wed Aug 03 22:06:43 2022 +0200
@@ -159,7 +159,6 @@
                 uint8_t * img_u8 = reinterpret_cast<uint8_t *> (img_fvec);
                 img_fvec[pixel] = (img_u8[pixel / 8] >> bit_number) & 0x01;
               }
-            break;
           }
         else if (image_data->bits_per_sample == 4)
           {
@@ -182,7 +181,6 @@
                 uint8_t * img_u8 = reinterpret_cast<uint8_t *> (img_fvec);
                 img_fvec[pixel] = (img_u8[pixel / 2] >> shift) & 0x0F;
               }
-            break;
           }
         else if (image_data->bits_per_sample != 8 &&
                  image_data->bits_per_sample != 16 &&
@@ -886,6 +884,39 @@
     if (! TIFFSetField(tif, tag_id, tag_data))
       error ("Failed to set tag value");
   }
+  
+  uint32_t get_rows_in_strip (uint32_t strip_no, uint32_t strip_count,
+                              uint32_t rows_per_strip,
+                              tiff_image_data *image_data)
+  {
+    // Calculate the expected number of elements in the strip data array
+    // All strips have equal number of rows except strips at the bottom
+    // of the image can have less number of rows
+    uint32_t rows_in_strip = rows_per_strip;
+    if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
+      {
+        // All strips have equal number of rows excpet strips at the bottom
+        // of the image can have less number of rows
+        if (strip_no == strip_count - 1)
+          rows_in_strip = image_data->height - rows_in_strip * strip_no;
+      }
+    else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE)
+      {
+        // For separate planes, we should check the last strip of each channel
+        uint32_t strips_per_channel
+          = strip_count / image_data->samples_per_pixel;
+        for (uint32_t boundary_strip = strips_per_channel - 1;
+             boundary_strip <= strip_count;
+             boundary_strip += strips_per_channel)
+          if (strip_no == boundary_strip)
+            rows_in_strip = image_data->height
+                            - rows_in_strip * (strip_no % strips_per_channel);
+      }
+    else
+      error ("Planar Configuration not supported");
+    
+    return rows_in_strip;
+  }
 
   template <typename T>
   void
@@ -901,35 +932,19 @@
     if (rows_in_strip > image_data->height)
       rows_in_strip = image_data->height;
     
-    uint32_t strip_count = TIFFNumberOfStrips (tif);
-    dim_vector strip_dimensions;
+    // LibTIFF uses zero-based indexing as opposed to Octave's 1-based
+    strip_no--;
 
-    // Calculate the expected number of elements in the strip data array
-    // All strips have equal number of rows except strips at the bottom
-    // of the image can have less number of rows
+    uint32_t strip_count = TIFFNumberOfStrips (tif);
+    rows_in_strip = get_rows_in_strip (strip_no, strip_count,
+                                       rows_in_strip, image_data);
+
+    dim_vector strip_dimensions;
     if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
-      {
-        // All strips have equal number of rows excpet strips at the bottom
-        // of the image can have less number of rows
-        if (strip_no == strip_count)
-          rows_in_strip = image_data->height - rows_in_strip * (strip_no - 1);
-        strip_dimensions = dim_vector (rows_in_strip, image_data->width,
-                                       image_data->samples_per_pixel);
-      }
+      strip_dimensions = dim_vector (rows_in_strip, image_data->width,
+                                     image_data->samples_per_pixel);
     else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE)
-      {
-        // For separate planes, we should check the last strip of each channel
-        uint32_t strips_per_channel
-          = strip_count / image_data->samples_per_pixel;
-        for (uint32_t boundary_strip = strips_per_channel;
-             boundary_strip <= strip_count;
-             boundary_strip += strips_per_channel)
-          if (strip_no == boundary_strip)
-            rows_in_strip = image_data->height - rows_in_strip
-                                                 * ((strip_no - 1)
-                                                    % (strips_per_channel));
-        strip_dimensions = dim_vector (rows_in_strip, image_data->width, 1);
-      }
+      strip_dimensions = dim_vector (rows_in_strip, image_data->width, 1);
     else
       error ("Planar configuration not supported");
 
@@ -966,8 +981,6 @@
     perm(2) = 0;
     strip_data = strip_data.permute (perm);
 
-    // LibTIFF uses zero-based indexing as opposed to Octave's 1-based
-    strip_no--;
     void *data_vec = strip_data.fortran_vec ();
     if (image_data->bits_per_sample == 8
         || image_data->bits_per_sample == 16
@@ -1213,6 +1226,86 @@
       }
   }
 
+  template <typename T>
+  void
+  write_stripped_image (TIFF *tif, T pixel_data, tiff_image_data *image_data)
+  {
+    // TODO(maged): remove this? ASSUMES pixel data dimensions are already validated
+
+    typedef typename T::element_type P;
+
+    // Permute pixel data to be aligned in memory to the way LibTIFF
+    // expects the data to be (i.e. channel x width x height for chunky
+    // and width x height x channel for separate planes)
+    Array<octave_idx_type> perm (dim_vector (3, 1));
+    if (image_data->planar_configuration == PLANARCONFIG_SEPARATE)
+      {
+        perm(0) = 1;
+        perm(1) = 0;
+        perm(2) = 2;
+      }
+    else
+      {
+        perm(0) = 2;
+        perm(1) = 1;
+        perm(2) = 0;
+      }
+    pixel_data = pixel_data.permute (perm);
+
+    uint32_t row_per_strip;
+    if (! TIFFGetFieldDefaulted (tif, TIFFTAG_ROWSPERSTRIP, &row_per_strip))
+      error ("Failed to obtain the RowPerStrip tag");
+    
+    // The default value is INT_MAX so we need to cap it to the image height
+    if (row_per_strip > image_data->height)
+      row_per_strip = image_data->height;
+
+    uint8_t *pixel_fvec = reinterpret_cast<uint8_t *> (pixel_data.fortran_vec ());
+    uint32_t strip_count = TIFFNumberOfStrips (tif);
+    tsize_t strip_size;
+    uint32_t rows_in_strip;
+    for (uint32_t strip = 0; strip < strip_count; strip++)
+      {
+        rows_in_strip = get_rows_in_strip (strip, strip_count,
+                                           row_per_strip, image_data);
+        strip_size = rows_in_strip * image_data->width * sizeof (P);
+        if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
+          strip_size *= image_data->samples_per_pixel;
+        if (! TIFFWriteEncodedStrip (tif, strip, pixel_fvec, strip_size))
+          error ("Failed to rite strip data");
+        pixel_fvec += strip_size;
+      }
+  }
+
+  template <typename T>
+  void
+  write_tiled_image (TIFF *tif, T pixel_data, tiff_image_data *image_data)
+  {
+    // TODO(maged): remove this? ASSUMES pixel data dimensions are already validated
+
+  }
+
+  template <typename T>
+  void
+  write_image (TIFF *tif, T pixel_data, tiff_image_data *image_data)
+  {
+    // TODO(maged): check behavior in matlab
+    if (image_data->height != pixel_data.dim1 ()
+        || image_data->width != pixel_data.dim2 ()
+        || pixel_data.ndims () > 3
+        || (image_data->samples_per_pixel > 1 && pixel_data.ndims () < 3)
+        || (pixel_data.ndims () > 2
+            && image_data->samples_per_pixel != pixel_data.dim3 ()))
+      error ("Dimensions of the input don't match image dimenions");
+    
+    if (image_data->is_tiled)
+      write_tiled_image<T> (tif, pixel_data, image_data);
+    else
+      write_stripped_image<T> (tif, pixel_data, image_data);
+
+  }
+
+
 #endif
 
   DEFUN_DLD (__open_tiff__, args, ,
@@ -1447,6 +1540,99 @@
 #endif
   }
 
+  DEFUN_DLD (__tiff_write__, args, ,
+             "Write the image data to the current IFD")
+  {
+#if defined (HAVE_TIFF)
+    int nargin = args.length ();
+
+    if (nargin < 2)
+      error ("Wrong number of arguments\n");
+    
+    TIFF *tif = (TIFF *)(args(0).uint64_value ());
+
+    // TODO(maged): check on windows
+    if (TIFFGetMode (tif) == O_RDONLY)
+      error ("Can't write data to a file opened in read-only mode");
+
+    // Obtain all necessary tags
+    tiff_image_data image_data (tif);
+
+    uint16_t sample_format;
+    if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format))
+      error ("Failed to obtain a value for sample format");
+
+    if (sample_format == 3)
+      {
+        if (image_data.bits_per_sample != 32
+            && image_data.bits_per_sample != 64)
+          error ("Floating point images are only supported for bit depths of 32 and 64");
+      }
+    else if (sample_format != 1 && sample_format != 4)
+      error ("Unsupported sample format");
+    
+    switch (image_data.bits_per_sample)
+      {
+      case 1:
+        // We need to check for both scalar and matrix types to handle single
+        // pixel image
+        if (args (1).is_bool_scalar () || args (1).is_bool_matrix ())
+          write_image<boolNDArray> (tif, args (1).bool_array_value (),
+                                    &image_data);
+        else
+          error ("Expected logical matrix for BiLevel image");
+        break;
+      case 8:
+        if (args (1).is_uint8_type ())
+          write_image<uint8NDArray> (tif, args (1).uint8_array_value (),
+                                     &image_data);
+        else
+          error ("Only uint8 data is allowed for uint images with bit depth of 8");
+        break;
+      case 16:
+        if (args (1).is_uint16_type ())
+          write_image<uint16NDArray> (tif, args (1).uint16_array_value (),
+                                      &image_data);
+        else
+          error ("Only uint16 data is allowed for uint images with bit depth of 16");
+        break;
+      case 32:
+        if (sample_format == 3)
+          if (args (1).is_single_type () || args (1).is_double_type ())
+            write_image<FloatNDArray> (tif, args (1).float_array_value (),
+                                       &image_data);
+          else
+            error ("Only single and double data are allowed for floating-point images");
+        else
+          if (args (1).is_uint32_type ())
+            write_image<uint32NDArray> (tif, args (1).uint32_array_value (),
+                                        &image_data);
+          else
+            error ("Only uint32 data is allowed for uint images with bit depth of 32");
+        break;
+      case 64:
+        if (sample_format == 3)
+          if (args (1).is_single_type () || args (1).is_double_type ())
+            write_image<NDArray> (tif, args (1).array_value (), &image_data);
+          else
+            error ("Only single and double data are allowed for floating-point images");
+        else  
+          if (args (1).is_uint64_type ())
+            write_image<uint64NDArray> (tif, args (1).uint64_array_value (),
+                                        &image_data);
+          else
+            error ("Only uint64 data is allowed for uint images with bit depth of 64");
+        break;
+      default:
+        error ("Unsupported bit depth");
+      }
+    
+    return octave_value_list ();
+#else
+    err_disabled_feature ("write", "Tiff");
+#endif
+  }
+
   DEFUN_DLD (__tiff_write_encoded_strip__, args, ,
              "Write an encoded strip to the image")
   {
--- a/scripts/io/Tiff.m	Wed Aug 03 03:39:30 2022 +0200
+++ b/scripts/io/Tiff.m	Wed Aug 03 22:06:43 2022 +0200
@@ -116,6 +116,13 @@
       argout = __tiff_read__ (t.tiff_handle);
     endfunction
 
+    function write (t, imageData)
+      if (t.closed)
+        error ("Image file was closed");
+      endif
+      __tiff_write__ (t.tiff_handle, imageData);
+    endfunction
+
     function writeEncodedStrip (t, stripNumber, imageData)
       if (t.closed)
         error ("Image file was closed");
@@ -188,7 +195,7 @@
 %!    img = Tiff (filename, "r");
 %!    data2 = img.read ();
 %!    assert (size (data2), ex_size);
-%!    assert (data2, resize (data, size (data2)), -1e5);
+%!    assert (data2, resize (data, size (data2)));
 %!    img.close ();
 %!endfunction
 
@@ -481,10 +488,10 @@
 %!  function test_fn (filename)
 %!    img = Tiff (filename, "w");
 %!    setTag (img, struct ("ImageLength", 20, "ImageWidth", 20,
-%!                         "BitsPerSample", 16, "RowsPerStrip", 2));
+%!                         "BitsPerSample", 16, "RowsPerStrip", 3));
 %!    data = uint16 (reshape (1:400, [20, 20]));
-%!    for strip = 1:10
-%!      writeEncodedStrip (img, strip, data(strip * 2 - 1: strip * 2, :));
+%!    for strip = 1:7
+%!      writeEncodedStrip (img, strip, data(strip * 3 - 2: min(strip * 3, 20), :));
 %!    endfor
 %!    img.close ();
 %!    verify_data (filename, data, [20, 20]);
@@ -514,13 +521,13 @@
 %!    img = Tiff (filename, "w");
 %!    setTag (img, struct ("ImageLength", 20, "ImageWidth", 20,
 %!                         "BitsPerSample", 16, "SamplesPerPixel", 3,
-%!                         "RowsPerStrip", 2, "PlanarConfiguration", 2,
+%!                         "RowsPerStrip", 3, "PlanarConfiguration", 2,
 %!                         "PhotometricInterpretation", 2));
 %!    data = uint16 (reshape (1:1200, [20, 20, 3]));
 %!    strip = 1;
 %!    for sample = 1:3
-%!      for row = 1:2:20
-%!        writeEncodedStrip (img, strip, data(row: row + 1, :, sample));
+%!      for row = 1:3:20
+%!        writeEncodedStrip (img, strip, data(row: min(row + 2, 20), :, sample));
 %!        strip = strip + 1;
 %!      endfor
 %!    endfor