changeset 31146:50402b8dfb4a

Tiff: added writeEncodedTile function for writing tiled images * __tiff__.cc(F__tiff_write_encoded_tile__): added internal function for handling writeEncodedStrip. * __tiff__.cc(process_strip_or_tile): refactored common logic for strips and tiles to a function. * __tiff__.cc(write_tile): implemented logic for checking and writing tile data to image. * Tiff.m: added writeEncodedStrip method.
author magedrifaat <magedrifaat@gmail.com>
date Sun, 31 Jul 2022 18:34:59 +0200
parents 2e11f9cb30b8
children 7af78a63d3c3
files libinterp/dldfcn/__tiff__.cc scripts/io/Tiff.m
diffstat 2 files changed, 243 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/dldfcn/__tiff__.cc	Sun Jul 31 03:55:42 2022 +0200
+++ b/libinterp/dldfcn/__tiff__.cc	Sun Jul 31 18:34:59 2022 +0200
@@ -299,7 +299,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)
           {
@@ -322,7 +321,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 &&
@@ -905,7 +903,7 @@
     dim_vector strip_dimensions;
 
     // Calculate the expected number of elements in the strip data array
-    // All strips have equal number of rows excpet strips at the bottom
+    // All strips have equal number of rows except strips at the bottom
     // of the image can have less number of rows
     if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
       {
@@ -934,12 +932,12 @@
       error ("Planar configuration not supported");
 
     if (strip_data.dim1 () > rows_in_strip)
-      warning ("The strip is composed of %ld rows but the input has %ld rows.",
+      warning ("The strip is composed of %u rows but the input has %ld rows.",
                rows_in_strip,
                strip_data.dim1 ());
     
     if (strip_data.dim2 () > image_data->width)
-      warning ("The image width is %ld but the input has %ld columns.",
+      warning ("The image width is %u but the input has %ld columns.",
                image_data->width,
                strip_data.dim2 ());
     
@@ -947,12 +945,12 @@
       {
         if (image_data->planar_configuration == PLANARCONFIG_CONTIG
             && strip_data.dim3 () > image_data->samples_per_pixel)
-          warning ("The strip is composed of %ld channels but the input has %ld channels.",
+          warning ("The strip is composed of %u channels but the input has %ld channels.",
                    image_data->samples_per_pixel,
                    strip_data.dim3 ());
         else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE
                 && strip_data.dim3 () > 1)
-          warning ("The strip is composed of %ld channel but the input has %ld channels.",
+          warning ("The strip is composed of %u channel but the input has %ld channels.",
                    1, strip_data.dim3 ());
       }
 
@@ -1017,6 +1015,201 @@
       }
   }
 
+  template <typename T>
+  void
+  write_tile (TIFF *tif, uint32_t tile_no, T tile_data,
+              tiff_image_data *image_data)
+  {
+    // TODO(maged): error for tiles not divisible by 16?
+    uint32_t tile_width, tile_height;
+    if (! TIFFGetField (tif, TIFFTAG_TILEWIDTH, &tile_width))
+      error ("Failed to get the tile width");
+    if (! TIFFGetField (tif, TIFFTAG_TILELENGTH, &tile_height))
+      error ("Failed to get the tile length");
+    
+    if (tile_no < 1 || tile_no > TIFFNumberOfTiles (tif))
+      error ("Tile number out of bounds");
+
+    // TODO(maged): what does matlab do for boundary tiles?
+    if (tile_data.dim1 () > tile_height)
+      warning ("The tile is composed of %u rows but input has %ld rows",
+               tile_height, tile_data.dim1 ());
+    if (tile_data.dim2 () > tile_width)
+      warning ("The tile is composed of %u columns but input has %ld columns",
+               tile_width, tile_data.dim2 ());
+    if (tile_data.ndims () > 2)
+      {
+        if (image_data->planar_configuration == PLANARCONFIG_CONTIG
+            && tile_data.dim3 () > image_data->samples_per_pixel)
+          warning ("The tile is composed of %u channels but input has %ld channels",
+                  image_data->samples_per_pixel, tile_data.dim3 ());
+        else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE
+                 && tile_data.dim3 () > 1)
+          warning ("The tile is composed of %u channels but input has %ld channels",
+                   1, tile_data.dim3 ());
+      }
+    
+    dim_vector tile_dimensions;
+    if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
+      tile_dimensions = dim_vector (tile_height, tile_width,
+                                    image_data->samples_per_pixel);
+    else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE)
+      tile_dimensions = dim_vector (tile_height, tile_width, 1);
+    else
+      error ("Planar configuration not supported");
+    
+    tile_data.resize (tile_dimensions);
+    Array<octave_idx_type> perm (dim_vector (3, 1));
+    perm(0) = 2;
+    perm(1) = 1;
+    perm(2) = 0;
+    tile_data = tile_data.permute (perm);
+    
+    // Octave indexing is 1-based while LibTIFF is zero-based
+    tile_no--;
+    void *data_vec = tile_data.fortran_vec ();
+    if (image_data->bits_per_sample == 8
+        || image_data->bits_per_sample == 16
+        || image_data->bits_per_sample == 32
+        || image_data->bits_per_sample == 64)
+      {
+        if (TIFFWriteEncodedTile (tif, tile_no, data_vec,
+                                  TIFFTileSize (tif)) == -1)
+          error ("Failed to write tile data to image");
+        
+      }
+    else if (image_data->bits_per_sample == 1)
+      {
+        if (image_data->samples_per_pixel != 1)
+          error ("Bi-Level images must have one channel only");
+        
+        // Create a buffer to hold the packed tile data
+        // Unique pointers are faster than vectors for constant size buffers
+        std::unique_ptr<uint8_t []> tile_ptr
+          = std::make_unique<uint8_t []> (TIFFTileSize (tif));
+        uint8_t *tile_buf = tile_ptr.get ();
+        uint8_t *data_u8 = reinterpret_cast<uint8_t *> (data_vec);
+        // Packing the pixel data into bits
+        for (uint32_t row = 0; row < tile_height; row++)
+          {
+            for (uint32_t col = 0; col < tile_width; col++)
+            {
+              uint8_t shift = 7 - col % 8;
+              tile_buf[row * tile_width/8 + col/8] |= data_u8[col] << shift;
+            }
+            data_u8 += tile_width;
+          }
+        if (TIFFWriteEncodedTile (tif, tile_no, tile_buf,
+                                  TIFFTileSize (tif)) == -1)
+          error ("Failed to write tile data to image");
+      }
+    else
+      {
+        error ("Unsupported bit depth");
+      }
+  }
+
+  template <typename T>
+  void
+  write_strip_or_tile (TIFF *tif, uint32_t strip_tile_no, T strip_data,
+                       tiff_image_data *image_data)
+  {
+    if (image_data->is_tiled)
+      write_tile<T> (tif, strip_tile_no, strip_data, image_data);
+    else
+      write_strip<T> (tif, strip_tile_no, strip_data, image_data);
+  }
+
+  void
+  process_strip_or_tile (TIFF *tif, uint32_t strip_tile_no,
+                         octave_value data_ov, tiff_image_data *image_data)
+  {
+
+    // SampleFormat tag is not a required field and has a default value of 1
+    // So we need to use TIFFGetFieldDefaulted in case it is not present in
+    // the file
+    uint16_t sample_format;
+    if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format))
+      error ("Failed to obtain a value for sample format");
+
+    // TODO(maged): add support for signed integer images
+    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");
+      }
+
+    // The standard specifies that a SampleFormat of 4 should be treated
+    // the same as 1 (unsigned integer)
+    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
+        // element strip
+        if (data_ov.is_bool_scalar () || data_ov.is_bool_matrix ())
+          write_strip_or_tile<boolNDArray> (tif, strip_tile_no,
+                                            data_ov.bool_array_value (),
+                                            image_data);
+        else
+          error ("Expected logical matrix for BiLevel image");
+        break;
+      case 8:
+        if (data_ov.is_uint8_type ())
+          write_strip_or_tile<uint8NDArray> (tif, strip_tile_no,
+                                             data_ov.uint8_array_value (),
+                                             image_data);
+        else
+          error ("Only uint8 data is allowed for uint images with bit depth of 8");
+        break;
+      case 16:
+        if (data_ov.is_uint16_type ())
+          write_strip_or_tile<uint16NDArray> (tif, strip_tile_no,
+                                              data_ov.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 (data_ov.is_single_type () || data_ov.is_double_type ())
+            write_strip_or_tile<FloatNDArray> (tif, strip_tile_no,
+                                               data_ov.float_array_value (),
+                                               image_data);
+          else
+            error ("Only single and double data are allowed for floating-point images");
+        else
+          if (data_ov.is_uint32_type ())
+            write_strip_or_tile<uint32NDArray> (tif, strip_tile_no,
+                                                data_ov.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 (data_ov.is_single_type () || data_ov.is_double_type ())
+            write_strip_or_tile<NDArray> (tif, strip_tile_no,
+                                          data_ov.array_value (),
+                                          image_data);
+          else
+            error ("Only single and double data are allowed for floating-point images");
+        else  
+          if (data_ov.is_uint64_type ())
+            write_strip_or_tile<uint64NDArray> (tif, strip_tile_no,
+                                                data_ov.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");
+      }
+  }
+
 #endif
 
   DEFUN_DLD (__open_tiff__, args, ,
@@ -1257,6 +1450,7 @@
 #if defined (HAVE_TIFF)
     int nargin = args.length ();
 
+    // TODO(maged): add support for YCbCr data
     if (nargin < 3)
       error ("Too few arguments provided\n");
     
@@ -1276,90 +1470,45 @@
     if (strip_no < 1 || strip_no > TIFFNumberOfStrips (tif))
       error ("Strip number out of range");
     
-    // SampleFormat tag is not a required field and has a default value of 1
-    // So we need to use TIFFGetFieldDefaulted in case it is not present in
-    // the file
-    uint16_t sample_format;
-    if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format))
-      error ("Failed to obtain a value for sample format");
-
-    // TODO(maged): add support for signed integer images
-    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");
-      }
-    // The standard specifies that a SampleFormat of 4 should be treated
-    // the same as 1 (unsigned integer)
-    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
-        // element strip
-        if (args(2).is_bool_scalar () || args(2).is_bool_matrix ())
-          write_strip<boolNDArray> (tif, strip_no,
-                                    args(2).bool_array_value (), &image_data);
-        else
-          error ("Expected logical matrix for BiLevel image");
-        break;
-      case 8:
-        if (args(2).is_uint8_type ())
-          write_strip<uint8NDArray> (tif, strip_no,
-                                     args(2).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(2).is_uint16_type ())
-          write_strip<uint16NDArray> (tif, strip_no,
-                                      args(2).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(2).is_single_type () || args(2).is_double_type ())
-            write_strip<FloatNDArray> (tif, strip_no,
-                                       args(2).float_array_value (),
-                                       &image_data);
-          else
-            error ("Only single and double data are allowed for floating-point images");
-        else
-          if (args(2).is_uint32_type ())
-            write_strip<uint32NDArray> (tif, strip_no,
-                                        args(2).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(2).is_single_type () || args(2).is_double_type ())
-            write_strip<NDArray> (tif, strip_no,
-                                  args(2).array_value (),
-                                  &image_data);
-          else
-            error ("Only single and double data are allowed for floating-point images");
-        else  
-          if (args(2).is_uint64_type ())
-            write_strip<uint64NDArray> (tif, strip_no,
-                                        args(2).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");
-      }
+    process_strip_or_tile (tif, strip_no, args(2), &image_data);
     
     return octave_value_list ();
 #else
     err_disabled_feature ("writeEncodedStrip", "Tiff");
 #endif
   }
