Example #1
0
void
ImageSpec::attribute (string_view name, TypeDesc type, string_view value)
{
    ImageIOParameter param (name, type, 1, NULL);
    TypeDesc::BASETYPE basetype = (TypeDesc::BASETYPE)type.basetype;

    if (basetype == TypeDesc::INT) {
        parse_elements<int> (name, type, "%d", value, param);
    } else if (basetype == TypeDesc::UINT) {
        parse_elements<unsigned int> (name, type, "%u", value, param);
    } else if (basetype == TypeDesc::FLOAT) {
        parse_elements<float> (name, type, "%f", value, param);
    } else if (basetype == TypeDesc::DOUBLE) {
        parse_elements<double> (name, type, "%lf", value, param);
    } else if (basetype == TypeDesc::INT64) {
        parse_elements<long long> (name, type, "%lld", value, param);
    } else if (basetype == TypeDesc::UINT64) {
        parse_elements<unsigned long long> (name, type, "%llu", value, param);
    } else if (basetype == TypeDesc::INT16) {
        parse_elements<short> (name, type, "%hd", value, param);
    } else if (basetype == TypeDesc::UINT16) {
        parse_elements<unsigned short> (name, type, "%hu", value, param);
    } else if (type == TypeDesc::STRING) {
        ustring s (value);
        param.init (name, TypeDesc::TypeString, 1, &s);
    }

    // Don't allow duplicates
    ImageIOParameter *f = find_attribute (name);
    if (f) {
        *f = param;
    } else {
        extra_attribs.push_back (param);
    }
}
Example #2
0
void
SgiOutput::create_and_write_header()
{
    sgi_pvt::SgiHeader sgi_header;
    sgi_header.magic = sgi_pvt::SGI_MAGIC;
    sgi_header.storage = sgi_pvt::VERBATIM;
    sgi_header.bpc = m_spec.format.size();

    if (m_spec.height == 1 && m_spec.nchannels == 1)
        sgi_header.dimension = sgi_pvt::ONE_SCANLINE_ONE_CHANNEL;
    else if (m_spec.nchannels == 1)
        sgi_header.dimension = sgi_pvt::MULTI_SCANLINE_ONE_CHANNEL;
    else 
        sgi_header.dimension = sgi_pvt::MULTI_SCANLINE_MULTI_CHANNEL;

    sgi_header.xsize = m_spec.width;
    sgi_header.ysize = m_spec.height;
    sgi_header.zsize = m_spec.nchannels;
    sgi_header.pixmin = 0;
    sgi_header.pixmax = (sgi_header.bpc == 1) ? 255 : 65535;
    sgi_header.dummy = 0;

    ImageIOParameter *ip = m_spec.find_attribute ("ImageDescription",
                                                   TypeDesc::STRING);
    if (ip && ip->data()) {
        const char** img_descr = (const char**)ip->data();
        strncpy (sgi_header.imagename, *img_descr, 80);
        sgi_header.imagename[79] = 0;
    }

    sgi_header.colormap = sgi_pvt::NORMAL;

    if (littleendian()) {
        swap_endian(&sgi_header.magic);
        swap_endian(&sgi_header.dimension);
        swap_endian(&sgi_header.xsize);
        swap_endian(&sgi_header.ysize);
        swap_endian(&sgi_header.zsize);
        swap_endian(&sgi_header.pixmin);
        swap_endian(&sgi_header.pixmax);
        swap_endian(&sgi_header.colormap);
    }

    fwrite(&sgi_header.magic, sizeof(sgi_header.magic), 1, m_fd);
    fwrite(&sgi_header.storage, sizeof(sgi_header.storage), 1, m_fd);
    fwrite(&sgi_header.bpc, sizeof(sgi_header.bpc), 1, m_fd);
    fwrite(&sgi_header.dimension, sizeof(sgi_header.dimension), 1, m_fd);
    fwrite(&sgi_header.xsize, sizeof(sgi_header.xsize), 1, m_fd);
    fwrite(&sgi_header.ysize, sizeof(sgi_header.ysize), 1, m_fd);
    fwrite(&sgi_header.zsize, sizeof(sgi_header.zsize), 1, m_fd);
    fwrite(&sgi_header.pixmin, sizeof(sgi_header.pixmin), 1, m_fd);
    fwrite(&sgi_header.pixmax, sizeof(sgi_header.pixmax), 1, m_fd);
    fwrite(&sgi_header.dummy, sizeof(sgi_header.dummy), 1, m_fd);
    fwrite(sgi_header.imagename, sizeof(sgi_header.imagename), 1, m_fd);
    fwrite(&sgi_header.colormap, sizeof(sgi_header.colormap), 1, m_fd);
    char dummy[404] = {0};
    fwrite(dummy, 404, 1, m_fd);
}
Example #3
0
void
ImageSpec::attribute (const std::string &name, TypeDesc type, const void *value)
{
    // Don't allow duplicates
    ImageIOParameter *f = find_attribute (name);
    if (! f) {
        extra_attribs.resize (extra_attribs.size() + 1);
        f = &extra_attribs.back();
    }
    f->init (name, type, 1, value);
}
Example #4
0
void
BmpOutput::create_and_write_bitmap_header (void)
{
    m_dib_header.size = WINDOWS_V3;
    m_dib_header.width = m_spec.width;
    m_dib_header.height = m_spec.height;
    m_dib_header.cplanes = 1;
    m_dib_header.compression = 0;

    m_dib_header.bpp = m_spec.nchannels << 3;

    m_dib_header.isize = m_spec.width * m_spec.height * m_spec.nchannels; 
    m_dib_header.hres = 0;
    m_dib_header.vres = 0;
    m_dib_header.cpalete = 0;
    m_dib_header.important = 0;

    ImageIOParameter *p = NULL;
    p = m_spec.find_attribute ("ResolutionUnit", TypeDesc::STRING);
    if (p && p->data()) {
        std::string res_units = *(char**)p->data ();
        if (Strutil::iequals (res_units, "m") ||
              Strutil::iequals (res_units, "pixel per meter")) {
            ImageIOParameter *resx = NULL, *resy = NULL;
            resx = m_spec.find_attribute ("XResolution", TypeDesc::INT32);
            if (resx && resx->data())
                m_dib_header.hres = *(int*)resx->data ();
            resy = m_spec.find_attribute ("YResolution", TypeDesc::INT32);
            if (resy && resy->data())
                m_dib_header.vres = *(int*)resy->data ();
        }
    }

    m_dib_header.write_header (m_fd);
}
Example #5
0
std::string
ImageSpec::metadata_val (const ImageIOParameter &p, bool human) const
{
    std::string out = format_raw_metadata (p, human ? 16 : 1024);

    if (human) {
        std::string nice;
        for (int e = 0;  explanation[e].oiioname;  ++e) {
            if (! strcmp (explanation[e].oiioname, p.name().c_str()) &&
                explanation[e].explainer) {
                nice = explanation[e].explainer (p, explanation[e].extradata);
                break;
            }
        }
        if (nice.length())
            out = out + " (" + nice + ")";
    }

    return out;
}
Example #6
0
static void
parse_elements (string_view name, TypeDesc type, const char *type_code,
                string_view value, ImageIOParameter &param)
{
    int num_items = type.numelements() * type.aggregate;
    T *data = (T*) param.data();
    // Erase any leading whitespace
    value.remove_prefix (value.find_first_not_of (" \t"));
    for (int i = 0;  i < num_items;  ++i) {
        // Make a temporary copy so we for sure have a 0-terminated string.
        std::string temp = value;
        // Grab the first value from it
        sscanf (temp.c_str(), type_code, &data[i]);
        // Skip the value (eat until we find a delimiter -- space, comma, tab)
        value.remove_prefix (value.find_first_of (" ,\t"));
        // Skip the delimiter
        value.remove_prefix (value.find_first_not_of (" ,\t"));
        if (value.empty())
            break;   // done if nothing left to parse
    }
}
Example #7
0
bool
TIFFOutput::open (const std::string &name, const ImageSpec &userspec,
                  OpenMode mode)
{
    if (mode == AppendMIPLevel) {
        error ("%s does not support MIP levels", format_name());
        return false;
    }

    close ();  // Close any already-opened file
    m_spec = userspec;  // Stash the spec

    // Check for things this format doesn't support
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
               m_spec.width, m_spec.height);
        return false;
    }
    if (m_spec.depth < 1)
        m_spec.depth = 1;

    // Open the file
    m_tif = TIFFOpen (name.c_str(), mode == AppendSubimage ? "a" : "w");
    if (! m_tif) {
        error ("Can't open \"%s\" for output.", name.c_str());
        return false;
    }

    TIFFSetField (m_tif, TIFFTAG_XPOSITION, (float)m_spec.x);
    TIFFSetField (m_tif, TIFFTAG_YPOSITION, (float)m_spec.y);
    TIFFSetField (m_tif, TIFFTAG_IMAGEWIDTH, m_spec.width);
    TIFFSetField (m_tif, TIFFTAG_IMAGELENGTH, m_spec.height);
    if ((m_spec.full_width != 0 || m_spec.full_height != 0) &&
        (m_spec.full_width != m_spec.width || m_spec.full_height != m_spec.height)) {
        TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLWIDTH, m_spec.full_width);
        TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLLENGTH, m_spec.full_height);
    }
    if (m_spec.tile_width) {
        TIFFSetField (m_tif, TIFFTAG_TILEWIDTH, m_spec.tile_width);
        TIFFSetField (m_tif, TIFFTAG_TILELENGTH, m_spec.tile_height);
    } else {
        // Scanline images must set rowsperstrip
        TIFFSetField (m_tif, TIFFTAG_ROWSPERSTRIP, 32);
    }
    TIFFSetField (m_tif, TIFFTAG_SAMPLESPERPIXEL, m_spec.nchannels);
    TIFFSetField (m_tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); // always
    
    int bps, sampformat;
    switch (m_spec.format.basetype) {
    case TypeDesc::INT8:
        bps = 8;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT8:
        bps = 8;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::INT16:
        bps = 16;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT16:
        bps = 16;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::HALF:
        // Silently change requests for unsupported 'half' to 'float'
        m_spec.set_format (TypeDesc::FLOAT);
    case TypeDesc::FLOAT:
        bps = 32;
        sampformat = SAMPLEFORMAT_IEEEFP;
        break;
    case TypeDesc::DOUBLE:
        bps = 64;
        sampformat = SAMPLEFORMAT_IEEEFP;
        break;
    default:
        error ("TIFF doesn't support %s images (\"%s\")",
               m_spec.format.c_str(), name.c_str());
        close();
        return false;
    }
    TIFFSetField (m_tif, TIFFTAG_BITSPERSAMPLE, bps);
    TIFFSetField (m_tif, TIFFTAG_SAMPLEFORMAT, sampformat);

    int photo = (m_spec.nchannels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK);
    TIFFSetField (m_tif, TIFFTAG_PHOTOMETRIC, photo);

    // ExtraSamples tag
    if (m_spec.nchannels > 3) {
        bool unass = m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0);
        short e = m_spec.nchannels-3;
        std::vector<unsigned short> extra (e);
        for (int c = 0;  c < e;  ++c) {
            if (m_spec.alpha_channel == (c+3))
                extra[c] = unass ? EXTRASAMPLE_UNASSALPHA : EXTRASAMPLE_ASSOCALPHA;
            else
                extra[c] = EXTRASAMPLE_UNSPECIFIED;
        }
        TIFFSetField (m_tif, TIFFTAG_EXTRASAMPLES, e, &extra[0]);
    }

    // Default to LZW compression if no request came with the user spec
    if (! m_spec.find_attribute("compression"))
        m_spec.attribute ("compression", "lzw");

    ImageIOParameter *param;
    const char *str = NULL;

    // Did the user request separate planar configuration?
    m_planarconfig = PLANARCONFIG_CONTIG;
    if ((param = m_spec.find_attribute("planarconfig", TypeDesc::STRING)) ||
        (param = m_spec.find_attribute("tiff:planarconfig", TypeDesc::STRING))) {
        str = *(char **)param->data();
        if (str && iequals (str, "separate"))
            m_planarconfig = PLANARCONFIG_SEPARATE;
    }
    TIFFSetField (m_tif, TIFFTAG_PLANARCONFIG, m_planarconfig);

    // Automatically set date field if the client didn't supply it.
    if (! m_spec.find_attribute("DateTime")) {
        time_t now;
        time (&now);
        struct tm mytm;
        Sysutil::get_local_time (&now, &mytm);
        std::string date = Strutil::format ("%4d:%02d:%02d %2d:%02d:%02d",
                               mytm.tm_year+1900, mytm.tm_mon+1, mytm.tm_mday,
                               mytm.tm_hour, mytm.tm_min, mytm.tm_sec);
        m_spec.attribute ("DateTime", date);
    }

    if (iequals (m_spec.get_string_attribute ("oiio:ColorSpace"), "sRGB"))
        m_spec.attribute ("Exif:ColorSpace", 1);

    // Deal with all other params
    for (size_t p = 0;  p < m_spec.extra_attribs.size();  ++p)
        put_parameter (m_spec.extra_attribs[p].name().string(),
                       m_spec.extra_attribs[p].type(),
                       m_spec.extra_attribs[p].data());

    std::vector<char> iptc;
    encode_iptc_iim (m_spec, iptc);
    if (iptc.size()) {
        iptc.resize ((iptc.size()+3) & (0xffff-3));  // round up
        TIFFSetField (m_tif, TIFFTAG_RICHTIFFIPTC, iptc.size()/4, &iptc[0]);
    }

    std::string xmp = encode_xmp (m_spec, true);
    if (! xmp.empty())
        TIFFSetField (m_tif, TIFFTAG_XMLPACKET, xmp.size(), xmp.c_str());
    
    TIFFCheckpointDirectory (m_tif);  // Ensure the header is written early
    m_checkpointTimer.start(); // Initialize the to the fileopen time
    m_checkpointItems = 0; // Number of tiles or scanlines we've written
    
    return true;
}
Example #8
0
bool
TGAOutput::close ()
{
    if (m_file) {
        // write out the TGA 2.0 data fields

        // FIXME: write out the developer area; according to Larry,
        // it's probably safe to ignore it altogether until someone complains
        // that it's missing :)

        fseek (m_file, 0, SEEK_END);

        // write out the thumbnail, if there is one
        int ofs_thumb = 0;
        {
            unsigned char tw = m_spec.get_int_attribute ("thumbnail_width", 0);
            if (tw) {
                unsigned char th = m_spec.get_int_attribute ("thumbnail_width",
                                                             0);
                if (th) {
                    int tc = m_spec.get_int_attribute ("thumbnail_nchannels",
                                                       0);
                    if (tc == m_spec.nchannels) {
                        ImageIOParameter *p =
                            m_spec.find_attribute ("thumbnail_image");
                        if (p) {
                            ofs_thumb = ftell (m_file);
                            if (bigendian())
                                swap_endian (&ofs_thumb);
                            // dump thumbnail size
                            fwrite (&tw, 1, 1, m_file);
                            fwrite (&th, 1, 1, m_file);
                            // dump thumbnail data
                            fwrite (p->data(), p->datasize(), 1, m_file);
                        }
                    }
                }
            }
        }
        
        // prepare the footer
        tga_footer foot = {(uint32_t)ftell (m_file), 0, "TRUEVISION-XFILE."};
        if (bigendian()) {
            swap_endian (&foot.ofs_ext);
            swap_endian (&foot.ofs_dev);
        }

        // write out the extension area
        // ext area size
        short tmpint = 495;
        if (bigendian())
            swap_endian (&tmpint);
        fwrite (&tmpint, sizeof (tmpint), 1, m_file);

        tmpint = 0;

        // author
        std::string tmpstr = m_spec.get_string_attribute ("Artist", "");
        fwrite (tmpstr.c_str(), std::min (tmpstr.length (), size_t(40)),
                1, m_file);
        // fill the rest with zeros
        for (int i = 41 - std::min (tmpstr.length (), size_t(40)); i > 0; i--)
            fwrite (&tmpint, 1, 1, m_file);

        // image comment
        tmpstr = m_spec.get_string_attribute ("ImageDescription", "");
        {
            char *p = (char *)tmpstr.c_str ();
            int w = 0;  // number of bytes written
            for (int pos = 0; w < 324 && pos < (int)tmpstr.length ();
                 w++, pos++) {
                // on line breaks, fill the remainder of the line with zeros
                if (p[pos] == '\n') {
                    while ((w + 1) % 81 != 0) {
                        fwrite (&tmpint, 1, 1, m_file);
                        w++;
                    }
                    continue;
                }
                fwrite (&p[pos], 1, 1, m_file);
                // null-terminate each line
                if ((w + 1) % 81 == 0) {
                    fwrite (&tmpint, 1, 1, m_file);
                    w++;
                }
            }
            // fill the rest with zeros
            for (; w < 324; w++)
                fwrite (&tmpint, 1, 1, m_file);
        }

        // timestamp
        tmpstr = m_spec.get_string_attribute ("DateTime", "");
        {
            unsigned short y, m, d, h, i, s;
            if (tmpstr.length () > 0)
                sscanf (tmpstr.c_str (), "%04hu:%02hu:%02hu %02hu:%02hu:%02hu",
                        &y, &m, &d, &h, &i, &s);
            else
                y = m = d = h = i = s = 0;
            if (bigendian()) {
                swap_endian (&y);
                swap_endian (&m);
                swap_endian (&d);
                swap_endian (&h);
                swap_endian (&i);
                swap_endian (&s);
            }
            fwrite (&m, sizeof (m), 1, m_file);
            fwrite (&d, sizeof (d), 1, m_file);
            fwrite (&y, sizeof (y), 1, m_file);
            fwrite (&h, sizeof (h), 1, m_file);
            fwrite (&i, sizeof (i), 1, m_file);
            fwrite (&s, sizeof (s), 1, m_file);
        }

        // job ID
        tmpstr = m_spec.get_string_attribute ("DocumentName", "");
        fwrite (tmpstr.c_str(), std::min (tmpstr.length (), size_t(40)),
                1, m_file);
        // fill the rest with zeros
        for (int i = 41 - std::min (tmpstr.length (), size_t(40)); i > 0; i--)
            fwrite (&tmpint, 1, 1, m_file);

        // job time
        tmpstr = m_spec.get_string_attribute ("targa:JobTime", "");
        {
            unsigned short h, m, s;
            if (tmpstr.length () > 0)
                sscanf (tmpstr.c_str (), "%hu:%02hu:%02hu", &h, &m, &s);
            else
                h = m = s = 0;
            if (bigendian()) {
                swap_endian (&h);
                swap_endian (&m);
                swap_endian (&s);
            }
            fwrite (&h, sizeof (h), 1, m_file);
            fwrite (&m, sizeof (m), 1, m_file);
            fwrite (&s, sizeof (s), 1, m_file);
        }

        // software ID - we advertise ourselves
        tmpstr = OIIO_INTRO_STRING;
        fwrite (tmpstr.c_str(), std::min (tmpstr.length (), size_t(40)),
                1, m_file);
        // fill the rest with zeros
        for (int i = 41 - std::min (tmpstr.length (), size_t(40)); i > 0; i--)
            fwrite (&tmpint, 1, 1, m_file);

        // software version
        {
            short v = OIIO_VERSION_MAJOR * 100
                    + OIIO_VERSION_MINOR * 10
                    + OIIO_VERSION_PATCH;
            if (bigendian())
                swap_endian (&v);
            fwrite (&v, sizeof (v), 1, m_file);
            fwrite (&tmpint, 1, 1, m_file);
        }

        // key colour
        // FIXME: what do we save here?
        fwrite (&tmpint, 2, 1, m_file);
        fwrite (&tmpint, 2, 1, m_file);

        // pixel aspect ratio
        {
            float ratio = m_spec.get_float_attribute ("PixelAspectRatio", 1.f);
            float EPS = 1E-5f;
            if (ratio >= (0.f+EPS) && ((ratio <= (1.f-EPS))||(ratio >= (1.f+EPS)))) {
                // FIXME: invent a smarter way to convert to a vulgar fraction?
                // numerator
                tmpint = (unsigned short)(ratio * 10000.f);
                fwrite (&tmpint, 2, 1, m_file);
                // denominator
                tmpint = 10000;
                fwrite (&tmpint, 2, 1, m_file);
                // reset tmpint value
                tmpint = 0;
            } else {
                // just dump two zeros in there
                fwrite (&tmpint, 2, 1, m_file);
                fwrite (&tmpint, 2, 1, m_file);
            }
        }

        // gamma
        {
            if (iequals (m_spec.get_string_attribute ("oiio:ColorSpace"), "GammaCorrected")) {
                float gamma = m_spec.get_float_attribute ("oiio:Gamma", 1.0);
                
                // FIXME: invent a smarter way to convert to a vulgar fraction?
                // NOTE: the spec states that only 1 decimal place of precision
                // is needed, thus the expansion by 10
                // numerator
                tmpint = (unsigned short)(gamma * 10.f);
                fwrite (&tmpint, 2, 1, m_file);
                // denominator
                tmpint = 10;
                fwrite (&tmpint, 2, 1, m_file);
                // reset tmpint value
                tmpint = 0;
            } else {
                // just dump two zeros in there
                fwrite (&tmpint, 2, 1, m_file);
                fwrite (&tmpint, 2, 1, m_file);
            }
        }

        // offset to colour correction table
        // FIXME: support this once it becomes clear how it's actually supposed
        // to be used... the spec is very unclear about this
        // for the time being just dump four NULL bytes
        fwrite (&tmpint, 2, 1, m_file);
        fwrite (&tmpint, 2, 1, m_file);

        // offset to thumbnail (endiannes has already been accounted for)
        fwrite (&ofs_thumb, 4, 1, m_file);

        // offset to scanline table
        // not used very widely, don't bother unless someone complains
        fwrite (&tmpint, 2, 1, m_file);
        fwrite (&tmpint, 2, 1, m_file);

        // alpha type
        {
            unsigned char at = (m_spec.nchannels % 2 == 0)
                             ? TGA_ALPHA_USEFUL : TGA_ALPHA_NONE;
            fwrite (&at, 1, 1, m_file);
        }

        // write out the TGA footer
        fwrite (&foot.ofs_ext, 1, sizeof (foot.ofs_ext), m_file);
        fwrite (&foot.ofs_dev, 1, sizeof (foot.ofs_dev), m_file);
        fwrite (&foot.signature, 1, sizeof (foot.signature), m_file);

        // close the stream
        fclose (m_file);
        m_file = NULL;
    }

    init ();      // re-initialize
    return true;  // How can we fail?
                  // Epicly. -- IneQuation
}
bool
TIFFOutput::open (const std::string &name, const ImageSpec &userspec,
                  OpenMode mode)
{
    if (mode == AppendMIPLevel) {
        error ("%s does not support MIP levels", format_name());
        return false;
    }

    close ();  // Close any already-opened file
    m_spec = userspec;  // Stash the spec

    // Check for things this format doesn't support
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
               m_spec.width, m_spec.height);
        return false;
    }
    if (m_spec.tile_width) {
       if (m_spec.tile_width  % 16 != 0 ||
           m_spec.tile_height % 16 != 0 ||
           m_spec.tile_height == 0) {
          error("Tile size must be a multiple of 16, you asked for %d x %d", m_spec.tile_width, m_spec.tile_height);
          return false;
       }
    }
    if (m_spec.depth < 1)
        m_spec.depth = 1;

    // Open the file
