changeset 31179:f294b800f002

Tiff.m: added tests for signed images and sub-directories.
author magedrifaat <magedrifaat@gmail.com>
date Wed, 17 Aug 2022 23:27:54 +0200
parents 14edd6b09efe
children ae78937e24d2
files libinterp/corefcn/__tiff__.cc scripts/io/Tiff.m
diffstat 2 files changed, 155 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/__tiff__.cc	Wed Aug 17 21:15:49 2022 +0200
+++ b/libinterp/corefcn/__tiff__.cc	Wed Aug 17 23:27:54 2022 +0200
@@ -119,16 +119,17 @@
       error ("The image file was closed");
   }
 
-  // Error if status is not 1 (success status for TIFFGetField)
+
   void
   validate_tiff_get_field (bool status, void *p_to_free=NULL)
   {
-      if (status != 1)
-        {
-          if (p_to_free != NULL)
-            _TIFFfree (p_to_free);
-          error ("Failed to read tag");
-        }
+    // Check if the return status of TIFFGetField is not 1
+    if (status != 1)
+      {
+        if (p_to_free != NULL)
+          _TIFFfree (p_to_free);
+        error ("Failed to read tag");
+      }
   }
 
   uint32_t get_rows_in_strip (uint32_t strip_no, uint32_t strip_count,
@@ -175,12 +176,17 @@
     if (! TIFFGetFieldDefaulted (tif, TIFFTAG_ROWSPERSTRIP, &rows_in_strip))
       error ("Failed to obtain a value for RowsPerStrip");
     
+    // The default for RowsPerStrip is INT_MAX so it should be capped to the
+    // image height
     if (rows_in_strip > image_data->height)
       rows_in_strip = image_data->height;
     
     uint32_t strip_count = TIFFNumberOfStrips (tif);
+    // Get the actual number of rows in the strip
     rows_in_strip = get_rows_in_strip (strip_no, strip_count,
                                        rows_in_strip, image_data);
+    
+    // Set the strip dimensions depending on the planar configuration
     dim_vector strip_dims;
     if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
       strip_dims = dim_vector (image_data->samples_per_pixel,
@@ -233,6 +239,7 @@
     else
       error ("Unsupported bit depth");
     
+    // Reorder the dimensions to the order expected by Octave
     Array<octave_idx_type> perm (dim_vector (3, 1));
     if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
       {
@@ -339,7 +346,7 @@
     
     tile_data = tile_data.permute (perm);
 
-    // Get the actual tile dimensions
+    // Get the actual tile dimensions for boundary tiles
     uint32_t tiles_across = (image_data->width + tile_width - 1)
                             / tile_width;
     uint32_t tiles_down = (image_data->height + tile_height - 1)
@@ -860,7 +867,6 @@
       }
   }
 
-  // Convert tag value to double
   octave_value
   interpret_scalar_tag_data (void *data, TIFFDataType tag_datatype)
   {
@@ -918,8 +924,6 @@
     return octave_value (retval);
   }
 
-  // Convert memory buffer into a suitable octave value
-  // depending on tag_datatype
   octave_value
   interpret_tag_data (void *data, uint32_t count, TIFFDataType tag_datatype)
   {
@@ -928,6 +932,7 @@
     // but doesn't do the same for arrays
     // TODO(maged): matlab returns double for array tags as well, except
     // for a selected set of tags (e.g. StripByteCounts)
+    // TODO(maged): refactor for loops
     if (count == 1 && tag_datatype != TIFF_ASCII)
       {
         retval = interpret_scalar_tag_data (data, tag_datatype);
@@ -1106,6 +1111,7 @@
     
     int type_size = TIFFDataWidth (TIFFFieldDataType (fip));
     
+    // TODO(maged): convert to unique_ptr
     void *data = _TIFFmalloc (type_size);
     if (tag_id == TIFFTAG_PLANARCONFIG)
       {
@@ -1225,14 +1231,18 @@
             error ("Failed to obtain the bit depth");
 
           uint32_t count = 1 << bits_per_sample;
-          uint16_t *ch1, *ch2, *ch3;
+          uint16_t *ch1 = NULL, *ch2 = NULL, *ch3 = NULL;
+          // TransferFunction can have 1 or 3 channels depending on the
+          // SamplesPerPixel but the library still expects three pointers
+          // to be passed and sets only the needed ones
           validate_tiff_get_field (TIFFGetField (tif, tag_id,
                                                  &ch1, &ch2, &ch3));
           if (ch2 == NULL)
             tag_data_ov = interpret_tag_data (ch1, count,
                                               TIFFFieldDataType (fip));
           else
-            { 
+            {
+              // Concatenate the channels into an n by 3 array
               OCTAVE_LOCAL_BUFFER (uint16NDArray, array_list, 3);
               dim_vector col_dims(count, 1);
               array_list[0] = interpret_tag_data (ch1,
@@ -1588,6 +1598,10 @@
         }
       case TIFFTAG_SUBIFD:
         {
+          // Setting the SubIFD invloves setting their count only
+          // and the library will set the offsets correctly in
+          // the next calls to writeDirectory, but an array of offsets
+          // is still required so an array of zeroes is passed
           uint16_t subifd_count = tag_ov.uint16_scalar_value ();
           std::unique_ptr<uint64_t []> subifd_offsets
             = std::make_unique<uint64_t []> (subifd_count);
@@ -2394,6 +2408,10 @@
           == supported_modes.cend ())
       error ("Invalid mode for openning Tiff file: %s", mode.c_str ());
     
+    // LibTIFF doesn't support r+ mode but it appears that the difference
+    // between "a" and "r+" in the context of LibTIFF is that in "a" mode
+    // the directory is not set while in r+ the directory is set to the
+    // first directory in the file
     bool is_rplus = false;
     if (mode == "r+")
       {
@@ -2714,6 +2732,8 @@
     uint8NDArray img (img_dims);
     uint32_t *img_ptr = reinterpret_cast <uint32_t *> (img.fortran_vec ());
     
+    // The TIFFRGBAImageGet interface is used instead of TIFFReadRGBAImage
+    // to have control on the requested orientation of the image
     TIFFRGBAImage img_config;
     char emsg[1024];
     if (! TIFFRGBAImageOK (tif, emsg)
@@ -3455,7 +3475,6 @@
     
     uint64_t offset = args(1).uint64_scalar_value ();
 
-    // TODO(maged): check if matlab validates the offset
     int found = 0;
     for (uint16_t i = 0; i < count; i++)
       if (offset == offsets[i])
--- a/scripts/io/Tiff.m	Wed Aug 17 21:15:49 2022 +0200
+++ b/scripts/io/Tiff.m	Wed Aug 17 23:27:54 2022 +0200
@@ -413,6 +413,27 @@
 %!        "Invalid mode for openning Tiff file: rh");
 %!  fail ("Tiff ([tempname() \".tif\"], \"r\")", "Failed to open Tiff file");
 
+## test r+ mode modifies exisitng images
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag (img, struct (
+%!      "ImageLength", 1, "ImageWidth", 1,
+%!      "BitsPerSample", 8
+%!    ));
+%!    write (img, uint8 ([210]));
+%!    img.close ();
+%!    img = Tiff (filename, "r+");
+%!    setTag (img, "Make", "test_tag");
+%!    img.rewriteDirectory ();
+%!    img.close ();
+%!    img = Tiff (filename);
+%!    assert (img.read(), uint8 ([210]));
+%!    assert (getTag (img, "Make"), "test_tag");
+%!    img.close();
+%!  endfunction
+%!  file_wrapper (@test_fn)
+
 ## test one-pixel grayscale image
 %!testif HAVE_TIFF
 %!  function test_fn (filename)
@@ -762,7 +783,8 @@
 %!    writeEncodedStrip (img, 1, data);
 %!    img.close();
 %!    img = Tiff (filename, "r");
-%!    fail ("writeEncodedStrip(img, 1, [1])", "Can't write data to a file opened in read-only mode");
+%!    fail ("writeEncodedStrip(img, 1, [1])",
+%!          "Can't write data to a file opened in read-only mode");
 %!    img.close();
 %!  endfunction
 %!  file_wrapper (@test_fn);
@@ -1225,6 +1247,22 @@
 %!  endfunction
 %!  file_wrapper (@test_fn);
 
+## test write signed integer image
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag (img, struct (
+%!      "ImageLength", 10, "ImageWidth", 10, "SamplesPerPixel", 3,
+%!      "BitsPerSample", 16, "SampleFormat", Tiff.SampleFormat.Int,
+%!      "PhotometricInterpretation", Tiff.Photometric.RGB,
+%!      "PlanarConfiguration", Tiff.PlanarConfiguration.Chunky));
+%!    data = int16 (reshape (-150:149, [10, 10, 3]));
+%!    write (img, data);
+%!    img.close ();
+%!    verify_data (filename, data, [10, 10, 3]);
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
 ## test write method tiled BiLevel image
 %!testif HAVE_TIFF
 %!  function test_fn (filename)
@@ -1470,6 +1508,24 @@
 %!  endfunction
 %!  file_wrapper (@test_fn);
 
+## test readRGBAImage with orientation
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct (
+%!      "ImageLength", 10, "ImageWidth", 10,
+%!      "BitsPerSample", 8, "SamplesPerPixel", 3,
+%!      "PhotometricInterpretation", 2,
+%!      "PlanarConfiguration", 1,
+%!      "Orientation", Tiff.Orientation.BottomRight
+%!    ));
+%!    data = uint8 (reshape (1:300, [10, 10, 3]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBAImage (img);
+%!    assert (rgb, data(end:-1:1, end:-1:1, :));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
 ## test readRGBAImage with alpha
 %!testif HAVE_TIFF
 %!  function test_fn (filename)
@@ -1508,6 +1564,25 @@
 %!  endfunction
 %!  file_wrapper (@test_fn);
 
+## test readRGBAStrip with orientation
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct (
+%!      "ImageLength", 10, "ImageWidth", 10,
+%!      "BitsPerSample", 8, "SamplesPerPixel", 3,
+%!      "PhotometricInterpretation", 2,
+%!      "PlanarConfiguration", 1,
+%!      "RowsPerStrip", 3,
+%!      "Orientation", Tiff.Orientation.BottomRight
+%!    ));
+%!    data = uint8 (randi ([0,255], [10, 10, 3]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBAStrip (img, 1);
+%!    assert (rgb, data(3:-1:1,end:-1:1,:));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
 ## test readRGBAStrip boundary strip
 %!testif HAVE_TIFF
 %!  function test_fn (filename)
@@ -1566,6 +1641,25 @@
 %!  endfunction
 %!  file_wrapper (@test_fn);
 
+## test readRGBATile ith orientation
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct (
+%!      "ImageLength", 40, "ImageWidth", 40,
+%!      "BitsPerSample", 8, "SamplesPerPixel", 3,
+%!      "PhotometricInterpretation", 2,
+%!      "PlanarConfiguration", 1,
+%!      "TileLength", 16, "TileWidth", 32,
+%!      "Orientation", Tiff.Orientation.BottomRight
+%!    ));
+%!    data = uint8 (randi ([0,255], [40, 40, 3]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBATile (img, 1, 1);
+%!    assert (rgb, data(16:-1:1,32:-1:1,:));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
 ## test readRGBATile boundary tile
 %!testif HAVE_TIFF
 %!  function test_fn (filename)
@@ -1628,4 +1722,31 @@
 %!    img.setDirectory (1);
 %!    assert (img.currentDirectory, 1);
 %!  endfunction
+%!  file_wrapper (@test_fn)
+
+## test creating and reading subdirectories
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct(
+%!      "ImageLength", 10, "ImageWidth", 10,
+%!      "BitsPerSample", 8, "SubIFD", 1
+%!    ));
+%!    data = uint8 (reshape (1:100, [10, 10]));
+%!    write (img, data);
+%!    img.writeDirectory ();
+%!    setTag(img, struct(
+%!      "ImageLength", 15, "ImageWidth", 15,
+%!      "BitsPerSample", 8
+%!    ));
+%!    data_subdir = uint8 (reshape (1:225, [15, 15]));
+%!    write (img, data_subdir);
+%!    img.close();
+%!    img = Tiff (filename);
+%!    assert (img.read(), data);
+%!    offsets = getTag (img, "SubIFD");
+%!    img.setSubDirectory (offsets (1));
+%!    assert (img.read(), data_subdir);
+%!    img.close();
+%!  endfunction
 %!  file_wrapper (@test_fn)
\ No newline at end of file