# HG changeset patch # User magedrifaat # Date 1660771674 -7200 # Node ID f294b800f0026b105ab424280946c6a8616acfb8 # Parent 14edd6b09efe4acf4f9f409bc20e00348f7c6804 Tiff.m: added tests for signed images and sub-directories. diff -r 14edd6b09efe -r f294b800f002 libinterp/corefcn/__tiff__.cc --- 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 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 subifd_offsets = std::make_unique (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 (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]) diff -r 14edd6b09efe -r f294b800f002 scripts/io/Tiff.m --- 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