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); } }
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); }
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); }
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); }
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; }
static void parse_elements (string_view name, TypeDesc type, const char *type_code, string_view value, ImageIOParameter ¶m) { 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 } }
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; }
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; }
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); }
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; }
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; }
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; }
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; }
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 }
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; }
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; }
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; }
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 }