+
+  DEFUN_DLD (__tiff_write_encoded_tile__, args, ,
+             "Write an encoded tile to the image")
+  {
+#if defined (HAVE_TIFF)
+    int nargin = args.length ();
+
+    // TODO(maged): add support for YCbCr data
+    if (nargin < 3)
+      error ("Too few arguments provided\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);
+
+    if (! image_data.is_tiled)
+      error ("Can't write tiles to a stripped image");
+
+    uint32_t tile_no = args (1).uint32_scalar_value ();
+    if (tile_no < 1 || tile_no > TIFFNumberOfTiles (tif))
+      error ("Tile number out of range");
+
+    process_strip_or_tile (tif, tile_no, args(2), &image_data);
+    
+    return octave_value_list ();
+#else
+    err_disabled_feature ("writeEncodedTile", "Tiff");
+#endif
+  }
 }
--- a/scripts/io/Tiff.m	Sun Jul 31 03:55:42 2022 +0200
+++ b/scripts/io/Tiff.m	Sun Jul 31 18:34:59 2022 +0200
@@ -123,6 +123,13 @@
       __tiff_write_encoded_strip__ (t.tiff_handle, stripNumber, imageData);
     endfunction
 
+    function writeEncodedTile (t, tileNumber, imageData)
+      if (t.closed)
+        error ("Image file was closed");
+      endif
+      __tiff_write_encoded_tile__ (t.tiff_handle, tileNumber, imageData);
+    endfunction
+
     % TODO(maged): add documentation and make print_usage work
   endmethods
 endclassdef