Exemple #1
0
void ImageWriter::plugSet( Gaffer::Plug *plug )
{
	if ( plug == fileNamePlug() )
	{
		std::string fileName = fileNamePlug()->getValue();
		fileName = Context::current()->substitute( fileName );
		ImageOutput *out = ImageOutput::create( fileName.c_str() );

		if ( !out )
		{
			throw IECore::Exception( boost::str( boost::format( "Invalid filename: %s" ) % fileName ) );
			return;
		}

		unsigned flags = writeModePlug()->getFlags();
		if ( out->supports( "tiles" ) )
		{
			writeModePlug()->setFlags( flags & ~Gaffer::Plug::ReadOnly );	
		}
		else
		{
			if ( !(flags & Gaffer::Plug::ReadOnly) )
			{
				writeModePlug()->setValue( Scanline );
			}
			writeModePlug()->setFlags( flags | Gaffer::Plug::ReadOnly );
		}

	}
}
Exemple #2
0
static int
output_file (int argc, const char *argv[])
{
    ASSERT (argc == 2 && !strcmp(argv[0],"-o"));
    std::string filename = argv[1];
    if (! ot.curimg.get()) {
        std::cerr << "oiiotool ERROR: -o " << filename << " did not have any current image to output.\n";
        return 0;
    }
    if (ot.noclobber && Filesystem::exists(filename)) {
        std::cerr << "oiiotool ERROR: Output file \"" << filename 
                  << "\" already exists, not overwriting.\n";
        return 0;
    }
    if (ot.verbose)
        std::cout << "Writing " << argv[1] << "\n";
    ImageOutput *out = ImageOutput::create (filename.c_str());
    if (! out) {
        std::cerr << "oiiotool ERROR: " << geterror() << "\n";
        return 0;
    }
    bool supports_displaywindow = out->supports ("displaywindow");
    ot.read ();
    ImageRecRef saveimg = ot.curimg;
    ImageRecRef ir (ot.curimg);

    if (! supports_displaywindow && ot.output_autocrop &&
        (ir->spec()->x != ir->spec()->full_x ||
         ir->spec()->y != ir->spec()->full_y ||
         ir->spec()->width != ir->spec()->full_width ||
         ir->spec()->height != ir->spec()->full_height)) {
        const char *argv[] = { "croptofull" };
        int action_croptofull (int argc, const char *argv[]); // forward decl
        action_croptofull (1, argv);
        ir = ot.curimg;
    }

    ImageOutput::OpenMode mode = ImageOutput::Create;  // initial open
    for (int s = 0, send = ir->subimages();  s < send;  ++s) {
        for (int m = 0, mend = ir->miplevels(s);  m < mend;  ++m) {
            ImageSpec spec = *ir->spec(s,m);
            adjust_output_options (spec, ot);
            if (! out->open (filename, spec, mode)) {
                std::cerr << "oiiotool ERROR: " << out->geterror() << "\n";
                return 0;
            }
            if (! (*ir)(s,m).write (out)) {
                std::cerr << "oiiotool ERROR: " << (*ir)(s,m).geterror() << "\n";
                return 0;
            }
            if (mend > 1) {
                if (out->supports("mipmap")) {
                    mode = ImageOutput::AppendMIPLevel;  // for next level
                } else if (out->supports("multiimage")) {
                    mode = ImageOutput::AppendSubimage;
                } else {
                    std::cout << "oiiotool WARNING: " << out->format_name() 
                              << " does not support MIP-maps for " 
                              << filename << "\n";
                    break;
                }
            }
        }
        mode = ImageOutput::AppendSubimage;  // for next subimage
        if (send > 1 && ! out->supports("multiimage")) {
            std::cout << "oiiotool WARNING: " << out->format_name() 
                      << " does not support multiple subimages for " 
                      << filename << "\n";
            break;
        }
    }

    out->close ();
    delete out;

    if (ot.output_adjust_time) {
        std::string metadatatime = ir->spec(0,0)->get_string_attribute ("DateTime");
        std::time_t in_time = ir->time();
        if (! metadatatime.empty())
            DateTime_to_time_t (metadatatime.c_str(), in_time);
        boost::filesystem::last_write_time (filename, in_time);
    }

    ot.curimg = saveimg;
    return 0;
}
Exemple #3
0
static void
write_mipmap (ImageBuf &img, const ImageSpec &outspec_template,
              std::string outputfilename, std::string outformat,
              TypeDesc outputdatatype, bool mipmap)
{
    ImageSpec outspec = outspec_template;
    outspec.set_format (outputdatatype);

    // Find an ImageIO plugin that can open the output file, and open it
    Timer writetimer;
    ImageOutput *out = ImageOutput::create (outformat.c_str());
    if (! out) {
        std::cerr 
            << "maketx ERROR: Could not find an ImageIO plugin to write " 
            << outformat << " files:" << geterror() << "\n";
        exit (EXIT_FAILURE);
    }
    if (! out->supports ("tiles")) {
        std::cerr << "maketx ERROR: \"" << outputfilename
                  << "\" format does not support tiled images\n";
        exit (EXIT_FAILURE);
    }
    if (mipmap && !out->supports ("multiimage") && !out->supports ("mipmap")) {
        std::cerr << "maketx ERROR: \"" << outputfilename
                  << "\" format does not support multires images\n";
        exit (EXIT_FAILURE);
    }

    if (! mipmap && ! strcmp (out->format_name(), "openexr")) {
        // Send hint to OpenEXR driver that we won't specify a MIPmap
        outspec.attribute ("openexr:levelmode", 0 /* ONE_LEVEL */);
    }

    if (mipmap && ! strcmp (out->format_name(), "openexr")) {
        outspec.attribute ("openexr:roundingmode", 0 /* ROUND_DOWN */);
    }

    // OpenEXR always uses border sampling for environment maps
    if ((envlatlmode || envcubemode) &&
            !strcmp(out->format_name(), "openexr")) {
        src_samples_border = true;
        outspec.attribute ("oiio:updirection", "y");
        outspec.attribute ("oiio:sampleborder", 1);
    }
    if (envlatlmode && src_samples_border)
        fix_latl_edges (img);

    if (! out->open (outputfilename.c_str(), outspec)) {
        std::cerr << "maketx ERROR: Could not open \"" << outputfilename
                  << "\" : " << out->geterror() << "\n";
        exit (EXIT_FAILURE);
    }

    // Write out the image
    if (verbose) {
        std::cout << "  Writing file: " << outputfilename << std::endl;
        std::cout << "  Filter \"" << filter->name() << "\" width = " 
                  << filter->width() << "\n";
    }

    bool ok = true;
    ok &= img.write (out);
    stat_writetime += writetimer();

    if (mipmap) {  // Mipmap levels:
        if (verbose)
            std::cout << "  Mipmapping...\n" << std::flush;
        ImageBuf tmp;
        ImageBuf *big = &img, *small = &tmp;
        while (ok && (outspec.width > 1 || outspec.height > 1)) {
            Timer miptimer;
            // Resize a factor of two smaller
            ImageSpec smallspec = outspec;
            smallspec.width = big->spec().width;
            smallspec.height = big->spec().height;
            smallspec.depth = big->spec().depth;
            if (smallspec.width > 1)
                smallspec.width /= 2;
            if (smallspec.height > 1)
                smallspec.height /= 2;
            smallspec.full_width  = smallspec.width;
            smallspec.full_height = smallspec.height;
            smallspec.full_depth  = smallspec.depth;
            smallspec.set_format (TypeDesc::FLOAT);
            small->alloc (smallspec);  // Realocate with new size

            if (filtername == "box" && filter->width() == 1.0f)
                parallel_image (resize_block, small, big,
                                smallspec.x, smallspec.x+smallspec.width,
                                smallspec.y, smallspec.y+smallspec.height,
                                nthreads);
            else
                parallel_image (resize_block_HQ, small, big,
                                smallspec.x, smallspec.x+smallspec.width,
                                smallspec.y, smallspec.y+smallspec.height,
                                nthreads);

            stat_miptime += miptimer();
            outspec = smallspec;
            outspec.set_format (outputdatatype);
            if (envlatlmode && src_samples_border)
                fix_latl_edges (*small);

            Timer writetimer;
            // If the format explicitly supports MIP-maps, use that,
            // otherwise try to simulate MIP-mapping with multi-image.
            bool ok = false;
            ImageOutput::OpenMode mode = out->supports ("mipmap") ?
                ImageOutput::AppendMIPLevel : ImageOutput::AppendSubimage;
            if (! out->open (outputfilename.c_str(), outspec, mode)) {
                std::cerr << "maketx ERROR: Could not append \"" << outputfilename
                          << "\" : " << out->geterror() << "\n";
                exit (EXIT_FAILURE);
            }
            ok &= small->write (out);
            stat_writetime += writetimer();
            if (verbose)
                std::cout << "    " << smallspec.width << 'x' 
                          << smallspec.height << "\n" << std::flush;
            std::swap (big, small);
        }
    }

    if (verbose)
        std::cout << "  Wrote file: " << outputfilename << std::endl;
    writetimer.reset ();
    writetimer.start ();
    if (ok)
        ok &= out->close ();
    stat_writetime += writetimer ();
    delete out;

    if (! ok) {
        std::cerr << "maketx ERROR writing \"" << outputfilename
                  << "\" : " << out->geterror() << "\n";
        exit (EXIT_FAILURE);
    }

}
Exemple #4
0
static bool
convert_file (const std::string &in_filename, const std::string &out_filename)
{
    if (noclobber && Filesystem::exists(out_filename)) {
        std::cerr << "iconvert ERROR: Output file already exists \""
                  << out_filename << "\"\n";
        return false;
    }

    if (verbose)
        std::cout << "Converting " << in_filename << " to " << out_filename << "\n";

    std::string tempname = out_filename;
    if (tempname == in_filename) {
        tempname = out_filename + ".tmp"
                    + Filesystem::extension (out_filename);
    }

    // Find an ImageIO plugin that can open the input file, and open it.
    ImageInput *in = ImageInput::open (in_filename.c_str());
    if (! in) {
        std::string err = geterror();
        std::cerr << "iconvert ERROR: " 
                  << (err.length() ? err : Strutil::format("Could not open \"%s\"", in_filename))
                  << "\n";
        delete in;
        return false;
    }
    ImageSpec inspec = in->spec();
    std::string metadatatime = inspec.get_string_attribute ("DateTime");

    // Find an ImageIO plugin that can open the output file, and open it
    ImageOutput *out = ImageOutput::create (tempname.c_str());
    if (! out) {
        std::cerr 
            << "iconvert ERROR: Could not find an ImageIO plugin to write \"" 
            << out_filename << "\" :" << geterror() << "\n";
        delete in;
        return false;
    }

    // In order to deal with formats that support subimages, but not
    // subimage appending, we gather them all first.
    std::vector<ImageSpec> subimagespecs;
    if (out->supports("multiimage") && !out->supports("appendsubimage")) {
        ImageCache *imagecache = ImageCache::create ();
        int nsubimages = 0;
        ustring ufilename (in_filename);
        imagecache->get_image_info (ufilename, 0, 0, ustring("subimages"),
                                    TypeDesc::TypeInt, &nsubimages);
        if (nsubimages > 1) {
            subimagespecs.resize (nsubimages);
            for (int i = 0;  i < nsubimages;  ++i) {
                ImageSpec inspec = *imagecache->imagespec (ufilename, i, 0,
                                                           true /*native*/);
                subimagespecs[i] = inspec;
                adjust_spec (in, out, inspec, subimagespecs[i]);
            }
        }
        ImageCache::destroy (imagecache);
    }

    bool ok = true;
    bool mip_to_subimage_warning = false;
    for (int subimage = 0;
           ok && in->seek_subimage(subimage,0,inspec);
           ++subimage) {

        if (subimage > 0 &&  !out->supports ("multiimage")) {
            std::cerr << "iconvert WARNING: " << out->format_name()
                      << " does not support multiple subimages.\n";
            std::cerr << "\tOnly the first subimage has been copied.\n";
            break;  // we're done
        }

        int miplevel = 0;
        do {
            // Copy the spec, with possible change in format
            ImageSpec outspec = inspec;
            bool nocopy = adjust_spec (in, out, inspec, outspec);
        
            if (miplevel > 0) {
                // Moving to next MIP level
                ImageOutput::OpenMode mode;
                if (out->supports ("mipmap"))
                    mode = ImageOutput::AppendMIPLevel;
                else if (out->supports ("multiimage") &&
                         out->supports ("appendsubimage")) {
                    mode = ImageOutput::AppendSubimage; // use if we must
                    if (! mip_to_subimage_warning
                        && strcmp(out->format_name(),"tiff")) {
                        std::cerr << "iconvert WARNING: " << out->format_name()
                                  << " does not support MIPmaps.\n";
                        std::cerr << "\tStoring the MIPmap levels in subimages.\n";
                    }
                    mip_to_subimage_warning = true;
                } else {
                    std::cerr << "iconvert WARNING: " << out->format_name()
                              << " does not support MIPmaps.\n";
                    std::cerr << "\tOnly the first level has been copied.\n";
                    break;  // on to the next subimage
                }
                ok = out->open (tempname.c_str(), outspec, mode);
            } else if (subimage > 0) {
                // Moving to next subimage
                ok = out->open (tempname.c_str(), outspec,
                                ImageOutput::AppendSubimage);
            } else {
                // First time opening
                if (subimagespecs.size())
                    ok = out->open (tempname.c_str(), int(subimagespecs.size()),
                                    &subimagespecs[0]);
                else
                    ok = out->open (tempname.c_str(), outspec, ImageOutput::Create);
            }
            if (! ok) {
                std::string err = out->geterror();
                std::cerr << "iconvert ERROR: " 
                          << (err.length() ? err : Strutil::format("Could not open \"%s\"", out_filename))
                          << "\n";
                ok = false;
                break;
            }

            if (! nocopy) {
                ok = out->copy_image (in);
                if (! ok)
                    std::cerr << "iconvert ERROR copying \"" << in_filename 
                              << "\" to \"" << out_filename << "\" :\n\t" 
                              << out->geterror() << "\n";
            } else {
                // Need to do it by hand for some reason.  Future expansion in which
                // only a subset of channels are copied, or some such.
                std::vector<char> pixels ((size_t)outspec.image_bytes(true));
                ok = in->read_image (outspec.format, &pixels[0]);
                if (! ok) {
                    std::cerr << "iconvert ERROR reading \"" << in_filename 
                              << "\" : " << in->geterror() << "\n";
                } else {
                    ok = out->write_image (outspec.format, &pixels[0]);
                    if (! ok)
                        std::cerr << "iconvert ERROR writing \"" << out_filename 
                                  << "\" : " << out->geterror() << "\n";
                }
            }
        
            ++miplevel;
        } while (ok && in->seek_subimage(subimage,miplevel,inspec));
    }

    out->close ();
    delete out;
    in->close ();
    delete in;

    // Figure out a time for the input file -- either one supplied by
    // the metadata, or the actual time stamp of the input file.
    std::time_t in_time;
    if (metadatatime.empty() ||
           ! DateTime_to_time_t (metadatatime.c_str(), in_time))
        in_time = Filesystem::last_write_time (in_filename);

    if (out_filename != tempname) {
        if (ok) {
            Filesystem::remove (out_filename);
            Filesystem::rename (tempname, out_filename);
        }
        else
            Filesystem::remove (tempname);
    }

    // If user requested, try to adjust the file's modification time to
    // the creation time indicated by the file's DateTime metadata.
    if (ok && adjust_time)
        Filesystem::last_write_time (out_filename, in_time);

    return ok;
}
bool
ImageBufAlgo::make_texture (ImageBufAlgo::MakeTextureMode mode,
                            const std::vector<std::string> &filenames,
                            const std::string &_outputfilename,
                            const ImageSpec &_configspec,
                            std::ostream *outstream_ptr)
{
    ASSERT (mode >= 0 && mode < ImageBufAlgo::_MakeTxLast);
    Timer alltime;
    ImageSpec configspec = _configspec;
//    const char *modenames[] = { "texture map", "shadow map",
//                                "latlong environment map" };
    std::stringstream localstream; // catch output when user doesn't want it
    std::ostream &outstream (outstream_ptr ? *outstream_ptr : localstream);

    double stat_readtime = 0;
    double stat_writetime = 0;
    double stat_resizetime = 0;
    double stat_miptime = 0;
    double stat_colorconverttime = 0;

    std::string filename = filenames[0];
    if (! Filesystem::exists (filename)) {
        outstream << "maketx ERROR: \"" << filename << "\" does not exist\n";
        return false;
    }
    std::string outputfilename = _outputfilename.length() ? _outputfilename
        : Filesystem::replace_extension (filename, ".tx");

    // When was the input file last modified?
    std::time_t in_time = Filesystem::last_write_time (filename);

    // When in update mode, skip making the texture if the output already
    // exists and has the same file modification time as the input file.
    bool updatemode = configspec.get_int_attribute ("maketx:updatemode");
    if (updatemode && Filesystem::exists (outputfilename) &&
        (in_time == Filesystem::last_write_time (outputfilename))) {
        outstream << "maketx: no update required for \"" 
                  << outputfilename << "\"\n";
        return true;
    }

    bool shadowmode = (mode == ImageBufAlgo::MakeTxShadow);
    bool envlatlmode = (mode == ImageBufAlgo::MakeTxEnvLatl);

    // Find an ImageIO plugin that can open the output file, and open it
    std::string outformat = configspec.get_string_attribute ("maketx:fileformatname",
                                                             outputfilename);
    ImageOutput *out = ImageOutput::create (outformat.c_str());
    if (! out) {
        outstream 
            << "maketx ERROR: Could not find an ImageIO plugin to write " 
            << outformat << " files:" << geterror() << "\n";
        return false;
    }
    if (! out->supports ("tiles")) {
        outstream << "maketx ERROR: \"" << outputfilename
                  << "\" format does not support tiled images\n";
        return false;
    }

    ImageBuf src (filename);
    src.init_spec (filename, 0, 0); // force it to get the spec, not read

    // The cache might mess with the apparent data format.  But for the 
    // purposes of what we should output, figure it out now, before the
    // file has been read and cached.
    TypeDesc out_dataformat = src.spec().format;

    if (configspec.format != TypeDesc::UNKNOWN)
        out_dataformat = configspec.format;
    
    // We cannot compute the prman / oiio options until after out_dataformat
    // has been determined, as it's required (and can potentially change 
    // out_dataformat too!)
    if (configspec.get_int_attribute("maketx:prman_options"))
        out_dataformat = set_prman_options (out_dataformat, configspec);
    else if (configspec.get_int_attribute("maketx:oiio_options"))
        out_dataformat = set_oiio_options (out_dataformat, configspec);

    // Read the full file locally if it's less than 1 GB, otherwise
    // allow the ImageBuf to use ImageCache to manage memory.
    bool read_local = (src.spec().image_bytes() < size_t(1024*1024*1024));

    bool verbose = configspec.get_int_attribute ("maketx:verbose");
    if (verbose)
        outstream << "Reading file: " << filename << std::endl;
    Timer readtimer;
    if (! src.read (0, 0, read_local)) {
        outstream 
            << "maketx ERROR: Could not read \"" 
            << filename << "\" : " << src.geterror() << "\n";
        return false;
    }
    stat_readtime += readtimer();
    
    // If requested - and we're a constant color - make a tiny texture instead
    // Only safe if the full/display window is the same as the data window.
    // Also note that this could affect the appearance when using "black"
    // wrap mode at runtime.
    std::vector<float> constantColor(src.nchannels());
    bool isConstantColor = false;
    if (configspec.get_int_attribute("maketx:constant_color_detect") &&
        src.spec().x == 0 && src.spec().y == 0 && src.spec().z == 0 &&
        src.spec().full_x == 0 && src.spec().full_y == 0 &&
        src.spec().full_z == 0 && src.spec().full_width == src.spec().width &&
        src.spec().full_height == src.spec().height &&
        src.spec().full_depth == src.spec().depth) {
        isConstantColor = ImageBufAlgo::isConstantColor (src, &constantColor[0]);
        if (isConstantColor) {
            // Reset the image, to a new image, at the tile size
            ImageSpec newspec = src.spec();
            newspec.width  = std::min (configspec.tile_width, src.spec().width);
            newspec.height = std::min (configspec.tile_height, src.spec().height);
            newspec.depth  = std::min (configspec.tile_depth, src.spec().depth);
            newspec.full_width  = newspec.width;
            newspec.full_height = newspec.height;
            newspec.full_depth  = newspec.depth;
            std::string name = src.name() + ".constant_color";
            src.reset(name, newspec);
            ImageBufAlgo::fill (src, &constantColor[0]);
            if (verbose) {
                outstream << "  Constant color image detected. ";
                outstream << "Creating " << newspec.width << "x" << newspec.height << " texture instead.\n";
            }
        }
    }
    
    int nchannels = configspec.get_int_attribute ("maketx:nchannels", -1);

    // If requested -- and alpha is 1.0 everywhere -- drop it.
    if (configspec.get_int_attribute("maketx:opaque_detect") &&
          src.spec().alpha_channel == src.nchannels()-1 &&
          nchannels <= 0 &&
          ImageBufAlgo::isConstantChannel(src,src.spec().alpha_channel,1.0f)) {
        ImageBuf newsrc(src.name() + ".noalpha", src.spec());
        ImageBufAlgo::setNumChannels (newsrc, src, src.nchannels()-1);
        src.copy (newsrc);
        if (verbose) {
            outstream << "  Alpha==1 image detected. Dropping the alpha channel.\n";
        }
    }

    // If requested - and we're a monochrome image - drop the extra channels
    if (configspec.get_int_attribute("maketx:monochrome_detect") &&
          nchannels <= 0 &&
          src.nchannels() == 3 && src.spec().alpha_channel < 0 &&  // RGB only
          ImageBufAlgo::isMonochrome(src)) {
        ImageBuf newsrc(src.name() + ".monochrome", src.spec());
        ImageBufAlgo::setNumChannels (newsrc, src, 1);
        src.copy (newsrc);
        if (verbose) {
            outstream << "  Monochrome image detected. Converting to single channel texture.\n";
        }
    }

    // If we've otherwise explicitly requested to write out a
    // specific number of channels, do it.
    if ((nchannels > 0) && (nchannels != src.nchannels())) {
        ImageBuf newsrc(src.name() + ".channels", src.spec());
        ImageBufAlgo::setNumChannels (newsrc, src, nchannels);
        src.copy (newsrc);
        if (verbose) {
            outstream << "  Overriding number of channels to " << nchannels << "\n";
        }
    }
    
    if (shadowmode) {
        // Some special checks for shadow maps
        if (src.spec().nchannels != 1) {
            outstream << "maketx ERROR: shadow maps require 1-channel images,\n"
                      << "\t\"" << filename << "\" is " 
                      << src.spec().nchannels << " channels\n";
            return false;
        }
        // Shadow maps only make sense for floating-point data.
        if (out_dataformat != TypeDesc::FLOAT &&
              out_dataformat != TypeDesc::HALF &&
              out_dataformat != TypeDesc::DOUBLE)
            out_dataformat = TypeDesc::FLOAT;
    }

    if (configspec.get_int_attribute("maketx:set_full_to_pixels")) {
        // User requested that we treat the image as uncropped or not
        // overscan
        ImageSpec &spec (src.specmod());
        spec.full_x = spec.x = 0;
        spec.full_y = spec.y = 0;
        spec.full_z = spec.z = 0;
        spec.full_width = spec.width;
        spec.full_height = spec.height;
        spec.full_depth = spec.depth;
    }

    // Copy the input spec
    const ImageSpec &srcspec = src.spec();
    ImageSpec dstspec = srcspec;
    bool orig_was_volume = srcspec.depth > 1 || srcspec.full_depth > 1;
    bool orig_was_crop = (srcspec.x > srcspec.full_x ||
                          srcspec.y > srcspec.full_y ||
                          srcspec.z > srcspec.full_z ||
                          srcspec.x+srcspec.width < srcspec.full_x+srcspec.full_width ||
                          srcspec.y+srcspec.height < srcspec.full_y+srcspec.full_height ||
                          srcspec.z+srcspec.depth < srcspec.full_z+srcspec.full_depth);
    bool orig_was_overscan = (srcspec.x < srcspec.full_x &&
                              srcspec.y < srcspec.full_y &&
                              srcspec.x+srcspec.width > srcspec.full_x+srcspec.full_width &&
                              srcspec.y+srcspec.height > srcspec.full_y+srcspec.full_height &&
                              (!orig_was_volume || (srcspec.z < srcspec.full_z &&
                                                    srcspec.z+srcspec.depth > srcspec.full_z+srcspec.full_depth)));
    // Make the output not a crop window
    if (orig_was_crop) {
        dstspec.x = 0;
        dstspec.y = 0;
        dstspec.z = 0;
        dstspec.width = srcspec.full_width;
        dstspec.height = srcspec.full_height;
        dstspec.depth = srcspec.full_depth;
        dstspec.full_x = 0;
        dstspec.full_y = 0;
        dstspec.full_z = 0;
        dstspec.full_width = dstspec.width;
        dstspec.full_height = dstspec.height;
        dstspec.full_depth = dstspec.depth;
    }
    if (orig_was_overscan)
        configspec.attribute ("wrapmodes", "black,black");

    if ((dstspec.x < 0 || dstspec.y < 0 || dstspec.z < 0) &&
        (out && !out->supports("negativeorigin"))) {
        // User passed negative origin but the output format doesn't
        // support it.  Try to salvage the situation by shifting the
        // image into the positive range.
        if (dstspec.x < 0) {
            dstspec.full_x -= dstspec.x;
            dstspec.x = 0;
        }
        if (dstspec.y < 0) {
            dstspec.full_y -= dstspec.y;
            dstspec.y = 0;
        }
        if (dstspec.z < 0) {
            dstspec.full_z -= dstspec.z;
            dstspec.z = 0;
        }
    }

    // Make the output tiled, regardless of input
    dstspec.tile_width  = configspec.tile_width  ? configspec.tile_width  : 64;
    dstspec.tile_height = configspec.tile_height ? configspec.tile_height : 64;
    dstspec.tile_depth  = configspec.tile_depth  ? configspec.tile_depth  : 1;

    // Try to force zip (still can be overriden by configspec
    dstspec.attribute ("compression", "zip");
    // Always prefer contiguous channels, unless overridden by configspec
    dstspec.attribute ("planarconfig", "contig");
    // Default to black wrap mode, unless overridden by configspec
    dstspec.attribute ("wrapmodes", "black,black");

    if (configspec.get_int_attribute ("maketx:ignore_unassoc"))
        dstspec.erase_attribute ("oiio:UnassociatedAlpha");

    // Put a DateTime in the out file, either now, or matching the date
    // stamp of the input file (if update mode).
    time_t date;
    if (updatemode)
        date = in_time;  // update mode: use the time stamp of the input
    else
        time (&date);    // not update: get the time now
    dstspec.attribute ("DateTime", datestring(date));

    std::string cmdline = configspec.get_string_attribute ("maketx:full_command_line");
    if (! cmdline.empty()) {
        // Append command to image history
        std::string history = dstspec.get_string_attribute ("Exif:ImageHistory");
        if (history.length() && ! Strutil::iends_with (history, "\n"))
            history += std::string("\n");
        history += cmdline;
        dstspec.attribute ("Exif:ImageHistory", history);
    }

    bool prman_metadata = configspec.get_int_attribute ("maketx:prman_metadata");
    if (shadowmode) {
        dstspec.attribute ("textureformat", "Shadow");
        if (prman_metadata)
            dstspec.attribute ("PixarTextureFormat", "Shadow");
    } else if (envlatlmode) {
        dstspec.attribute ("textureformat", "LatLong Environment");
        configspec.attribute ("wrapmodes", "periodic,clamp");
        if (prman_metadata)
            dstspec.attribute ("PixarTextureFormat", "Latlong Environment");
    } else {
        dstspec.attribute ("textureformat", "Plain Texture");
        if (prman_metadata)
            dstspec.attribute ("PixarTextureFormat", "Plain Texture");
    }

    // FIXME -- should we allow tile sizes to reduce if the image is
    // smaller than the tile size?  And when we do, should we also try
    // to make it bigger in the other direction to make the total tile
    // size more constant?

    // If --checknan was used and it's a floating point image, check for
    // nonfinite (NaN or Inf) values and abort if they are found.
    if (configspec.get_int_attribute("maketx:checknan") &&
                    (srcspec.format.basetype == TypeDesc::FLOAT ||
                     srcspec.format.basetype == TypeDesc::HALF ||
                     srcspec.format.basetype == TypeDesc::DOUBLE)) {
        int found_nonfinite = 0;
        ImageBufAlgo::parallel_image (boost::bind(check_nan_block, &src, _1, boost::ref(found_nonfinite)),
                                      OIIO::get_roi(dstspec));
        if (found_nonfinite) {
            if (found_nonfinite > 3)
                outstream << "maketx ERROR: ...and Nan/Inf at "
                          << (found_nonfinite-3) << " other pixels\n";
            return false;
        }
    }
    
    // Fix nans/infs (if requested
    ImageBufAlgo::NonFiniteFixMode fixmode = ImageBufAlgo::NONFINITE_NONE;
    std::string fixnan = configspec.get_string_attribute("maketx:fixnan");
    if (fixnan.empty() || fixnan == "none") { }
    else if (fixnan == "black") { fixmode = ImageBufAlgo::NONFINITE_BLACK; }
    else if (fixnan == "box3") { fixmode = ImageBufAlgo::NONFINITE_BOX3; }
    else {
        outstream << "maketx ERROR: Unknown --fixnan mode " << " fixnan\n";
        return false;
    }
    
    int pixelsFixed = 0;
    if (!ImageBufAlgo::fixNonFinite (src, src, fixmode, &pixelsFixed)) {
        outstream << "maketx ERROR: Error fixing nans/infs.\n";
        return false;
    }
    
    if (verbose && pixelsFixed>0) {
        outstream << "  Warning: " << pixelsFixed << " nan/inf pixels fixed.\n";
    }
    
    
    
    // Color convert the pixels, if needed, in place.  If a color
    // conversion is required we will promote the src to floating point
    // (or there wont be enough precision potentially).  Also,
    // independently color convert the constant color metadata
    ImageBuf * ccSrc = &src;    // Ptr to cc'd src image
    ImageBuf colorBuffer;
    std::string incolorspace = configspec.get_string_attribute ("incolorspace");
    std::string outcolorspace = configspec.get_string_attribute ("outcolorspace");
    if (!incolorspace.empty() && !outcolorspace.empty() && incolorspace != outcolorspace) {
        if (src.spec().format != TypeDesc::FLOAT) {
            ImageSpec floatSpec = src.spec();
            floatSpec.set_format(TypeDesc::FLOAT);
            colorBuffer.reset("bitdepth promoted", floatSpec);
            ccSrc = &colorBuffer;
        }
        
        Timer colorconverttimer;
        ColorConfig colorconfig;
        if (verbose) {
            outstream << "  Converting from colorspace " << incolorspace 
                      << " to colorspace " << outcolorspace << std::endl;
        }
        
        if (colorconfig.error()) {
            outstream << "Error Creating ColorConfig\n";
            outstream << colorconfig.geterror() << std::endl;
            return false;
        }
        
        ColorProcessor * processor = colorconfig.createColorProcessor (
            incolorspace.c_str(), outcolorspace.c_str());
        
        if (!processor || colorconfig.error()) {
            outstream << "Error Creating Color Processor." << std::endl;
            outstream << colorconfig.geterror() << std::endl;
            return false;
        }
        
        bool unpremult = configspec.get_int_attribute ("maketx:unpremult");
        if (unpremult && verbose)
            outstream << "  Unpremulting image..." << std::endl;
        
        if (!ImageBufAlgo::colorconvert (*ccSrc, src, processor, unpremult)) {
            outstream << "Error applying color conversion to image.\n";
            return false;
        }
        
        if (isConstantColor) {
            if (!ImageBufAlgo::colorconvert (&constantColor[0],
                static_cast<int>(constantColor.size()), processor, unpremult)) {
                outstream << "Error applying color conversion to constant color.\n";
                return false;
            }
        }

        ColorConfig::deleteColorProcessor(processor);
        processor = NULL;
        stat_colorconverttime += colorconverttimer();
    }

    // Force float for the sake of the ImageBuf math
    dstspec.set_format (TypeDesc::FLOAT);

    // Handle resize to power of two, if called for
    if (configspec.get_int_attribute("maketx:resize")  &&  ! shadowmode) {
        dstspec.width = pow2roundup (dstspec.width);
        dstspec.height = pow2roundup (dstspec.height);
        dstspec.full_width = dstspec.width;
        dstspec.full_height = dstspec.height;
    }

    bool do_resize = false;
    // Resize if we're up-resing for pow2
    if (dstspec.width != srcspec.width || dstspec.height != srcspec.height ||
          dstspec.full_depth != srcspec.full_depth)
        do_resize = true;
    // resize if the original was a crop
    if (orig_was_crop)
        do_resize = true;
    // resize if we're converting from non-border sampling to border sampling
    // (converting TO an OpenEXR environment map).
    if (envlatlmode && 
        (Strutil::iequals(configspec.get_string_attribute("maketx:fileformatname"),"openexr") ||
         Strutil::iends_with(outputfilename,".exr")))
        do_resize = true;

    if (do_resize && orig_was_overscan &&
        out && !out->supports("displaywindow")) {
        outstream << "maketx ERROR: format " << out->format_name()
                  << " does not support separate display windows,\n"
                  << "              which is necessary when combining resizing"
                  << " and an input image with overscan.";
        return false;
    }

    std::string filtername = configspec.get_string_attribute ("maketx:filtername", "box");
    Filter2D *filter = setup_filter (filtername);
    if (! filter) {
        outstream << "maketx ERROR: could not make filter '" << filtername << "\n";
        return false;
    }

    Timer resizetimer;
    ImageBuf dst ("temp", dstspec);
    ImageBuf *toplevel = &dst;    // Ptr to top level of mipmap
    if (! do_resize) {
        // Don't need to resize
        if (dstspec.format == ccSrc->spec().format) {
            // Even more special case, no format change -- just use
            // the original copy.
            toplevel = ccSrc;
        } else {
            ImageBufAlgo::parallel_image (boost::bind(copy_block,&dst,ccSrc,_1),
                                          OIIO::get_roi(dstspec));
        }
    } else {
        // Resize
        if (verbose)
            outstream << "  Resizing image to " << dstspec.width 
                      << " x " << dstspec.height << std::endl;
        if (filtername == "box" && filter->width() == 1.0f)
            ImageBufAlgo::parallel_image (boost::bind(resize_block, &dst, ccSrc, _1, envlatlmode),
                                          OIIO::get_roi(dstspec));
        else
            ImageBufAlgo::parallel_image (boost::bind(resize_block_HQ, &dst, ccSrc, _1, filter),
                                          OIIO::get_roi(dstspec));
    }
    stat_resizetime += resizetimer();

    
    // Update the toplevel ImageDescription with the sha1 pixel hash and constant color
    std::string desc = dstspec.get_string_attribute ("ImageDescription");
    bool updatedDesc = false;
    
    // Eliminate any SHA-1 or ConstantColor hints in the ImageDescription.
    if (desc.size()) {
        desc = boost::regex_replace (desc, boost::regex("SHA-1=[[:xdigit:]]*[ ]*"), "");
        static const char *fp_number_pattern =
            "([+-]?((?:(?:[[:digit:]]*\\.)?[[:digit:]]+(?:[eE][+-]?[[:digit:]]+)?)))";
        const std::string color_pattern =
            std::string ("ConstantColor=(\\[?") + fp_number_pattern + ",?)+\\]?[ ]*";
        desc = boost::regex_replace (desc, boost::regex(color_pattern), "");
        updatedDesc = true;
    }
    
    // The hash is only computed for the top mipmap level of pixel data.
    // Thus, any additional information that will effect the lower levels
    // (such as filtering information) needs to be manually added into the
    // hash.
    std::ostringstream addlHashData;
    addlHashData << filter->name() << " ";
    addlHashData << filter->width() << " ";
    
    std::string hash_digest = ImageBufAlgo::computePixelHashSHA1 (*toplevel,
        addlHashData.str());
    if (hash_digest.length()) {
        if (desc.length())
            desc += " ";
        desc += "SHA-1=";
        desc += hash_digest;
        if (verbose)
            outstream << "  SHA-1: " << hash_digest << std::endl;
        updatedDesc = true;
        dstspec.attribute ("oiio:SHA-1", hash_digest);
    }
    
    if (isConstantColor) {
        std::ostringstream os; // Emulate a JSON array
        os << "[";
        for (unsigned int i=0; i<constantColor.size(); ++i) {
            if (i!=0) os << ",";
            os << constantColor[i];
        }
        os << "]";
        
        if (desc.length())
            desc += " ";
        desc += "ConstantColor=";
        desc += os.str();
        if (verbose)
            outstream << "  ConstantColor: " << os.str() << std::endl;
        updatedDesc = true;
        dstspec.attribute ("oiio:ConstantColor", os.str());
    }
    
    if (updatedDesc) {
        dstspec.attribute ("ImageDescription", desc);
    }


    if (configspec.get_float_attribute("fovcot") == 0.0f)
        configspec.attribute("fovcot", float(srcspec.full_width) / 
                                       float(srcspec.full_height));


    maketx_merge_spec (dstspec, configspec);

    // Write out, and compute, the mipmap levels for the speicifed image
    bool nomipmap = configspec.get_int_attribute ("maketx:nomipmap");
    bool ok = write_mipmap (mode, *toplevel, dstspec, outputfilename,
                            out, out_dataformat, !shadowmode && !nomipmap,
                            filter, configspec, outstream,
                            stat_writetime, stat_miptime);
    delete out;  // don't need it any more

    // If using update mode, stamp the output file with a modification time
    // matching that of the input file.
    if (ok && updatemode)
        Filesystem::last_write_time (outputfilename, in_time);

    Filter2D::destroy (filter);

    if (verbose || configspec.get_int_attribute("maketx:stats")) {
        double all = alltime();
        outstream << Strutil::format ("maketx run time (seconds): %5.2f\n", all);;

        outstream << Strutil::format ("  file read:       %5.2f\n", stat_readtime);
        outstream << Strutil::format ("  file write:      %5.2f\n", stat_writetime);
        outstream << Strutil::format ("  initial resize:  %5.2f\n", stat_resizetime);
        outstream << Strutil::format ("  mip computation: %5.2f\n", stat_miptime);
        outstream << Strutil::format ("  color convert:   %5.2f\n", stat_colorconverttime);
        outstream << Strutil::format ("  unaccounted:     %5.2f\n",
                                      all-stat_readtime-stat_writetime-stat_resizetime-stat_miptime);
        size_t kb = Sysutil::memory_used(true) / 1024;
        outstream << Strutil::format ("maketx memory used: %5.1f MB\n",
                                      (double)kb/1024.0);
    }

    return ok;
}