changeset 31171:8bf3fa6b6977

Tiff: added readRGBAStrip and readRGBATile methods * __tiff__.cc: added internal functions for reading strips and tiles using LibTIFF's RGBA interface. * Tiff.m: added readRGBAStrip and readRGBATile methods to the Tiff class and added unit tests for the ne methods.
author magedrifaat <magedrifaat@gmail.com>
date Sun, 14 Aug 2022 02:40:03 +0200
parents 72a159bc5a4c
children 3f5f1404af8a
files libinterp/corefcn/__tiff__.cc scripts/io/Tiff.m
diffstat 2 files changed, 312 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/__tiff__.cc	Sat Aug 13 17:36:12 2022 +0200
+++ b/libinterp/corefcn/__tiff__.cc	Sun Aug 14 02:40:03 2022 +0200
@@ -2059,6 +2059,11 @@
           error ("Invalid mode for openning Tiff file: %s", mode.c_str ());
       }
     
+    // LibTIFF does Strip chopping by default, which makes the organization of
+    // data exposed to the user different from the actual file, so we need to
+    // force disable this behavior by adding 'c' flag to the mode
+    mode = mode + 'c';
+    
     TIFF *tif = TIFFOpen (filename.c_str (), mode.c_str ());
     
     if (! tif)
@@ -2336,7 +2341,7 @@
   }
 
   DEFUN (__tiff_read_rgba_image__, args, ,
-             "Read the image data in rgba mode")
+         "Read the image data in rgba mode")
   {
 #if defined (HAVE_TIFF)
     int nargin = args.length ();
@@ -2348,6 +2353,8 @@
 
     tiff_image_data image_data (tif);
 
+    // Start with reversed dimensions to be aligned with LibTIFF and
+    // permute to the correct order later
     dim_vector img_dims (4, image_data.width, image_data.height);
     uint8NDArray img (img_dims);
     uint32_t *img_ptr = reinterpret_cast <uint32_t *> (img.fortran_vec ());
@@ -2356,12 +2363,14 @@
                                      img_ptr, 1))
       error ("Failed to read image");
     
+    // Permute to the correct Octave dimension order
     Array<octave_idx_type> perm (dim_vector (3, 1));
     perm(0) = 2;
     perm(1) = 1;
     perm(2) = 0;
     img = img.permute (perm);
 
+    // Slice the data into RGB and alpha
     Array<idx_vector> idx (dim_vector (3, 1));
     idx(0) = idx_vector (':');
     idx(1) = idx_vector (':');
@@ -2375,7 +2384,176 @@
     retval(1) = octave_value (alpha);
     return retval;
 #else
