# HG changeset patch # User magedrifaat # Date 1661715243 -7200 # Node ID 0cdb7f35641e00fcd0c1ecf4e8eec7720d92aaba # Parent c142c153034c3bc9ca5404f0e666c8b7a192710d Tiff: added handler for imfinfo that uses the Tiff interface * __tiff__.cc (F__tiff_imfinfo__): implemented handler for imfinfo function that uses the Tiff interface to retrieve format specific information about tiff images. diff -r c142c153034c -r 0cdb7f35641e libinterp/corefcn/__tiff__.cc --- a/libinterp/corefcn/__tiff__.cc Sat Aug 27 23:06:54 2022 +0200 +++ b/libinterp/corefcn/__tiff__.cc Sun Aug 28 21:34:03 2022 +0200 @@ -4,6 +4,7 @@ #include #include +#include #include "defun.h" #include "ov.h" @@ -13,7 +14,10 @@ #include "errwarn.h" #include "fcntl-wrappers.h" +#include "file-stat.h" #include "file-ops.h" +#include "str-vec.h" +#include "oct-time.h" #if defined (HAVE_TIFF) # include @@ -100,6 +104,9 @@ // This doesn't completely solve the issue as LibTIFF will refuse // to write data to images of the tag is not set. // see: https://www.asmail.be/msg0054918184.html + // This is now solved by this commit: + // https://gitlab.com/libtiff/libtiff/-/commit/ac6ddaf678fff597610cb217705b55508240f065 + // So this shouldn't be needed for future LibTIFF releases > 4.4.0 planar_configuration = 1; is_tiled = TIFFIsTiled(tif); @@ -3120,7 +3127,7 @@ TIFF *tif = tiff_handle->get_file (); bool is_tiled = static_cast (TIFFIsTiled (tif)); - return octave_value_list (octave_value (is_tiled)); + return ovl (is_tiled); #else err_disabled_feature ("isTiled", "Tiff"); #endif @@ -3147,7 +3154,7 @@ error ("The image is tiled not stripped"); double strip_count = static_cast (TIFFNumberOfStrips (tif)); - return octave_value_list (octave_value (strip_count)); + return ovl (strip_count); #else err_disabled_feature ("numberOfStrips", "Tiff"); #endif @@ -3174,7 +3181,7 @@ error ("The image is stripped not tiled"); double tile_count = static_cast (TIFFNumberOfTiles (tif)); - return octave_value_list (octave_value (tile_count)); + return ovl (tile_count); #else err_disabled_feature ("numberOfTiles", "Tiff"); #endif @@ -3231,7 +3238,7 @@ double strip_number = TIFFComputeStrip (tif, row, plane) + 1; if (strip_number > TIFFNumberOfStrips (tif)) strip_number = TIFFNumberOfStrips (tif); - return octave_value_list (octave_value (strip_number)); + return ovl (strip_number); #else err_disabled_feature ("computeStrip", "Tiff"); #endif @@ -3297,7 +3304,7 @@ double tile_number = TIFFComputeTile (tif, col, row, 0, plane) + 1; if (tile_number > TIFFNumberOfTiles (tif)) tile_number = TIFFNumberOfTiles (tif); - return octave_value_list (octave_value (tile_number)); + return ovl (tile_number); #else err_disabled_feature ("computeTile", "Tiff"); #endif @@ -3323,7 +3330,7 @@ uint16_t dir = TIFFCurrentDirectory (tif); dir++; - return octave_value_list (octave_value (static_cast (dir))); + return ovl (static_cast (dir)); #else err_disabled_feature ("currentDirectory", "Tiff"); #endif @@ -3348,7 +3355,7 @@ bool is_last = TIFFLastDirectory (tif); - return octave_value_list (octave_value (is_last)); + return ovl (is_last); #else err_disabled_feature ("lastDirectory", "Tiff"); #endif @@ -3519,7 +3526,7 @@ { #if defined (HAVE_TIFF) std::string version = std::string (TIFFGetVersion ()); - return octave_value_list (octave_value (version)); + return ovl (version); #else err_disabled_feature ("getVersion", "Tiff"); #endif @@ -3686,7 +3693,7 @@ } else if (strcasecmp (param_cstr, "info") == 0) { - // FIXME: is this useful for our use case? + // This isn't very useful here, ignoring it } else error ("imread: invalid PARAMETER '%s'", param_cstr); @@ -3711,7 +3718,6 @@ retval (0) = read_float_image (tif, &image_data); break; default: - // FIXME: should this fallback to magick instead? error ("Unsupported sample format"); } @@ -3721,7 +3727,7 @@ // than the image size because the entire image will be read first octave_value_list idx (3); // Need to use range because normal idx_vector constuctor handles steps - // in a wrong way (FIXME?) + // in a wrong way idx(0) = idx_vector (range (row_region (0), row_region (1), row_region(2))); idx(1) = idx_vector (range (col_region (0), col_region (1), @@ -3762,7 +3768,11 @@ if (! (args(0).isnumeric () || args(0).is_bool_matrix () || args(0).is_bool_scalar ())) error ("imwrite: Expected image matrix as first argument"); - + + // The argument checks here are very similar to the ones found in + // __imwrite__.m and imwrite_filename.m, the code is duplicated here since + // the original code is tailored to the generic library graphicsmagick + // so it is not suitable to be used directly for this case uint16_t offset = 1; std::string filename; octave_value cmap = octave_value (NDArray ()); @@ -3789,12 +3799,14 @@ if ((nargin - offset) % 2 != 0) error ("imwrite: no pair for all arguments (odd number left)"); + // Setting the default value for the rest of the parameters bool append = false; uint16_t compression = COMPRESSION_NONE; uint32_t rows_per_strip = UINT32_MAX; float x_res = 72, y_res = 72; std::string description = ""; + // Extract the values for the paramters provided for (uint16_t arg_idx = offset; arg_idx < nargin; arg_idx+=2) { if (! args(arg_idx).is_string ()) @@ -3818,13 +3830,11 @@ else if (comp == "lzw") compression = COMPRESSION_LZW; else if (comp == "deflate") - // FIXME: check matlab if this is adobe deflate compression = COMPRESSION_DEFLATE; else if (comp == "jpeg") compression = COMPRESSION_JPEG; else if (comp == "ccitt") - // FIXME: check matlab which one this is - compression = COMPRESSION_CCITT_T4; + compression = COMPRESSION_CCITTRLE; else if (comp == "fax3") compression = COMPRESSION_CCITTFAX3; else if (comp == "fax4") @@ -3885,17 +3895,20 @@ error ("imread: invalid PARAMETER '%s'", param_cstr); } - // FIXME: does matlab error if appending to non existant file? + // Matlab specifies that JPEG compression must also specify RowsPerStrip + // and must be a value divisible by 8 if (compression == COMPRESSION_JPEG && (rows_per_strip == UINT32_MAX || rows_per_strip % 8 != 0)) error ("imwrite: RowsPerStrip option must be specified for jpeg compression and must be divisible by 8"); + // These compression schemes are only available for binary images if (! (args(0).is_bool_matrix () || args(0).is_bool_scalar ()) - && (compression == COMPRESSION_CCITT_T4 + && (compression == COMPRESSION_CCITTRLE || compression == COMPRESSION_CCITTFAX3 || compression == COMPRESSION_CCITTFAX4)) error ("imwrite: the specified compression scheme is for binary images only"); + // Matlab rejects 4D data for TIFF images dim_vector img_dims = args(0).dims (); if (args(0).ndims () != 2 && args(0).ndims () != 3) error ("imwrite: expected 2-dimensional or 3-dimensional image matrix"); @@ -3963,6 +3976,8 @@ if (! TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample)) error ("Failed to set BitsPerSample tag"); + // Infer the photometric interpretation of the image from the color map + // and the number of channels of the input uint16_t photometric = PHOTOMETRIC_MINISBLACK; if (cmap.numel () > 0) { @@ -3995,4 +4010,307 @@ err_disabled_feature ("imwrite", "Tiff"); #endif } + + DEFUN (__tiff_imfinfo__, args, , + "Handler for imfinfo that uses Tiff interface") + { +#if defined (HAVE_TIFF) + int nargin = args.length (); + + if (nargin < 1) + error ("imfinfo: missing filename argument"); + + if (! args(0).is_string ()) + error ("imfinfo: filename must be a string"); + + std::string filename = args(0).string_value (); + + const sys::file_stat fs (filename); + if (! fs) + error ("imfinfo: error reading '%s': %s", filename.c_str (), + fs.error ().c_str ()); + + TIFF *tif = TIFFOpen (filename.c_str (), "rc"); + if (! tif) + error ("imfinfo: error reading '%s': LibTIFF failed to read file", + filename.c_str ()); + + // The destructor for this object will be called when the function returnd + // or in case of an error so the file will always get closed at the end + octave_tiff_handle tiff_handle (tif); + + // A lot of the logic here is copied from __magick_finfo__ due to the + // great similarity between the two functions but this function is + // format specific so a lot of the details are different + uint16_t dir_count = TIFFNumberOfDirectories (tif); + + // Null terminated char* list to be used to create a string_vector + static const char *fields[] = { + "Filename", + "FileModDate", + "FileSize", + "Format", + "FormatVersion", + "Width", + "Height", + "BitDepth", + "ColorType", + "FormatSignature", + "ByteOrder", + "NewSubFileType", + "BitsPerSample", + "Compression", + "PhotometricInterpretation", + "StripOffsets", + "SamplesPerPixel", + "RowsPerStrip", + "StripByteCounts", + "XResolution", + "YResolution", + "ResolutionUnit", + "Colormap", + "PlanarConfiguration", + "TileWidth", + "TileLength", + "TileOffsets", + "TileByteCounts", + "Orientation", + "FillOrder", + "GrayResponseUnit", + "MaxSampleValue", + "MinSampleValue", + "Thresholding", + "Offset", + "ImageDescription", + nullptr + }; + + // A map to be used as a struct array to hold a scalar_map for each + // directory + octave_map info (dim_vector (dir_count, 1), string_vector (fields)); + + // populate template_info with the info that is common between all + // directories in the file + octave_scalar_map template_info = (string_vector (fields)); + template_info.setfield ("Format", octave_value ("tif")); + template_info.setfield ("FormatVersion", octave_value ("")); + const sys::localtime mtime (fs.mtime ()); + const std::string filetime = mtime.strftime ("%e-%b-%Y %H:%M:%S"); + template_info.setfield ("Filename", octave_value (filename)); + template_info.setfield ("FileModDate", octave_value (filetime)); + template_info.setfield ("FileSize", octave_value (fs.size ())); + + // Extract the image signature (first 4 bytes in file) + std::ifstream tif_file; +#if defined (OCTAVE_USE_WINDOWS_API) + std::wstring wname = sys::u8_to_wstring (filename); + tif_file.open (wname.c_str (), std::ios_base::binary); +#else + tif_file.open (filename.c_str (), std::ios_base::binary); +#endif + uint8NDArray signature (dim_vector (1, 4));; + tif_file.read (reinterpret_cast (signature.fortran_vec ()), 4); + tif_file.close (); + template_info.setfield ("FormatSignature", octave_value (signature)); + + std::string byte_order + = TIFFIsBigEndian (tif)? "big-endian": "little-endian"; + template_info.setfield ("ByteOrder", octave_value (byte_order)); + + // Extract directory specific information + for (uint16_t dir = 0; dir < dir_count; dir++) + { + octave_scalar_map dir_info (template_info); + + // Switch to the directory + if (! TIFFSetDirectory (tif, dir)) + error ("imfinfo: Failed to access frame %d\n", dir); + + tiff_image_data image_data (tif); + dir_info.setfield ("Width", octave_value (image_data.width)); + + dir_info.setfield ("Height", octave_value (image_data.height)); + + uint16_t bit_depth = image_data.samples_per_pixel + * image_data.bits_per_sample; + dir_info.setfield ("BitDepth", octave_value (bit_depth)); + + std::string planar = "unrecognized"; + if (image_data.planar_configuration == 1) + planar = "Chunky"; + else if (image_data.planar_configuration == 2) + planar = "Separate"; + dir_info.setfield ("PlanarConfiguration", octave_value (planar)); + + // Extract photometric information as well as color map if exists + std::string color_str, photometric_str; + uint16_t photometric = PHOTOMETRIC_MINISBLACK; + octave_value cmap = octave_value (Matrix ()); + const TIFFField *fip; + TIFFGetField (tif, TIFFTAG_PHOTOMETRIC, &photometric); + switch (photometric) + { + case PHOTOMETRIC_MINISBLACK: + color_str = "grayscale"; + photometric_str = "BlasckIsZero"; + break; + case PHOTOMETRIC_MINISWHITE: + color_str = "grayscale"; + photometric_str = "WhiteIsZero"; + break; + case PHOTOMETRIC_RGB: + color_str = "truecolor"; + photometric_str = "RGB"; + break; + case PHOTOMETRIC_PALETTE: + color_str = "indexed"; + photometric_str = "RGB Palette"; + fip = TIFFFieldWithTag (tif, TIFFTAG_COLORMAP); + cmap = get_field_data (tif, fip); + break; + case PHOTOMETRIC_SEPARATED: + color_str = "CMYK"; + photometric_str = "CMYK"; + break; + default: + color_str = "undefined"; + photometric_str = "undefined"; + } + dir_info.setfield ("ColorType", octave_value(color_str)); + dir_info.setfield ("PhotometricInterpretation", + octave_value(photometric_str)); + dir_info.setfield ("Colormap", octave_value (cmap)); + + fip = TIFFFieldWithTag (tif, TIFFTAG_SUBFILETYPE); + dir_info.setfield ("NewSubFileType", get_field_data (tif, fip)); + + fip = TIFFFieldWithTag (tif, TIFFTAG_BITSPERSAMPLE); + dir_info.setfield ("BitsPerSample", get_field_data (tif, fip)); + + fip = TIFFFieldWithTag (tif, TIFFTAG_SAMPLESPERPIXEL); + dir_info.setfield ("SamplesPerPixel", get_field_data (tif, fip)); + + // Use LibTIFF's compression codec to extract compression scheme name + uint16_t compression; + TIFFGetFieldDefaulted (tif, TIFFTAG_COMPRESSION, &compression); + std::string comp_str = "unrecognized"; + const TIFFCodec *codec = TIFFFindCODEC (compression); + if (codec) + comp_str = codec->name; + dir_info.setfield ("Compression", octave_value (comp_str)); + + // Set strip-specific and tile-specific fields accordingly + bool tiled = TIFFIsTiled (tif); + fip = TIFFFieldWithTag (tif, TIFFTAG_TILELENGTH); + dir_info.setfield ("TileLength", tiled? get_field_data (tif, fip) + : octave_value (Matrix ())); + fip = TIFFFieldWithTag (tif, TIFFTAG_TILEWIDTH); + dir_info.setfield ("TileWidth", tiled? get_field_data (tif, fip) + : octave_value (Matrix ())); + fip = TIFFFieldWithTag (tif, TIFFTAG_TILEOFFSETS); + dir_info.setfield ("TileOffsets", tiled? get_field_data (tif, fip) + : octave_value (Matrix ())); + fip = TIFFFieldWithTag (tif, TIFFTAG_TILEBYTECOUNTS); + dir_info.setfield ("TileByteCounts", tiled? get_field_data (tif, fip) + : octave_value (Matrix ())); + fip = TIFFFieldWithTag (tif, TIFFTAG_ROWSPERSTRIP); + dir_info.setfield ("RowsPerStrip", tiled? octave_value (Matrix ()) + : get_field_data (tif, fip)); + fip = TIFFFieldWithTag (tif, TIFFTAG_STRIPOFFSETS); + dir_info.setfield ("StripOffsets", tiled? octave_value (Matrix ()) + : get_field_data (tif, fip)); + fip = TIFFFieldWithTag (tif, TIFFTAG_STRIPBYTECOUNTS); + dir_info.setfield ("StripByteCounts", tiled? octave_value (Matrix ()) + : get_field_data (tif, fip)); + + uint16_t res; + if (TIFFGetField (tif, TIFFTAG_XRESOLUTION, &res)) + dir_info.setfield ("XResolution", octave_value (res)); + else + dir_info.setfield ("XResolution", octave_value (Matrix ())); + + if (TIFFGetField (tif, TIFFTAG_YRESOLUTION, &res)) + dir_info.setfield ("YResolution", octave_value (res)); + else + dir_info.setfield ("YResolution", octave_value (Matrix ())); + + TIFFGetFieldDefaulted (tif, TIFFTAG_RESOLUTIONUNIT, &res); + std::string res_unit = "Inch"; + if (res == 1) + res_unit = "None"; + else if (res == 3) + res_unit = "Centimeter"; + dir_info.setfield ("ResolutionUnit", octave_value(res_unit)); + + fip = TIFFFieldWithTag (tif, TIFFTAG_ORIENTATION); + dir_info.setfield ("Orientation", get_field_data (tif, fip)); + + fip = TIFFFieldWithTag (tif, TIFFTAG_FILLORDER); + dir_info.setfield ("FillOrder", get_field_data (tif, fip)); + + // The current version of LibTIFF (4.4.0) doesn't set the deafult + // value for GrayResponseUnit corectly, so we can't use + // TIFFGetFieldDefaulted, instead we set the default value ourselves + double gray_response_unit = 0.01; + uint16_t gray_unit_val; + if (TIFFGetField (tif, TIFFTAG_GRAYRESPONSEUNIT, &gray_unit_val)) + { + switch (gray_unit_val) + { + case GRAYRESPONSEUNIT_10S: + gray_response_unit = 0.1; + break; + case GRAYRESPONSEUNIT_100S: + gray_response_unit = 0.01; + break; + case GRAYRESPONSEUNIT_1000S: + gray_response_unit = 0.001; + break; + case GRAYRESPONSEUNIT_10000S: + gray_response_unit = 0.0001; + break; + case GRAYRESPONSEUNIT_100000S: + gray_response_unit = 0.00001; + break; + } + } + dir_info.setfield ("GrayResponseUnit", + octave_value (gray_response_unit)); + + // The current version of LibTIFF (4.4.0) doesn't set the deafult + // value for MinSampleValue and MaxSampleValue, so we can't use + // TIFFGetFieldDefaulted, instead we set the default value ourselves + uint16_t min_sample_value = 0; + uint16_t max_sample_value = (1<