# HG changeset patch # User magedrifaat # Date 1660437603 -7200 # Node ID 8bf3fa6b6977cd77b7229fd79cb5cb9e107d4428 # Parent 72a159bc5a4ca44410c8448578db3c04dd09d519 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. diff -r 72a159bc5a4c -r 8bf3fa6b6977 libinterp/corefcn/__tiff__.cc --- 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 (img.fortran_vec ()); @@ -2356,12 +2363,14 @@ img_ptr, 1)) error ("Failed to read image"); + // Permute to the correct Octave dimension order Array 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 (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 (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 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 (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 (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 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 (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 } diff -r 72a159bc5a4c -r 8bf3fa6b6977 scripts/io/Tiff.m --- 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);