-    err_disabled_feature ("readEncodedTile", "Tiff");
+    err_disabled_feature ("readRGBAImage", "Tiff");
+#endif
+  }
+
+  DEFUN (__tiff_read_rgba_strip__, args, ,
+         "Read the strip data containing the given row in rgba mode")
+  {
+#if defined (HAVE_TIFF)
+    int nargin = args.length ();
+
+    if (nargin != 2)
+      error ("Wrong number of arguments");
+    
+    TIFF *tif = (TIFF *)(args(0).uint64_value ());
+
+    // TODO(maged): check matlab behavior for missing/ wrong/ out of bounds row
+    uint32_t row = args (1).uint32_scalar_value ();
+    
+    tiff_image_data image_data (tif);
+    if (image_data.is_tiled)
+      error ("The image is tiled not stripped");
+      
+    if (row < 1 || row > image_data.height)
+      error ("Row out of bounds of the image");
+    
+    // TODO(maged): check if matlab require the first row in strip as well
+    // Convert from 1-based indexing to zero-based
+    row--;
+
+    uint32_t rows_in_strip;
+    if (! TIFFGetFieldDefaulted (tif, TIFFTAG_ROWSPERSTRIP, &rows_in_strip))
+      error ("Failed to obtain a value for RowsPerStrip");
+
+    if (rows_in_strip > image_data.height)
+      rows_in_strip = image_data.height;
+    
+    // LibTIFF requires the row to be the first row in the strip
+    // so this removes any offset to reach the first row
+    row -= row % rows_in_strip;
+
+    // The exact number of rows in the strip is needed for boundary strips
+    uint32_t strip_no = TIFFComputeStrip (tif, row, 0);
+    rows_in_strip = get_rows_in_strip (strip_no, TIFFNumberOfStrips (tif),
+                                        rows_in_strip, &image_data);
+
+    // Start with reversed dimensions to be aligned with LibTIFF and
+    // permute to the correct order later
+    dim_vector strip_dims (4, image_data.width, rows_in_strip);
+    uint8NDArray strip_data (strip_dims);
+    uint32_t *strip_ptr
+      = reinterpret_cast <uint32_t *> (strip_data.fortran_vec ());
+    
+    // TODO(maged): check if matlab does anything with orientation tag
+    if (! TIFFReadRGBAStrip (tif, row, strip_ptr))
+      error ("Failed to read strip");
+    
+    // Permute to the correct order of dimensions for Octave
+    Array<octave_idx_type> perm (dim_vector (3, 1));
+    perm(0) = 2;
+    perm(1) = 1;
+    perm(2) = 0;
+    strip_data = strip_data.permute (perm);
+
+    // Slice the data into RGB and alpha
+    // The rows are reversed because LibTIFF assumes a bottom-left orientation
+    Array<idx_vector> idx (dim_vector (3, 1));
+    idx(0) = idx_vector (rows_in_strip - 1, -1, -1);
+    idx(1) = idx_vector (':');
+    idx(2) = idx_vector (0, 3);
+    uint8NDArray rgb = uint8NDArray (strip_data.index (idx));
+    idx(2) = idx_vector (3);
+    uint8NDArray alpha = uint8NDArray (strip_data.index (idx));
+    
+    octave_value_list retval (2);
+    retval(0) = octave_value (rgb);
+    retval(1) = octave_value (alpha);
+    return retval;
+#else
+    err_disabled_feature ("readRGBAStrip", "Tiff");
+#endif
+  }
+
+  DEFUN (__tiff_read_rgba_tile__, args, ,
+         "Read the stile data containing the given row and column in rgba mode")
+  {
+#if defined (HAVE_TIFF)
+    int nargin = args.length ();
+
+    if (nargin != 3)
+      error ("Wrong number of arguments");
+    
+    TIFF *tif = (TIFF *)(args(0).uint64_value ());
+
+    // TODO(maged): check matlab behavior for missing/ wrong/ out of bounds vals
+    uint32_t row = args (1).uint32_scalar_value ();
+    uint32_t col = args (2).uint32_scalar_value ();
+    
+    tiff_image_data image_data (tif);
+    if (! image_data.is_tiled)
+      error ("The image is stripped not tiled");
+    
+    if (row < 1 || row > image_data.height)
+      error ("Row out of bounds of the image");
+    if (col < 1 || col > image_data.width)
+      error ("Column out of bounds of the image");
+    
+    // TODO(maged): check if matlab require the first row,col in tile as well
+    // Convert from 1-based indexing to zero-based
+    row--;
+    col--;
+
+    uint32_t tile_width, tile_height;
+    if (! TIFFGetField (tif, TIFFTAG_TILELENGTH, &tile_height))
+      error ("Failed to obtain a value for TileLength");
+    if (! TIFFGetField (tif, TIFFTAG_TILEWIDTH, &tile_width))
+      error ("Failed to obtain a value for TileWidth");
+    
+    // LibTIFF requires the row and columns to be the top-left corner of the
+    // tile, so this removes any offset to reach the top-left row and column
+    // of the tile
+    row -= row % tile_height;
+    col -= col % tile_width;
+    
+    // Start with reversed dimensions to be aligned with LibTIFF and
+    // permute to the correct order later
+    dim_vector tile_dims (4, tile_width, tile_height);
+    uint8NDArray tile_data (tile_dims);
+    uint32_t *tile_ptr
+      = reinterpret_cast <uint32_t *> (tile_data.fortran_vec ());
+    
+    // TODO(maged): check if matlab does anything with orientation tag
+    if (! TIFFReadRGBATile (tif, col, row, tile_ptr))
+      error ("Failed to read tile");
+    
+    // Permute to the correct order of dimensions for Octave
+    Array<octave_idx_type> perm (dim_vector (3, 1));
+    perm(0) = 2;
+    perm(1) = 1;
+    perm(2) = 0;
+    tile_data = tile_data.permute (perm);
+
+    // Calculate the correct dimensions for boundary tiles
+    uint32_t corrected_height = tile_height;
+    uint32_t corrected_width = tile_width;
+    if (row + tile_height > image_data.height)
+      corrected_height = image_data.height - row;
+    if (col + tile_width > image_data.width)
+      corrected_width = image_data.width - col;
+    
+    // Slice the data into RGB and alpha
+    // LibTIFF assumes the image has bottom-left orientation and returns
+    // the rows flipped vertically, so we need to reverse them and remove
+    // the padding which is at the top since the rows are flipped
+    Array<idx_vector> idx (dim_vector (3, 1));
+    // Must cast the unsigned values to signed because otherwise the output
+    // can't be negative (C++ is the best).
+    idx(0) = idx_vector (tile_height - 1,
+                         int64_t(tile_height - corrected_height) - 1, -1);
+    idx(1) = idx_vector (0, corrected_width);
+    idx(2) = idx_vector (0, 3);
+    uint8NDArray rgb = uint8NDArray (tile_data.index (idx));
+    idx(2) = idx_vector (3);
+    uint8NDArray alpha = uint8NDArray (tile_data.index (idx));
+    
+    octave_value_list retval (2);
+    retval(0) = octave_value (rgb);
+    retval(1) = octave_value (alpha);
+    return retval;
+#else
+    err_disabled_feature ("readRGBATile", "Tiff");
 #endif
   }
 
