# HG changeset patch # User magedrifaat # Date 1661634414 -7200 # Node ID c142c153034c3bc9ca5404f0e666c8b7a192710d # Parent 6a2bb6f4e41ebb1213182eab0a063fdaa866ced0 Tiff: implemented imwrite handler that uses the Tiff interface * __tiff__.cc (F__tiff_imwrite__): implemented internal function to act as imwrite handler for tiff images but uses the new Tiff interface. diff -r 6a2bb6f4e41e -r c142c153034c libinterp/corefcn/__tiff__.cc --- a/libinterp/corefcn/__tiff__.cc Sat Aug 27 16:23:59 2022 +0200 +++ b/libinterp/corefcn/__tiff__.cc Sat Aug 27 23:06:54 2022 +0200 @@ -13,6 +13,7 @@ #include "errwarn.h" #include "fcntl-wrappers.h" +#include "file-ops.h" #if defined (HAVE_TIFF) # include @@ -126,6 +127,13 @@ return ov.isnumeric () && ov.isreal () && ov.is_scalar_type (); } + bool + is_colormap (octave_value ov) + { + return ov.isnumeric () && ov.isreal () && ov.isfloat () + && ov.ndims () == 2 && ov.columns () == 3; + } + // A map of tag names supported by matlab, there are some differences // than LibTIFF's names (e.g. Photometric vs PhotometricInerpretation) static const std::map tag_name_map = { @@ -2741,6 +2749,11 @@ // The TIFFRGBAImageGet interface is used instead of TIFFReadRGBAImage // to have control on the requested orientation of the image + // Matlab R2022a handles the orientation for each individual strip + // or tile on its own, this results in a distorted image which is + // not useful, and is probably a bug. So this deviated from matlab + // by applying the orientation to the entire image to produce more + // useful results. TIFFRGBAImage img_config; char emsg[1024]; if (! TIFFRGBAImageOK (tif, emsg) @@ -3599,7 +3612,7 @@ if (is_numeric_scalar (val)) page = val.uint16_scalar_value (); else - error ("imread: %s must be a scalar", param_cstr); + error ("imread: %s must be a numeric scalar", param_cstr); } } @@ -3736,4 +3749,250 @@ err_disabled_feature ("imread", "Tiff"); #endif } + + DEFUN (__tiff_imwrite__, args, , + "Handler for imwrite that uses Tiff interface") + { +#if defined (HAVE_TIFF) + int nargin = args.length (); + + if (nargin < 2) + error ("imwrite: Wrong number of arguments"); + + if (! (args(0).isnumeric () || args(0).is_bool_matrix () + || args(0).is_bool_scalar ())) + error ("imwrite: Expected image matrix as first argument"); + + uint16_t offset = 1; + std::string filename; + octave_value cmap = octave_value (NDArray ()); + if (args(1).is_string ()) + { + filename = args(1).string_value (); + offset++; + } + else if (nargin > 2 && is_colormap (args(1)) && args(2).is_string ()) + { + filename = args(2).string_value (); + cmap = args(1); + offset += 2; + } + else + error ("imwrite: no FILENAME specified"); + + filename = sys::file_ops::tilde_expand (filename); + + if (nargin > offset && (nargin - offset) % 2 != 0 + && args(offset).is_string ()) + offset++; + + if ((nargin - offset) % 2 != 0) + error ("imwrite: no pair for all arguments (odd number left)"); + + 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 = ""; + + for (uint16_t arg_idx = offset; arg_idx < nargin; arg_idx+=2) + { + if (! args(arg_idx).is_string ()) + error ("imwrite: PARAM in PARAM/VALUE pair must be string"); + + const char *param_cstr = args(arg_idx).string_value ().c_str (); + if (strcasecmp (param_cstr, "colorspace") == 0) + { + // Matlab specifies three colorspaces: RGB, CIELAB and ICCLAB. + // Of the three, only RGB is currently supported so this tag + // can be ignored. + } + else if (strcasecmp (param_cstr, "compression") == 0) + { + if (! args(arg_idx + 1).is_string ()) + error ("imwrite: value for %s option must be a string", + param_cstr); + std::string comp = args(arg_idx + 1).string_value (); + if (comp == "packbits") + compression = COMPRESSION_PACKBITS; + 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; + else if (comp == "fax3") + compression = COMPRESSION_CCITTFAX3; + else if (comp == "fax4") + compression = COMPRESSION_CCITTFAX4; + else + error ("imwrite: invalid compression '%s'", comp.c_str ()); + } + else if (strcasecmp (param_cstr, "Description") == 0) + { + if (! args(arg_idx + 1).is_string ()) + error ("imwrite: value for %s option must be a string", + param_cstr); + description = args(arg_idx + 1).string_value (); + } + else if (strcasecmp (param_cstr, "resolution") == 0) + { + if (! args(arg_idx + 1).isnumeric ()) + error ("imwrite: value for %s option must be numeric", + param_cstr); + NDArray res = args(arg_idx + 1).array_value (); + if (res.numel () == 1) + { + x_res = res(0); + y_res = res(0); + } + else if (res.numel () == 2) + { + x_res = res(0); + y_res = res(1); + } + else + error ("imwrite: value for %s option must be either a scalar value or a two-element vector", + param_cstr); + } + else if (strcasecmp (param_cstr, "RowsPerStrip") == 0) + { + if (! args(arg_idx + 1).isnumeric () + || ! args(arg_idx + 1).is_scalar_type ()) + error ("imwrite: value for %s option must be a scalar value", + param_cstr); + rows_per_strip = args(arg_idx + 1).uint32_scalar_value (); + } + else if (strcasecmp (param_cstr, "WriteMode") == 0) + { + if (! args(arg_idx + 1).is_string ()) + error ("imwrite: value for %s option must be a string", + param_cstr); + std::string wmode = args(arg_idx + 1).string_value (); + if (wmode == "overwrite") + append = false; + else if (wmode == "append") + append = true; + else + error ("imwrite: value for %s option must be either 'overwrite' or 'append'", + param_cstr); + } + else + error ("imread: invalid PARAMETER '%s'", param_cstr); + } + + // FIXME: does matlab error if appending to non existant file? + 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"); + + if (! (args(0).is_bool_matrix () || args(0).is_bool_scalar ()) + && (compression == COMPRESSION_CCITT_T4 + || compression == COMPRESSION_CCITTFAX3 + || compression == COMPRESSION_CCITTFAX4)) + error ("imwrite: the specified compression scheme is for binary images only"); + + 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"); + + if (args(0).ndims () > 2 && img_dims(2) > 1 && cmap.numel () > 0) + error ("imwrite: palette images must be 1 channel only"); + + TIFF *tif; + if (append) + tif = TIFFOpen (filename.c_str (), "a"); + else + tif = TIFFOpen (filename.c_str (), "w"); + + if (! tif) + error ("Failed to open file %s", filename.c_str ()); + + // A simple way to make sure the file will be closed when the function + // returns or when an error occurs as the destructor will always be called + octave_tiff_handle tiff_handle (tif); + + // Set all necessary tags + if (! TIFFSetField (tif, TIFFTAG_IMAGELENGTH, + static_cast(img_dims(0))) + || ! TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, + static_cast(img_dims(1)))) + error ("Failed to set image dimensions"); + + if (img_dims.ndims () > 2) + if (! TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, img_dims(2))) + error ("Failed to set the number of samples"); + + if (! TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) + error ("Failed to set the planar configuration"); + + if (! TIFFSetField (tif, TIFFTAG_COMPRESSION, compression)) + error ("Failed to set the compressoin tag"); + + if (! TIFFSetField (tif, TIFFTAG_XRESOLUTION, x_res) + || ! TIFFSetField (tif, TIFFTAG_YRESOLUTION, y_res)) + error ("Failed to set resolution tag"); + + if (! TIFFSetField (tif, TIFFTAG_IMAGEDESCRIPTION, description.c_str ())) + error ("Failed to set description tag"); + + if (rows_per_strip == UINT32_MAX) + rows_per_strip = TIFFDefaultStripSize (tif, 0); + + if (! TIFFSetField (tif, TIFFTAG_ROWSPERSTRIP, rows_per_strip)) + error ("Failed to set the RowsPerStrip tag"); + + uint16_t bits_per_sample; + if (args(0).is_bool_matrix () || args(0).is_bool_scalar ()) + bits_per_sample = 1; + else if (args(0).is_uint8_type ()) + bits_per_sample = 8; + else if (args(0).is_uint16_type ()) + bits_per_sample = 16; + else if (args(0).is_uint32_type () || args(0).is_single_type ()) + bits_per_sample = 32; + else if (args(0).is_double_type ()) + bits_per_sample = 64; + else + error ("imwrite: unsupported image data type"); + + if (! TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample)) + error ("Failed to set BitsPerSample tag"); + + uint16_t photometric = PHOTOMETRIC_MINISBLACK; + if (cmap.numel () > 0) + { + photometric = PHOTOMETRIC_PALETTE; + set_field_data (tif, TIFFFieldWithTag (tif, TIFFTAG_COLORMAP), + cmap); + } + else if (img_dims(2) == 3) + photometric = PHOTOMETRIC_RGB; + else if (img_dims(2) == 4) + photometric = PHOTOMETRIC_SEPARATED; + + if (! TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, photometric)) + error ("Failed to set the photometric tag"); + + tiff_image_data image_data (tif); + if (args(0).is_bool_scalar () || args(0).is_bool_matrix () + || args(0).is_uint8_type () || args(0).is_uint16_type () + || args(0).is_uint32_type ()) + write_unsigned_image (tif, args(0), &image_data); + else + { + if (!TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)) + error ("Failed to set SampleFormat tag"); + write_float_image (tif, args(0).as_double (), &image_data); + } + + return octave_value_list (); +#else + err_disabled_feature ("imwrite", "Tiff"); +#endif + } } diff -r 6a2bb6f4e41e -r c142c153034c scripts/image/PKG_ADD --- a/scripts/image/PKG_ADD Sat Aug 27 16:23:59 2022 +0200 +++ b/scripts/image/PKG_ADD Sat Aug 27 23:06:54 2022 +0200 @@ -1,7 +1,7 @@ if __have_feature__ ("TIFF") format = imformats("tif"); format.read = @__tiff_imread__; - format.write = @__imwrite__; + format.write = @__tiff_imwrite__; format.info = @__tiff_imfinfo__; imformats("update", "tif", format); endif \ No newline at end of file