view libinterp/dldfcn/__tiff__.cc @ 31115:5b3465a9c340

Tiff read: support for reading image arranged in planes (RRRGGGBBB) * __tiff__.cc (read_stripped_image): Added support for reading stripped images with PlanarConfiguration=Separate (RRRGGGBBB) as opposed to (RGBRGBRGB).
author magedrifaat <magedrifaat@gmail.com>
date Sat, 16 Jul 2022 19:05:09 +0200
parents 9dead1249449
children 530dbd1d6b07
line wrap: on
line source

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <string>
#include <iostream>

#include "defun-dld.h"
#include "ov.h"
#include "ovl.h"
#include "error.h"

#include "errwarn.h"

#if defined (HAVE_TIFF)
#  include <tiffio.h>
#endif

// TODO(maged): Fix warnings

namespace octve
{
#if defined (HAVE_TIFF)

  struct tiff_image_data
  {
  public:
    uint32_t width;
    uint32_t height;
    uint16_t samples_per_pixel;
    uint16_t bits_per_sample;
    uint16_t planar_configuration;
    uint16_t is_tiled;
  };

  template <typename T>
  octave_value
  read_stripped_image (TIFF *tif, tiff_image_data *image_data)
  {
    typedef typename T::element_type P;

    T img = T (dim_vector (image_data->height, image_data->width,
                            image_data->samples_per_pixel));
    P *img_fvec = img.fortran_vec ();

    // Obtain the necessary data for handling the strips
    uint32_t strip_count = TIFFNumberOfStrips (tif);
    tdata_t buf = _TIFFmalloc (TIFFStripSize (tif));
    if (! buf)
      error ("Failed to allocate buffer for strip data");

    uint32_t row_index = 0;
    uint32_t row_size_in_bits = image_data->width
                                * image_data->bits_per_sample;
    if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
      row_size_in_bits *= image_data->samples_per_pixel;
    // According to the Tiff format specification, the row size is
    // padded at least up to the next byte, so we add padding to
    // complete the byte
    row_size_in_bits += (8 - row_size_in_bits % 8) % 8;
    for (uint32_t strip = 0; strip < strip_count; strip++)
      {
        // Can't rely on StripByteCounts because in compressed images
        // the byte count reflect the actual number of bytes stored
        // in the file not the size of the uncompressed strip
        uint32_t strip_bytes = TIFFReadEncodedStrip (tif, strip, buf, -1);
        if (strip_bytes == -1)
          error ("Failed to read strip data");
        
        uint32_t rows_in_strip = strip_bytes * 8 / row_size_in_bits;
        uint32_t plane_size = image_data->width * image_data->height;
        for (uint32_t row_subindex = 0;
              row_subindex < rows_in_strip;
              row_subindex++)
          {
            for (uint32_t column = 0;
                  column < image_data->width;
                  column++)
              {
                uint32_t row_offset = row_size_in_bits / 8
                                      * row_subindex;
                // TODO(maged): support arbitrary BitsPerSample?
                if (image_data->bits_per_sample >= 8
                    && image_data->bits_per_sample % 8 == 0)
                  {
                    // TODO(maged): clean up the math for both cases
                    if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
                      for (uint16_t sample = 0;
                            sample < image_data->samples_per_pixel; sample++)
                        // The memory organization of fvec is inverted from
                        // what would be expected for a normal C-like array.
                        // It is treated as samples * columns * rows as
                        // opposed to rows * columns * samples.
                        img_fvec[sample * plane_size
                                + column * image_data->height
                                + row_index + row_subindex]
                          = ((P *)buf)[row_subindex * image_data->width
                                      * image_data->samples_per_pixel
                                      + column * image_data->samples_per_pixel
                                      + sample];
                    else
                      {
                        uint16_t channel_no = strip
                                              * image_data->samples_per_pixel
                                              / strip_count;
                        uint16_t corrected_row = (row_index + row_subindex)
                                                 % image_data->height;
                        img_fvec[channel_no * plane_size
                                 + column * image_data->height
                                 + corrected_row]
                          = ((P *)buf)[row_subindex * image_data->width
                                       + column];
                      }
                  }
                else if (image_data->bits_per_sample == 4
                          && image_data->samples_per_pixel == 1)
                  {
                    // TODO(maged): Check FillOrder for completeness
                    uint8_t nibble
                      = ((uint8_t *)buf)[row_offset + column / 2];
                    // Extract the needed nibble from the byte
                    nibble = (column % 2 == 0? nibble >> 4: nibble) & 0x0F;
                    img_fvec[(row_index + row_subindex)
                              + column * image_data->height] = nibble;
                  }
                else if (image_data->bits_per_sample == 1
                          && image_data->samples_per_pixel == 1)
                  {
                    uint8_t byte
                      = ((uint8_t *)buf)[row_offset + column / 8];
                    // Extract the needed bit from the byte
                    uint8_t bit = (byte >> (7 - column % 8)) & 0x01;
                    img_fvec[(row_index + row_subindex)
                              + column * image_data->height] = bit;
                  }
                else
                  error ("Unsupported bit depth");
              }
          }
        row_index += rows_in_strip;
      }
    _TIFFfree (buf);

    return octave_value(img);
  }