--- a/scripts/io/Tiff.m	Sat Aug 13 17:36:12 2022 +0200
+++ b/scripts/io/Tiff.m	Sun Aug 14 02:40:03 2022 +0200
@@ -260,6 +260,20 @@
       [RGB, alpha] = __tiff_read_rgba_image__ (t.tiff_handle);
     endfunction
 
+    function [RGB, alpha] = readRGBAStrip (t, row)
+      if (t.closed)
+        error ("Image file was closed");
+      endif
+      [RGB, alpha] = __tiff_read_rgba_strip__ (t.tiff_handle, row);
+    endfunction
+
+    function [RGB, alpha] = readRGBATile (t, row, col)
+      if (t.closed)
+        error ("Image file was closed");
+      endif
+      [RGB, alpha] = __tiff_read_rgba_tile__ (t.tiff_handle, row, col);
+    endfunction
+
     function write (t, imageData)
       if (t.closed)
         error ("Image file was closed");
@@ -1034,7 +1048,7 @@
 %!    writeEncodedStrip (img, 1, uint8 (reshape (1:20, [2, 10])));
 %!    assert (computeStrip (img, 1, 2), 6);
 %!    assert (computeStrip (img, 100, 1), 5);
-%!    img = Tiff ("test.tif", "w");
+%!    img = Tiff (filename, "w");
 %!    setTag (img, "TileWidth", 16);
 %!    setTag (img, "TileLength", 16);
 %!    fail ("computeStrip (img, 1, 1)", "The image is tiled not stripped");
@@ -1062,7 +1076,7 @@
 %!    writeEncodedTile (img, 1, uint8 (reshape (1:256, [16, 16])));
 %!    assert (computeTile (img, [1, 1], 2), 5);
 %!    assert (computeTile (img, [100, 100], 1), 4);
-%!    img = Tiff ("test.tif", "w");
+%!    img = Tiff (filename, "w");
 %!    fail ("computeTile (img, 1, 1)", "The image is stripped not tiled");
 %!  endfunction
 %!  file_wrapper (@test_fn);
@@ -1494,3 +1508,119 @@
 %!    assert (alpha, data(:,:,4));
 %!  endfunction
 %!  file_wrapper (@test_fn);
+
+## test readRGBAStrip
+%!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
+%!    ));
+%!    data = uint8 (randi ([0,255], [10, 10, 3]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBAStrip (img, 1);
+%!    assert (rgb, data(1:3,:,:));
+%!    assert (alpha, uint8 (repmat ([255], [3, 10])));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
+## test readRGBAStrip boundary strip
+%!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
+%!    ));
+%!    data = uint8 (randi ([0,255], [10, 10, 3]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBAStrip (img, 10);
+%!    assert (rgb, data(10,:,:));
+%!    assert (alpha, uint8 (repmat ([255], [1, 10])));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
+## test readRGBAStrip with alpha
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct (
+%!      "ImageLength", 10, "ImageWidth", 10,
+%!      "BitsPerSample", 8, "SamplesPerPixel", 4,
+%!      "PhotometricInterpretation", 2,
+%!      "PlanarConfiguration", 1,
+%!      "RowsPerStrip", 3,
+%!      "ExtraSamples", 1
+%!    ));
+%!    data = uint8 (randi ([0,255], [10, 10, 4]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBAStrip (img, 1);
+%!    assert (rgb, data(1:3,:, 1:3));
+%!    assert (alpha, data (1:3, :, 4));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
+## test readRGBATile
+%!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
+%!    ));
+%!    data = uint8 (randi ([0,255], [40, 40, 3]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBATile (img, 1, 1);
+%!    assert (rgb, data(1:16,1:32,:));
+%!    assert (alpha, uint8 (repmat ([255], [16, 32])));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
+## test readRGBATile boundary tile
+%!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
+%!    ));
+%!    data = uint8 (randi ([0,255], [40, 40, 3]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBATile (img, 40, 40);
+%!    assert (rgb, data(33:end,33:end,:));
+%!    assert (alpha, uint8 (repmat ([255], [8, 8])));
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
+## test readRGBATile ith alpha
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct (
+%!      "ImageLength", 40, "ImageWidth", 40,
+%!      "BitsPerSample", 8, "SamplesPerPixel", 4,
+%!      "PhotometricInterpretation", 2,
+%!      "PlanarConfiguration", 1,
+%!      "TileLength", 16, "TileWidth", 32,
+%!      "ExtraSamples", 1
+%!    ));
+%!    data = uint8 (randi ([0,255], [40, 40, 4]));
+%!    write (img, data);
+%!    [rgb, alpha] = readRGBATile (img, 1, 1);
+%!    assert (rgb, data(1:16,1:32,1:3));
+%!    assert (alpha, data(1:16,1:32,4));
+%!  endfunction
+%!  file_wrapper (@test_fn);