#ifdef _WIN32
    std::wstring wname = Strutil::utf8_to_utf16 (name);
    m_tif = TIFFOpenW (wname.c_str(), mode == AppendSubimage ? "a" : "w");
#else
    m_tif = TIFFOpen (name.c_str(), mode == AppendSubimage ? "a" : "w");
#endif
    if (! m_tif) {
        error ("Can't open \"%s\" for output.", name.c_str());
        return false;
    }

    // N.B. Clamp position at 0... TIFF is internally incapable of having
    // negative origin.
    TIFFSetField (m_tif, TIFFTAG_XPOSITION, (float)std::max (0, m_spec.x));
    TIFFSetField (m_tif, TIFFTAG_YPOSITION, (float)std::max (0, m_spec.y));

    TIFFSetField (m_tif, TIFFTAG_IMAGEWIDTH, m_spec.width);
    TIFFSetField (m_tif, TIFFTAG_IMAGELENGTH, m_spec.height);
    if ((m_spec.full_width != 0 || m_spec.full_height != 0) &&
        (m_spec.full_width != m_spec.width || m_spec.full_height != m_spec.height)) {
        TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLWIDTH, m_spec.full_width);
        TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLLENGTH, m_spec.full_height);
    }
    if (m_spec.tile_width) {
        TIFFSetField (m_tif, TIFFTAG_TILEWIDTH, m_spec.tile_width);
        TIFFSetField (m_tif, TIFFTAG_TILELENGTH, m_spec.tile_height);
    } else {
        // Scanline images must set rowsperstrip
        TIFFSetField (m_tif, TIFFTAG_ROWSPERSTRIP, 32);
    }
    TIFFSetField (m_tif, TIFFTAG_SAMPLESPERPIXEL, m_spec.nchannels);
    int orientation = m_spec.get_int_attribute("Orientation", 1);
    TIFFSetField (m_tif, TIFFTAG_ORIENTATION, orientation);
    
    int bps, sampformat;
    switch (m_spec.format.basetype) {
    case TypeDesc::INT8:
        bps = 8;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT8:
        bps = 8;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::INT16:
        bps = 16;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT16:
        bps = 16;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::INT32:
        bps = 32;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT32:
        bps = 32;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::HALF:
        // Silently change requests for unsupported 'half' to 'float'
        m_spec.set_format (TypeDesc::FLOAT);
    case TypeDesc::FLOAT:
        bps = 32;
        sampformat = SAMPLEFORMAT_IEEEFP;
        break;
    case TypeDesc::DOUBLE:
        bps = 64;
        sampformat = SAMPLEFORMAT_IEEEFP;
        break;
    default:
        // Everything else, including UNKNOWN -- default to 8 bit
        bps = 8;
        sampformat = SAMPLEFORMAT_UINT;
        m_spec.set_format (TypeDesc::UINT8);
        break;
    }
    TIFFSetField (m_tif, TIFFTAG_BITSPERSAMPLE, bps);
    TIFFSetField (m_tif, TIFFTAG_SAMPLEFORMAT, sampformat);

    int photo = (m_spec.nchannels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK);
    TIFFSetField (m_tif, TIFFTAG_PHOTOMETRIC, photo);

    // ExtraSamples tag
    if (m_spec.nchannels > 3) {
        bool unass = m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0);
        short e = m_spec.nchannels-3;
        std::vector<unsigned short> extra (e);
        for (int c = 0;  c < e;  ++c) {
            if (m_spec.alpha_channel == (c+3))
                extra[c] = unass ? EXTRASAMPLE_UNASSALPHA : EXTRASAMPLE_ASSOCALPHA;
            else
                extra[c] = EXTRASAMPLE_UNSPECIFIED;
        }
        TIFFSetField (m_tif, TIFFTAG_EXTRASAMPLES, e, &extra[0]);
    }

    // Default to ZIP compression if no request came with the user spec
    if (! m_spec.find_attribute("compression"))
        m_spec.attribute ("compression", "zip");

    ImageIOParameter *param;
    const char *str = NULL;

    // Did the user request separate planar configuration?
    m_planarconfig = PLANARCONFIG_CONTIG;
    if ((param = m_spec.find_attribute("planarconfig", TypeDesc::STRING)) ||
        (param = m_spec.find_attribute("tiff:planarconfig", TypeDesc::STRING))) {
        str = *(char **)param->data();
        if (str && Strutil::iequals (str, "separate")) {
            m_planarconfig = PLANARCONFIG_SEPARATE;
            if (! m_spec.tile_width) {
                // I can only seem to make separate planarconfig work when
                // rowsperstrip is 1.
                TIFFSetField (m_tif, TIFFTAG_ROWSPERSTRIP, 1);
            }
        }
    }
    TIFFSetField (m_tif, TIFFTAG_PLANARCONFIG, m_planarconfig);

    // Automatically set date field if the client didn't supply it.
    if (! m_spec.find_attribute("DateTime")) {
        time_t now;
        time (&now);
        struct tm mytm;
        Sysutil::get_local_time (&now, &mytm);
        std::string date = Strutil::format ("%4d:%02d:%02d %2d:%02d:%02d",
                               mytm.tm_year+1900, mytm.tm_mon+1, mytm.tm_mday,
                               mytm.tm_hour, mytm.tm_min, mytm.tm_sec);
        m_spec.attribute ("DateTime", date);
    }

    // Write ICC profile, if we have anything
    const ImageIOParameter* icc_profile_parameter = m_spec.find_attribute(ICC_PROFILE_ATTR);
    if (icc_profile_parameter != NULL) {
        unsigned char *icc_profile = (unsigned char*)icc_profile_parameter->data();
        uint32 length = icc_profile_parameter->type().size();
        if (icc_profile && length)
            TIFFSetField (m_tif, TIFFTAG_ICCPROFILE, length, icc_profile);
    }

    if (Strutil::iequals (m_spec.get_string_attribute ("oiio:ColorSpace"), "sRGB"))
        m_spec.attribute ("Exif:ColorSpace", 1);

    // Deal with missing XResolution or YResolution, or a PixelAspectRatio
    // that contradicts them.
    float X_density = m_spec.get_float_attribute ("XResolution", 1.0f);
    float Y_density = m_spec.get_float_attribute ("YResolution", 1.0f);
    float aspect = m_spec.get_float_attribute ("PixelAspectRatio", 1.0f);
    if (X_density < 1.0f || Y_density < 1.0f || aspect*X_density != Y_density) {
        if (X_density < 1.0f || Y_density < 1.0f) {
            X_density = Y_density = 1.0f;
            m_spec.attribute ("ResolutionUnit", "none");
        }
        m_spec.attribute ("XResolution", X_density);
        m_spec.attribute ("YResolution", X_density * aspect);
    }

    // Deal with all other params
    for (size_t p = 0;  p < m_spec.extra_attribs.size();  ++p)
        put_parameter (m_spec.extra_attribs[p].name().string(),
                       m_spec.extra_attribs[p].type(),
                       m_spec.extra_attribs[p].data());

    std::vector<char> iptc;
    encode_iptc_iim (m_spec, iptc);
    if (iptc.size()) {
        iptc.resize ((iptc.size()+3) & (0xffff-3));  // round up
        TIFFSetField (m_tif, TIFFTAG_RICHTIFFIPTC, iptc.size()/4, &iptc[0]);
    }

    std::string xmp = encode_xmp (m_spec, true);
    if (! xmp.empty())
        TIFFSetField (m_tif, TIFFTAG_XMLPACKET, xmp.size(), xmp.c_str());
    
    TIFFCheckpointDirectory (m_tif);  // Ensure the header is written early
    m_checkpointTimer.start(); // Initialize the to the fileopen time
    m_checkpointItems = 0; // Number of tiles or scanlines we've written

    m_dither = (m_spec.format == TypeDesc::UINT8) ?
                    m_spec.get_int_attribute ("oiio:dither", 0) : 0;

    return true;
}
Example #10
0
bool
DPXOutput::open (const std::string &name, const ImageSpec &userspec,
                 OpenMode mode)
{
    if (mode == Create) {
        m_subimage = 0;
        if (m_subimage_specs.size() < 1) {
            m_subimage_specs.resize (1);
            m_subimage_specs[0] = userspec;
            m_subimages_to_write = 1;
        }
    } else if (mode == AppendSubimage) {
        if (m_write_pending)
            write_buffer ();
        ++m_subimage;
        if (m_subimage >= m_subimages_to_write) {
            error ("Exceeded the pre-declared number of subimages (%d)",
                   m_subimages_to_write);
            return false;
        }
        return prep_subimage (m_subimage, true);
        // Nothing else to do, the header taken care of when we opened with
        // Create.
    } else if (mode == AppendMIPLevel) {
        error ("DPX does not support MIP-maps");
        return false;
    }

    // From here out, all the heavy lifting is done for Create
    ASSERT (mode == Create);

    if (is_opened())
        close ();  // Close any already-opened file
    m_stream = new OutStream();
    if (! m_stream->Open(name.c_str ())) {
        error ("Could not open file \"%s\"", name.c_str ());
        return false;
    }
    m_dpx.SetOutStream (m_stream);
    m_dpx.Start ();
    m_subimage = 0;

    ImageSpec &m_spec (m_subimage_specs[m_subimage]); // alias the spec

    // Check for things this format doesn't support
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
               m_spec.width, m_spec.height);
        return false;
    }

    if (m_spec.depth < 1)
        m_spec.depth = 1;
    else if (m_spec.depth > 1) {
        error ("DPX does not support volume images (depth > 1)");
        return false;
    }

    // some metadata
    std::string software = m_spec.get_string_attribute ("Software", "");
    std::string project = m_spec.get_string_attribute ("DocumentName", "");
    std::string copyright = m_spec.get_string_attribute ("Copyright", "");
    std::string datestr = m_spec.get_string_attribute ("DateTime", "");
    if (datestr.size () >= 19) {
        // libdpx's date/time format is pretty close to OIIO's (libdpx uses
        // %Y:%m:%d:%H:%M:%S%Z)
        // NOTE: the following code relies on the DateTime attribute being properly
        // formatted!
        // assume UTC for simplicity's sake, fix it if someone complains
        datestr[10] = ':';
        datestr.replace (19, -1, "Z");
    }

    // check if the client wants endianness reverse to native
    // assume big endian per Jeremy's request, unless little endian is
    // explicitly specified
    std::string endian = m_spec.get_string_attribute ("oiio:Endian", littleendian() ? "little" : "big");
    m_wantSwap = (littleendian() != Strutil::iequals (endian, "little"));

    m_dpx.SetFileInfo (name.c_str (),                               // filename
        datestr.c_str (),                                           // cr. date
        software.empty () ? OIIO_INTRO_STRING : software.c_str (),  // creator
        project.empty () ? NULL : project.c_str (),                 // project
        copyright.empty () ? NULL : copyright.c_str (),             // copyright
        m_spec.get_int_attribute ("dpx:EncryptKey", ~0),            // encryption key
        m_wantSwap);

    // image info
    m_dpx.SetImageInfo (m_spec.width, m_spec.height);

    for (int s = 0;  s < m_subimages_to_write;  ++s) {
        prep_subimage (s, false);
        m_dpx.header.SetBitDepth (s, m_bitdepth);
        ImageSpec &spec (m_subimage_specs[s]);
        bool datasign = (spec.format == TypeDesc::INT8 ||
                         spec.format == TypeDesc::INT16);
        m_dpx.SetElement (s, m_desc, m_bitdepth, m_transfer, m_cmetr,
                          m_packing, dpx::kNone, datasign,
                          spec.get_int_attribute ("dpx:LowData", 0xFFFFFFFF),
                          spec.get_float_attribute ("dpx:LowQuantity", std::numeric_limits<float>::quiet_NaN()),
                          spec.get_int_attribute ("dpx:HighData", 0xFFFFFFFF),
                          spec.get_float_attribute ("dpx:HighQuantity", std::numeric_limits<float>::quiet_NaN()),
                          spec.get_int_attribute ("dpx:EndOfLinePadding", 0),
                          spec.get_int_attribute ("dpx:EndOfImagePadding", 0));
        std::string desc = spec.get_string_attribute ("ImageDescription", "");
        m_dpx.header.SetDescription (s, desc.c_str());
    }

    m_dpx.header.SetXScannedSize (m_spec.get_float_attribute
        ("dpx:XScannedSize", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetYScannedSize (m_spec.get_float_attribute
        ("dpx:YScannedSize", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetFramePosition (m_spec.get_int_attribute
        ("dpx:FramePosition", 0xFFFFFFFF));
    m_dpx.header.SetSequenceLength (m_spec.get_int_attribute
        ("dpx:SequenceLength", 0xFFFFFFFF));
    m_dpx.header.SetHeldCount (m_spec.get_int_attribute
        ("dpx:HeldCount", 0xFFFFFFFF));
    m_dpx.header.SetFrameRate (m_spec.get_float_attribute
        ("dpx:FrameRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetShutterAngle (m_spec.get_float_attribute
        ("dpx:ShutterAngle", std::numeric_limits<float>::quiet_NaN()));
    // FIXME: should we write the input version through or always default to 2.0?
    /*tmpstr = m_spec.get_string_attribute ("dpx:Version", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetVersion (tmpstr.c_str ());*/
    std::string tmpstr;
    tmpstr = m_spec.get_string_attribute ("dpx:FrameId", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetFrameId (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:SlateInfo", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetSlateInfo (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:SourceImageFileName", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetSourceImageFileName (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:InputDevice", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetInputDevice (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:InputDeviceSerialNumber", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetInputDeviceSerialNumber (tmpstr.c_str ());
    m_dpx.header.SetInterlace (m_spec.get_int_attribute ("dpx:Interlace", 0xFF));
    m_dpx.header.SetFieldNumber (m_spec.get_int_attribute ("dpx:FieldNumber", 0xFF));
    m_dpx.header.SetHorizontalSampleRate (m_spec.get_float_attribute
        ("dpx:HorizontalSampleRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetVerticalSampleRate (m_spec.get_float_attribute
        ("dpx:VerticalSampleRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetTemporalFrameRate (m_spec.get_float_attribute
        ("dpx:TemporalFrameRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetTimeOffset (m_spec.get_float_attribute
        ("dpx:TimeOffset", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetBlackLevel (m_spec.get_float_attribute
        ("dpx:BlackLevel", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetBlackGain (m_spec.get_float_attribute
        ("dpx:BlackGain", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetBreakPoint (m_spec.get_float_attribute
        ("dpx:BreakPoint", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetWhiteLevel (m_spec.get_float_attribute
        ("dpx:WhiteLevel", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetIntegrationTimes (m_spec.get_float_attribute
        ("dpx:IntegrationTimes", std::numeric_limits<float>::quiet_NaN()));
    float aspect = m_spec.get_float_attribute ("PixelAspectRatio", 1.0f);
    int aspect_num, aspect_den;
    float_to_rational (aspect, aspect_num, aspect_den);
    m_dpx.header.SetAspectRatio (0, aspect_num);
    m_dpx.header.SetAspectRatio (1, aspect_den);
    m_dpx.header.SetXOffset ((unsigned int)std::max (0, m_spec.x));
    m_dpx.header.SetYOffset ((unsigned int)std::max (0, m_spec.y));
    m_dpx.header.SetXOriginalSize ((unsigned int)m_spec.full_width);
    m_dpx.header.SetYOriginalSize ((unsigned int)m_spec.full_height);

    static int DpxOrientations[] = { 0,
        dpx::kLeftToRightTopToBottom, dpx::kRightToLeftTopToBottom,
        dpx::kLeftToRightBottomToTop, dpx::kRightToLeftBottomToTop,
        dpx::kTopToBottomLeftToRight, dpx::kTopToBottomRightToLeft,
        dpx::kBottomToTopLeftToRight, dpx::kBottomToTopRightToLeft };
    int orient = m_spec.get_int_attribute ("Orientation", 0);
    orient = DpxOrientations[clamp (orient, 0, 8)];
    m_dpx.header.SetImageOrientation ((dpx::Orientation)orient);

    ImageIOParameter *tc = m_spec.find_attribute("smpte:TimeCode", TypeDesc::TypeTimeCode, false);
    if (tc) {
        unsigned int *timecode = (unsigned int*) tc->data();
        m_dpx.header.timeCode = timecode[0];
        m_dpx.header.userBits = timecode[1];
    }
    else {
        std::string timecode = m_spec.get_string_attribute ("dpx:TimeCode", "");
        int tmpint = m_spec.get_int_attribute ("dpx:TimeCode", ~0);
        if (timecode.size () > 0)
            m_dpx.header.SetTimeCode (timecode.c_str ());
        else if (tmpint != ~0)
        m_dpx.header.timeCode = tmpint;
        m_dpx.header.userBits = m_spec.get_int_attribute ("dpx:UserBits", ~0);
    }

    ImageIOParameter *kc = m_spec.find_attribute("smpte:KeyCode", TypeDesc::TypeKeyCode, false);
    if (kc) {
        int *array = (int*) kc->data();
        set_keycode_values(array);

        // See if there is an overloaded dpx:Format
        std::string format = m_spec.get_string_attribute ("dpx:Format", "");
        if (format.size () > 0)
            m_dpx.header.SetFormat (format.c_str ());
    }

    std::string srcdate = m_spec.get_string_attribute ("dpx:SourceDateTime", "");
    if (srcdate.size () >= 19) {
        // libdpx's date/time format is pretty close to OIIO's (libdpx uses
        // %Y:%m:%d:%H:%M:%S%Z)
        // NOTE: the following code relies on the DateTime attribute being properly
        // formatted!
        // assume UTC for simplicity's sake, fix it if someone complains
        srcdate[10] = ':';
        srcdate.replace (19, -1, "Z");
        m_dpx.header.SetSourceTimeDate (srcdate.c_str ());
    }

    // set the user data size
    ImageIOParameter *user = m_spec.find_attribute ("dpx:UserData");
    if (user && user->datasize () > 0 && user->datasize () <= 1024 * 1024) {
        m_dpx.SetUserData (user->datasize ());
    }

    // commit!
    if (!m_dpx.WriteHeader ()) {
        error ("Failed to write DPX header");
        return false;
    }

    // write the user data
    if (user && user->datasize () > 0 && user->datasize() <= 1024 * 1024) {
        if (!m_dpx.WriteUserData ((void *)user->data ())) {
            error ("Failed to write user data");
            return false;
        }
    }

    m_dither = (m_spec.format == TypeDesc::UINT8) ?
                    m_spec.get_int_attribute ("oiio:dither", 0) : 0;

    // If user asked for tiles -- which this format doesn't support, emulate
    // it by buffering the whole image.
    if (m_spec.tile_width && m_spec.tile_height)
        m_tilebuffer.resize (m_spec.image_bytes());

    return prep_subimage (m_subimage, true);
}
Example #11
0
bool
DPXOutput::open (const std::string &name, const ImageSpec &userspec,
                 OpenMode mode)
{
    close ();  // Close any already-opened file

    if (mode != Create) {
        error ("%s does not support subimages or MIP levels", format_name());
        return false;
    }

    m_spec = userspec;  // Stash the spec

    // open the image
    m_stream = new OutStream();
    if (! m_stream->Open(name.c_str ())) {
        error ("Could not open file \"%s\"", name.c_str ());
        return false;
    }

    // Check for things this format doesn't support
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
                       m_spec.width, m_spec.height);
                       return false;
    }

    if (m_spec.depth < 1)
        m_spec.depth = 1;
    else if (m_spec.depth > 1) {
        error ("DPX does not support volume images (depth > 1)");
        return false;
    }

    if (m_spec.format == TypeDesc::UINT8
        || m_spec.format == TypeDesc::INT8)
        m_datasize = dpx::kByte;
    else if (m_spec.format == TypeDesc::UINT16
        || m_spec.format == TypeDesc::INT16)
        m_datasize = dpx::kWord;
    else if (m_spec.format == TypeDesc::FLOAT
        || m_spec.format == TypeDesc::HALF) {
        m_spec.format = TypeDesc::FLOAT;
        m_datasize = dpx::kFloat;
    } else if (m_spec.format == TypeDesc::DOUBLE)
        m_datasize = dpx::kDouble;
    else {
        // use 16-bit unsigned integers as a failsafe
        m_spec.format = TypeDesc::UINT16;
        m_datasize = dpx::kWord;
    }

    // check if the client is giving us raw data to write
    m_wantRaw = m_spec.get_int_attribute ("dpx:RawData", 0) != 0;

    m_dpx.SetOutStream (m_stream);

    // start out the file
    m_dpx.Start ();

    // some metadata
    std::string project = m_spec.get_string_attribute ("DocumentName", "");
    std::string copyright = m_spec.get_string_attribute ("Copyright", "");
    m_dpx.SetFileInfo (name.c_str (),                       // filename
        NULL,                                               // TODO: cr. date
        OIIO_INTRO_STRING,                                  // creator
        project.empty () ? NULL : project.c_str (),         // project
        copyright.empty () ? NULL : copyright.c_str ());    // copyright

    // image info
    m_dpx.SetImageInfo (m_spec.width, m_spec.height);

    // determine descriptor
    m_desc = get_descriptor_from_string
        (m_spec.get_string_attribute ("dpx:ImageDescriptor", ""));

    // transfer function
    dpx::Characteristic transfer;
    
    std::string colorspace = m_spec.get_string_attribute ("oiio:ColorSpace", "");
    if (iequals (colorspace, "Linear"))  transfer = dpx::kLinear;
    else if (iequals (colorspace, "GammaCorrected")) transfer = dpx::kUserDefined;
    else if (iequals (colorspace, "Rec709")) transfer = dpx::kITUR709;
    else if (iequals (colorspace, "KodakLog")) transfer = dpx::kLogarithmic;
    else {
        std::string dpxtransfer = m_spec.get_string_attribute ("dpx:Transfer", "");
        transfer = get_characteristic_from_string (dpxtransfer);
    }
    
    // colorimetric
    m_cmetr = get_characteristic_from_string
        (m_spec.get_string_attribute ("dpx:Colorimetric", "User defined"));

    // select packing method
    dpx::Packing packing;
    std::string tmpstr = m_spec.get_string_attribute ("dpx:ImagePacking", "Filled, method A");
    if (iequals (tmpstr, "Packed"))
        packing = dpx::kPacked;
    else if (iequals (tmpstr, "Filled, method B"))
        packing = dpx::kFilledMethodB;
    else
        packing = dpx::kFilledMethodA;

    // calculate target bit depth
    int bitDepth = m_spec.get_int_attribute ("oiio:BitsPerSample",
        m_spec.format.size () * 8);
    if (bitDepth % 8 != 0 && bitDepth != 10 && bitDepth != 12) {
        error ("Unsupported bit depth %d", bitDepth);
        return false;
    }
    
    // see if we'll need to convert or not
    if (m_desc == dpx::kRGB || m_desc == dpx::kRGBA) {
        // shortcut for RGB(A) that gets the job done
        m_bytes = m_spec.scanline_bytes ();
        m_wantRaw = true;
    } else {
        m_bytes = dpx::QueryNativeBufferSize (m_desc, m_datasize, m_spec.width, 1);
        if (m_bytes == 0 && !m_wantRaw) {
            error ("Unable to deliver native format data from source data");
            return false;
        } else if (m_bytes < 0) {
            // no need to allocate another buffer
            if (!m_wantRaw)
                m_bytes = m_spec.scanline_bytes ();
            else
                m_bytes = -m_bytes;
        }
    }

    if (m_bytes < 0)
        m_bytes = -m_bytes;

    m_dpx.SetElement (0, m_desc, bitDepth, transfer, m_cmetr,
        packing, dpx::kNone, (m_spec.format == TypeDesc::INT8
            || m_spec.format == TypeDesc::INT16) ? 1 : 0);

    // commit!
    if (!m_dpx.WriteHeader ()) {
        error ("Failed to write DPX header");
        return false;
    }

    // user data
    ImageIOParameter *user = m_spec.find_attribute ("dpx:UserData");
    if (user && user->datasize () > 0) {
        if (user->datasize () > 1024 * 1024) {
            error ("User data block size exceeds 1 MB");
            return false;
        }
        // FIXME: write the missing libdpx code
        /*m_dpx.SetUserData (user->datasize ());
        if (!m_dpx.WriteUserData ((void *)user->data ())) {
            error ("Failed to write user data");
            return false;
        }*/
    }

    // reserve space for the image data buffer
    m_buf.reserve (m_bytes * m_spec.height);

    return true;
}
Example #12
0
bool
ZfileOutput::open (const std::string &name, const ImageSpec &userspec,
                   OpenMode mode)
{
    if (mode != Create) {
        error ("%s does not support subimages or MIP levels", format_name());
        return false;
    }

    close ();  // Close any already-opened file
    m_gz = 0;
    m_file = NULL;
    m_spec = userspec;  // Stash the spec

    // Check for things this format doesn't support
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
               m_spec.width, m_spec.height);
        return false;
    }
    if (m_spec.depth < 1)
        m_spec.depth = 1;
    if (m_spec.depth > 1) {
        error ("%s does not support volume images (depth > 1)", format_name());
        return false;
    }

    if (m_spec.nchannels != 1) {
        error ("Zfile only supports 1-4 channels, not %d", m_spec.nchannels);
        return false;
    }

    // Force float
    if (m_spec.format != TypeDesc::FLOAT)
        m_spec.format = TypeDesc::FLOAT;

    ZfileHeader header;
    header.magic = zfile_magic;
    header.width = (int)m_spec.width;
    header.height = (int)m_spec.height;

    ImageIOParameter *p;
    static float ident[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
    if ((p = m_spec.find_attribute ("worldtocamera", TypeDesc::TypeMatrix)))
        memcpy (header.worldtocamera, p->data(), 16*sizeof(float));
    else
        memcpy (header.worldtocamera, ident, 16*sizeof(float));
    if ((p = m_spec.find_attribute ("worldtoscreen", TypeDesc::TypeMatrix)))
        memcpy (header.worldtoscreen, p->data(), 16*sizeof(float));
    else
        memcpy (header.worldtoscreen, ident, 16*sizeof(float));

    if (m_spec.get_string_attribute ("compression", "none") != std::string("none")) {
        FILE *fd = Filesystem::fopen (name, "wb");

        if (fd) {
            m_gz = gzdopen (fileno (fd), "wb");
            if (!m_gz)
                fclose (fd);
        }
    }
    else
        m_file = Filesystem::fopen (name, "wb");
    if (! m_file  &&  ! m_gz) {
        error ("Could not open file \"%s\"", name.c_str());
        return false;
    }

    if (m_gz)
        gzwrite (m_gz, &header, sizeof(header));
    else {
    	size_t b = fwrite (&header, sizeof(header), 1, m_file);
    	if (b != 1) {
            error ("Failed write zfile::open (err: %d)", b);
            return false;
    	}
    }

    return true;
}
Example #13
0
bool
DPXOutput::open (const std::string &name, const ImageSpec &userspec,
                 OpenMode mode)
{
    close ();  // Close any already-opened file

    if (mode != Create) {
        error ("%s does not support subimages or MIP levels", format_name());
        return false;
    }

    m_spec = userspec;  // Stash the spec

    // open the image
    m_stream = new OutStream();
    if (! m_stream->Open(name.c_str ())) {
        error ("Could not open file \"%s\"", name.c_str ());
        return false;
    }

    // Check for things this format doesn't support
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
                       m_spec.width, m_spec.height);
                       return false;
    }

    if (m_spec.depth < 1)
        m_spec.depth = 1;
    else if (m_spec.depth > 1) {
        error ("DPX does not support volume images (depth > 1)");
        return false;
    }

    if (m_spec.format == TypeDesc::UINT8
        || m_spec.format == TypeDesc::INT8)
        m_datasize = dpx::kByte;
    else if (m_spec.format == TypeDesc::UINT16
        || m_spec.format == TypeDesc::INT16)
        m_datasize = dpx::kWord;
    else if (m_spec.format == TypeDesc::FLOAT
        || m_spec.format == TypeDesc::HALF) {
        m_spec.format = TypeDesc::FLOAT;
        m_datasize = dpx::kFloat;
    } else if (m_spec.format == TypeDesc::DOUBLE)
        m_datasize = dpx::kDouble;
    else {
        // use 16-bit unsigned integers as a failsafe
        m_spec.format = TypeDesc::UINT16;
        m_datasize = dpx::kWord;
    }

    // check if the client is giving us raw data to write
    m_wantRaw = m_spec.get_int_attribute ("dpx:RawData", 0) != 0;
    
    // check if the client wants endianness reverse to native
    // assume big endian per Jeremy's request, unless little endian is
    // explicitly specified
    std::string tmpstr = m_spec.get_string_attribute ("oiio:Endian", littleendian() ? "little" : "big");
    m_wantSwap = (littleendian() != Strutil::iequals (tmpstr, "little"));

    m_dpx.SetOutStream (m_stream);

    // start out the file
    m_dpx.Start ();

    // some metadata
    std::string project = m_spec.get_string_attribute ("DocumentName", "");
    std::string copyright = m_spec.get_string_attribute ("Copyright", "");
    tmpstr = m_spec.get_string_attribute ("DateTime", "");
    if (tmpstr.size () >= 19) {
        // libdpx's date/time format is pretty close to OIIO's (libdpx uses
        // %Y:%m:%d:%H:%M:%S%Z)
        // NOTE: the following code relies on the DateTime attribute being properly
        // formatted!
        // assume UTC for simplicity's sake, fix it if someone complains
        tmpstr[10] = ':';
        tmpstr.replace (19, -1, "Z");
    }
    m_dpx.SetFileInfo (name.c_str (),                       // filename
        tmpstr.c_str (),                                    // cr. date
        OIIO_INTRO_STRING,                                  // creator
        project.empty () ? NULL : project.c_str (),         // project
        copyright.empty () ? NULL : copyright.c_str (),     // copyright
        m_spec.get_int_attribute ("dpx:EncryptKey", ~0),    // encryption key
        m_wantSwap);

    // image info
    m_dpx.SetImageInfo (m_spec.width, m_spec.height);

    // determine descriptor
    m_desc = get_descriptor_from_string
        (m_spec.get_string_attribute ("dpx:ImageDescriptor", ""));

    // transfer function
    dpx::Characteristic transfer;
    
    std::string colorspace = m_spec.get_string_attribute ("oiio:ColorSpace", "");
    if (Strutil::iequals (colorspace, "Linear"))  transfer = dpx::kLinear;
    else if (Strutil::iequals (colorspace, "GammaCorrected")) transfer = dpx::kUserDefined;
    else if (Strutil::iequals (colorspace, "Rec709")) transfer = dpx::kITUR709;
    else if (Strutil::iequals (colorspace, "KodakLog")) transfer = dpx::kLogarithmic;
    else {
        std::string dpxtransfer = m_spec.get_string_attribute ("dpx:Transfer", "");
        transfer = get_characteristic_from_string (dpxtransfer);
    }
    
    // colorimetric
    m_cmetr = get_characteristic_from_string
        (m_spec.get_string_attribute ("dpx:Colorimetric", "User defined"));

    // select packing method
    dpx::Packing packing;
    tmpstr = m_spec.get_string_attribute ("dpx:Packing", "Filled, method A");
    if (Strutil::iequals (tmpstr, "Packed"))
        packing = dpx::kPacked;
    else if (Strutil::iequals (tmpstr, "Filled, method B"))
        packing = dpx::kFilledMethodB;
    else
        packing = dpx::kFilledMethodA;

    // calculate target bit depth
    int bitDepth = m_spec.format.size () * 8;
    if (m_spec.format == TypeDesc::UINT16) {
        bitDepth = m_spec.get_int_attribute ("oiio:BitsPerSample", 16);
        if (bitDepth != 10 && bitDepth != 12 && bitDepth != 16) {
            error ("Unsupported bit depth %d", bitDepth);
            return false;
        }
    }
    m_dpx.header.SetBitDepth (0, bitDepth);

    // Bug workaround: libDPX doesn't appear to correctly support
    // "filled method A" for 12 bit data.  Does anybody care what
    // packing/filling we use?  Punt and just use "packed".
    if (bitDepth == 12)
        packing = dpx::kPacked;
    
    // see if we'll need to convert or not
    if (m_desc == dpx::kRGB || m_desc == dpx::kRGBA) {
        // shortcut for RGB(A) that gets the job done
        m_bytes = m_spec.scanline_bytes ();
        m_wantRaw = true;
    } else {
        m_bytes = dpx::QueryNativeBufferSize (m_desc, m_datasize, m_spec.width, 1);
        if (m_bytes == 0 && !m_wantRaw) {
            error ("Unable to deliver native format data from source data");
            return false;
        } else if (m_bytes < 0) {
            // no need to allocate another buffer
            if (!m_wantRaw)
                m_bytes = m_spec.scanline_bytes ();
            else
                m_bytes = -m_bytes;
        }
    }

    if (m_bytes < 0)
        m_bytes = -m_bytes;

    m_dpx.SetElement (0, m_desc, bitDepth, transfer, m_cmetr,
        packing, dpx::kNone, (m_spec.format == TypeDesc::INT8
            || m_spec.format == TypeDesc::INT16) ? 1 : 0,
        m_spec.get_int_attribute ("dpx:LowData", 0xFFFFFFFF),
        m_spec.get_float_attribute ("dpx:LowQuantity", std::numeric_limits<float>::quiet_NaN()),
        m_spec.get_int_attribute ("dpx:HighData", 0xFFFFFFFF),
        m_spec.get_float_attribute ("dpx:HighQuantity", std::numeric_limits<float>::quiet_NaN()),
        m_spec.get_int_attribute ("dpx:EndOfLinePadding", 0),
        m_spec.get_int_attribute ("dpx:EndOfImagePadding", 0));

    m_dpx.header.SetXScannedSize (m_spec.get_float_attribute
        ("dpx:XScannedSize", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetYScannedSize (m_spec.get_float_attribute
        ("dpx:YScannedSize", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetFramePosition (m_spec.get_int_attribute
        ("dpx:FramePosition", 0xFFFFFFFF));
    m_dpx.header.SetSequenceLength (m_spec.get_int_attribute
        ("dpx:SequenceLength", 0xFFFFFFFF));
    m_dpx.header.SetHeldCount (m_spec.get_int_attribute
        ("dpx:HeldCount", 0xFFFFFFFF));
    m_dpx.header.SetFrameRate (m_spec.get_float_attribute
        ("dpx:FrameRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetShutterAngle (m_spec.get_float_attribute
        ("dpx:ShutterAngle", std::numeric_limits<float>::quiet_NaN()));
    // FIXME: should we write the input version through or always default to 2.0?
    /*tmpstr = m_spec.get_string_attribute ("dpx:Version", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetVersion (tmpstr.c_str ());*/
    tmpstr = m_spec.get_string_attribute ("dpx:Format", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetFormat (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:FrameId", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetFrameId (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:SlateInfo", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetSlateInfo (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:SourceImageFileName", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetSourceImageFileName (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:InputDevice", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetInputDevice (tmpstr.c_str ());
    tmpstr = m_spec.get_string_attribute ("dpx:InputDeviceSerialNumber", "");
    if (tmpstr.size () > 0)
        m_dpx.header.SetInputDeviceSerialNumber (tmpstr.c_str ());
    m_dpx.header.SetInterlace (m_spec.get_int_attribute ("dpx:Interlace", 0xFF));
    m_dpx.header.SetFieldNumber (m_spec.get_int_attribute ("dpx:FieldNumber", 0xFF));
    m_dpx.header.SetHorizontalSampleRate (m_spec.get_float_attribute
        ("dpx:HorizontalSampleRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetVerticalSampleRate (m_spec.get_float_attribute
        ("dpx:VerticalSampleRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetTemporalFrameRate (m_spec.get_float_attribute
        ("dpx:TemporalFrameRate", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetTimeOffset (m_spec.get_float_attribute
        ("dpx:TimeOffset", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetBlackLevel (m_spec.get_float_attribute
        ("dpx:BlackLevel", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetBlackGain (m_spec.get_float_attribute
        ("dpx:BlackGain", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetBreakPoint (m_spec.get_float_attribute
        ("dpx:BreakPoint", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetWhiteLevel (m_spec.get_float_attribute
        ("dpx:WhiteLevel", std::numeric_limits<float>::quiet_NaN()));
    m_dpx.header.SetIntegrationTimes (m_spec.get_float_attribute
        ("dpx:IntegrationTimes", std::numeric_limits<float>::quiet_NaN()));
    float aspect = m_spec.get_float_attribute ("PixelAspectRatio", 1.0f);
    int aspect_num, aspect_den;
    float_to_rational (aspect, aspect_num, aspect_den);
    m_dpx.header.SetAspectRatio (0, aspect_num);
    m_dpx.header.SetAspectRatio (1, aspect_den);

    tmpstr = m_spec.get_string_attribute ("dpx:TimeCode", "");
    int tmpint = m_spec.get_int_attribute ("dpx:TimeCode", ~0);
    if (tmpstr.size () > 0)
        m_dpx.header.SetTimeCode (tmpstr.c_str ());
    else if (tmpint != ~0)
        m_dpx.header.timeCode = tmpint;
    m_dpx.header.userBits = m_spec.get_int_attribute ("dpx:UserBits", ~0);
    tmpstr = m_spec.get_string_attribute ("dpx:SourceDateTime", "");
    if (tmpstr.size () >= 19) {
        // libdpx's date/time format is pretty close to OIIO's (libdpx uses
        // %Y:%m:%d:%H:%M:%S%Z)
        // NOTE: the following code relies on the DateTime attribute being properly
        // formatted!
        // assume UTC for simplicity's sake, fix it if someone complains
        tmpstr[10] = ':';
        tmpstr.replace (19, -1, "Z");
        m_dpx.header.SetSourceTimeDate (tmpstr.c_str ());
    }
    
    // commit!
    if (!m_dpx.WriteHeader ()) {
        error ("Failed to write DPX header");
        return false;
    }

    // user data
    ImageIOParameter *user = m_spec.find_attribute ("dpx:UserData");
    if (user && user->datasize () > 0) {
        if (user->datasize () > 1024 * 1024) {
            error ("User data block size exceeds 1 MB");
            return false;
        }
        // FIXME: write the missing libdpx code
        /*m_dpx.SetUserData (user->datasize ());
        if (!m_dpx.WriteUserData ((void *)user->data ())) {
            error ("Failed to write user data");
            return false;
        }*/
    }

    // reserve space for the image data buffer
    m_buf.reserve (m_bytes * m_spec.height);

    return true;
}
Example #14
0
bool
RawInput::open (const std::string &name, ImageSpec &newspec, ImageSpec &config)
{
    int ret;

    // open the image
    if ( (ret = m_processor.open_file(name.c_str()) ) != LIBRAW_SUCCESS) {
        error ("Could not open file \"%s\", %s", name.c_str(), libraw_strerror(ret));
        return false;
    }

    if ( (ret = m_processor.unpack() ) != LIBRAW_SUCCESS) {
        error ("Could not unpack \"%s\", %s",name.c_str(), libraw_strerror(ret));
        return false;
    }

    // Forcing the Libraw to adjust sizes based on the capture device orientation
    m_processor.adjust_sizes_info_only();
 
    // Set file information
    m_spec = ImageSpec(m_processor.imgdata.sizes.iwidth,
                       m_processor.imgdata.sizes.iheight,
                       3, // LibRaw should only give us 3 channels
                       TypeDesc::UINT16);

    // Output 16 bit images
    m_processor.imgdata.params.output_bps = 16;

    // Set the gamma curve to Linear
    m_spec.attribute("oiio:ColorSpace","Linear");
    m_processor.imgdata.params.gamm[0] = 1.0;
    m_processor.imgdata.params.gamm[1] = 1.0;

    // Check to see if the user has explicitly set the output colorspace primaries
    ImageIOParameter *csp = config.find_attribute ("raw:ColorSpace", TypeDesc::STRING, false);
    if (csp) {
        static const char *colorspaces[] = { "raw",
                                             "sRGB",
                                             "Adobe",
                                             "Wide",
                                             "ProPhoto",
                                             "XYZ", NULL
                                             };

        std::string cs = *(const char**) csp->data();
        size_t c;
        for (c=0; c < sizeof(colorspaces) / sizeof(colorspaces[0]); c++)
            if (cs == colorspaces[c])
                break;
        if (cs == colorspaces[c])
            m_processor.imgdata.params.output_color = c;
        else {
            error("raw:ColorSpace set to unknown value");
            return false;
        }
        // Set the attribute in the output spec
        m_spec.attribute("raw:ColorSpace", cs);
    } else {
        // By default we use sRGB primaries for simplicity
        m_processor.imgdata.params.output_color = 1;
        m_spec.attribute("raw:ColorSpace", "sRGB");
    }

    // Exposure adjustment
    ImageIOParameter *ex = config.find_attribute ("raw:Exposure", TypeDesc::FLOAT, false);
    
    if (ex) {
        float exposure = *(float*)ex->data();
        if (exposure < 0.25f || exposure > 8.0f) {
            error("raw:Exposure invalid value. range 0.25f - 8.0f");
            return false;
        }
        m_processor.imgdata.params.exp_correc = 1; // enable exposure correction
        m_processor.imgdata.params.exp_shift = exposure; // set exposure correction
        // Set the attribute in the output spec
        m_spec.attribute ("raw:Exposure", exposure);
    }

    // Interpolation quality
    // note: LibRaw must be compiled with demosaic pack GPL2 to use
    // demosaic algorithms 5-9. It must be compiled with demosaic pack GPL3 for 
    // algorithm 10. If either of these packs are not includeded, it will silently use option 3 - AHD
    ImageIOParameter *dm = config.find_attribute ("raw:Demosaic", TypeDesc::STRING, false);
    if (dm) {
        static const char *demosaic_algs[] = { "linear",
                                               "VNG",
                                               "PPG",
                                               "AHD",
                                               "DCB",
                                               "Modified AHD",
                                               "AFD",
                                               "VCD",
                                               "Mixed",
                                               "LMMSE",
                                               "AMaZE",
                                               // Future demosaicing algorithms should go here
                                               NULL
                                               };

        std::string demosaic = *(const char**) dm->data();
        size_t d;
        for (d=0; d < sizeof(demosaic_algs) / sizeof(demosaic_algs[0]); d++)
            if (demosaic == demosaic_algs[d])
                break;
        if (demosaic == demosaic_algs[d])
            m_processor.imgdata.params.user_qual = d;
        else if (demosaic == "none") {
            // See if we can access the Bayer patterned data for this raw file
            libraw_decoder_info_t decoder_info;
            m_processor.get_decoder_info(&decoder_info);
            if (!(decoder_info.decoder_flags & LIBRAW_DECODER_FLATFIELD)) {
                error("Unable to extract unbayered data from file \"%s\"", name.c_str());
                return false;
            }

            // User has selected no demosaicing, so no processing needs to be done
            m_process = false;

            // The image width and height may be different now, so update with new values
            // Also we will only be reading back a single, bayered channel
            m_spec.width = m_processor.imgdata.sizes.raw_width;
            m_spec.height = m_processor.imgdata.sizes.raw_height;
            m_spec.nchannels = 1;
            m_spec.channelnames.clear();
            m_spec.channelnames.push_back("R");

            // Also, any previously set demosaicing options are void, so remove them
            m_spec.erase_attribute("oiio:Colorspace", TypeDesc::STRING);
            m_spec.erase_attribute("raw:Colorspace", TypeDesc::STRING);
            m_spec.erase_attribute("raw:Exposure", TypeDesc::STRING);

        }
        else {
            error("raw:Demosaic set to unknown value");
            return false;
        }
        // Set the attribute in the output spec
        m_spec.attribute("raw:Demosaic", demosaic);
    } else {
        m_processor.imgdata.params.user_qual = 3;
        m_spec.attribute("raw:Demosaic", "AHD");
    }

    // Metadata

    const libraw_image_sizes_t &sizes (m_processor.imgdata.sizes);
    m_spec.attribute ("PixelAspectRatio", (float)sizes.pixel_aspect);
    // FIXME: sizes. top_margin, left_margin, raw_pitch, flip, mask?

    const libraw_iparams_t &idata (m_processor.imgdata.idata);
    if (idata.make[0])
        m_spec.attribute ("Make", idata.make);
    if (idata.model[0])
        m_spec.attribute ("Model", idata.model);
    // FIXME: idata. dng_version, is_foveon, colors, filters, cdesc

    const libraw_colordata_t &color (m_processor.imgdata.color);
    m_spec.attribute("Exif:Flash", (int) color.flash_used);
    if (color.model2[0])
        m_spec.attribute ("Software", color.model2);

    // FIXME -- all sorts of things in this struct

    const libraw_imgother_t &other (m_processor.imgdata.other);
    m_spec.attribute ("Exif:ISOSpeedRatings", (int) other.iso_speed);
    m_spec.attribute ("ExposureTime", other.shutter);
    m_spec.attribute ("Exif:ShutterSpeedValue", -log2f(other.shutter));
    m_spec.attribute ("FNumber", other.aperture);
    m_spec.attribute ("Exif:ApertureValue", 2.0f * log2f(other.aperture));
    m_spec.attribute ("Exif:FocalLength", other.focal_len);
    struct tm * m_tm = localtime(&m_processor.imgdata.other.timestamp);
    char datetime[20];
    strftime (datetime, 20, "%Y-%m-%d %H:%M:%S", m_tm);
    m_spec.attribute ("DateTime", datetime);
    // FIXME: other.shot_order
    // FIXME: other.gpsdata
    if (other.desc[0])
        m_spec.attribute ("ImageDescription", other.desc);
    if (other.artist[0])
        m_spec.attribute ("Artist", other.artist);

    // FIXME -- thumbnail possibly in m_processor.imgdata.thumbnail

    read_tiff_metadata (name);

    // Copy the spec to return to the user
    newspec = m_spec;
    return true;
}
Example #15
0
void
TIFFInput::readspec (bool read_meta)
{
    uint32 width = 0, height = 0, depth = 0;
    unsigned short nchans = 1;
    TIFFGetField (m_tif, TIFFTAG_IMAGEWIDTH, &width);
    TIFFGetField (m_tif, TIFFTAG_IMAGELENGTH, &height);
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_IMAGEDEPTH, &depth);
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_SAMPLESPERPIXEL, &nchans);

    if (read_meta) {
        // clear the whole m_spec and start fresh
        m_spec = ImageSpec ((int)width, (int)height, (int)nchans);
    } else {
        // assume m_spec is valid, except for things that might differ
        // between MIP levels
        m_spec.width = (int)width;
        m_spec.height = (int)height;
        m_spec.depth = (int)depth;
        m_spec.full_x = 0;
        m_spec.full_y = 0;
        m_spec.full_z = 0;
        m_spec.full_width = (int)width;
        m_spec.full_height = (int)height;
        m_spec.full_depth = (int)depth;
        m_spec.nchannels = (int)nchans;
    }

    float x = 0, y = 0;
    TIFFGetField (m_tif, TIFFTAG_XPOSITION, &x);
    TIFFGetField (m_tif, TIFFTAG_YPOSITION, &y);
    m_spec.x = (int)x;
    m_spec.y = (int)y;
    m_spec.z = 0;
    // FIXME? - TIFF spec describes the positions as in resolutionunit.
    // What happens if this is not unitless pixels?  Are we interpreting
    // it all wrong?

    if (TIFFGetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLWIDTH, &width) == 1
          && width > 0)
        m_spec.full_width = width;
    if (TIFFGetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLLENGTH, &height) == 1
          && height > 0)
        m_spec.full_height = height;

    if (TIFFIsTiled (m_tif)) {
        TIFFGetField (m_tif, TIFFTAG_TILEWIDTH, &m_spec.tile_width);
        TIFFGetField (m_tif, TIFFTAG_TILELENGTH, &m_spec.tile_height);
        TIFFGetFieldDefaulted (m_tif, TIFFTAG_TILEDEPTH, &m_spec.tile_depth);
    } else {
        m_spec.tile_width = 0;
        m_spec.tile_height = 0;
        m_spec.tile_depth = 0;
    }

    m_bitspersample = 8;
    TIFFGetField (m_tif, TIFFTAG_BITSPERSAMPLE, &m_bitspersample);
    m_spec.attribute ("oiio:BitsPerSample", (int)m_bitspersample);

    unsigned short sampleformat = SAMPLEFORMAT_UINT;
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_SAMPLEFORMAT, &sampleformat);
    switch (m_bitspersample) {
    case 1:
    case 2:
    case 4:
    case 6:
        // Make 1, 2, 4, 6 bpp look like byte images
    case 8:
        if (sampleformat == SAMPLEFORMAT_UINT)
            m_spec.set_format (TypeDesc::UINT8);
        else if (sampleformat == SAMPLEFORMAT_INT)
            m_spec.set_format (TypeDesc::INT8);
        else m_spec.set_format (TypeDesc::UINT8);  // punt
        break;
    case 10:
    case 12:
    case 14:
        // Make 10, 12, 14 bpp look like 16 bit images
    case 16:
        if (sampleformat == SAMPLEFORMAT_UINT)
            m_spec.set_format (TypeDesc::UINT16);
        else if (sampleformat == SAMPLEFORMAT_INT)
            m_spec.set_format (TypeDesc::INT16);
        else if (sampleformat == SAMPLEFORMAT_IEEEFP)
            m_spec.set_format (TypeDesc::HALF); // not to spec, but why not?
        else
            m_spec.set_format (TypeDesc::UNKNOWN);
        break;
    case 32:
        if (sampleformat == SAMPLEFORMAT_IEEEFP)
            m_spec.set_format (TypeDesc::FLOAT);
        else if (sampleformat == SAMPLEFORMAT_UINT)
            m_spec.set_format (TypeDesc::UINT32);
        else if (sampleformat == SAMPLEFORMAT_INT)
            m_spec.set_format (TypeDesc::INT32);
        else
            m_spec.set_format (TypeDesc::UNKNOWN);
        break;
    case 64:
        if (sampleformat == SAMPLEFORMAT_IEEEFP)
            m_spec.set_format (TypeDesc::DOUBLE);
        else
            m_spec.set_format (TypeDesc::UNKNOWN);
        break;
    default:
        m_spec.set_format (TypeDesc::UNKNOWN);
        break;
    }

    // If we've been instructed to skip reading metadata, because it is
    // guaranteed to be identical to what we already have in m_spec,
    // skip everything following.
    if (! read_meta)
        return;

    // Use the table for all the obvious things that can be mindlessly
    // shoved into the image spec.
    for (int i = 0;  tiff_tag_table[i].name;  ++i)
        find_tag (tiff_tag_table[i].tifftag,
                  tiff_tag_table[i].tifftype, tiff_tag_table[i].name);

    // Now we need to get fields "by hand" for anything else that is less
    // straightforward...

    m_photometric = (m_spec.nchannels == 1 ? PHOTOMETRIC_MINISBLACK : PHOTOMETRIC_RGB);
    TIFFGetField (m_tif, TIFFTAG_PHOTOMETRIC, &m_photometric);
    m_spec.attribute ("tiff:PhotometricInterpretation", (int)m_photometric);
    if (m_photometric == PHOTOMETRIC_PALETTE) {
        // Read the color map
        unsigned short *r = NULL, *g = NULL, *b = NULL;
        TIFFGetField (m_tif, TIFFTAG_COLORMAP, &r, &g, &b);
        ASSERT (r != NULL && g != NULL && b != NULL);
        m_colormap.clear ();
        m_colormap.insert (m_colormap.end(), r, r + (1 << m_bitspersample));
        m_colormap.insert (m_colormap.end(), g, g + (1 << m_bitspersample));
        m_colormap.insert (m_colormap.end(), b, b + (1 << m_bitspersample));
        // Palette TIFF images are always 3 channels (to the client)
        m_spec.nchannels = 3;
        m_spec.default_channel_names ();
        // FIXME - what about palette + extra (alpha?) channels?  Is that
        // allowed?  And if so, ever encountered in the wild?
    }

    TIFFGetFieldDefaulted (m_tif, TIFFTAG_PLANARCONFIG, &m_planarconfig);
    m_separate = (m_planarconfig == PLANARCONFIG_SEPARATE &&
                  m_spec.nchannels > 1 &&
                  m_photometric != PHOTOMETRIC_PALETTE);
    m_spec.attribute ("tiff:PlanarConfiguration", (int)m_planarconfig);
    if (m_planarconfig == PLANARCONFIG_SEPARATE)
        m_spec.attribute ("planarconfig", "separate");
    else
        m_spec.attribute ("planarconfig", "contig");

    int compress = 0;
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_COMPRESSION, &compress);
    m_spec.attribute ("tiff:Compression", compress);
    switch (compress) {
    case COMPRESSION_NONE :
        m_spec.attribute ("compression", "none");
        break;
    case COMPRESSION_LZW :
        m_spec.attribute ("compression", "lzw");
        break;
    case COMPRESSION_CCITTRLE :
        m_spec.attribute ("compression", "ccittrle");
        break;
    case COMPRESSION_DEFLATE :
    case COMPRESSION_ADOBE_DEFLATE :
        m_spec.attribute ("compression", "zip");
        break;
    case COMPRESSION_PACKBITS :
        m_spec.attribute ("compression", "packbits");
        break;
    default:
        break;
    }

    int rowsperstrip = -1;
    if (! m_spec.tile_width) {
        TIFFGetField (m_tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
        if (rowsperstrip > 0)
            m_spec.attribute ("tiff:RowsPerStrip", rowsperstrip);
    }

    // The libtiff docs say that only uncompressed images, or those with
    // rowsperstrip==1, support random access to scanlines.
    m_no_random_access = (compress != COMPRESSION_NONE && rowsperstrip != 1);

    short resunit = -1;
    TIFFGetField (m_tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
    switch (resunit) {
    case RESUNIT_NONE : m_spec.attribute ("ResolutionUnit", "none"); break;
    case RESUNIT_INCH : m_spec.attribute ("ResolutionUnit", "in"); break;
    case RESUNIT_CENTIMETER : m_spec.attribute ("ResolutionUnit", "cm"); break;
    }

    get_matrix_attribute ("worldtocamera", TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA);
    get_matrix_attribute ("worldtoscreen", TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN);
    get_int_attribute ("tiff:subfiletype", TIFFTAG_SUBFILETYPE);
    // FIXME -- should subfiletype be "conventionized" and used for all
    // plugins uniformly? 

    // Do we care about fillorder?  No, the TIFF spec says, "We
    // recommend that FillOrder=2 (lsb-to-msb) be used only in
    // special-purpose applications".  So OIIO will assume msb-to-lsb
    // convention until somebody finds a TIFF file in the wild that
    // breaks this assumption.

    // Special names for shadow maps
    char *s = NULL;
    TIFFGetField (m_tif, TIFFTAG_PIXAR_TEXTUREFORMAT, &s);
    if (s)
        m_emulate_mipmap = true;
    if (s && ! strcmp (s, "Shadow")) {
        for (int c = 0;  c < m_spec.nchannels;  ++c)
            m_spec.channelnames[c] = "z";
    }

    unsigned short *sampleinfo = NULL;
    unsigned short extrasamples = 0;
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);
    // std::cerr << "Extra samples = " << extrasamples << "\n";
    bool alpha_is_unassociated = false;  // basic assumption
    if (extrasamples) {
        // If the TIFF ExtraSamples tag was specified, use that to figure
        // out the meaning of alpha.
        int colorchannels = 3;
        if (m_photometric == PHOTOMETRIC_MINISWHITE ||
              m_photometric == PHOTOMETRIC_MINISBLACK ||
              m_photometric == PHOTOMETRIC_PALETTE ||
              m_photometric == PHOTOMETRIC_MASK)
            colorchannels = 1;
        for (int i = 0, c = colorchannels;
             i < extrasamples && c < m_spec.nchannels;  ++i, ++c) {
            // std::cerr << "   extra " << i << " " << sampleinfo[i] << "\n";
            if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) {
                // This is the alpha channel, associated as usual
                m_spec.alpha_channel = c;
            } else if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) {
                // This is the alpha channel, but color is unassociated
                m_spec.alpha_channel = c;
                alpha_is_unassociated = true;
                m_spec.attribute ("oiio:UnassociatedAlpha", 1);
            } else {
                DASSERT (sampleinfo[i] == EXTRASAMPLE_UNSPECIFIED);
                // This extra channel is not alpha at all.  Undo any
                // assumptions we previously made about this channel.
                if (m_spec.alpha_channel == c) {
                    m_spec.channelnames[c] = Strutil::format("channel%d", c);
                    m_spec.alpha_channel = -1;
                }
            }
        }
        if (m_spec.alpha_channel >= 0)
            m_spec.channelnames[m_spec.alpha_channel] = "A";
    }
    // Will we need to do alpha conversions?
    m_convert_alpha = (m_spec.alpha_channel >= 0 && alpha_is_unassociated &&
                       ! m_keep_unassociated_alpha);

    // N.B. we currently ignore the following TIFF fields:
    // GrayResponseCurve GrayResponseUnit
    // MaxSampleValue MinSampleValue
    // NewSubfileType SubfileType(deprecated)
    // Colorimetry fields

    // Search for an EXIF IFD in the TIFF file, and if found, rummage 
    // around for Exif fields.
#if TIFFLIB_VERSION > 20050912    /* compat with old TIFF libs - skip Exif */
    int exifoffset = 0;
    if (TIFFGetField (m_tif, TIFFTAG_EXIFIFD, &exifoffset) &&
            TIFFReadEXIFDirectory (m_tif, exifoffset)) {
        for (int i = 0;  exif_tag_table[i].name;  ++i)
            find_tag (exif_tag_table[i].tifftag, exif_tag_table[i].tifftype,
                      exif_tag_table[i].name);
        // I'm not sure what state TIFFReadEXIFDirectory leaves us.
        // So to be safe, close and re-seek.
        TIFFClose (m_tif);
#ifdef _WIN32
        std::wstring wfilename = Filesystem::path_to_windows_native (m_filename);
        m_tif = TIFFOpenW (wfilename.c_str(), "rm");
#else
        m_tif = TIFFOpen (m_filename.c_str(), "rm");
#endif
        TIFFSetDirectory (m_tif, m_subimage);

        // A few tidbits to look for
        ImageIOParameter *p;
        if ((p = m_spec.find_attribute ("Exif:ColorSpace", TypeDesc::INT))) {
            // Exif spec says that anything other than 0xffff==uncalibrated
            // should be interpreted to be sRGB.
            if (*(const int *)p->data() != 0xffff)
                m_spec.attribute ("oiio::ColorSpace", "sRGB");
        }
    }
#endif

#if TIFFLIB_VERSION >= 20051230
    // Search for IPTC metadata in IIM form -- but older versions of
    // libtiff botch the size, so ignore it for very old libtiff.
    int iptcsize = 0;
    const void *iptcdata = NULL;
    if (TIFFGetField (m_tif, TIFFTAG_RICHTIFFIPTC, &iptcsize, &iptcdata)) {
        std::vector<uint32> iptc ((uint32 *)iptcdata, (uint32 *)iptcdata+iptcsize);
        if (TIFFIsByteSwapped (m_tif))
            TIFFSwabArrayOfLong ((uint32*)&iptc[0], iptcsize);
        decode_iptc_iim (&iptc[0], iptcsize*4, m_spec);
    }
#endif

    // Search for an XML packet containing XMP (IPTC, Exif, etc.)
    int xmlsize = 0;
    const void *xmldata = NULL;
    if (TIFFGetField (m_tif, TIFFTAG_XMLPACKET, &xmlsize, &xmldata)) {
        // std::cerr << "Found XML data, size " << xmlsize << "\n";
        if (xmldata && xmlsize) {
            std::string xml ((const char *)xmldata, xmlsize);
            decode_xmp (xml, m_spec);
        }
    }

#if 0
    // Experimental -- look for photoshop data
    int photoshopsize = 0;
    const void *photoshopdata = NULL;
    if (TIFFGetField (m_tif, TIFFTAG_PHOTOSHOP, &photoshopsize, &photoshopdata)) {
        std::cerr << "Found PHOTOSHOP data, size " << photoshopsize << "\n";
        if (photoshopdata && photoshopsize) {
//            std::string photoshop ((const char *)photoshopdata, photoshopsize);
//            std::cerr << "PHOTOSHOP:\n" << photoshop << "\n---\n";
        }
    }
#endif
}
Example #16
0
bool
OpenEXROutput::open (const std::string &name, const ImageSpec &userspec,
                     OpenMode mode)
{
    if (mode == AppendSubimage) {
        error ("%s does not support subimages", format_name());
        return false;
    }

    if (mode == AppendMIPLevel && (m_output_scanline || m_output_tiled)) {
        // Special case for appending to an open file -- we don't need
        // to close and reopen
        if (m_spec.tile_width && m_levelmode != Imf::ONE_LEVEL) {
            // OpenEXR does not support differing tile sizes on different
            // MIP-map levels.  Reject the open() if not using the original
            // tile sizes.
            if (userspec.tile_width != m_spec.tile_width ||
                userspec.tile_height != m_spec.tile_height) {
                error ("OpenEXR tiles must have the same size on all MIPmap levels");
                return false;
            }
            // Copy the new mip level size.  Keep everything else from the
            // original level.
            m_spec.width = userspec.width;
            m_spec.height = userspec.height;
            // N.B. do we need to copy anything else from userspec?
            ++m_miplevel;
            return true;
        }
    }

    m_spec = userspec;  // Stash the spec

    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
               userspec.width, userspec.height);
        return false;
    }
    if (m_spec.depth < 1)
        m_spec.depth = 1;
    if (m_spec.depth > 1) {
        error ("%s does not support volume images (depth > 1)", format_name());
        return false;
    }

    if (m_spec.full_width <= 0)
        m_spec.full_width = m_spec.width;
    if (m_spec.full_height <= 0)
        m_spec.full_height = m_spec.height;

    // Force use of one of the three data types that OpenEXR supports
    switch (m_spec.format.basetype) {
    case TypeDesc::UINT:
        m_spec.format = TypeDesc::UINT;
        break;
    case TypeDesc::FLOAT:
    case TypeDesc::DOUBLE:
        m_spec.format = TypeDesc::FLOAT;
        break;
    default:
        // Everything else defaults to half
        m_spec.format = TypeDesc::HALF;
    }

    Imath::Box2i dataWindow (Imath::V2i (m_spec.x, m_spec.y),
                             Imath::V2i (m_spec.width + m_spec.x - 1,
                                         m_spec.height + m_spec.y - 1));
    Imath::Box2i displayWindow (Imath::V2i (m_spec.full_x, m_spec.full_y),
                                Imath::V2i (m_spec.full_width+m_spec.full_x-1,
                                            m_spec.full_height+m_spec.full_y-1));
    m_header = new Imf::Header (displayWindow, dataWindow);

    // Insert channels into the header.  Also give the channels names if
    // the user botched it.
    static const char *default_chan_names[] = { "R", "G", "B", "A" };
    m_spec.channelnames.resize (m_spec.nchannels);
    for (int c = 0;  c < m_spec.nchannels;  ++c) {
        if (m_spec.channelnames[c].empty())
            m_spec.channelnames[c] = (c<4) ? default_chan_names[c]
                                           : Strutil::format ("unknown %d", c);
        TypeDesc format = m_spec.channelformats.size() ?
                                  m_spec.channelformats[c] : m_spec.format;
        Imf::PixelType ptype;
        switch (format.basetype) {
        case TypeDesc::UINT:
            ptype = Imf::UINT;
            format = TypeDesc::UINT;
            break;
        case TypeDesc::FLOAT:
        case TypeDesc::DOUBLE:
            ptype = Imf::FLOAT;
            format = TypeDesc::FLOAT;
            break;
        default:
            // Everything else defaults to half
            ptype = Imf::HALF;
            format = TypeDesc::HALF;
        }
        
#ifdef OPENEXR_VERSION_IS_1_6_OR_LATER
        // Hint to lossy compression methods that indicates whether
        // human perception of the quantity represented by this channel
        // is closer to linear or closer to logarithmic.  Compression
        // methods may optimize image quality by adjusting pixel data
        // quantization acording to this hint.
        
        bool pLinear = iequals (m_spec.get_string_attribute ("oiio:ColorSpace", "Linear"), "Linear");
#endif
        m_pixeltype.push_back (ptype);
        if (m_spec.channelformats.size())
            m_spec.channelformats[c] = format;
        m_header->channels().insert (m_spec.channelnames[c].c_str(),
                                     Imf::Channel(ptype, 1, 1
#ifdef OPENEXR_VERSION_IS_1_6_OR_LATER
                                     , pLinear
#endif
                                     ));
    }
    ASSERT (m_pixeltype.size() == (size_t)m_spec.nchannels);

    // Default to ZIP compression if no request came with the user spec.
    if (! m_spec.find_attribute("compression"))
        m_spec.attribute ("compression", "zip");

    // Default to increasingY line order, same as EXR.
    if (! m_spec.find_attribute("openexr:lineOrder"))
        m_spec.attribute ("openexr:lineOrder", "increasingY");

    // Automatically set date field if the client didn't supply it.
    if (! m_spec.find_attribute("DateTime")) {
        time_t now;
        time (&now);
        struct tm mytm;
        Sysutil::get_local_time (&now, &mytm);
        std::string date = Strutil::format ("%4d:%02d:%02d %2d:%02d:%02d",
                               mytm.tm_year+1900, mytm.tm_mon+1, mytm.tm_mday,
                               mytm.tm_hour, mytm.tm_min, mytm.tm_sec);
        m_spec.attribute ("DateTime", date);
    }

    m_nsubimages = 1;
    m_subimage = 0;
    m_nmiplevels = 1;
    m_miplevel = 0;

    // Figure out if we are a mipmap or an environment map
    ImageIOParameter *param = m_spec.find_attribute ("textureformat");
    const char *textureformat = param ? *(char **)param->data() : NULL;
    m_levelmode = Imf::ONE_LEVEL;  // Default to no MIP-mapping
    m_roundingmode = m_spec.get_int_attribute ("openexr:roundingmode",
                                               Imf::ROUND_DOWN);

    if (textureformat) {
        if (iequals (textureformat, "Plain Texture")) {
            m_levelmode = m_spec.get_int_attribute ("openexr:levelmode",
                                                    Imf::MIPMAP_LEVELS);
        } else if (iequals (textureformat, "CubeFace Environment")) {
            m_levelmode = m_spec.get_int_attribute ("openexr:levelmode",
                                                    Imf::MIPMAP_LEVELS);
            m_header->insert ("envmap", Imf::EnvmapAttribute(Imf::ENVMAP_CUBE));
        } else if (iequals (textureformat, "LatLong Environment")) {
            m_levelmode = m_spec.get_int_attribute ("openexr:levelmode",
                                                    Imf::MIPMAP_LEVELS);
            m_header->insert ("envmap", Imf::EnvmapAttribute(Imf::ENVMAP_LATLONG));
        } else if (iequals (textureformat, "Shadow")) {
            m_levelmode = Imf::ONE_LEVEL;  // Force one level for shadow maps
        }

        if (m_levelmode == Imf::MIPMAP_LEVELS) {
            // Compute how many mip levels there will be
            int w = m_spec.width;
            int h = m_spec.height;
            while (w > 1 && h > 1) {
                if (m_roundingmode == Imf::ROUND_DOWN) {
                    w = w / 2;
                    h = h / 2;
                } else {
                    w = (w + 1) / 2;
                    h = (h + 1) / 2;
                }
                w = std::max (1, w);
                h = std::max (1, h);
                ++m_nmiplevels;
            }
        }
    }

    // Deal with all other params
    for (size_t p = 0;  p < m_spec.extra_attribs.size();  ++p)
        put_parameter (m_spec.extra_attribs[p].name().string(),
                       m_spec.extra_attribs[p].type(),
                       m_spec.extra_attribs[p].data());

    try {
        if (m_spec.tile_width) {
            m_header->setTileDescription (
                Imf::TileDescription (m_spec.tile_width, m_spec.tile_height,
                                      Imf::LevelMode(m_levelmode),
                                      Imf::LevelRoundingMode(m_roundingmode)));
            m_output_tiled = new Imf::TiledOutputFile (name.c_str(), *m_header);
        } else {
            m_output_scanline = new Imf::OutputFile (name.c_str(), *m_header);
        }
    }
    catch (const std::exception &e) {
        error ("OpenEXR exception: %s", e.what());
        m_output_scanline = NULL;
        return false;
    }
    if (! m_output_scanline && ! m_output_tiled) {
        error ("Unknown error opening EXR file");
        return false;
    }

    return true;
}
Example #17
0
bool
TIFFOutput::open (const std::string &name, const ImageSpec &userspec,
                  OpenMode mode)
{
    if (mode == AppendMIPLevel) {
        error ("%s does not support MIP levels", format_name());
        return false;
    }

    close ();  // Close any already-opened file
    m_spec = userspec;  // Stash the spec

    // Check for things this format doesn't support
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
               m_spec.width, m_spec.height);
        return false;
    }
    if (m_spec.tile_width) {
       if (m_spec.tile_width  % 16 != 0 ||
           m_spec.tile_height % 16 != 0 ||
           m_spec.tile_height == 0) {
          error("Tile size must be a multiple of 16, you asked for %d x %d", m_spec.tile_width, m_spec.tile_height);
          return false;
       }
    }
    if (m_spec.depth < 1)
        m_spec.depth = 1;

    // Open the file
#ifdef _WIN32
    std::wstring wname = Strutil::utf8_to_utf16 (name);
    m_tif = TIFFOpenW (wname.c_str(), mode == AppendSubimage ? "a" : "w");
#else
    m_tif = TIFFOpen (name.c_str(), mode == AppendSubimage ? "a" : "w");
#endif
    if (! m_tif) {
        error ("Can't open \"%s\" for output.", name.c_str());
        return false;
    }

    // N.B. Clamp position at 0... TIFF is internally incapable of having
    // negative origin.
    TIFFSetField (m_tif, TIFFTAG_XPOSITION, (float)std::max (0, m_spec.x));
    TIFFSetField (m_tif, TIFFTAG_YPOSITION, (float)std::max (0, m_spec.y));

    TIFFSetField (m_tif, TIFFTAG_IMAGEWIDTH, m_spec.width);
    TIFFSetField (m_tif, TIFFTAG_IMAGELENGTH, m_spec.height);
    if ((m_spec.full_width != 0 || m_spec.full_height != 0) &&
        (m_spec.full_width != m_spec.width || m_spec.full_height != m_spec.height)) {
        TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLWIDTH, m_spec.full_width);
        TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLLENGTH, m_spec.full_height);
    }
    if (m_spec.tile_width) {
        TIFFSetField (m_tif, TIFFTAG_TILEWIDTH, m_spec.tile_width);
        TIFFSetField (m_tif, TIFFTAG_TILELENGTH, m_spec.tile_height);
    } else {
        // Scanline images must set rowsperstrip
        TIFFSetField (m_tif, TIFFTAG_ROWSPERSTRIP, 32);
    }
    TIFFSetField (m_tif, TIFFTAG_SAMPLESPERPIXEL, m_spec.nchannels);
    int orientation = m_spec.get_int_attribute("Orientation", 1);
    TIFFSetField (m_tif, TIFFTAG_ORIENTATION, orientation);
    
    m_bitspersample = m_spec.get_int_attribute ("oiio:BitsPerSample");
    int sampformat;
    switch (m_spec.format.basetype) {
    case TypeDesc::INT8:
        m_bitspersample = 8;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT8:
        if (m_bitspersample != 2 && m_bitspersample != 4)
            m_bitspersample = 8;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::INT16:
        m_bitspersample = 16;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT16:
        if (m_bitspersample != 10 && m_bitspersample != 12)
            m_bitspersample = 16;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::INT32:
        m_bitspersample = 32;
        sampformat = SAMPLEFORMAT_INT;
        break;
    case TypeDesc::UINT32:
        m_bitspersample = 32;
        sampformat = SAMPLEFORMAT_UINT;
        break;
    case TypeDesc::HALF:
#if 0
        // Adobe extension, see http://chriscox.org/TIFFTN3d1.pdf
        // Unfortunately, Nuke 9.0, and probably many other apps we care
        // about, cannot read 16 bit float TIFFs correctly. Revisit this
        // again in future releases. (comment added Feb 2015)
        m_bitspersample = 16;
        sampformat = SAMPLEFORMAT_IEEEFP;
        break;
#else
        // Silently change requests for unsupported 'half' to 'float'
        m_spec.set_format (TypeDesc::FLOAT);
#endif
    case TypeDesc::FLOAT:
        m_bitspersample = 32;
        sampformat = SAMPLEFORMAT_IEEEFP;
        break;
    case TypeDesc::DOUBLE:
        m_bitspersample = 64;
        sampformat = SAMPLEFORMAT_IEEEFP;
        break;
    default:
        // Everything else, including UNKNOWN -- default to 8 bit
        m_bitspersample = 8;
        sampformat = SAMPLEFORMAT_UINT;
        m_spec.set_format (TypeDesc::UINT8);
        break;
    }
    TIFFSetField (m_tif, TIFFTAG_BITSPERSAMPLE, m_bitspersample);
    TIFFSetField (m_tif, TIFFTAG_SAMPLEFORMAT, sampformat);

    m_photometric = (m_spec.nchannels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK);

    string_view comp = m_spec.get_string_attribute("Compression", "zip");
    if (Strutil::iequals (comp, "jpeg") &&
        (m_spec.format != TypeDesc::UINT8 || m_spec.nchannels != 3)) {
        comp = "zip";   // can't use JPEG for anything but 3xUINT8
    }
    m_compression = tiff_compression_code (comp);
    TIFFSetField (m_tif, TIFFTAG_COMPRESSION, m_compression);

    // Use predictor when using compression
    if (m_compression == COMPRESSION_LZW || m_compression == COMPRESSION_ADOBE_DEFLATE) {
        if (m_spec.format == TypeDesc::FLOAT || m_spec.format == TypeDesc::DOUBLE || m_spec.format == TypeDesc::HALF) {
            TIFFSetField (m_tif, TIFFTAG_PREDICTOR, PREDICTOR_FLOATINGPOINT);
            // N.B. Very old versions of libtiff did not support this
            // predictor.  It's possible that certain apps can't read
            // floating point TIFFs with this set.  But since it's been
            // documented since 2005, let's take our chances.  Comment
            // out the above line if this is problematic.
        }
        else if (m_bitspersample == 8 || m_bitspersample == 16) {
            // predictors not supported for unusual bit depths (e.g. 10)
            TIFFSetField (m_tif, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);
        }
    } else if (m_compression == COMPRESSION_JPEG) {
        TIFFSetField (m_tif, TIFFTAG_JPEGQUALITY,
                      m_spec.get_int_attribute("CompressionQuality", 95));
        TIFFSetField (m_tif, TIFFTAG_ROWSPERSTRIP, 64);
        m_spec.attribute ("tiff:RowsPerStrip", 64);
        if (m_photometric == PHOTOMETRIC_RGB) {
            // Compression works so much better when we ask the library to
            // auto-convert RGB to YCbCr.
            TIFFSetField (m_tif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
            m_photometric = PHOTOMETRIC_YCBCR;
        }
    }
    m_outputchans = m_spec.nchannels;
    if (m_photometric == PHOTOMETRIC_RGB) {
        // There are a few ways in which we allow allow the user to specify
        // translation to different photometric types.
        string_view photo = m_spec.get_string_attribute("tiff:ColorSpace");
        if (Strutil::iequals (photo, "CMYK")) {
            // CMYK: force to 4 channel output, either uint8 or uint16
            m_photometric = PHOTOMETRIC_SEPARATED;
            m_outputchans = 4;
            TIFFSetField (m_tif, TIFFTAG_SAMPLESPERPIXEL, m_outputchans);
            if (m_spec.format != TypeDesc::UINT8 || m_spec.format != TypeDesc::UINT16) {
                m_spec.format = TypeDesc::UINT8;
                m_bitspersample = 8;
                TIFFSetField (m_tif, TIFFTAG_BITSPERSAMPLE, m_bitspersample);
                TIFFSetField (m_tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
            }
        }
    }

    TIFFSetField (m_tif, TIFFTAG_PHOTOMETRIC, m_photometric);

    // ExtraSamples tag
    if (m_spec.nchannels > 3 && m_photometric != PHOTOMETRIC_SEPARATED) {
        bool unass = m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0);
        short e = m_spec.nchannels-3;
        std::vector<unsigned short> extra (e);
        for (int c = 0;  c < e;  ++c) {
            if (m_spec.alpha_channel == (c+3))
                extra[c] = unass ? EXTRASAMPLE_UNASSALPHA : EXTRASAMPLE_ASSOCALPHA;
            else
                extra[c] = EXTRASAMPLE_UNSPECIFIED;
        }
        TIFFSetField (m_tif, TIFFTAG_EXTRASAMPLES, e, &extra[0]);
    }

    ImageIOParameter *param;
    const char *str = NULL;

    // Did the user request separate planar configuration?
    m_planarconfig = PLANARCONFIG_CONTIG;
    if ((param = m_spec.find_attribute("planarconfig", TypeDesc::STRING)) ||
        (param = m_spec.find_attribute("tiff:planarconfig", TypeDesc::STRING))) {
        str = *(char **)param->data();
        if (str && Strutil::iequals (str, "separate"))
            m_planarconfig = PLANARCONFIG_SEPARATE;
    }
    // Can't deal with the headache of separate image planes when using
    // bit packing, or CMYK. Just punt by forcing contig in those cases.
    if (m_bitspersample != spec().format.size()*8 ||
            m_photometric == PHOTOMETRIC_SEPARATED)
        m_planarconfig = PLANARCONFIG_CONTIG;
    if (m_planarconfig == PLANARCONFIG_SEPARATE) {
        if (! m_spec.tile_width) {
            // I can only seem to make separate planarconfig work when
            // rowsperstrip is 1.
            TIFFSetField (m_tif, TIFFTAG_ROWSPERSTRIP, 1);
        }
    }
    TIFFSetField (m_tif, TIFFTAG_PLANARCONFIG, m_planarconfig);

    // Automatically set date field if the client didn't supply it.
    if (! m_spec.find_attribute("DateTime")) {
        time_t now;
        time (&now);
        struct tm mytm;
        Sysutil::get_local_time (&now, &mytm);
        std::string date = Strutil::format ("%4d:%02d:%02d %2d:%02d:%02d",
                               mytm.tm_year+1900, mytm.tm_mon+1, mytm.tm_mday,
                               mytm.tm_hour, mytm.tm_min, mytm.tm_sec);
        m_spec.attribute ("DateTime", date);
    }

    // Write ICC profile, if we have anything
    const ImageIOParameter* icc_profile_parameter = m_spec.find_attribute(ICC_PROFILE_ATTR);
    if (icc_profile_parameter != NULL) {
        unsigned char *icc_profile = (unsigned char*)icc_profile_parameter->data();
        uint32 length = icc_profile_parameter->type().size();
        if (icc_profile && length)
            TIFFSetField (m_tif, TIFFTAG_ICCPROFILE, length, icc_profile);
    }

    if (Strutil::iequals (m_spec.get_string_attribute ("oiio:ColorSpace"), "sRGB"))
        m_spec.attribute ("Exif:ColorSpace", 1);

    // Deal with missing XResolution or YResolution, or a PixelAspectRatio
    // that contradicts them.
    float X_density = m_spec.get_float_attribute ("XResolution", 1.0f);
    float Y_density = m_spec.get_float_attribute ("YResolution", 1.0f);
    float aspect = m_spec.get_float_attribute ("PixelAspectRatio", 1.0f);
    if (X_density < 1.0f || Y_density < 1.0f || aspect*X_density != Y_density) {
        if (X_density < 1.0f || Y_density < 1.0f) {
            X_density = Y_density = 1.0f;
            m_spec.attribute ("ResolutionUnit", "none");
        }
        m_spec.attribute ("XResolution", X_density);
        m_spec.attribute ("YResolution", X_density * aspect);
    }

    // Deal with all other params
    for (size_t p = 0;  p < m_spec.extra_attribs.size();  ++p)
        put_parameter (m_spec.extra_attribs[p].name().string(),
                       m_spec.extra_attribs[p].type(),
                       m_spec.extra_attribs[p].data());

    std::vector<char> iptc;
    encode_iptc_iim (m_spec, iptc);
    if (iptc.size()) {
        iptc.resize ((iptc.size()+3) & (0xffff-3));  // round up
        TIFFSetField (m_tif, TIFFTAG_RICHTIFFIPTC, iptc.size()/4, &iptc[0]);
    }

    std::string xmp = encode_xmp (m_spec, true);
    if (! xmp.empty())
        TIFFSetField (m_tif, TIFFTAG_XMLPACKET, xmp.size(), xmp.c_str());
    
    TIFFCheckpointDirectory (m_tif);  // Ensure the header is written early
    m_checkpointTimer.start(); // Initialize the to the fileopen time
    m_checkpointItems = 0; // Number of tiles or scanlines we've written

    m_dither = (m_spec.format == TypeDesc::UINT8) ?
                    m_spec.get_int_attribute ("oiio:dither", 0) : 0;

    return true;
}
Example #18
0
OIIO_PLUGIN_EXPORTS_END


bool
HdrOutput::open (const std::string &name, const ImageSpec &newspec,
                 OpenMode mode)
{
    if (mode != Create) {
        error ("%s does not support subimages or MIP levels", format_name());
        return false;
    }

    // Save spec for later use
    m_spec = newspec;
    // HDR always behaves like floating point
    m_spec.set_format (TypeDesc::FLOAT);

    // Check for things HDR can't support
    if (m_spec.nchannels != 3) {
        error ("HDR can only support 3-channel images");
        return false;
    }
    if (m_spec.width < 1 || m_spec.height < 1) {
        error ("Image resolution must be at least 1x1, you asked for %d x %d",
               m_spec.width, m_spec.height);
        return false;
    }
    if (m_spec.depth < 1)
        m_spec.depth = 1;
    if (m_spec.depth > 1) {
        error ("%s does not support volume images (depth > 1)", format_name());
        return false;
    }

    m_spec.set_format (TypeDesc::FLOAT);   // Native rgbe is float32 only

    m_fd = Filesystem::fopen (name, "wb");
    if (m_fd == NULL) {
        error ("Unable to open file");
        return false;
    }

    rgbe_header_info h;
    h.valid = 0;

    // Most readers seem to think that rgbe files are valid only if they
    // identify themselves as from "RADIANCE".
    h.valid |= RGBE_VALID_PROGRAMTYPE;
    Strutil::safe_strcpy (h.programtype, "RADIANCE", sizeof(h.programtype));

    ImageIOParameter *p;
    p = m_spec.find_attribute ("Orientation", TypeDesc::INT);
    if (p) {
        h.valid |= RGBE_VALID_ORIENTATION;
        h.orientation = * (int *)p->data();
    }

    // FIXME -- should we do anything about gamma, exposure, software,
    // pixaspect, primaries?  (N.B. rgbe.c doesn't even handle most of them)

    int r = RGBE_WriteHeader (m_fd, m_spec.width, m_spec.height, &h, rgbe_error);
    if (r != RGBE_RETURN_SUCCESS)
        error ("%s", rgbe_error);

    return true;
}
Example #19
0
void
TIFFInput::readspec ()
{
    uint32 width = 0, height = 0, depth = 0;
    unsigned short nchans = 1;
    TIFFGetField (m_tif, TIFFTAG_IMAGEWIDTH, &width);
    TIFFGetField (m_tif, TIFFTAG_IMAGELENGTH, &height);
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_IMAGEDEPTH, &depth);
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_SAMPLESPERPIXEL, &nchans);

    m_spec = ImageSpec ((int)width, (int)height, (int)nchans);

    float x = 0, y = 0;
    TIFFGetField (m_tif, TIFFTAG_XPOSITION, &x);
    TIFFGetField (m_tif, TIFFTAG_YPOSITION, &y);
    m_spec.x = (int)x;
    m_spec.y = (int)y;
    m_spec.z = 0;
    // FIXME? - TIFF spec describes the positions as in resolutionunit.
    // What happens if this is not unitless pixels?  Are we interpreting
    // it all wrong?

    if (TIFFGetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLWIDTH, &width) == 1
          && width > 0)
        m_spec.full_width = width;
    if (TIFFGetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLLENGTH, &height) == 1
          && height > 0)
        m_spec.full_height = height;

    if (TIFFIsTiled (m_tif)) {
        TIFFGetField (m_tif, TIFFTAG_TILEWIDTH, &m_spec.tile_width);
        TIFFGetField (m_tif, TIFFTAG_TILELENGTH, &m_spec.tile_height);
        TIFFGetFieldDefaulted (m_tif, TIFFTAG_TILEDEPTH, &m_spec.tile_depth);
    } else {
        m_spec.tile_width = 0;
        m_spec.tile_height = 0;
        m_spec.tile_depth = 0;
    }

    m_bitspersample = 8;
    TIFFGetField (m_tif, TIFFTAG_BITSPERSAMPLE, &m_bitspersample);
    m_spec.attribute ("oiio:BitsPerSample", (int)m_bitspersample);

    unsigned short sampleformat = SAMPLEFORMAT_UINT;
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_SAMPLEFORMAT, &sampleformat);
    switch (m_bitspersample) {
    case 1:
    case 2:
    case 4:
        // Make 1, 2, 4 bpp look like byte images
    case 8:
        if (sampleformat == SAMPLEFORMAT_UINT)
            m_spec.set_format (TypeDesc::UINT8);
        else if (sampleformat == SAMPLEFORMAT_INT)
            m_spec.set_format (TypeDesc::INT8);
        else m_spec.set_format (TypeDesc::UINT8);  // punt
        break;
    case 16:
        if (sampleformat == SAMPLEFORMAT_UINT)
            m_spec.set_format (TypeDesc::UINT16);
        else if (sampleformat == SAMPLEFORMAT_INT)
            m_spec.set_format (TypeDesc::INT16);
        break;
    case 32:
        if (sampleformat == SAMPLEFORMAT_IEEEFP)
            m_spec.set_format (TypeDesc::FLOAT);
        break;
    case 64:
        if (sampleformat == SAMPLEFORMAT_IEEEFP)
            m_spec.set_format (TypeDesc::DOUBLE);
        break;
    default:
        m_spec.set_format (TypeDesc::UNKNOWN);
        break;
    }

    // Use the table for all the obvious things that can be mindlessly
    // shoved into the image spec.
    for (int i = 0;  tiff_tag_table[i].name;  ++i)
        find_tag (tiff_tag_table[i].tifftag,
                  tiff_tag_table[i].tifftype, tiff_tag_table[i].name);

    // Now we need to get fields "by hand" for anything else that is less
    // straightforward...

    m_photometric = (m_spec.nchannels == 1 ? PHOTOMETRIC_MINISBLACK : PHOTOMETRIC_RGB);
    TIFFGetField (m_tif, TIFFTAG_PHOTOMETRIC, &m_photometric);
    m_spec.attribute ("tiff:PhotometricInterpretation", (int)m_photometric);
    if (m_photometric == PHOTOMETRIC_PALETTE) {
        // Read the color map
        unsigned short *r = NULL, *g = NULL, *b = NULL;
        TIFFGetField (m_tif, TIFFTAG_COLORMAP, &r, &g, &b);
        ASSERT (r != NULL && g != NULL && b != NULL);
        m_colormap.clear ();
        m_colormap.insert (m_colormap.end(), r, r + (1 << m_bitspersample));
        m_colormap.insert (m_colormap.end(), g, g + (1 << m_bitspersample));
        m_colormap.insert (m_colormap.end(), b, b + (1 << m_bitspersample));
        // Palette TIFF images are always 3 channels (to the client)
        m_spec.nchannels = 3;
        m_spec.default_channel_names ();
    }

    TIFFGetFieldDefaulted (m_tif, TIFFTAG_PLANARCONFIG, &m_planarconfig);
    m_spec.attribute ("tiff:PlanarConfiguration", (int)m_planarconfig);
    if (m_planarconfig == PLANARCONFIG_SEPARATE)
        m_spec.attribute ("planarconfig", "separate");
    else
        m_spec.attribute ("planarconfig", "contig");

    int compress = 0;
    TIFFGetFieldDefaulted (m_tif, TIFFTAG_COMPRESSION, &compress);
    m_spec.attribute ("tiff:Compression", compress);
    switch (compress) {
    case COMPRESSION_NONE :
        m_spec.attribute ("compression", "none");
        break;
    case COMPRESSION_LZW :
        m_spec.attribute ("compression", "lzw");
        break;
    case COMPRESSION_CCITTRLE :
        m_spec.attribute ("compression", "ccittrle");
        break;
    case COMPRESSION_DEFLATE :
    case COMPRESSION_ADOBE_DEFLATE :
        m_spec.attribute ("compression", "zip");
        break;
    case COMPRESSION_PACKBITS :
        m_spec.attribute ("compression", "packbits");
        break;
    default:
        break;
    }

    int rowsperstrip = -1;
    if (! m_spec.tile_width) {
        TIFFGetField (m_tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
        if (rowsperstrip > 0)
            m_spec.attribute ("tiff:RowsPerStrip", rowsperstrip);
    }

    // The libtiff docs say that only uncompressed images, or those with
    // rowsperstrip==1, support random access to scanlines.
    m_no_random_access = (compress != COMPRESSION_NONE && rowsperstrip != 1);

    short resunit = -1;
    TIFFGetField (m_tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
    switch (resunit) {
    case RESUNIT_NONE : m_spec.attribute ("ResolutionUnit", "none"); break;
    case RESUNIT_INCH : m_spec.attribute ("ResolutionUnit", "in"); break;
    case RESUNIT_CENTIMETER : m_spec.attribute ("ResolutionUnit", "cm"); break;
    }

    get_matrix_attribute ("worldtocamera", TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA);
    get_matrix_attribute ("worldtoscreen", TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN);
    get_int_attribute ("tiff:subfiletype", TIFFTAG_SUBFILETYPE);
    // FIXME -- should subfiletype be "conventionized" and used for all
    // plugins uniformly? 

    // FIXME: do we care about fillorder for 1-bit and 4-bit images?

    // Special names for shadow maps
    char *s = NULL;
    TIFFGetField (m_tif, TIFFTAG_PIXAR_TEXTUREFORMAT, &s);
    if (s)
        m_emulate_mipmap = true;
    if (s && ! strcmp (s, "Shadow")) {
        for (int c = 0;  c < m_spec.nchannels;  ++c)
            m_spec.channelnames[c] = "z";
    }

    // N.B. we currently ignore the following TIFF fields:
    // ExtraSamples
    // GrayResponseCurve GrayResponseUnit
    // MaxSampleValue MinSampleValue
    // NewSubfileType SubfileType(deprecated)
    // Colorimetry fields

    // Search for an EXIF IFD in the TIFF file, and if found, rummage 
    // around for Exif fields.
#if TIFFLIB_VERSION > 20050912    /* compat with old TIFF libs - skip Exif */
    int exifoffset = 0;
    if (TIFFGetField (m_tif, TIFFTAG_EXIFIFD, &exifoffset) &&
            TIFFReadEXIFDirectory (m_tif, exifoffset)) {
        for (int i = 0;  exif_tag_table[i].name;  ++i)
            find_tag (exif_tag_table[i].tifftag, exif_tag_table[i].tifftype,
                      exif_tag_table[i].name);
        // I'm not sure what state TIFFReadEXIFDirectory leaves us.
        // So to be safe, close and re-seek.
        TIFFClose (m_tif);
        m_tif = TIFFOpen (m_filename.c_str(), "rm");
        TIFFSetDirectory (m_tif, m_subimage);

        // A few tidbits to look for
        ImageIOParameter *p;
        if ((p = m_spec.find_attribute ("Exif:ColorSpace", TypeDesc::INT))) {
            // Exif spec says that anything other than 0xffff==uncalibrated
            // should be interpreted to be sRGB.
            if (*(const int *)p->data() != 0xffff)
                m_spec.attribute ("oiio::ColorSpace", "sRGB");
        }
    }
#endif

#if TIFFLIB_VERSION >= 20051230
    // Search for IPTC metadata in IIM form -- but older versions of
    // libtiff botch the size, so ignore it for very old libtiff.
    int iptcsize = 0;
    const void *iptcdata = NULL;
    if (TIFFGetField (m_tif, TIFFTAG_RICHTIFFIPTC, &iptcsize, &iptcdata)) {
        std::vector<uint32> iptc ((uint32 *)iptcdata, (uint32 *)iptcdata+iptcsize);
        if (TIFFIsByteSwapped (m_tif))
            TIFFSwabArrayOfLong ((uint32*)&iptc[0], iptcsize);
        decode_iptc_iim (&iptc[0], iptcsize*4, m_spec);
    }
#endif

    // Search for an XML packet containing XMP (IPTC, Exif, etc.)
    int xmlsize = 0;
    const void *xmldata = NULL;
    if (TIFFGetField (m_tif, TIFFTAG_XMLPACKET, &xmlsize, &xmldata)) {
        // std::cerr << "Found XML data, size " << xmlsize << "\n";
        if (xmldata && xmlsize) {
            std::string xml ((const char *)xmldata, xmlsize);
            decode_xmp (xml, m_spec);
        }
    }

#if 0
    // Experimental -- look for photoshop data
    int photoshopsize = 0;
    const void *photoshopdata = NULL;
    if (TIFFGetField (m_tif, TIFFTAG_PHOTOSHOP, &photoshopsize, &photoshopdata)) {
        std::cerr << "Found PHOTOSHOP data, size " << photoshopsize << "\n";
        if (photoshopdata && photoshopsize) {
//            std::string photoshop ((const char *)photoshopdata, photoshopsize);
//            std::cerr << "PHOTOSHOP:\n" << photoshop << "\n---\n";
        }
    }
#endif
}