  template <typename T>
  octave_value
  read_image (TIFF *tif, tiff_image_data *image_data)
  {
    if (image_data->is_tiled)
      // TODO(maged): Implement tiled images
      error ("Tiled images are not implemented yet");
    else
      return read_stripped_image<T> (tif, image_data);
  }

  // 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");
        }
  }

  // Convert tag value to double
  octave_value
  interpret_scalar_tag_data (void *data, TIFFDataType tag_datatype)
  {
    double retval;

    switch (tag_datatype)
      {
      case TIFF_BYTE:
      case TIFF_UNDEFINED:
        {
          retval = (double)(*((uint8_t *)data));
          break;
        }
      case TIFF_SHORT:
        {
          retval = (double)(*((uint16_t *)data));
          break;
        }
      case TIFF_LONG:
        {
          retval = (double)(*((uint32_t *)data));
          break;
        }
      case TIFF_LONG8:
        {
          retval = (double)(*((uint64_t *)data));
          break;
        }
      case TIFF_RATIONAL:
        {
          error ("TIFF_RATIONAL should have at least 2 elements but got only 1");
          break;
        }
      case TIFF_SBYTE:
        {
          retval = (double)(*((int8_t *)data));
          break;
        }
      case TIFF_SSHORT:
        {
          retval = (double)(*((int16_t *)data));
          break;
        }
      case TIFF_SLONG:
        {
          retval = (double)(*((int32_t *)data));
          break;
        }
      case TIFF_SLONG8:
        {
          retval = (double)(*((int64_t *)data));
          break;
        }
      case TIFF_FLOAT:
        {
          retval = *((float *)data);
          break;
        }
      case TIFF_DOUBLE:
        {
          retval = *((double *)data);
          break;
        }
      case TIFF_SRATIONAL:
        {
          error ("TIFF_SRATIONAL should have at least 2 elements but got only 1");
          break;
        }
      case TIFF_IFD:
      case TIFF_IFD8:
        error ("Unimplemented IFFD data type");
        break;
      default:
        error ("Unsupported tag data type");
      }

    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)
  {
    octave_value retval;
    // Apparently matlab converts scalar numerical values into double
    // but doesn't do the same for arrays
    if (count == 1 && tag_datatype != TIFF_ASCII)
      {
        retval = interpret_scalar_tag_data (data, tag_datatype);
      }
    else
      {
        dim_vector arr_dims (1, count);

        switch (tag_datatype)
          {
          case TIFF_BYTE:
          case TIFF_UNDEFINED:
            {
              uint8NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((uint8_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_ASCII:
            {
              retval = octave_value (*(char **)data);
              break;
            }
          case TIFF_SHORT:
            {
              uint16NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((uint16_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_LONG:
            {
              uint32NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((uint32_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_LONG8:
            {
              uint64NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((uint64_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_RATIONAL:
            {
              NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i+=2)
                {
                  arr(i / 2) = (float)((uint32_t *)data)[i] 
                                / (float)((uint32_t *)data)[i+1];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_SBYTE:
            {
              int8NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((int8_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_SSHORT:
            {
              int16NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((int16_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_SLONG:
            {
              int32NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((int32_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_SLONG8:
            {
              int64NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((int64_t *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_FLOAT:
            {
              NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
                {
                  arr(i) = ((float *)data)[i];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_DOUBLE:
            {
              NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i++)
              {
                  arr(i) = ((double *)data)[i];
              }
              retval = octave_value (arr);
              break;
            }
          case TIFF_SRATIONAL:
            {
              NDArray arr (arr_dims);
              for (uint32_t i = 0; i < count; i+=2)
                {
                  arr(i / 2) = (float)((int32_t *)data)[i] 
                                / (float)((int32_t *)data)[i+1];
                }
              retval = octave_value (arr);
              break;
            }
          case TIFF_IFD:
          case TIFF_IFD8:
            // TODO(maged): implement IFD datatype?
            error ("Unimplemented IFFD data type");
            break;
          default:
            error ("Unsupported tag data type");
          }
      }

    return retval;
  }

  octave_value
  get_scalar_field_data (TIFF *tif, const TIFFField *fip)
  {
    uint32_t tag_id = TIFFFieldTag (fip);

    // TIFFFieldReadCount returns VARIABLE for some scalar tags
    // (e.g. Compression) But TIFFFieldPassCount seems consistent
    // Since scalar tags are the last to be handled, any tag that
    // require a count to be passed is an unsupported tag.
    if (TIFFFieldPassCount (fip))
      error ("Unsupported tag");
    
    // TODO(maged): test this function vs actual data type size
    int type_size = TIFFDataWidth (TIFFFieldDataType (fip));
    void *data = _TIFFmalloc (type_size);
    validate_tiff_get_field (TIFFGetField (tif, tag_id, data), data);
    octave_value tag_data_ov = interpret_tag_data (data, 1,
                                                   TIFFFieldDataType (fip));
    _TIFFfree (data);

    return tag_data_ov;
  }

  octave_value
  get_array_field_data (TIFF *tif, const TIFFField *fip, uint32_t array_size)
  {
    void *data;
    validate_tiff_get_field (TIFFGetField (tif, TIFFFieldTag (fip), &data));
      
    return interpret_tag_data (data, array_size, TIFFFieldDataType (fip));
  }

  octave_value_list
  get_field_data (TIFF *tif, const TIFFField *fip)
  {
    octave_value_list tag_data_ovl (1);
    uint32_t tag_id = TIFFFieldTag (fip);

    // TODO(maged): find/create images to test the special tags
    switch (tag_id)
      {
      case TIFFTAG_STRIPBYTECOUNTS:
      case TIFFTAG_STRIPOFFSETS:
        tag_data_ovl = get_array_field_data (tif, fip, 
                                            TIFFNumberOfStrips (tif));
        break;
      case TIFFTAG_TILEBYTECOUNTS:
      case TIFFTAG_TILEOFFSETS:
        tag_data_ovl = get_array_field_data (tif, fip, TIFFNumberOfTiles (tif));
        break;
      case TIFFTAG_YCBCRCOEFFICIENTS:
        tag_data_ovl = get_array_field_data (tif, fip, 3);
        break;
      case TIFFTAG_REFERENCEBLACKWHITE:
        tag_data_ovl = get_array_field_data (tif, fip, 6);
        break;
      case TIFFTAG_GRAYRESPONSECURVE:
        {
          uint16_t bits_per_sample;
          if (! TIFFGetField (tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample))
            error ("Failed to obtain the bit depth");
          
          tag_data_ovl = get_array_field_data (tif, fip, 1<<bits_per_sample);
          break;
        }
      case TIFFTAG_COLORMAP:
        {
          // TODO(maged): Fix output formatting to be consistent with matlab
          // Matlab returns a float colormap?
          uint16_t bits_per_sample;
          if (! TIFFGetField (tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample))
            error ("Failed to obtain the bit depth");
                
          if (bits_per_sample > 24)
            error ("Too high bit depth for a palette image");

          uint32_t count = 1 << bits_per_sample;
          uint16_t *red, *green, *blue;
          validate_tiff_get_field (TIFFGetField (tif, TIFFTAG_COLORMAP,
                                                 &red, &green, &blue));
          
          uint16NDArray mat_out (dim_vector (count, 3));

          uint16NDArray red_array
            = interpret_tag_data (red,
                                  count,
                                  TIFFFieldDataType (fip)).uint16_array_value ();
          uint16NDArray green_array
            = interpret_tag_data (green,
                                  count,
                                  TIFFFieldDataType (fip)).uint16_array_value ();
          uint16NDArray blue_array
            = interpret_tag_data (blue,
                                  count,
                                  TIFFFieldDataType (fip)).uint16_array_value ();
          
          octave_uint16 *out_ptr = mat_out.fortran_vec ();
          memcpy (out_ptr, red_array.fortran_vec (), sizeof(uint16_t) * count);
          out_ptr += count;
          memcpy (out_ptr, green_array.fortran_vec (), sizeof(uint16_t) * count);
          out_ptr += count;
          memcpy (out_ptr, blue_array.fortran_vec (), sizeof(uint16_t) * count);

          tag_data_ovl(0) = octave_value (mat_out);
          break;
        }
      case TIFFTAG_TRANSFERFUNCTION:
        {
          uint16_t samples_per_pixel;
          if (! TIFFGetField (tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel))
            error ("Failed to obtain the number of samples per pixel");

          uint16_t bits_per_sample;
          if (! TIFFGetField (tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample))
            error ("Failed to obtain the number of samples per pixel");

          uint32_t count = 1 << bits_per_sample;
          uint16_t *ch1, *ch2, *ch3;
          if (samples_per_pixel == 1)
            {
              validate_tiff_get_field (TIFFGetField (tif, TIFFTAG_COLORMAP, &ch1));
              tag_data_ovl(0) 
                = interpret_tag_data (ch1, count, TIFFFieldDataType (fip));
            }
          else
            {
              validate_tiff_get_field (TIFFGetField (tif, TIFFTAG_COLORMAP,
                                                     &ch1, &ch2, &ch3));
              
              uint16NDArray mat_out (dim_vector (count, 3));

              uint16NDArray ch1_array
                = interpret_tag_data (ch1,
                                      count,
                                      TIFFFieldDataType (fip)).uint16_array_value ();
              uint16NDArray ch2_array
                = interpret_tag_data (ch2,
                                      count,
                                      TIFFFieldDataType (fip)).uint16_array_value ();
              uint16NDArray ch3_array
                = interpret_tag_data (ch3,
                                      count,
                                      TIFFFieldDataType (fip)).uint16_array_value ();
              
              octave_uint16 *out_ptr = mat_out.fortran_vec ();
              memcpy (out_ptr, ch1_array.fortran_vec (), sizeof(uint16_t) * count);
              out_ptr += count;
              memcpy (out_ptr, ch2_array.fortran_vec (), sizeof(uint16_t) * count);
              out_ptr += count;
              memcpy (out_ptr, ch3_array.fortran_vec (), sizeof(uint16_t) * count);

              tag_data_ovl(0) = octave_value (mat_out);
            }
          break;
        }
      case TIFFTAG_PAGENUMBER:
      case TIFFTAG_HALFTONEHINTS:
      case TIFFTAG_DOTRANGE:
      case TIFFTAG_YCBCRSUBSAMPLING:
        {
          // TODO(maged): fix bug where only first one is returned
          uint16_t tag_part1, tag_part2;
          validate_tiff_get_field (TIFFGetField (tif, tag_id,
                                                 &tag_part1, &tag_part2));
          tag_data_ovl(0)
            = interpret_tag_data (&tag_part1, 1, TIFFFieldDataType (fip));
          tag_data_ovl(1)
            = interpret_tag_data (&tag_part2, 1, TIFFFieldDataType (fip));
          break;
        }
      case TIFFTAG_SUBIFD:
        {
          uint16_t count;
          uint64_t *offsets;
          validate_tiff_get_field (TIFFGetField (tif, tag_id, &count, &offsets));
          tag_data_ovl(0) = interpret_tag_data (offsets, count,
                                                TIFFFieldDataType (fip));
          break;
        }
      case TIFFTAG_EXTRASAMPLES:
        {
          uint16_t count;
          uint16_t *types;
          validate_tiff_get_field (TIFFGetField (tif, tag_id, &count, &types));
          tag_data_ovl(0) = interpret_tag_data (types, count,
                                                TIFFFieldDataType (fip));
          break;
        }
      // TODO(maged): These tags are more complex to implement
      //  will be implemented and tested later.
      case TIFFTAG_XMLPACKET:
      case TIFFTAG_RICHTIFFIPTC:
      case TIFFTAG_PHOTOSHOP:
      case TIFFTAG_ICCPROFILE:
        {
          error ("Complex Tags not implemented");
          break;
        }
      // These tags are not mentioned in the LibTIFF documentation
      // but are handled correctly by the library
      case TIFFTAG_ZIPQUALITY:
      case TIFFTAG_SGILOGDATAFMT:
      case TIFFTAG_GRAYRESPONSEUNIT:
        {
          tag_data_ovl = get_scalar_field_data (tif, fip);
          break;
        }
      default:
        tag_data_ovl = get_scalar_field_data (tif, fip);
    }
    
    return tag_data_ovl;
  }
#endif

  DEFUN_DLD (__open_tiff__, args, nargout,
             "Open a Tiff file and return its handle")
  {
#if defined (HAVE_TIFF)
    int nargin = args.length ();

    if (nargin == 0 || nargin > 2)
      {
        // TODO(maged): return invalid object instead??
        error ("No filename supplied\n");
      }

    std::string filename = args (0).string_value ();
    std::string mode = "r";

    // TODO(maged): check valid mode
    if (nargin == 2)
      mode = args (1).string_value ();
    
    // TODO(maged): Look into unwind action
    TIFF *tif = TIFFOpen (filename.c_str (), mode.c_str ());
    
    if (! tif)
      error ("Failed to open Tiff file\n");

    // TODO(maged): use inheritance of octave_base_value instead
    octave_value tiff_ov = octave_value ((uint64_t)tif);
    return octave_value_list (tiff_ov);
#else
    err_disabled_feature ("Tiff", "Tiff");
#endif
  }


  DEFUN_DLD (__close_tiff__, args, nargout,
             "Close a tiff file")
  {
#if defined (HAVE_TIFF)
    int nargin = args.length ();

    if (nargin == 0)
      error ("No handle provided\n");
    
    TIFF *tif = (TIFF *)(args (0).uint64_value ());
    TIFFClose (tif);

    return octave_value_list ();
#else
    err_disabled_feature ("close", "Tiff");
#endif
  }


  DEFUN_DLD (__tiff_get_tag__, args, nargout,
             "Get the value of a tag from a tiff image")
  {
#if defined (HAVE_TIFF)
    int nargin = args.length ();

    if (nargin == 0)
      error ("No handle provided\n");
    
    if (nargin < 2)
      error ("No tag name provided\n");
    
    TIFF *tif = (TIFF *)(args (0).uint64_value ());

    uint32_t tag_id;
    const TIFFField *fip;
    if (args (1).type_name () == "string")
      {
        std::string tagName = args (1).string_value ();
        fip = TIFFFieldWithName (tif, tagName.c_str ());
        if (! fip)
          error ("Tiff tag not found");
        
        tag_id = TIFFFieldTag (fip);
      }
    else
      {
        tag_id = args (1).int_value ();
        fip = TIFFFieldWithTag (tif, tag_id);
        // TODO(maged): Handle other types of errors (e.g. unsupported tags)
        if (! fip)
          error ("Tiff tag not found");
      }


    octave_value_list tag_data_ovl = get_field_data (tif, fip);

    return tag_data_ovl;
#else
    err_disabled_feature ("getTag", "Tiff");
#endif
  }

  DEFUN_DLD (__tiff_read__, args, nargout,
             "Read the image in the current IFD")
  {
#if defined (HAVE_TIFF)
    int nargin = args.length ();

    if (nargin == 0)
      error ("No handle provided\n");
    
    TIFF *tif = (TIFF *)(args (0).uint64_value ());

    // Check: Strips vs Tiles
    //        Planar Configuration
    //        SamplesPerPixel and bits_per_smaple
    //        nargout and ycbcr
    //        ExtendedSamples? TransferFunction? GrayResponse? ColorMap?
    //        What about floating point images?

    // Obtain all necessary tags
    // The main purpose of this struct is to hold all the necessary tags that
    // will be common among all the different cases of handling the the image
    // data to avoid repeating the same calls to TIFFGetField in the different
    // functions as each call is a possible point of failure
    tiff_image_data image_data;
    if (! TIFFGetField (tif, TIFFTAG_IMAGEWIDTH, &image_data.width))
      error ("Failed to read image width");

    if (! TIFFGetField (tif, TIFFTAG_IMAGELENGTH, &image_data.height))
      error ("Failed to read image height");
    
    if (! TIFFGetField (tif, TIFFTAG_SAMPLESPERPIXEL,
                        &image_data.samples_per_pixel))
      error ("Failed to read the SamplesPerPixel tag");

    if (! TIFFGetField (tif, TIFFTAG_BITSPERSAMPLE,
                        &image_data.bits_per_sample))
      error ("Failed to read the BitsPerSample tag");
    
    if (! TIFFGetField (tif, TIFFTAG_PLANARCONFIG,
                        &image_data.planar_configuration))
      error ("Failed to read the PlanarConfiguration tag");
    
    image_data.is_tiled = TIFFIsTiled(tif);
    
    octave_value_list retval;
    switch (image_data.bits_per_sample)
      {
      case 1:
        retval(0) = read_image<boolNDArray> (tif, &image_data);
        break;
      case 4:
      case 8:
        retval(0) = read_image<uint8NDArray> (tif, &image_data);
        break;
      case 16:
        retval(0) = read_image<uint16NDArray> (tif, &image_data);
        break;
      case 32:
        retval(0) = read_image<uint32NDArray> (tif, &image_data);
        break;
      case 64:
        retval(0) = read_image<uint64NDArray> (tif, &image_data);
        break;
      default:
        error ("Unsupported bit depth");
      }
    
    return retval;
#else
    err_disabled_feature ("read", "Tiff");
#endif
  }
}