# HG changeset patch # User magedrifaat # Date 1662080789 -7200 # Node ID 93eb0d6e7f62f0fa6bc0d4281f4ea0e00fd07633 # Parent 1604c8812b67ac5ab82267ba9b49c6c6509f5ce4 __tiff_imwrite__: converted to private octave function * libinterp/corefcn/__tiff__.cc: removed internal function __tiff_imwrite__. * scripts/image/private/__tiff_imwrite__.m: added private function __tiff_imwrite__. * scripts/image/module.mk: added entry for __tiff_imwrite__.m. diff -r 1604c8812b67 -r 93eb0d6e7f62 libinterp/corefcn/__tiff__.cc --- a/libinterp/corefcn/__tiff__.cc Thu Sep 01 01:56:20 2022 +0200 +++ b/libinterp/corefcn/__tiff__.cc Fri Sep 02 03:06:29 2022 +0200 @@ -128,13 +128,6 @@ } } - 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 PhotometricInterpretation) static const std::map tag_name_map = { @@ -3584,261 +3577,6 @@ #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"); - - // 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 ()); - 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)"); - - // 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 ()) - 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") - compression = COMPRESSION_DEFLATE; - else if (comp == "jpeg") - compression = COMPRESSION_JPEG; - else if (comp == "ccitt") - compression = COMPRESSION_CCITTRLE; - 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); - } - - // 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_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"); - - 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"); - - // 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) - { - 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 - } - DEFUN (__tiff_imfinfo__, args, , "Handler for imfinfo that uses Tiff interface") { diff -r 1604c8812b67 -r 93eb0d6e7f62 scripts/image/module.mk --- a/scripts/image/module.mk Thu Sep 01 01:56:20 2022 +0200 +++ b/scripts/image/module.mk Fri Sep 02 03:06:29 2022 +0200 @@ -7,6 +7,7 @@ %reldir%/private/__imread__.m \ %reldir%/private/__imwrite__.m \ %reldir%/private/__tiff_imread__.m \ + %reldir%/private/__tiff_imwrite__.m \ %reldir%/private/colorspace_conversion_input_check.m \ %reldir%/private/colorspace_conversion_revert.m \ %reldir%/private/imageIO.m \ diff -r 1604c8812b67 -r 93eb0d6e7f62 scripts/image/private/__tiff_imwrite__.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/image/private/__tiff_imwrite__.m Fri Sep 02 03:06:29 2022 +0200 @@ -0,0 +1,247 @@ +function __tiff_imwrite__ (img, varargin) + ## A lot of the input sanitising logic here is copied from + ## scripts/image/__imwrite__.m and adapted to the needs of the Tiff + ## interface + + if (nargin < 2 || ! (isnumeric (img) || islogical (img))) + print_usage ("imwrite"); + endif + + [filename, ext, map, param_list] = imwrite_filename (varargin{:}); + + if (isempty (img)) + error ("imwrite: invalid empty image"); + elseif (issparse (img) || issparse (map)) + error ("imwrite: sparse images are not supported"); + endif + + if (rem (numel (param_list), 2) != 0) + error ("imwrite: no pair for all arguments (odd number left)"); + endif + + writemode = "w"; + compression = Tiff.Compression.None; + rows_per_strip = -1; + res = [72, 72]; + description = ""; + alpha = []; + + for idx = 1:2:numel (param_list) + switch (tolower (param_list{idx})) + case "alpha" + alpha = param_list{idx + 1}; + if (! isnumeric (alpha)) + error ("imwrite: value for %s option must be a numeric matrix", + param_list{idx}); + elseif (size (alpha, 3) != 1) + error ("imwrite: 3rd dimension of matrix for %s must be singleton", + param_list{idx}); + elseif (ndims (alpha) > 4 + || any (size (alpha)([1 2]) != size (img)([1 2])) + || size (alpha, 4) != size (img, 4)) + error ("imwrite: matrix for %s must have same dimension as image", + param_list{idx}); + endif + + case "colorspace" + ## Matlab specifies three colorspaces: RGB, CIELAB and ICCLAB. + ## Of the three, only RGB is currently supported so this tag + ## can be ignored. + + case "compression" + switch (tolower (param_list{idx + 1})) + case "packbits" + compression = Tiff.Compression.PackBits; + case "lzw" + compression = Tiff.Compression.LZW; + case "deflate" + compression = Tiff.Compression.Deflate; + case "jpeg" + compression = Tiff.Compression.JPEG; + case "ccitt" + compression = Tiff.Compression.CCITTRLE; + case "fax3" + compression = Tiff.Compression.CCITTFax3; + case "fax4" + compression = Tiff.Compression.CCITTFax4; + otherwise + error ("imwrite: invalid compression '%s'", compression); + endswitch + + case "description" + if (! ischar (param_list{idx + 1})) + error ("imwrite: value for %s option must be a string", + param_list{idx}); + endif + description = param_list{idx + 1}; + + case "resolution" + if (! isnumeric (param_list{idx + 1}) + || all (numel (param_list) != [1, 2])) + error ("imwrite: value for %s option must be either a scalar value or a two-element vector", + param_list{idx}); + endif + resolution = param_list{idx + 1}; + if (numel (resolution) == 1) + resolution(2) = resolution(1); + endif + + case "rowsperstrip" + if (! isnumeric (param_list{idx + 1}) + || ! isscalar (param_list{idx + 1})) + error ("imwrite: value for %s option must be a numeric scalar", + param_list{idx}); + endif + rows_per_strip = param_list{idx + 1}; + + case "writemode" + if (! ischar (param_list{idx + 1}) + || ! any (strcmpi (param_list{idx + 1}, {"append", "overwrite"}))) + error ('imwrite: value for %s option must be "append" or "overwrite"', + param_list{idx}); + endif + if (strcmpi (param_list{idx + 1}, {"append"})) + writemode = "a"; + endif + + otherwise + error ("imwrite: invalid PARAMETER '%s'", param_list{idx}); + endswitch + endfor + + if (! isempty (map)) + if (! iscolormap (map)) + error ("imwrite: invalid MAP for indexed image"); + elseif (ndims (img) != 2) + error ("imwrite: indexed image must have 2 (found %i)", ndims (img)); + endif + + ## If the image is floating point, then we convert it to integer because + ## it represents indices of the color map which must be integers. Also, + ## if it's floating point, it has an offset of 1 + if (isfloat (img)) + if (rows (map) <= 256) + img = uint8 (img - 1); + else + img = uint16 (img - 1); + endif + endif + + ## Fill in the colormap as required with rgb (0, 0, 0) + nColors = rows (map); + if (islogical (img)) + required_colors = 2; + else + required_colors = double (intmax (class (img))) + 1; + endif + + if (nColors < required_colors) + warning ("imwrite: MAP has not enough colors. Filling with black"); + map(nColors+1:required_colors,:) = 0; + endif + endif + + if (ndims (img) > 4) + error ("imwrite: invalid %d-dimensional image data", ndims (img)); + elseif (all (size (img, 3) != [1 3 4])) + ## 1, 3, or 4 for grayscle, RGB, and CMYK respectively + error ("imwrite: IMG 3rd dimension must be 1, 3, or 4"); + endif + + ## Matlab specifies that JPEG compression must also specify RowsPerStrip + ## and must be a value divisible by 8 + if (compression == Tiff.Compression.JPEG + && (rows_per_strip == -1 || rem (rows_per_strip, 8) != 0)) + error ("imwrite: RowsPerStrip option must be specified for jpeg compression and must be divisible by 8"); + endif + + ## These compression schemes are only available for binary images + if (! (islogical (img)) + && any (compression == [Tiff.Compression.CCITTRLE, + Tiff.Compression.CCITTFax3, + Tiff.Compression.CCITTFax4])) + error ("imwrite: the specified compression scheme is for binary images only"); + endif + + ## Set the image tag data + tags.ImageLength = size (img, 1); + tags.ImageWidth = size (img, 2); + + ## See if SamplesPerPixel should account for the alpha channel + if (! isempty (alpha)) + tags.SamplesPerPixel = size (img, 3) + 1; + tags.ExtraSamples = Tiff.ExtraSamples.AssociatedAlpha; + else + tags.SamplesPerPixel = size (img, 3); + endif + + tags.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky; + tags.Compression = compression; + tags.XResolution = res(1); + tags.YResolution = res(2); + tags.ImageDescription = description; + tags.BitsPerSample = infer_bitdepth (class (img)); + + ## Infer the photometric interpretation of the image from the color map + ## and the number of channels of the input + tags.Photometric = Tiff.Photometric.MinIsBlack; + if (! isempty (map)) + tags.Photometric = Tiff.Photometric.Palette; + tags.ColorMap = map; + elseif (size(img, 3) == 3) + tags.Photometric = Tiff.Photometric.RGB; + elseif (size(img, 3) == 4) + tags.Photometric = Tiff.Photometric.CMYK; + endif + + ## Set the correct sample format for floating-point and signed types + if (isfloat (img)) + tags.SampleFormat = Tiff.SampleFormat.IEEEFP; + elseif (any (strcmp (class (img), {"int8", "int16", "int32"}))) + tags.SampleFormat = Tiff.SampleFormat.Int; + endif + + ## Add the alpha channel to the image data + if (! isempty (alpha)) + img = cat (3, img, alpha); + endif + + tif = Tiff (filename, writemode); + + for dir_idx = 1:size(img, 4) + tif.setTag (tags); + ## Try to get a reasonable size for RowsPerStrip if not set + ## This must be done after the other tags are set so LibTIFF + ## can make an informed calculation. + % if (rows_per_strip == -1) + % rows_per_strip = tif.getDefaultStripLength (); + % endif + if (rows_per_strip != -1) + tif.setTag (Tiff.TagID.RowsPerStrip, rows_per_strip); + endif + + tif.write (img(:,:,:, dir_idx)); + + tif.writeDirectory (); + endfor + + tif.close (); + +endfunction + +function bitdepth = infer_bitdepth (img_class) + switch (img_class) + case "logical" + bitdepth = 1; + case {"int8", "uint8"} + bitdepth = 8; + case {"int16", "uint16"} + bitdepth = 16; + case {"int32", "uint32", "single"} + bitdepth = 32; + case "double" + bitdepth = 64; + otherwise + error ("imwrite: Unsupported data type for image data"); + endswitch +endfunction