int tiff_print_page(gx_device_printer *dev, TIFF *tif, int min_feature_size) { int code = 0; byte *data; int size = gdev_mem_bytes_per_scan_line((gx_device *)dev); int max_size = max(size, TIFFScanlineSize(tif)); int row; int bpc = dev->color_info.depth / dev->color_info.num_components; void *min_feature_data = NULL; int line_lag = 0; int filtered_count; data = gs_alloc_bytes(dev->memory, max_size, "tiff_print_page(data)"); if (data == NULL) return_error(gs_error_VMerror); if (bpc != 1) min_feature_size = 1; if (min_feature_size > 1) { code = min_feature_size_init(dev->memory, min_feature_size, dev->width, dev->height, &min_feature_data); if (code < 0) goto cleanup; } code = TIFFCheckpointDirectory(tif); memset(data, 0, max_size); for (row = 0; row < dev->height && code >= 0; row++) { code = gdev_prn_copy_scan_lines(dev, row, data, size); if (code < 0) break; if (min_feature_size > 1) { filtered_count = min_feature_size_process(data, min_feature_data); if (filtered_count == 0) line_lag++; } if (row - line_lag >= 0) { #if defined(ARCH_IS_BIG_ENDIAN) && (!ARCH_IS_BIG_ENDIAN) if (bpc == 16) TIFFSwabArrayOfShort((uint16 *)data, dev->width * dev->color_info.num_components); #endif code = TIFFWriteScanline(tif, data, row - line_lag, 0); } } for (row -= line_lag ; row < dev->height && code >= 0; row++) { filtered_count = min_feature_size_process(data, min_feature_data); code = TIFFWriteScanline(tif, data, row, 0); } if (code >= 0) code = TIFFWriteDirectory(tif); cleanup: if (min_feature_size > 1) min_feature_size_dnit(min_feature_data); gs_free_object(dev->memory, data, "tiff_print_page(data)"); return code; }
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 #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; } 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 && 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); } if (Strutil::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; }
int main(int argc, char *argv[]) { int row; uint8_t image_buffer[8192]; TIFF *tiff_file; struct tm *tm; time_t now; char buf[133]; float x_resolution; float y_resolution; int i; int opt; int compression; int photo_metric; int fill_order; int output_t4_options; compression = COMPRESSION_CCITT_T6; output_t4_options = 0; photo_metric = PHOTOMETRIC_MINISWHITE; fill_order = FILLORDER_LSB2MSB; while ((opt = getopt(argc, argv, "126ir")) != -1) { switch (opt) { case '1': compression = COMPRESSION_CCITT_T4; output_t4_options = GROUP3OPT_FILLBITS; break; case '2': compression = COMPRESSION_CCITT_T4; output_t4_options = GROUP3OPT_FILLBITS | GROUP3OPT_2DENCODING; break; case '6': compression = COMPRESSION_CCITT_T6; output_t4_options = 0; break; case 'i': photo_metric = PHOTOMETRIC_MINISBLACK; break; case 'r': fill_order = FILLORDER_MSB2LSB; break; default: //usage(); exit(2); break; } } for (i = 0; sequence[i].name; i++) { if ((tiff_file = TIFFOpen(sequence[i].name, "w")) == NULL) exit(2); /* Prepare the directory entry fully before writing the image, or libtiff complains */ TIFFSetField(tiff_file, TIFFTAG_COMPRESSION, compression); if (output_t4_options) TIFFSetField(tiff_file, TIFFTAG_T4OPTIONS, output_t4_options); TIFFSetField(tiff_file, TIFFTAG_FAXMODE, FAXMODE_CLASSF); TIFFSetField(tiff_file, TIFFTAG_IMAGEWIDTH, sequence[i].width); TIFFSetField(tiff_file, TIFFTAG_BITSPERSAMPLE, 1); TIFFSetField(tiff_file, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(tiff_file, TIFFTAG_SAMPLESPERPIXEL, 1); TIFFSetField(tiff_file, TIFFTAG_ROWSPERSTRIP, -1L); TIFFSetField(tiff_file, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tiff_file, TIFFTAG_PHOTOMETRIC, photo_metric); TIFFSetField(tiff_file, TIFFTAG_FILLORDER, fill_order); x_resolution = sequence[i].x_res/100.0f; y_resolution = sequence[i].y_res/100.0f; TIFFSetField(tiff_file, TIFFTAG_XRESOLUTION, floorf(x_resolution*2.54f + 0.5f)); TIFFSetField(tiff_file, TIFFTAG_YRESOLUTION, floorf(y_resolution*2.54f + 0.5f)); TIFFSetField(tiff_file, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); TIFFSetField(tiff_file, TIFFTAG_SOFTWARE, "spandsp"); if (gethostname(buf, sizeof(buf)) == 0) TIFFSetField(tiff_file, TIFFTAG_HOSTCOMPUTER, buf); TIFFSetField(tiff_file, TIFFTAG_IMAGEDESCRIPTION, "Blank test image"); TIFFSetField(tiff_file, TIFFTAG_MAKE, "soft-switch.org"); TIFFSetField(tiff_file, TIFFTAG_MODEL, "test data"); time(&now); tm = localtime(&now); sprintf(buf, "%4d/%02d/%02d %02d:%02d:%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); TIFFSetField(tiff_file, TIFFTAG_DATETIME, buf); TIFFSetField(tiff_file, TIFFTAG_IMAGELENGTH, sequence[i].length); TIFFSetField(tiff_file, TIFFTAG_PAGENUMBER, 0, 1); TIFFSetField(tiff_file, TIFFTAG_CLEANFAXDATA, CLEANFAXDATA_CLEAN); TIFFSetField(tiff_file, TIFFTAG_IMAGEWIDTH, sequence[i].width); TIFFCheckpointDirectory(tiff_file); /* Write the image first.... */ for (row = 0; row < sequence[i].length; row++) { memset(image_buffer, 0, sequence[i].width/8 + 1); if (TIFFWriteScanline(tiff_file, image_buffer, row, 0) < 0) { printf("Write error at row %d.\n", row); exit(2); } } /* ....then the directory entry, and libtiff is happy. */ TIFFWriteDirectory(tiff_file); TIFFClose(tiff_file); } return 0; }
bool TIFFOutput::write_scanline (int y, int z, TypeDesc format, const void *data, stride_t xstride) { m_spec.auto_stride (xstride, format, spec().nchannels); const void *origdata = data; data = to_native_scanline (format, data, xstride, m_scratch, m_dither, y, z); // Handle weird photometric/color spaces if (m_photometric == PHOTOMETRIC_SEPARATED && m_convert_rgb_to_cmyk) data = convert_to_cmyk (spec().width, data); // Handle weird bit depths if (spec().format.size()*8 != m_bitspersample) { // Move to scratch area if not already there imagesize_t nbytes = spec().scanline_bytes(); int nvals = spec().width * m_outputchans; if (data == origdata) { m_scratch.assign ((unsigned char *)data, (unsigned char *)data+nbytes); data = &m_scratch[0]; } if (spec().format == TypeDesc::UINT16 && m_bitspersample == 10) { convert_pack_bits<unsigned short, 10> ((unsigned short *)data, nvals); } else if (spec().format == TypeDesc::UINT16 && m_bitspersample == 12) { convert_pack_bits<unsigned short, 12> ((unsigned short *)data, nvals); } else if (spec().format == TypeDesc::UINT8 && m_bitspersample == 4) { convert_pack_bits<unsigned char, 4> ((unsigned char *)data, nvals); } else if (spec().format == TypeDesc::UINT8 && m_bitspersample == 2) { convert_pack_bits<unsigned char, 2> ((unsigned char *)data, nvals); } else { ASSERT (0 && "unsupported bit conversion -- shouldn't reach here"); } } y -= m_spec.y; if (m_planarconfig == PLANARCONFIG_SEPARATE) { // Convert from contiguous (RGBRGBRGB) to separate (RRRGGGBBB) int plane_bytes = m_spec.width * m_spec.format.size(); std::vector<unsigned char> scratch2 (m_spec.scanline_bytes()); std::swap (m_scratch, scratch2); m_scratch.resize (m_spec.scanline_bytes()); contig_to_separate (m_spec.width, (const char *)data, (char *)&m_scratch[0]); for (int c = 0; c < m_spec.nchannels; ++c) { if (TIFFWriteScanline (m_tif, (tdata_t)&m_scratch[plane_bytes*c], y, c) < 0) { std::string err = oiio_tiff_last_error(); error ("TIFFWriteScanline failed writing line y=%d,z=%d (%s)", y, z, err.size() ? err.c_str() : "unknown error"); return false; } } } else { // No contig->separate is necessary. But we still use scratch // space since TIFFWriteScanline is destructive when // TIFFTAG_PREDICTOR is used. if (data == origdata) { m_scratch.assign ((unsigned char *)data, (unsigned char *)data+m_spec.scanline_bytes()); data = &m_scratch[0]; } if (TIFFWriteScanline (m_tif, (tdata_t)data, y) < 0) { std::string err = oiio_tiff_last_error(); error ("TIFFWriteScanline failed writing line y=%d,z=%d (%s)", y, z, err.size() ? err.c_str() : "unknown error"); return false; } } // Should we checkpoint? Only if we have enough scanlines and enough // time has passed (or if using JPEG compression, for which it seems // necessary). ++m_checkpointItems; if ((m_checkpointTimer() > DEFAULT_CHECKPOINT_INTERVAL_SECONDS || m_compression == COMPRESSION_JPEG) && m_checkpointItems >= MIN_SCANLINES_OR_TILES_PER_CHECKPOINT) { TIFFCheckpointDirectory (m_tif); m_checkpointTimer.lap(); m_checkpointItems = 0; } return true; }
bool TIFFOutput::write_exif_data () { #if defined(TIFF_VERSION_BIG) && TIFFLIB_VERSION >= 20120922 // Older versions of libtiff do not support writing Exif directories if (m_spec.get_int_attribute ("tiff:write_exif", 1) == 0) { // The special metadata "tiff:write_exif", if present and set to 0 // (the default is 1), will cause us to skip outputting Exif data. // This is useful in cases where we think the TIFF file will need to // be read by an app that links against an old version of libtiff // that will have trouble reading the Exif directory. return true; } // First, see if we have any Exif data at all bool any_exif = false; for (size_t i = 0, e = m_spec.extra_attribs.size(); i < e; ++i) { const ParamValue &p (m_spec.extra_attribs[i]); int tag, tifftype, count; if (exif_tag_lookup (p.name(), tag, tifftype, count) && tifftype != TIFF_NOTYPE) { if (tag == EXIFTAG_SECURITYCLASSIFICATION || tag == EXIFTAG_IMAGEHISTORY || tag == EXIFTAG_ISOSPEEDRATINGS) continue; // libtiff doesn't understand these any_exif = true; break; } } if (! any_exif) return true; if (m_compression == COMPRESSION_JPEG) { // For reasons we don't understand, JPEG-compressed TIFF seems // to not output properly without a directory checkpoint here. TIFFCheckpointDirectory (m_tif); } // First, finish writing the current directory if (! TIFFWriteDirectory (m_tif)) { error ("failed TIFFWriteDirectory()"); return false; } // Create an Exif directory if (TIFFCreateEXIFDirectory (m_tif) != 0) { error ("failed TIFFCreateEXIFDirectory()"); return false; } for (size_t i = 0, e = m_spec.extra_attribs.size(); i < e; ++i) { const ParamValue &p (m_spec.extra_attribs[i]); int tag, tifftype, count; if (exif_tag_lookup (p.name(), tag, tifftype, count) && tifftype != TIFF_NOTYPE) { if (tag == EXIFTAG_SECURITYCLASSIFICATION || tag == EXIFTAG_IMAGEHISTORY || tag == EXIFTAG_ISOSPEEDRATINGS) continue; // libtiff doesn't understand these bool ok = false; if (tifftype == TIFF_ASCII) { ok = TIFFSetField (m_tif, tag, *(char**)p.data()); } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) && p.type() == TypeDesc::SHORT) { ok = TIFFSetField (m_tif, tag, (int)*(short *)p.data()); } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) && p.type() == TypeDesc::INT) { ok = TIFFSetField (m_tif, tag, *(int *)p.data()); } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) && p.type() == TypeDesc::FLOAT) { ok = TIFFSetField (m_tif, tag, *(float *)p.data()); } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) && p.type() == TypeDesc::DOUBLE) { ok = TIFFSetField (m_tif, tag, *(double *)p.data()); } if (! ok) { // std::cout << "Unhandled EXIF " << p.name() << " " << p.type() << "\n"; } } } // Now write the directory of Exif data uint64 dir_offset = 0; if (! TIFFWriteCustomDirectory (m_tif, &dir_offset)) { error ("failed TIFFWriteCustomDirectory() of the Exif data"); return false; } // Go back to the first directory, and add the EXIFIFD pointer. // std::cout << "diffdir = " << tiffdir << "\n"; TIFFSetDirectory (m_tif, 0); TIFFSetField (m_tif, TIFFTAG_EXIFIFD, dir_offset); #endif return true; // all is ok }
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); // Handle display window or "full" size. Note that TIFF can't represent // nonzero offsets of the full size, so we may need to expand the // display window to encompass the origin. 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 || m_spec.full_x != 0 || m_spec.full_y != 0)) { TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLWIDTH, m_spec.full_width+m_spec.full_x); TIFFSetField (m_tif, TIFFTAG_PIXAR_IMAGEFULLLENGTH, m_spec.full_height+m_spec.full_y); } 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: // 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) // For now, the default is to NOT write this (instead writing float) // unless the "tiff:half" attribute is nonzero -- use the global // OIIO attribute, but override with a specific attribute for this // file. if (m_spec.get_int_attribute("tiff:half", OIIO::get_int_attribute("tiff:half"))) { m_bitspersample = 16; } else { // Silently change requests for unsupported 'half' to 'float' m_bitspersample = 32; m_spec.set_format (TypeDesc::FLOAT); } sampformat = SAMPLEFORMAT_IEEEFP; break; 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); } if (m_compression == COMPRESSION_ADOBE_DEFLATE) { int q = m_spec.get_int_attribute ("tiff:zipquality", -1); if (q >= 0) TIFFSetField (m_tif, TIFFTAG_ZIPQUALITY, OIIO::clamp(q, 1, 9)); } } 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") || Strutil::iequals (photo, "color separated")) { // User has requested via the "tiff:ColorSpace" attribute that // the file be written as color separated channels. m_photometric = PHOTOMETRIC_SEPARATED; 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); } if (source_is_rgb(m_spec)) { // Case: RGB -> CMYK, do the conversions per pixel m_convert_rgb_to_cmyk = true; m_outputchans = 4; // output 4, not 4 chans TIFFSetField (m_tif, TIFFTAG_SAMPLESPERPIXEL, m_outputchans); TIFFSetField (m_tif, TIFFTAG_INKSET, INKSET_CMYK); } else if (source_is_cmyk(m_spec)) { // Case: CMYK -> CMYK (do not transform) m_convert_rgb_to_cmyk = false; TIFFSetField (m_tif, TIFFTAG_INKSET, INKSET_CMYK); } else { // Case: arbitrary inks m_convert_rgb_to_cmyk = false; TIFFSetField (m_tif, TIFFTAG_INKSET, INKSET_MULTIINK); std::string inknames; for (int i = 0; i < m_spec.nchannels; ++i) { if (i) inknames.insert (inknames.size(), 1, '\0'); if (i < (int)m_spec.channelnames.size()) inknames.insert (inknames.size(), m_spec.channelnames[i]); else inknames.insert (inknames.size(), Strutil::format("ink%d", i)); } TIFFSetField (m_tif, TIFFTAG_INKNAMES, int(inknames.size()+1), &inknames[0]); TIFFSetField (m_tif, TIFFTAG_NUMBEROFINKS, m_spec.nchannels); } } } 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]); } ParamValue *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 %02d:%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 ParamValue* 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; }